Curso de Python / Trabajar con fechas, horas y zonas horarias

Utiliza date para un día del calendario y datetime con zona horaria cuando necesites representar un instante. En Python, distinguir una fecha sin zona de una fecha consciente de ella evita confundir la hora que muestra el reloj con el momento que representa.

Elegir el dato que necesita la aplicación

date representa una fecha; datetime, una fecha y una hora; timedelta, una duración. Una fecha-hora sin información de zona se denomina ingenua o naive. Una consciente o aware tiene información suficiente de zona para interpretar su desplazamiento respecto de UTC, el tiempo universal coordinado.

Si un gasto solo necesita su día de compra, inventar una hora de medianoche añade una interpretación innecesaria. Si registras el instante de una operación, define una convención común de almacenamiento y convierte su representación al mostrarlo. Esta decisión será importante en el proyecto de gestión de gastos.

Convertir un instante UTC a la hora de Seúl

Guarda el ejemplo en dates.py y ejecútalo con python dates.py. Si aparece ZoneInfoNotFoundError, instala los datos de zonas en el entorno del proyecto mediante python -m pip install tzdata y repite la ejecución.

from datetime import date, datetime, timedelta, timezone
from zoneinfo import ZoneInfo

day = date.fromisoformat("2026-10-17")
print(day + timedelta(days=7))
utc = datetime(2026, 10, 17, 0, 0, tzinfo=timezone.utc)
seoul = utc.astimezone(ZoneInfo("Asia/Seoul"))
print(utc.isoformat())
print(seoul.isoformat())
2026-10-24
2026-10-17T00:00:00+00:00
2026-10-17T09:00:00+09:00

La suma añade siete días al calendario. La conversión muestra el mismo instante a las 00:00 UTC y a las 09:00 en Seúl. Añadir treinta días no equivale a avanzar un mes: los meses tienen distinta duración y sus extremos necesitan una regla de negocio.

Comparar conversión y asignación de zona

El archivo independiente zones.py permite observar una diferencia que puede pasar inadvertida si solo se mira el formato:

from datetime import datetime, timezone
from zoneinfo import ZoneInfo
utc = datetime(2026, 10, 17, 0, 0, tzinfo=timezone.utc)
print(utc.astimezone(ZoneInfo("Asia/Seoul")).isoformat())
print(utc.replace(tzinfo=ZoneInfo("Asia/Seoul")).isoformat())
try:
    print(datetime(2026, 10, 17) - utc)
except TypeError:
    print("naive and aware cannot be subtracted")
2026-10-17T09:00:00+09:00
2026-10-17T00:00:00+09:00
naive and aware cannot be subtracted

astimezone conserva el instante y adapta la hora mostrada. replace(tzinfo=...) conserva los campos del reloj y les asigna otra interpretación; no es una sustitución general de la conversión. Restar un valor ingenuo y uno consciente produce TypeError en este ejemplo. Si desconoces la zona de origen, no atribuyas UTC a los datos para silenciar el error.

zoneinfo utiliza la base IANA del sistema o el paquete tzdata. Algunos sistemas, especialmente Windows, pueden necesitar este último. La documentación de las fuentes de zonas horarias explica esa dependencia. Las reglas territoriales cambian, por lo que esos datos también requieren mantenimiento.

Probar límites antes de guardar los datos

  1. Cambia la hora UTC a 23 y comprueba que en Seúl aparece el día siguiente. Restaura después cero.
  2. Intenta interpretar 2026-02-30 con date.fromisoformat y observa ValueError.
  3. Decide si tu campo representa un día comercial o un instante y mantén esa definición en entrada, almacenamiento e informes.

Al guardar estos valores en JSON, define explícitamente su formato y conserva el desplazamiento cuando sea necesario. Una cadena con apariencia de fecha no demuestra por sí sola que el día exista o que todas sus interpretaciones de zona sean válidas.