Convertir cadenas a fecha y hora en Python

En este tutorial, convertiremos cadenas a fecha y hora en Python, tratando con zonas horarias. También usaremos dateutil, Maya y Arrow para convertir Strings a datetime.

Introducción

Uno de los muchos problemas comunes que enfrentamos en el desarrollo de software es el manejo de fechas y horas. Después de obtener una cadena de fecha y hora de una API, por ejemplo, necesitamos convertirla a un formato legible por humanos. Nuevamente, si se usa la misma API en diferentes zonas horarias, la conversión será diferente. Una buena biblioteca de fecha y hora debería convertir la hora según la zona horaria. Este es solo uno de los muchos matices que deben manejarse cuando se trata de fechas y horas.

Afortunadamente, Python viene con el módulo incorporado datetime para manejar fechas y horas. Como probablemente hayas adivinado, viene con varias funciones para manipular fechas y horas. Con este módulo, podemos analizar fácilmente cualquier cadena de fecha y hora y convertirla en un objeto de fecha y hora.

Conversión de cadenas usando fecha y hora

El módulo fecha y hora consta de tres tipos de objetos diferentes: date, time y datetime. Obviamente, el objeto date contiene la fecha, time contiene la hora y datetime contiene tanto la fecha como la hora.

Por ejemplo, el siguiente código imprimirá la fecha y hora actuales:

1
2
3
import datetime

print ('Current date/time: {}'.format(datetime.datetime.now()))

Ejecutar este código imprimirá algo similar a esto:

1
2
$ python3 datetime-print-1.py
Current date/time: 2018-06-29 08:15:27.243860

