Zonas horarias: el mayor reto técnico de UTasks
Antes de crear UTasks nos pusimos un objetivo ambicioso: hacer una herramienta que fuera a la vez sencilla, cómoda y fiable. Pero de todos los problemas técnicos que tuvimos que resolver, el manejo de las zonas horarias resultó ser uno de los más difíciles.
Cómo descubrimos que las zonas horarias son difíciles de verdad
Sección titulada «Cómo descubrimos que las zonas horarias son difíciles de verdad»Casi desde el principio, personas de distintos países empezaron a usar nuestro gestor de tareas, y enseguida nos encontramos con el reto de admitir varias zonas horarias.
Las zonas horarias no son solo la diferencia de hora entre regiones. Esconden una complejidad sorprendente: los cambios de hora estacionales (horario de verano y de invierno), los cambios históricos de las normas, las distintas fechas de cambio en cada país e incluso en las regiones de un mismo país. Si a eso le sumas las preferencias de los usuarios, la programación de tareas para fechas concretas y la sincronización automática entre dispositivos, se entiende el tamaño del problema.
Términos. Porque no hay forma de evitarlos.
Sección titulada «Términos. Porque no hay forma de evitarlos.»Una zona horaria tiene dos propiedades básicas: un nombre (por ejemplo, Moscú) y un desfase, la diferencia de hora respecto a la zona cero, medida en minutos.
Ejemplo: la zona horaria de Moscú: nombre, Moscú; desfase, +3 horas respecto a UTC, que equivale a −180 minutos.
UTC (tiempo universal coordinado): el punto de referencia mundial aceptado para la hora en la Tierra.
GMT (hora media de Greenwich): la zona cero, con un desfase de 0. Aunque GMT y UTC representan el mismo desfase, GMT aparece más a menudo en las interfaces porque es el término más conocido. En UTasks, por ejemplo, la hora de Moscú se muestra como GMT+3.
Toda la lógica de las zonas horarias en UTasks funciona respecto a UTC, pero la interfaz muestra los desfases respecto a GMT para que se lean mejor.
Cómo maneja UTasks las zonas horarias
Sección titulada «Cómo maneja UTasks las zonas horarias»Elección de la zona horaria
Sección titulada «Elección de la zona horaria»UTasks gestiona la hora teniendo en cuenta dos datos:
- La zona horaria del dispositivo del usuario
- La zona horaria elegida en la configuración de UTasks
Cuando el usuario se fía de la zona horaria del dispositivo
Sección titulada «Cuando el usuario se fía de la zona horaria del dispositivo»Si el usuario no ha fijado una zona horaria propia, UTasks se sincroniza automáticamente con la zona horaria del sistema del dispositivo.
Ejemplo: un usuario en Moscú usa la hora de Moscú (GMT+3). Si viaja a Novosibirsk, UTasks toma la nueva zona horaria de la configuración del sistema del teléfono.
Cuando el usuario fija la zona horaria a mano en UTasks
Sección titulada «Cuando el usuario fija la zona horaria a mano en UTasks»Un usuario puede fijar una zona horaria concreta en la configuración de UTasks. Esa zona horaria se mantiene fija esté donde esté físicamente el dispositivo.
Ejemplo: un usuario trabaja con la hora de Moscú, pero está de viaje de trabajo en Vladivostok (GMT+10). En la app elige Moscú (GMT+3) para que las tareas se muestren en su zona horaria de siempre. Tendrá que llevar las dos zonas horarias en la cabeza para no liarse, pero no le envidiamos.
La hora de la tarea
Sección titulada «La hora de la tarea»Además de la diferencia entre la zona horaria del sistema y la de la app, hay otra capa de complejidad ligada a cómo funciona la hora de las tareas.
Si una tarea solo tiene fecha y no una hora concreta, aparece el mismo día, digamos el lunes 28 de octubre, para los usuarios de Moscú, Vladivostok y Lisboa por igual.
Pero si la tarea tiene una hora concreta, los usuarios de Moscú y de Vladivostok la verán a horas distintas en su reloj, porque las ciudades están en zonas horarias diferentes. Un usuario de Lisboa, además, tendrá que lidiar con el cambio al horario de verano.
El caso más sencillo
Sección titulada «El caso más sencillo»Imaginemos (puramente en hipótesis) que Denis vive en Kazán, que tiene un UTC+3 fijo, sin horario de verano.
Denis abre UTasks y el sistema:
- lee su zona horaria del dispositivo
- guarda el desfase de esa zona horaria: −180 minutos (para pasar la hora local a UTC hay que restar 180 minutos; puede parecer poco intuitivo frente al formato habitual «GMT+3», pero en programación lo normal es trabajar con la hora local y convertirla a UTC para guardarla)
Denis crea una tarea para el 10 de noviembre a las 10:00, hora local. Para guardarla, UTasks lee el desfase guardado (−180 min) y convierte la hora local (10:00) a UTC restando el desfase.
Resultado: la tarea se guarda como 10 de noviembre, 07:00 UTC.
¿Por qué guardarlo todo en UTC?
Sección titulada «¿Por qué guardarlo todo en UTC?»Guardar todos los datos de hora en UTC simplifica el procesamiento en las apps en las que los usuarios trabajan desde distintas zonas horarias:
- Las notificaciones se envían correctamente según la zona horaria de cada participante
- Una misma franja horaria se muestra correctamente a usuarios de distintas regiones
- No hay conversiones repetidas en el servidor: toda la transformación se hace en el cliente
Cuando Denis vuelve a mirar su tarea, el servidor devuelve la hora en UTC (10 de noviembre, 07:00). UTasks vuelve a aplicar el desfase (−180 min), y Denis ve la tarea el 10 de noviembre a las 10:00, su hora local.
Cómo ven la misma tarea los compañeros de Denis:
Sección titulada «Cómo ven la misma tarea los compañeros de Denis:»Denis crea una tarea para todo el equipo: el 10 de noviembre a las 10:00, hora de Moscú.
La tarea se guarda como 07:00 UTC.
- Liosha (Kaliningrado, GMT+2): 07:00 UTC + 2 h = 09:00, hora de Kaliningrado
- Kamil (Astaná, GMT+6): 07:00 UTC + 6 h = 13:00, hora de Astaná
- Leysan (Maldivas, GMT+5): 07:00 UTC + 5 h = 12:00, hora de Maldivas (en pleno calor)
Este algoritmo sencillo funciona bien mientras la zona horaria del sistema del usuario coincida con el lugar donde está y no cambie.
Un caso más complicado
Sección titulada «Un caso más complicado»Las cosas se complican cuando el usuario elige en la app una zona horaria distinta de la del sistema del dispositivo.
Supongamos que Denis se ha mudado a Lisboa: desayuna pastel de nata, se relaja con un oporto y va a clases de defensa personal, pero mentalmente sigue en Kazán, vendiendo su garaje y cincuenta tarros de cristal vacíos. En la configuración de UTasks elige a mano la hora de Moscú (UTC+3) para mantener todas sus tareas con el horario de siempre.
Denis está físicamente en Lisboa, pero crea una tarea para «la semana que viene a las 10:00», con la idea de que esa hora sea la de Moscú.
El problema: todas las operaciones con fechas en el código se hacen en el contexto de la zona horaria del sistema del dispositivo, en este caso, Lisboa (UTC+1). Si UTasks usara un algoritmo ingenuo, procesaría la fecha con la zona horaria del sistema (Lisboa) y no con la de Moscú.
Qué sale mal con el enfoque ingenuo:
Sección titulada «Qué sale mal con el enfoque ingenuo:»Denis pone una tarea para las 10:00 hora de Moscú → se aplica el desfase de Lisboa (+1 en verano) → la tarea se envía al servidor como 09:00 UTC (que son las 12:00 en Moscú) en lugar de las 07:00 UTC previstas (las 10:00 en Moscú) → la tarea se guarda con la hora equivocada, aunque la app muestre unas 10:00 aparentemente correctas → las notificaciones llegan con dos horas de retraso.
Nuestra solución:
Sección titulada «Nuestra solución:»- UTasks calcula la diferencia entre la zona horaria del sistema del dispositivo (Lisboa) y la que el usuario eligió en la configuración (Moscú). En este ejemplo: 2 horas (UTC+3 − UTC+1).
- Antes de guardar, UTasks ajusta la hora local con esa diferencia: Denis escribió las 10:00 hora de Moscú → se corrige la hora local del dispositivo (Lisboa): 10:00 − 2 h = 08:00, hora de Lisboa.
- UTasks convierte la hora ajustada a UTC: 07:00 UTC, que son justo las 10:00 en Moscú, tal como esperaba Denis.
El caso más difícil: cambios de desfase dentro de una zona horaria
Sección titulada «El caso más difícil: cambios de desfase dentro de una zona horaria»La lógica se complica todavía más cuando el desfase de una zona horaria cambia por el paso al horario de verano o de invierno.
Tomemos Lisboa, donde los relojes se atrasan el 27 de octubre a las 02:00, hora local de verano.
El 10 de octubre (cuando Lisboa todavía tiene el horario de verano), Denis compra entradas para un concierto de Beyoncé el 10 de noviembre (cuando Lisboa ya tendrá el horario de invierno) y crea un recordatorio en UTasks: 10 de noviembre, 10:00 – «Comprar flores para Beyoncé».
Esto plantea varias preguntas:
- ¿Denis se refiere a las 10:00 en horario de invierno (UTC+0) o a las 10:00 en horario de verano (UTC+1)?
- ¿Cómo debe tener en cuenta UTasks el cambio de desfase para guardar la hora correcta?
- ¿Un ramo de 20 € está a la altura de Beyoncé, o debería saltarse el oporto la noche anterior y darse el capricho de uno de 25 €?
Los cambios de desfase dependen de las normas vigentes de las zonas horarias, que los gobiernos pueden cambiar y cambian (suprimen el horario de verano, cambian la fecha del cambio, etc.). Por eso es imposible predecir el comportamiento exacto sin información actualizada.
Solución: usar la hora del sistema del dispositivo
Sección titulada «Solución: usar la hora del sistema del dispositivo»El sistema operativo conoce las normas vigentes del horario de verano. En lugar de mantener su propia base de datos de zonas horarias, UTasks se lo delega al sistema operativo:
- Pedir la hora formateada para la zona horaria de destino. Cuando Denis elige una fecha en el calendario (digamos, el 10 de noviembre a las 10:00), UTasks le pide al sistema: «Formatea esta fecha para la zona horaria de Lisboa».
- Para el 10 de octubre (horario de verano), el sistema devuelve 10:00, 10 de octubre, UTC+1
- Para el 10 de noviembre (horario de invierno), el sistema devuelve 10:00, 10 de noviembre, UTC+0. El sistema operativo aplica automáticamente el desfase correcto para la fecha pedida.
- Sacar el desfase de la respuesta del sistema. El resultado formateado incluye el desfase (+1 en verano, 0 en invierno). UTasks lo lee y lo usa.
- Ajustar la hora antes de enviarla al servidor. UTasks calcula la diferencia entre la zona horaria del sistema del dispositivo (Moscú, UTC+3) y la que eligió el usuario (Lisboa), y ajusta la hora en consecuencia: Denis elige el 10 de noviembre a las 10:00. Zona horaria del dispositivo: Moscú (UTC+3). Cálculo: 10:00 − 3 + 0 = 07:00 UTC. El servidor recibe la hora ya corregida según la zona horaria del usuario y su desfase vigente.
Para mantener los desfases al día, UTasks comprueba con regularidad si ha cambiado el desfase de la zona horaria del sistema (por ejemplo, después del cambio del 28 de octubre). Si ha cambiado, la app se actualiza y a partir de entonces usa los valores nuevos.
Así, cuando Denis crea el 10 de octubre una tarea para el 10 de noviembre a las 10:00, UTasks da por hecho que sabe que va a cambiar la hora y que se refiere al desfase de invierno (UTC+0). El desfase correcto para el 10 de noviembre se aplica al guardar, y la hora se mantiene exacta.
Conclusiones
Sección titulada «Conclusiones»El manejo de las zonas horarias resultó ser uno de los retos técnicos más exigentes al crear UTasks, y uno de los que más nos enseñaron.
El principio básico: guardarlo todo en UTC es la base fiable que mantiene sincronizados a los usuarios de distintas regiones.
Pero crear una interfaz de verdad cómoda exige tener en cuenta muchas capas de matices: mostrar bien la hora según la zona horaria local y la elegida por el usuario, enviar las notificaciones a tiempo y respetar las normas de los desfases estacionales.
Nuestra solución equilibra la automatización (la sincronización con la hora del sistema del dispositivo) y la flexibilidad (la posibilidad de fijar la zona horaria a mano).
Una cosa más: nuestro desarrollador Liosha ejercita este músculo con regularidad cambiando a mano las zonas horarias en la app y comprobando los resultados uno a uno. Así detectamos los casos límite sutiles que ningún razonamiento abstracto puede prever del todo.