DEV Community

IagoLast
IagoLast

Posted on

UTC considered harmful

Decidí fundar TimeTime.in porque, al contrario de lo que mucha gente piensa, trabajar con fechas sigue siendo terriblemente complicado para un desarrollador. (No es casualidad que el Datepicker habitualmente sea de pago en muchas librerías de UI).

Hace no mucho tiempo, estaba en Twitter y me encontré un post de Ben Nadel, un desarrollador nada sospechoso de ser junior, contando un problema que había tenido manejando fechas y enlazando a un post explicando su solución.

Para mi sorpresa, su solución no es totalmente correcta, así que me he animado a escribir este post para evitar que más desarrolladores caigan en el mismo error.

¡Lo primero de todo!

Como no quiero hacer este post muy largo, voy a empezar introduciendo los conceptos mínimos necesarios para entenderlo.

Tiempo Civil vs UTC

Como mucha gente sabe, en Internet la mayoría de las fechas se representan usando tiempo UTC (en realidad se utiliza tiempo UNIX, que no es UTC y tiene bastantes problemas si queremos conseguir algo de seriedad, pero eso es material para otro post). Simplificando, podemos decir que el tiempo UTC representa el número de segundos a partir de un tiempo de referencia.

Como no es práctico hablar en segundos constantemente, los humanos utilizamos un sistema diferente para representar fechas, llamado tiempo civil. Los humanos no decimos “he nacido en 89891283”, decimos “he nacido el 4 de julio de 1992 en Pontevedra, España”.

Si hay algo importante en este texto, es que esta diferencia esté clara. Los humanos hablamos en tiempo civil y las máquinas en tiempo estandarizado, y si queremos tener un sistema informático que funcione, tenemos que ser capaces de transformar un formato en el otro de forma efectiva.

Husos horarios, zonas horarias y GMT offset

Simplificando, en 1884 se celebró la conferencia del meridiano, donde se acordó dividir la tierra en 24 husos horarios separados por 15º entre sí.

Los husos horarios son diferentes a las zonas horarias. Una zona horaria es una región geográfica que comparte una misma hora oficial. Es muy importante entender que una zona horaria es un concepto de tiempo civil cuyas normas pueden cambiar arbitrariamente.

Zonas horarias vs Husos Horarios

Por último, hay que hablar del offset GMT. El GMT offset es la diferencia en horas y minutos entre la hora de referencia (GMT) y una hora concreta.

(Muchas veces la gente habla de “offset UTC”, aunque cabe recordar que GMT es una zona horaria mientras que UTC es un estándar de tiempo.)

 ISO 8601

Es una normativa estándar que especifica la notación para representar, entre otros, fechas y horas. En esta normativa se indica que este es el formato para representar una fecha con horas, minutos, segundos y milisegundos:

yyyy-mm-ddThh:mm:ss.mmm

Además, este formato contempla que se pueda indicar la hora utilizando la zona horaria local, añadiendo cuánto desfase (offset) hay respecto a GMT. Si el desfase es 0, se añade una Z al final.

yyyy-mm-ddThh:mm:ss.mmmZ

Es decir, el mismo instante de tiempo (UTC) en España se llamará “26/07/2023 15:51:19 de España” y en Portugal, que está en una zona horaria anterior, se llamará “26/07/2023 14:51:19 de Portugal”.

ISO GMT UTC (ms) Tiempo civil
2023-07-26T13:51:19.724Z 2023-07-26T13:51:19.724Z 1690379479724 26/07/2023 15:51:19 de España
2023-07-26T14:51:19.724-01:00 2023-07-26T13:51:19.724Z 1690379479724 26/07/2023 14:51:19 de Portugal

Developers

Ahora que hemos visto todo esto, ¿qué implicaciones tiene para nosotros como desarrolladores?

Nivel 0: Timestamps

Mucha gente utiliza timestamps para representar fechas. Como hemos visto, los timestamps son un concepto de tiempo absoluto que no tiene en cuenta ni el offset ni la zona horaria.