Cuando no se proporciona un formato personalizado, se utiliza el formato de cadena predeterminado, es decir, el formato para "2018-06-29 08:15:27.243860" está en [ISO 8601](https://en.wikipedia.org/wiki /ISO_8601) (AAAA-MM-DDTHH:MM:SS.mmmmmm). Si nuestra cadena de entrada para crear un objeto fechahora está en el mismo formato ISO 8601, podemos analizarla fácilmente como un objeto fechahora.

Echemos un vistazo al siguiente código:

1
2
3
4
5
6
7
8
import datetime

date_time_str = '2018-06-29 08:15:27.243860'
date_time_obj = datetime.datetime.strptime(date_time_str, '%Y-%m-%d %H:%M:%S.%f')

print('Date:', date_time_obj.date())
print('Time:', date_time_obj.time())
print('Date-time:', date_time_obj)

Ejecutarlo imprimirá la fecha, hora y fecha-hora:

1
2
3
4
$ python3 datetime-print-2.py
Date: 2018-06-29
Time: 08:15:27.243860
Date-time: 2018-06-29 08:15:27.243860

En este ejemplo, estamos usando un nuevo método llamado strptime. Este método toma dos argumentos: el primero es la representación de cadena de la fecha y hora y el segundo es el formato de la cadena de entrada. Especificar el formato de esta manera hace que el análisis sea mucho más rápido ya que datetime no necesita intentar interpretar el formato por sí solo, lo que es mucho más costoso computacionalmente. El valor devuelto es del tipo datetime.

En nuestro ejemplo, "2018-06-29 08:15:27.243860" es la cadena de entrada y "%Y-%m-%d %H:%M:%S.%f" es el formato de nuestra cadena de fecha. El valor datetime devuelto se almacena en la variable date_time_obj. Dado que este es un objeto datetime, podemos llamar a los métodos date() y time() directamente en él. Como puede ver en la salida, imprime la parte 'fecha' y 'hora' de la cadena de entrada.

Quizás se pregunte cuál es el significado del formato "%Y-%m-%d %H:%M:%S.%f". Estos se conocen como tokens de formato. Cada token representa una parte diferente de la fecha y hora, como día, mes, año, etc. Consulta la [documentación de strptime](https://docs.python.org/3/library/datetime.html#strftime- and-strptime-behavior) para ver la lista de todos los diferentes tipos de código de formato compatibles con Python. Para una referencia rápida, esto es lo que estamos usando en el código anterior:

  • %Y: Año (4 dígitos)
  • %m: Mes
  • %d: Día del mes
  • %H: Hora (24 horas)
  • %M: Minutos
  • %S: Segundos
  • %f: Microsegundos

Se espera que todos estos tokens, excepto el año, tengan relleno cero.

Por lo tanto, si se conoce el formato de una cadena, se puede analizar fácilmente en un objeto datetime usando strptime. Déjame mostrarte otro ejemplo no trivial:

1
2
3
4
5
6
7
8
import datetime

date_time_str = 'Jun 28 2018 7:40AM'
date_time_obj = datetime.datetime.strptime(date_time_str, '%b %d %Y %I:%M%p')

print('Date:', date_time_obj.date())
print('Time:', date_time_obj.time())
print('Date-time:', date_time_obj)

En el siguiente resultado, puede ver que la cadena se analizó correctamente, ya que el objeto datetime la imprime correctamente aquí:

1
2
3
4
$ python3 datetime-print-3.py
Date: 2018-06-28
Time: 07:40:00
Date-time: 2018-06-28 07:40:00

Aquí hay algunos ejemplos más de formatos de tiempo de uso común y los tokens utilizados para el análisis:

1
2
3
4
5
"Jun 28 2018 at 7:40AM" -> "%b %d %Y at %I:%M%p"
"September 18, 2017, 22:19:55" -> "%B %d, %Y, %H:%M:%S"
"Sun,05/12/99,12:30PM" -> "%a,%d/%m/%y,%I:%M%p"
"Mon, 21 March, 2015" -> "%a, %d %B, %Y"
"2018-03-12T10:12:45Z" -> "%Y-%m-%dT%H:%M:%SZ"

Puede analizar una cadena de fecha y hora de cualquier formato utilizando la tabla mencionada en la documentación de strptime.

Manejo de zonas horarias y fechahora {#tratamiento de zonas horarias y fechahora}

El manejo de fechas y horas se vuelve más complejo cuando se trata de zonas horarias. Todos los ejemplos anteriores que hemos discutido son objetos ingenuos datetime, es decir, estos objetos no contienen ningún dato relacionado con la zona horaria. El objeto datetime tiene una variable que contiene la información de la zona horaria, tzinfo.

1
2
3
4
5
6
import datetime as dt

dtime = dt.datetime.now()

print(dtime)
print(dtime.tzinfo)

Este código imprimirá:

1
2
3
$ python3 datetime-tzinfo-1.py
2018-06-29 22:16:36.132767
None

La salida de ’tzinfo’ es ‘Ninguno’ ya que es un objeto ingenuo de ‘fecha y hora’. Para la conversión de zona horaria, una biblioteca llamada pytz está disponible para Python. Puede instalarlo como se describe en estas instrucciones. Ahora, usemos la biblioteca pytz para convertir la marca de tiempo anterior a [UTC] (https://en.wikipedia.org/wiki/Coordinated_Universal_Time).

1
2
3
4
5
6
7
import datetime as dt
import pytz

dtime = dt.datetime.now(pytz.utc)

print(dtime)
print(dtime.tzinfo)

Producción:

1
2
3
$ python3 datetime-tzinfo-2.py
2018-06-29 17:08:00.586525+00:00
UTC

+00:00 es la diferencia entre la hora mostrada y la hora UTC. En este ejemplo, el valor de tzinfo también es UTC, por lo tanto, el desplazamiento 00:00. En este caso, el objeto datetime es un objeto consciente de la zona horaria.

Del mismo modo, podemos convertir cadenas de fecha y hora a cualquier otra zona horaria. Por ejemplo, podemos convertir la cadena "2018-06-29 17:08:00.586525+00:00" a la zona horaria "America/New_York", como se muestra a continuación:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import datetime as dt
import pytz

date_time_str = '2018-06-29 17:08:00'
date_time_obj = dt.datetime.strptime(date_time_str, '%Y-%m-%d %H:%M:%S')

timezone = pytz.timezone('America/New_York')
timezone_date_time_obj = timezone.localize(date_time_obj)

print(timezone_date_time_obj)
print(timezone_date_time_obj.tzinfo)

Producción:

1
2
3
$ python3 datetime-tzinfo-3.py
2018-06-29 17:08:00-04:00
America/New_York

Primero, hemos convertido la cadena en un objeto datetime, date_time_obj. Luego lo convertimos en un objeto datetime habilitado para la zona horaria, timezone_date_time_obj. Dado que hemos configurado la zona horaria como "América/Nueva_York", la hora de salida muestra que está 4 horas atrasada con respecto a la hora UTC. Puede consultar esta página de wikipedia para encontrar la lista completa de zonas horarias disponibles.

Conversión de zonas horarias

Podemos convertir la zona horaria de un objeto datetime de una región a otra, como se muestra en el siguiente ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import datetime as dt
import pytz

timezone_nw = pytz.timezone('America/New_York')
nw_datetime_obj = dt.datetime.now(timezone_nw)

timezone_london = pytz.timezone('Europe/London')
london_datetime_obj = nw_datetime_obj.astimezone(timezone_london)


print('America/New_York:', nw_datetime_obj)
print('Europe/London:', london_datetime_obj)

Primero, creamos un objeto de fecha y hora con la hora actual y lo configuramos como la zona horaria "América/Nueva_York". Luego, usando el método astimezone(), hemos convertido esta datetime a la zona horaria "Europa/Londres". Ambos datetimes imprimirán diferentes valores como:

1
2
3
$ python3 datetime-tzinfo-4.py
America/New_York: 2018-06-29 22:21:41.349491-04:00
Europe/London: 2018-06-30 03:21:41.349491+01:00

Como era de esperar, las fechas y horas son diferentes ya que tienen una diferencia de aproximadamente 5 horas.

Uso de bibliotecas de terceros

El módulo datetime de Python puede convertir todos los diferentes tipos de cadenas en un objeto datetime. Pero el principal problema es que para hacer esto necesitas crear la cadena de código de formato apropiada que strptime pueda entender. Crear esta cadena lleva tiempo y hace que el código sea más difícil de leer. En su lugar, podemos usar otras bibliotecas de terceros para hacerlo más fácil.

En algunos casos, estas bibliotecas de terceros también tienen un mejor soporte integrado para manipular y comparar fechas y horas, y algunas incluso tienen zonas horarias integradas, por lo que no necesita incluir un paquete adicional.

Echemos un vistazo a algunas de estas bibliotecas en las siguientes secciones.

fecha útil

El módulo dateutil es una extensión del módulo datetime. Una ventaja es que no necesitamos pasar ningún código de análisis para analizar una cadena. Por ejemplo:

1
2
3
4
5
from dateutil.parser import parse

datetime = parse('2018-06-29 22:21:41')

print(datetime)

Esta función parse analizará la cadena automáticamente y la almacenará en la variable datetime. El análisis se realiza automáticamente. No tienes que mencionar ninguna cadena de formato. Intentemos analizar diferentes tipos de cadenas usando dateutil:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from dateutil.parser import parse

date_array = [
    '2018-06-29 08:15:27.243860',
    'Jun 28 2018 7:40AM',
    'Jun 28 2018 at 7:40AM',
    'September 18, 2017, 22:19:55',
    'Sun, 05/12/1999, 12:30PM',
    'Mon, 21 March, 2015',
    '2018-03-12T10:12:45Z',
    '2018-06-29 17:08:00.586525+00:00',
    '2018-06-29 17:08:00.586525+05:00',
    'Tuesday , 6th September, 2017 at 4:30pm'
]

for date in date_array:
    print('Parsing: ' + date)
    dt = parse(date)
    print(dt.date())
    print(dt.time())
    print(dt.tzinfo)
    print('\n')

Producción:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
$ python3 dateutil-1.py
Parsing: 2018-06-29 08:15:27.243860
2018-06-29
08:15:27.243860
None

Parsing: Jun 28 2018 7:40AM
2018-06-28
07:40:00
None

Parsing: Jun 28 2018 at 7:40AM
2018-06-28
07:40:00
None

Parsing: September 18, 2017, 22:19:55
2017-09-18
22:19:55
None

Parsing: Sun, 05/12/1999, 12:30PM
1999-05-12
12:30:00
None

Parsing: Mon, 21 March, 2015
2015-03-21
00:00:00
None

Parsing: 2018-03-12T10:12:45Z
2018-03-12
10:12:45
tzutc()

Parsing: 2018-06-29 17:08:00.586525+00:00
2018-06-29
17:08:00.586525
tzutc()

Parsing: 2018-06-29 17:08:00.586525+05:00
2018-06-29
17:08:00.586525
tzoffset(None, 18000)

Parsing: Tuesday , 6th September, 2017 at 4:30pm
2017-09-06
16:30:00
None

Puede ver que casi cualquier tipo de cadena se puede analizar fácilmente usando el módulo dateutil.

Si bien esto es conveniente, recuerde que tener que predecir el formato hace que el código sea mucho más lento, por lo que si su código requiere un alto rendimiento, es posible que este no sea el enfoque adecuado para su aplicación.

Maya

maya también hace que sea muy fácil analizar una cadena y cambiar las zonas horarias. Algunos ejemplos simples se muestran aquí:

1
2
3
4
5
6
import maya

dt = maya.parse('2018-04-29T17:45:25Z').datetime()
print(dt.date())
print(dt.time())
print(dt.tzinfo)

Producción:

1
2
3
4
$ python3 maya-1.py
2018-04-29
17:45:25
UTC

Para convertir la hora a una zona horaria diferente:

1
2
3
4
5
6
import maya

dt = maya.parse('2018-04-29T17:45:25Z').datetime(to_timezone='America/New_York', naive=False)
print(dt.date())
print(dt.time())
print(dt.tzinfo)

Producción:

1
2
3
4
$ python3 maya-2.py
2018-04-29
13:45:25
America/New_York

¿No es tan fácil de usar? Probemos maya con el mismo conjunto de cadenas que hemos usado con dateutil:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import maya

date_array = [
    '2018-06-29 08:15:27.243860',
    'Jun 28 2018 7:40AM',
    'Jun 28 2018 at 7:40AM',
    'September 18, 2017, 22:19:55',
    'Sun, 05/12/1999, 12:30PM',
    'Mon, 21 March, 2015',
    '2018-03-12T10:12:45Z',
    '2018-06-29 17:08:00.586525+00:00',
    '2018-06-29 17:08:00.586525+05:00',
    'Tuesday , 6th September, 2017 at 4:30pm'
]

for date in date_array:
    print('Parsing: ' + date)
    dt = maya.parse(date).datetime()
    print(dt)
    print(dt.date())
    print(dt.time())
    print(dt.tzinfo)

Producción:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
$ python3 maya-3.py
Parsing: 2018-06-29 08:15:27.243860
2018-06-29 08:15:27.243860+00:00
2018-06-29
08:15:27.243860
UTC

Parsing: Jun 28 2018 7:40AM
2018-06-28 07:40:00+00:00
2018-06-28
07:40:00
UTC

Parsing: Jun 28 2018 at 7:40AM
2018-06-28 07:40:00+00:00
2018-06-28
07:40:00
UTC

Parsing: September 18, 2017, 22:19:55
2017-09-18 22:19:55+00:00
2017-09-18
22:19:55
UTC

Parsing: Sun, 05/12/1999, 12:30PM
1999-05-12 12:30:00+00:00
1999-05-12
12:30:00
UTC

Parsing: Mon, 21 March, 2015
2015-03-21 00:00:00+00:00
2015-03-21
00:00:00
UTC

Parsing: 2018-03-12T10:12:45Z
2018-03-12 10:12:45+00:00
2018-03-12
10:12:45
UTC

Parsing: 2018-06-29 17:08:00.586525+00:00
2018-06-29 17:08:00.586525+00:00
2018-06-29
17:08:00.586525
UTC

Parsing: 2018-06-29 17:08:00.586525+05:00
2018-06-29 12:08:00.586525+00:00
2018-06-29
12:08:00.586525
UTC

Parsing: Tuesday , 6th September, 2017 at 4:30pm
2017-09-06 16:30:00+00:00
2017-09-06
16:30:00
UTC

Como puede ver, todos los formatos de fecha se analizaron correctamente.

Pero, ¿notaste la diferencia? Si no proporcionamos la información de la zona horaria, la convierte automáticamente a UTC. Por lo tanto, es importante tener en cuenta que debemos proporcionar los parámetros to_timezone e naive si la hora no está en UTC.

Flecha

Flecha es otra biblioteca para manejar fecha y hora en Python. Y como antes con maya, también calcula el formato de fecha y hora automáticamente. Una vez interpretado, devuelve un objeto datetime de Python desde el objeto flecha.

Intentemos esto con la misma cadena de ejemplo que hemos usado para maya:

1
2
3
4
5
6
import arrow

dt = arrow.get('2018-04-29T17:45:25Z')
print(dt.date())
print(dt.time())
print(dt.tzinfo)

Producción:

1
2
3
4
$ python3 arrow-1.py
2018-04-29
17:45:25
tzutc()

Y así es como puedes usar la flecha para convertir zonas horarias usando el método to:

1
2
3
4
5
6
import arrow

dt = arrow.get('2018-04-29T17:45:25Z').to('America/New_York')
print(dt)
print(dt.date())
print(dt.time())

Producción:

1
2
3
4
$ python3 arrow-2.py
2018-04-29T13:45:25-04:00
2018-04-29
13:45:25

Como puede ver, la cadena de fecha y hora se convierte a la región "América/Nueva_York".

Ahora, usemos nuevamente el mismo conjunto de cadenas que hemos usado anteriormente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import arrow

date_array = [
    '2018-06-29 08:15:27.243860',
    #'Jun 28 2018 7:40AM',
    #'Jun 28 2018 at 7:40AM',
    #'September 18, 2017, 22:19:55',
    #'Sun, 05/12/1999, 12:30PM',
    #'Mon, 21 March, 2015',
    '2018-03-12T10:12:45Z',
    '2018-06-29 17:08:00.586525+00:00',
    '2018-06-29 17:08:00.586525+05:00',
    #'Tuesday , 6th September, 2017 at 4:30pm'
]

for date in date_array:
    dt = arrow.get(date)
    print('Parsing: ' + date)
    print(dt)
    print(dt.date())
    print(dt.time())
    print(dt.tzinfo)

Este código fallará para las cadenas de fecha y hora que se han comentado, que es más de la mitad de nuestros ejemplos. La salida para otras cadenas será:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ python3 arrow-3.py
Parsing: 2018-06-29 08:15:27.243860
2018-06-29T08:15:27.243860+00:00
2018-06-29
08:15:27.243860
tzutc()

Parsing: 2018-03-12T10:12:45Z
2018-03-12T10:12:45+00:00
2018-03-12
10:12:45
tzutc()

Parsing: 2018-06-29 17:08:00.586525+00:00
2018-06-29T17:08:00.586525+00:00
2018-06-29
17:08:00.586525
tzoffset(None, 0)

Parsing: 2018-06-29 17:08:00.586525+05:00
2018-06-29T17:08:00.586525+05:00
2018-06-29
17:08:00.586525
tzoffset(None, 18000)

Para analizar correctamente las cadenas de fecha y hora que he comentado, deberá pasar los tokens de formato correspondientes para dar pistas a la biblioteca sobre cómo analizarlo. Por ejemplo, "MMM" para el nombre del mes, como "Jan, Feb, Mar", etc. Puede consultar esta guía para ver todos los disponibles. fichas

Conclusión

En este artículo, hemos mostrado diferentes formas de analizar una cadena en un objeto datetime en Python. Puede optar por la biblioteca datetime predeterminada de Python o cualquiera de las bibliotecas de terceros mencionadas en este artículo, entre muchas otras.

El principal problema con el paquete datetime predeterminado es que necesitamos especificar el código de análisis manualmente para casi todos los formatos de cadena de fecha y hora. Por lo tanto, si su formato de cadena cambia en el futuro, es probable que también tenga que cambiar su código. Pero muchas bibliotecas de terceros, como las mencionadas aquí, lo manejan automáticamente.

Otro problema al que nos enfrentamos es el manejo de las zonas horarias. La mejor manera de manejarlos es siempre almacenar la hora en su base de datos en formato UTC y luego convertirla a la zona horaria local del usuario cuando sea necesario.

Estas bibliotecas no solo son buenas para analizar cadenas, sino que también se pueden usar para muchos tipos diferentes de operaciones relacionadas con la fecha y la hora. Le animo a que revise los documentos para conocer las funcionalidades en detalle.

Licensed under CC BY-NC-SA 4.0