
JGlobalDateTime: compara instantes con zonas horarias fácilmente en Java
El desarrollo de aplicaciones ha evolucionado significativamente en la última década. Nadie hoy en día desarrolla aplicaciones (móviles, web o de escritorio) sin tener en cuenta que los usuarios potenciales pueden estar repartidos por todo el globo. Y, siendo así… hay que pensar en la sincronización. Nos guste o no nos guste, las zonas horarias existen y hay que lidiar con ellas. Preguntas como ¿Las 14.35 en Pekín coincide con las 21.12 en Afganistan? ¿Es antes o es después? pueden tener más o menos implicaciones dependiendo de qué estemos desarrollando. ¿Tenemos opciones en Java? Si las tenemos.
De un vistazo
Antes de la llegada de la versión 8 de Java, las opciones para gestionar las distintas zonas horarias dentro de nuestros desarrollos no eran precisamente abundantes ni demasiado buenas. Librerías como Joda-Time vinieron a cubrir ese espacio ofreciendo una solución más que satisfactoria. Tan satisfactoria que la librería ha ido creciendo y creciendo y ofrece una enorme cantidad de opciones. Si sólo necesitas realizar un uso muy limitado de la comparación entre zonas horarias, quizás te venga un poco grande.
Java 8 introdujo un nueva API para la gestión de fechas, horas, calendarios… con información de la zona horaria. Esta API simplifica enormemente el trabajo encomparación con las versiones predecesoras. Pero, como es habitual en Java, ofrece primitivas básicas que, si no se encapsulan, hacen el código fuente muy farragoso.
¿Qué es un instante?
Antes de continuar, me gustaría explicar algo que a muchas personas les confunde. Independientemente de que una fecha/hora se represente en distintas zonas horarias, el instante al que hacen referencia es único. Con un ejemplo se ve más claro:
- Alguien a las 16.00 en Madrid envía un SMS.
- A la misma vez, alguien en Londres, envía otro. Pero allí son las 15.00.
Es obvio que el instante, el momento, es el mismo. Si representamos este instante según la zona de Madrid, ambos han ocurrido a las 16.00. Pero si lo hacemos con la zona de Londres, ambos han ocurrido a las 15.00. Si cada uno lo representa según su zona horaria, parece que no es el mismo instante. Pero sólo lo parece.
La clave, por tanto, consiste en tener mecanismos para poder comparar dos fecha/hora independientemente de la zona horaria a la que se encuentre referenciada. Si algo ha pasado antes, a la vez o después que otro evento en el tiempo, esto debe poder saberse independientemente de la representación que se realice de ese instante.
La librería JGlobalDateTime
Hace un tiempo, me decidí a escribir una pequeña librería Java que me ayudara con la gestión y comparación de fechas y horas con información de zona horaria. La llamé JGlobalDateTime y la publiqué en GitHub para que cualquiera pudiera utilizarla y modificarla a su antojo.
¿Por qué otra librería para lo mismo?
Por diversas razones. El contexto en el que yo necesitaba algo así era el siguiente:
- Un backend java, tras toda una serie de firewalls, balanceadores, y un Enterprise Service Bus (ESB).
- Una aplicación cliente para Android.
- Aproximadamente 1.000.000 de usuarios repartidos en más de 80 países, usando la App móvil contra el backend.
Cada cliente, usuario de la aplicación Android, envía datos al backend. Los dato de todos los clientes están relacionados y el orden en el que dichos datos fueron generados por los distintos clientes (no el orden de llegada al backend), tenía especial relevancia. Necesitaba, por tanto, comparación de fechas y horas con zona horaria.
El cliente móvil enviaba dichos datos en formato JSON vía una conexión TLS y el backend los recibía y los procesaba teniendo en cuenta el instante en el que dichos datos fueron generados. Con lo cual me venía estupendamente poder comparar a partir de una fecha en formato texto.
Y, tras el procesado, el backend almacenaba en base de datos la información relativa al instante de origen de los datos, por lo que también era interesante poder transfomar dicha información en el formato del SGBDR que se estaba usando: MySQL/MariaDB.
Además, y seguramente esto es lo que más me decició a desarrollar JGlobalDateTime, las dependencias de la librería que utilizara debían ser pocas y el tamaño que tuviese el correspondiente JAR, tenían que ser mínimo. Joda-Time se me iba a bastante más de medio MB y la API de Java 8 «a pelo» me encenagaba el código fuente.
¿Para qué es útil JGlobalDateTime?
Pues comentado lo anterior, si tienes una aplicación con un backend y un cliente que se utiliza en cualquier parte del mundo y necesitas enviar información textual entre ambas partes… te viene bien JGlobalDateTime. Si además el cliente es móvil (tienes requisitos de espacio en la App) y vas a almacenar datos relativos a fechas y horas en base de datos, te vendrá aún mejor.
¿Qué licencia tiene?
La subí a GitHub bajo los términos de la licencia Apache 2.0. Puedes utilizarla, hacer un fork, ampliarla (si me haces pull-request de los cambios, mejor) bajo los términos de dicha licencia. El sitio del proyecto es JGlobalDateTime y puedes descargarte el JAR para utilizarlo, de la sección de releases.
¿Cómo funciona?
La idea principal es que JGlobalDateTime establece un punto de referencia común a partir del cual se realizan las comparaciones. En realidad da igual cuál sea, siempre que sea el mismo. A partir de ahí, algo que es hecho internamente, la operación es sencilla.
Importar el paquete
import com.manolodominguez.jglobaldatetime.JGlobalDateTime;
Crear variables
Las que sean necesarias. Por ejemplo, el siguiente fragmente crea cuatro.
JGlobalDateTime gdt1; JGlobalDateTime gdt2; JGlobalDateTime gdt3; JGlobalDateTime gdt4;
Instanciar variables
Existen múltiples constructores, lo que te permitirá instanciar las variables de diversas formas. Hay más, pero aquí van unos ejemplos:
// Con este constructor, creamos un JGlobalDateTime cuyo valor es la // fecha y hora actual con la zona horaria por defecto del sistema // donde esté corriendo el software. gdt1 = new JGlobalDateTime(); // Con este constructor creamos un JGlobalDateTime a partir de una String // en formato ZonedDateTime. Esto es lo que yo recibía en un mensaje JSON // desde cualquier parte del mundo. gdt2 = new JGlobalDateTime("2017-08-20T14:20:18.811-05:00[America/Chicago]"); // Con este constructor creamos un JGlobalDateTime a partir de un instante // concreto en milisegundos desde EPOCH. Automáticamente se referencia a la // zona horaria configurada en JGlobalDateTime gdt3 = new JGlobalDateTime(1428348018845L); // Con este constructor, creamos una JGlobalDateTime a partir de un Timestamp. // En este caso el Timestamp se crea "al vuelo", pero podría ser un Timestamp // devuelto por el SGBDR a través de una conexión JDBC. gdt4 = new JGlobalDateTime(new Timestamp(1428348018845L));
Acceder al valor de las variables
De esta forma se pueden acceder a la información de forma sencilla. En el siguiente caso, por ejemplo, se obtiene su valor en formato ZonedDateTime String, aunque cada variable se haya instanciado de una forma distinta. Por defecto, todas están transformadas/referenciadas a la misma zona horaria.
// Mostramos el contenido de todas las JGlobalDateTime. Todas // están referenciadas a la misma zona. // Si no se cambia, por defecto esta zona es Europe/Madrid. System.out.println("gdt1: " + gdt1.toNormalizedDateTimeString()); System.out.println("gdt2: " + gdt2.toNormalizedDateTimeString()); System.out.println("gdt3: " + gdt3.toNormalizedDateTimeString()); System.out.println("gdt4: " + gdt4.toNormalizedDateTimeString());
Cambiar la zona
En cualquier momento a cualquier instancia JGlobalDateTime se le puede cambiar la zona de referencia, lo cual es útil en diversas ocasiones.
// Referenciamos gdt1 a Taití gdt1.changeZoneID("Pacific/Tahiti"); // Referenciamos gdt3 a Sidney gdt3.changeZoneID("Australia/Sydney"); // Referenciamos gdt4 al Polo Sur. gdt4.changeZoneID("Antarctica/South_Pole");
Hacer comparaciones
Este era el objetivo principal de la librería. Pueden compararse dos JGlobalDateTime de forma sencilla. Además, no importa a qué zona horaria esté referenciada cada JGlobalDateTime: podrás comparar una hora que te venga referenciada a Pekín con una hora local de Madrid, por ejemplo.
// ¿Ha ocurrido gdt1 antes que gdt2? if (gdt1.isBefore(gdt2)) { System.out.println(gdt1.toNormalizedDateTimeString() + " happened before " + gdt2.toNormalizedDateTimeString()); // ¿Ha ocurrido después? } else if (gdt1.isAfter(gdt2)) { System.out.println(gdt1.toNormalizedDateTimeString() + " happened after " + gdt2.toNormalizedDateTimeString()); // Ok, ha ocurrido al mismo tiempo. } else { System.out.println(gdt1.toNormalizedDateTimeString() + " happened at the same time than " + gdt2.toNormalizedDateTimeString()); }
incluso pueden hacerse operaciones más difusas, pero igualmente útiles, del tipo «¿Ha pasado hace más de XXX días?» o «¿Ocurrirá en menos de una semana?».
// ¿Ocurrió gdt3 hace más de 3 años? if (gdt3.happenedSinceMoreThan(3, ChronoUnit.YEARS)) { System.out.println("gdt3 " + gdt3.toNormalizedDateTimeString() + " happened since more than 3 years ago"); // Ok, ocurrió hace tres años o menos } else { System.out.println("gdt3 " + gdt3.toNormalizedDateTimeString() + " happened 3 years ago or less"); } // ¿Quedan menos de siete días para que ocurra gdt1? if (gdt1.isGoingToHappenInLessThan(7, ChronoUnit.DAYS)) { System.out.println("gdt1 " + gdt1.toNormalizedDateTimeString() + " will happen in less than 7 days"); // Ok, quedan siete días o más. } else { System.out.println("gdt1 " + gdt1.toNormalizedDateTimeString() + " will happen in 7 days or more"); }
Añadir o sustraer unidades
Si trabajas en estos temas sabes que en ocasiones es necesario sumar días, semanas, horas… a una fecha concreta. JGlobalDateTime permite hacerlo de forma sencilla (y siempre podrás volver al valor original utilizando un método para tal efecto).
// Añadimos 4 minutos a gdt2 gdt2.increase(4, ChronoUnit.MINUTES); // Restamos un año a gdt3 gdt3.decrease(1, ChronoUnit.YEARS);
Conclusión
JGlobalDateTime tiene muchas otras opciones. En este artículo he intentado dejar plasmadas sólo las opciones más importantes. Está liberada con una licencia opensource bastante permisiva, por lo que puede serte útil en un momento dado y es muy probable que no tengas conflictos de licencias al incluirla en tu software. Si necesitas operaciones muy avanzadas con zona horaria, con calendarios… entonces usa otra librería como Joda-Time, pero si tus pretensiones son más reducidas, probablemente encuentres JGlobalDateTime una herramienta muy útil y que añadirá muy poco espacio extra a tu proyecto. En cualquier caso, lo importante es que haya opciones para elegir.
¿Qué te parece esta librería?