Este es el problema que tiene Ben y que explica en su blog. Tiene una aplicación de deportes en la que guarda datos de entrenamientos. Tiene una funcionalidad de “racha de entrenos” donde básicamente indica qué días el usuario ha entrenado.

En un primer momento, guardaba los entrenamientos como timestamps, el problema es que un usuario en la zona este de EEUU, se encuentra en offset GMT-4, esto quiere decir que si entrena a las 5 de la mañana y a las 10 de la noche ha entrenado dos días diferentes en la zona horaria GMT, pero el mismo día en GMT-4.

Por ejemplos como este, guardar timestamps es muchas veces insuficiente si queremos conservar la semántica de las fechas.

Nivel 1: GMT offset

Una solución habitual es almacenar las fechas incluyendo el offset GMT. De esta forma, al saber el offset de un usuario, sabemos si clasificar los entrenamientos en diferentes días o no. Sin embargo, esta aproximación tiene un problema: Asumir que las zonas horarias son estáticas.

Como hemos visto previamente, una zona horaria es diferente a un offset GMT. El ejemplo típico que todos conocemos es el cambio entre el horario de verano y el horario de invierno. Además, las propias zonas horarias pueden cambiar debido a decisiones políticas. Sin ir más lejos, España cambió de huso horario en 1940.

Nivel 2: ISO + Zona Horaria

Sin entrar en muchos detalles aburridos, puedo decir sin miedo a equivocarme que la forma correcta de almacenar y transmitir fechas suele ser utilizando formato ISO + Zona Horaria.

Pongamos un ejemplo. Supongamos que tengo una aplicación que permite reservar citas médicas. Como en España la sanidad no va todo lo bien que debería, me dan cita para dentro de meses.

Tu cita ha sido programada para el Sábado 20 de julio de 2024 a las 09:00 de la mañana, hora española peninsular.

Esta fecha y hora, están expresadas en tiempo civil.

Imaginemos que guardamos la fecha en mi base de datos almacenando fecha y GMT offset en formato ISO.

2024-07-20T09:00:00.000+02:00
Enter fullscreen mode Exit fullscreen mode

Esta fecha se corresponde con un instante UTC determinado:

1721458800000
Enter fullscreen mode Exit fullscreen mode

Y a su vez se corresponde con esta fecha en GMT:

Sat, 20 Jul 2024 07:00:00 GMT
Enter fullscreen mode Exit fullscreen mode

Ahora imaginemos que entre medias, algunos países de Europa, incluido España, deciden eliminar el horario de verano. Para cuando llegue el día en nuestra base de datos, seguimos teniendo:

2024-07-20T09:00:00.000+02:00 (1721458800000 UTC)
Enter fullscreen mode Exit fullscreen mode

Que transformado al hipotético horario civil español después del cambio de hora será:

El Sábado 20 de julio de 2024 a las 08:00 de la mañana, hora española peninsular.

Sin embargo, ¿es esto lo que queremos? ¿Queremos persistir el instante UTC original al que se correspondía la fecha civil? ¿O queremos conservar la hora inicial a la que el paciente pidió la cita médica?

En mi opinión, cuando una persona pide cita para el médico a las 9 de la mañana, no le importa el tiempo UTC o GMT ni los cambios horarios que se realicen en su país entre medias. Le importa que su cita sea su hora civil local.

En mi caso cuando digo “Sábado 20 de julio de 2024 a las 09:00 de la mañana, hora española peninsular”, no me importa lo que pueda pasar entre medias, ¡quiero mi cita a las 09:00 de la mañana!

 Conclusiones

En este texto, sólo hemos tocado la superficie de todas las implicaciones y trampas que te puedes encontrar manejando fechas por poner algunas:

De momento siempre que manejes fechas te recomiendo tener este texto en cuenta y si es necesario persistir la zona horaria junto a la fecha para conservar tanta información como sea posible.

Top comments (0)