Datos temporales y algoritmos aplicados
Gestionar una fecha del calendario
1. Noción de fecha del calendario
Una fecha es, simplemente, la combinación de un día, un mes y un año. Los tres elementos son obligatorios. No existe la noción de instante, de segundos, de minutos o de horas.
No hay nada más sencillo que gestionar una fecha, basta con crear un objeto datetime.date asignándole el año, el mes y el día.
>>> import datetime
>>> d=datetime.date(2009, 7, 22)
>>> d
datetime.date(2009, 7, 22)
En caso de existir algún error en los parámetros, se genera una excepción muy clara:
>>> d2=datetime.date(2009, 2, 30)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: day is out of range for month
Dicho objeto proporciona:
>>> dir(datetime.date)
['__add__', '__class__', '__delattr__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',
'__init__', '__le__', '__lt__', '__ne__', '__new__', '__radd__',
'__reduce__', '__reduce_ex__', '__repr__', '__rsub__',
'__setattr__', '__sizeof__', '__str__', '__sub__',
'__subclasshook__', 'ctime', 'day', 'fromordinal',
'fromtimestamp', 'isocalendar', 'isoformat', 'isoweekday', 'max',
'min', 'month', 'replace', 'resolution', 'strftime', 'timetuple',
'today', 'toordinal', 'weekday', 'year']
Dicho objeto posee tres propiedades que se pasan al constructor:
>>> d.day
22
>>> d.month
7
>>> d.year
2009
2. Trabajar con una fecha
Es posible recuperar el día de la semana según la norma española (el lunes es 0, el domingo es 6) o según la norma ISO (0 para el domingo hasta 6 para el sábado, según la norma inglesa):
>>> d.weekday()
2
>>>...
Gestionar un horario o un momento de la jornada
1. Noción de instante
La noción de instante cubre la noción de tiempo que transcurre en una jornada, en función de las reglas habituales y con una precisión que depende del contexto. Por ejemplo, para saber qué hora es, basta con conocer la hora y el minuto. Cuando se desea una mayor precisión, es posible agregar la noción de segundo y, por último, cuando se desea medir el tiempo con la máxima precisión, es posible descender a la millonésima de segundo.
No es posible obtener una mayor precisión, que viene dada por el hardware (y su cadencia) del sistema. Estas nociones de precisión son visibles en la firma del fabricante y en la representación del objeto:
>>> datetime.time()
datetime.time(0, 0)
>>> datetime.time(13, 56)
datetime.time(13, 56)
>>> datetime.time(13, 56, 12)
datetime.time(13, 56, 12)
>>> datetime.time(13, 56, 12, 54321)
datetime.time(13, 56, 12, 54321)
Este objeto es ideal para trabajar sobre momentos de la jornada sin considerar el día al que se aplica. Por ejemplo, para trabajar sobre una agenda, se utilizan objetos datetime.date para las columnas y datetime.time para las filas.
De este modo, es posible comparar horarios y resulta más sencillo gestionar los tramos horarios de una jornada. Esto puede, también, servir para medir los tiempos de ejecución...
Gestionar un instante absoluto
1. Noción de instante absoluto
Se trata de identificar un instante mediante el uso conjunto de una fecha y un instante de la jornada, situando un evento con una exactitud determinada.
Por ejemplo, se sabe exactamente cuándo comenzó el eclipse del 11 de agosto de 1999 (en el Atlántico Norte) y cuándo terminó (en la India):
>>> inicio_eclipse=datetime.datetime(1999, 8, 11, 8, 26, 17, 600000)
>>> inicio_eclipse
datetime.datetime(1999, 8, 11, 8, 26, 17, 600000)
>>> fin_eclipse=datetime.datetime(1999, 8, 11, 13, 40, 8, 500000)
Una vez se tienen los dos instantes absolutos, resulta muy sencillo recuperar la duración:
>>> fin_eclipse-inicio_eclipse
datetime.timedelta(0, 18830, 900000)
2. Relación con las nociones anteriores
El objeto datetime.datetime permite gestionar una fecha, exactamente como el objeto datetime.date.
>>> d=datetime.datetime(2009, 7, 22)
>>> d
datetime.datetime(2009, 7, 22, 0, 0)
Este objeto tiene una precisión mayor:
>>> datetime.date.resolution
datetime.timedelta(1)
>>> datetime.datetime.resolution
datetime.timedelta(0, 0, 1)
Como se verá más adelante, el primer elemento se corresponde con una granularidad de un día, el segundo con un segundo y el tercero con un microsegundo.
Por otro lado, ambos objetos, datetime.datetime y datetime.date, están relacionados:
>>> type.mro(datetime.datetime)
[<class 'datetime.datetime'>, <class 'datetime.date'>...
Gestionar una diferencia entre dos fechas o instantes
1. Noción de diferencia y de resolución
Python permite trabajar sobre la diferencia entre dos objetos datetime.date, datetime.time o datetime.datetime respetando su resolución, es decir, el intervalo más pequeño en el que tiene sentido obtener una diferencia (por ejemplo, dos objetos datetime.date con una diferencia de dos horas entre sí serán iguales si la resolución es de un día).
He aquí la firma del constructor:
datetime.timedelta([days [, seconds[, microseconds[, milliseconds[,
minutes, [hours, [weeks]]]]]]])
He aquí lo que devuelve cuando se utiliza cada parámetro de manera unitaria:
>>> datetime.timedelta(1)
datetime.timedelta(1)
>>> datetime.timedelta(0, 1)
datetime.timedelta(0, 1)
>>> datetime.timedelta(0, 0, 1)
datetime.timedelta(0, 0, 1)
>>> datetime.timedelta(0, 0, 0, 1)
datetime.timedelta(0, 0, 1000)
>>> datetime.timedelta(0, 0, 0, 0, 1)
datetime.timedelta(0, 60)
>>> datetime.timedelta(0, 0, 0, 0, 0, 1)
datetime.timedelta(0, 3600)
>>> datetime.timedelta(0, 0, 0, 0, 0, 0, 1)
datetime.timedelta(7)
De este modo, la representación, sean cuales sean los parámetros utilizados en el constructor, se basa únicamente en la distinción entre los días, los segundos y los microsegundos.
Esto significa que dos valores idénticos construidos de manera diferente tienen la misma representación. Además, es posible utilizar parámetros nombrados:
>>> datetime.timedelta(weeks=2)
datetime.timedelta(14)
>>> datetime.timedelta(days=14) ...
Especificidades de los husos horarios
Es posible gestionar el huso horario de referencia de manera muy simple:
>>> tz=datetime.timezone.utc
Los demás se construyen conociendo sus desfases respecto a este huso horario de referencia.
>>> tz=datetime.timezone(datetime.timedelta(hours=6))
>>> tz.tzname(datetime.datetime.now())
'UTC+06:00'
>>> tz.utcoffset(datetime.datetime.now())
datetime.timedelta(0, 21600)
>>> 21600/3600
6.0
He aquí los límites:
>>> datetime.timezone.min
datetime.timezone(datetime.timedelta(-1, 60))
>>> datetime.timezone.min.tzname(datetime.datetime.now())
'UTC-23:59'
>>> datetime.timezone.max
datetime.timezone(datetime.timedelta(0, 86340))
>>> datetime.timezone.max.tzname(datetime.datetime.now())
'UTC+23:59'
Si se quiere personalizar un huso horario, en particular dándole un nombre concreto o un desfase no estándar, hay que utilizar la clase datetime.tzinfo y sobrecargarla, como se muestra a continuación:
>>> class MyOffset(datetime.tzinfo):
... """My Offset"""
... def __init__(self, offset, name):
... self.__offset = datetime.timedelta(minutes=offset) ...
Problemáticas de bajo nivel
1. Timestamp y struct_time
Python incluye varias formas de gestionar las fechas que tienen como resolución una jornada o un milisegundo. Se han expuesto más arriba.
Python posee, a su vez, una estructura struct_time que se parece a la estructura de C:
Índice |
Clave |
Mínimo |
Máximo |
0 |
tm_year |
1900 |
|
1 |
tm_month |
1 |
12 |
2 |
tm_day |
1 |
31 |
3 |
tm_hour |
0 |
23 |
4 |
tm_min |
0 |
60 |
5 |
tm_sec |
0 |
60 |
6 |
tm_wday |
0 (lunes) |
6 (domingo) |
7 |
tm_yday |
1 |
366 |
8 |
tm_isdst |
-1 |
1 |
Los años pueden almacenarse con formatos de tres o cuatro cifras. Los valores 69 a 99, incluidos, representan los años 1969 a 1999; los valores 0 a 68, incluidos, representan los años 2000 a 2068, y los valores 100 a 1899, incluidos, están prohibidos, conforme se hace en C. Por el contrario, los meses van de 1 a 12 en Python en lugar de 0 a 11 como en C.
El atributo tm_isdst puede tomar los valores -1, 0 y 1 y permite gestionar el hecho de que la fecha sea local o UTC (DST: daylight saving time).
Existen puentes entre un timestamp y la estructura struct_time:
Función |
DST |
Origen |
Destino |
time.gmtime() |
UTC |
timestamp |
struc_time |
calendar.timegm() |
UTC |
struc_time |
timestamp |
time.localtime() |
local |
timestamp |
struc_time |
time.mktime() |
local |
struc_time |
timestamp |
Estas problemáticas son de bajo nivel y el módulo time se utiliza con poca frecuencia; datetime responde de manera natural a la mayoría de problemáticas.
2. Medidas de rendimiento
Para medir el tiempo que ha tomado un algoritmo en la realización de una operación existen dos maneras de proceder. O bien se mide el tiempo efectivo entre el inicio del algoritmo y su final, o bien se mide el tiempo que el procesador ha asignado realmente al proceso que ejecuta el algoritmo. De este modo, podemos medir una diferencia entre dos timestamps (número de segundos que han transcurrido desde el 1 de enero de 1970) o una diferencia entre dos valores de tiempo de procesador una vez iniciado el algoritmo. Por ejemplo, en la consola:
>>> time.clock()
0.54
El tiempo evoluciona, pero el tiempo de procesador consumido evoluciona poco:
>>> time.clock(), time.time()
(0.54, 1314188162.305353)
>>> time.clock(), time.time() ...
Uso del calendario
1. Presentación del módulo calendar
Python provee un módulo que ofrece todas las funcionalidades necesarias para gestionar un calendario o una fecha en un calendario:
>>> import calendar
>>> dir(calendar)
['Calendar', 'EPOCH', 'FRIDAY', 'February', 'HTMLCalendar',
'IllegalMonthError', 'IllegalWeekdayError', 'January',
'LocaleHTMLCalendar', 'LocaleTextCalendar', 'MONDAY', 'SATURDAY',
'SUNDAY', 'THURSDAY', 'TUESDAY', 'TextCalendar', 'WEDNESDAY',
'_EPOCH_ORD', '__all__', '__builtins__', '__cached__', '__doc__',
'__file__', '__name__', '__package__', '_colwidth', '_locale',
'_localized_day', '_localized_month', '_spacing', 'c', 'calendar',
'datetime', 'day_abbr', 'day_name', 'different_locale', 'error',
'firstweekday', 'format', 'formatstring', 'isleap', 'leapdays',
'main', 'mdays', 'month', 'month_abbr', 'month_name',
'monthcalendar', 'monthrange', 'prcal', 'prmonth', 'prweek',
'setfirstweekday', 'sys', 'timegm', 'week', 'weekday',
'weekheader']
Veamos, en primer lugar, las constantes. EPOCH devuelve el año de origen del timestamp UNIX. De este modo, un timestamp de 0 se corresponde con el 1 de enero de 1970, a medianoche.
>>> calendar.EPOCH
1970
A continuación, tenemos los días de la semana:
>>> calendar.MONDAY
0
>>> calendar.TUESDAY
1
>>> calendar.WEDNESDAY
2
>>> calendar.THURSDAY
3
>>> calendar.FRIDAY
4
>>> calendar.SATURDAY
5
>>> calendar.SUNDAY
6
Y el número del mes de enero:
>>> calendar.January
1
Son constantes que conviene utilizar en el código, en lugar de utilizar...