Práctica recomendada de Java para la manipulación / almacenamiento de datos para usuarios geográficamente diversos

He leído todas las demás preguntas y respuestas sobre manipulación de fechas, pero ninguna de ellas parece ofrecer una respuesta satisfactoria a mi preocupación.

Tengo un proyecto con usuarios geográficamente diversos que usa Date en algunas de sus clases y datos. Lo que pasa es que estoy buscando una manera eficiente de manipular las Fechas para los diferentes usuarios en sus respectivas zonas horarias, la mayoría de las respuestas sugieren el uso de la biblioteca Joda para la manipulación de Date , que aún no entiendo porque todavía no he encontrado ninguna operación que no se puede hacer con Java tradicional, por lo que si alguien puede explicar qué puedo hacer con Joda que no se puede hacer con Java tradicional, entonces puedo considerar usarlo.

Finalmente llegué al enfoque de usar System.currentTimeMillis() para guardar mis fechas en la base de datos (cualquier base de datos). Esto me evitaría preocuparme sobre qué zona horaria está usando la base de datos para almacenar las fechas. Si deseo consultar la base de datos para una fecha específica o rango de fechas, realizaría las consultas utilizando el valor long de la Date que deseo consultar:

 SELECT * FROM table1 WHERE date1>=1476653369000 

Y al recuperar un ResultSet , ResultSet el valor long recuperado de la base de datos en una Date legible utilizando la zona horaria del usuario que solicita los datos.

 Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(resultSet.getLong(1)); cal.setTimeZone(TimeZone.getTimeZone("Asia/Calcutta")); Date myDate = cal.getTime(); 

Según algunas opiniones que he leído, algunas personas dicen enfáticamente que el almacenamiento de System.currentTimeMillis() definitivamente no es la mejor práctica, sin embargo, por alguna razón, todos olvidan decir POR QUÉ no es recomendable. ¿Me estoy perdiendo de algo? ¿Esto causa un problema de rendimiento para las conversiones Long->Date / Date->Long ? ¿Hay algún caso de uso que no se puede lograr al usar Long lugar de Date en la base de datos? ¿Alguien puede publicar una explicación racional sobre esto?

Por otro lado, suponiendo que sigo usando los valores de Date para almacenar fechas en la base de datos, ¿hay alguna manera de evitar preocuparse por las zonas horarias mientras maneja la Date base de datos?

Gracias por adelantado.

He leído todas las otras preguntas y respuestas sobre la manipulación de fechas

No, ciertamente no los leíste todos.

  • Habría aprendido que tanto las clases heredadas de fecha y hora (como java.util.Date y java.util.Calendar ) como el proyecto Joda-Time se suplantan por las clases java.time (1,890 resultados para la búsqueda en ‘java’. hora’).
  • Habría aprendido a no rastrear los valores de fecha y hora como un recuento de la época. La depuración y el registro se vuelve muy difícil con los errores que no se descubren ya que los humanos no pueden descifrar el significado de un entero largo como fecha y hora. Y debido a que muchas granularidades de conteo (segundos enteros, milisegundos, microsegundos, nanosegundos, días completos y más) y al menos un par de docenas de épocas se emplean en varios proyectos de software crean ambigüedad sobre sus datos con suposiciones que conducen a errores, interpretaciones erróneas y Confusión.
  • Habría aprendido a utilizar los tipos de fecha y hora en su base de datos para rastrear los valores de fecha y hora.
  • Habría aprendido a trabajar y almacenar valores de fecha y hora en UTC. Ajuste en un huso horario solo cuando lo requiera la lógica o según lo esperado por el usuario para la presentación. “Piensa global, presente local”.
  • Habría aprendido que, si bien es un valiente esfuerzo de la industria, las clases heredadas de fecha y hora están mal diseñadas, son confusas y problemáticas. Vea ¿Qué pasa con Java Date & Time API? para alguna discusión. Joda-Time fue la primera buena biblioteca de fecha-hora en la industria e inspiró su reemplazo, las clases java.time integradas en Java 8 y posteriores.

Seré breve, ya que todo esto ya se ha cubierto muchas veces en Stack Overflow.

Trabaja en UTC. En Java eso significa que la clase Instant es comúnmente utilizada. La clase Instant representa un momento en la línea de tiempo en UTC con una resolución de nanosegundos (hasta nueve (9) dígitos de una fracción decimal).

 Instant instant = Instant.now(); 

Cualquier base de datos seria como Postgres rastrea los valores de fecha y hora en UTC. Su controlador JDBC maneja los detalles de la conversión de datos almacenados internamente de la base de datos a un tipo de Java. Los controladores JDBC que cumplen con JDBC 4.2 y posteriores pueden manejar directamente los tipos de java.time a través de los métodos PreparedStatement::setObject & ResultSet::getObject .

 myPreparedStatement.setObject( … , instant ); 

Para los controladores no conformes, recurra al uso de tipos java.sql como java.sql.Timestamp para comunicarse con la base de datos y convertir a / desde tipos java.time a través de nuevos métodos agregados a las clases antiguas. Los detalles internos de cómo maneja la base de datos los valores de fecha y hora pueden ser bastante diferentes de cómo lo hace java.time. En su mayor parte, el controlador JDBC oculta todos los detalles esenciales de usted. Pero un problema crítico es la resolución, que debe estudiar en su base de datos. Las clases java.time manejan los tiempos de fecha con una resolución de hasta nanosegundos pero su base de datos puede no. Por ejemplo, Postgres usa una resolución de microsegundos . Así que ir de ida y vuelta significa pérdida de datos. Desea utilizar los métodos de truncamiento en las clases java.time para que coincida con su base de datos.

 myPreparedStatement.setTimestamp( … , java.sql.Timestamp.from( instant ) ); 

Entonces, no hay zona horaria involucrada. Así que no “preocuparse por las zonas horarias mientras maneja la fecha de la base de datos”.

Cuando desee ver el mismo momento a través de la lente del reloj de pared de una región, aplique un ZoneId para obtener un ZonedDateTime .

 ZoneId z = ZoneId.of( "Asia/Kolkata" ); ZonedDateTime zdt = instant.atZone( z ); 

Al volver a fechar una fecha zonal en la base de datos, extraiga un Instant .

 Instant instant = zdt.toInstant(); 

Tenga en cuenta que, en cualquier momento, la fecha y la hora varían según el huso horario en todo el mundo. Por lo tanto, si importa un momento exacto, como cuando vence un contrato, tenga cuidado con el uso de un valor de solo fecha. Utilice un valor de fecha y hora para el momento exacto o almacene el huso horario deseado junto con la fecha, para que el momento exacto pueda calcularse más tarde.

 LocalDate ld = LocalDate.of( 2016, 1 , 1 ); // Determine the first moment of 2016-01-01 as it happens in Kolkata. ZonedDateTime zdt = ld.atStartOfDay( ZoneId.of( "Asia/Kolkata" ) ); Instant instant = zdt.toInstant(); // Adjust to UTC and store. 

Acerca de java.time

El marco java.time está integrado en Java 8 y posterior. Estas clases suplantan a las antiguas clases heredadas de fecha y hora, como java.util.Date , .Calendar y java.text.SimpleDateFormat .

El proyecto Joda-Time , ahora en modo de mantenimiento , aconseja la migración a java.time.

Para obtener más información, consulte el Tutorial de Oracle . Y busque Stack Overflow para obtener muchos ejemplos y explicaciones. La especificación es JSR 310 .

¿Dónde obtener las clases de java.time?

  • Java SE 8 y SE 9 y posteriores
    • Incorporado.
    • Parte de la API Java estándar con una implementación integrada.
    • Java 9 agrega algunas características y correcciones menores.
  • Java SE 6 y SE 7
    • Gran parte de la funcionalidad de java.time se transfiere a Java 6 y 7 en ThreeTen-Backport .
  • Androide
    • El proyecto ThreeTenABP adapta ThreeTen-Backport (mencionado anteriormente) específicamente para Android.
    • Vea Cómo usar …

El proyecto ThreeTen-Extra amplía java.time con clases adicionales. Este proyecto es un terreno de prueba para posibles adiciones futuras a java.time. Puede encontrar algunas clases útiles aquí, como Interval , YearWeek , YearQuarter y más .

¿Qué puedo hacer con Joda que no se puede hacer con Java tradicional?

En realidad, no se trata de lo que puede o no puede hacer con Java tradicional. Se trata más acerca de cómo funciona la API de la biblioteca para que escribir mejor (más sólido y correcto) código que el Java tradicional.

Tanto es así que, a partir de Java 8, la API de Joda se copió / adoptó más o menos al pie de la letra con solo los nombres de los paquetes modificados e incorporados en la biblioteca estándar Java 8 SE.

Por lo tanto, si está utilizando Java 8, debe elegir la nueva API, y si no, debería considerar que el uso de Joda le permitirá, al menos, comprar un camino sencillo hacia la actualización / migración a Java 8 cuando sea capaz de hacerlo.

Algunos ejemplos:

  • API consistente para tipos de fecha y hora.
  • Los objetos de fecha / hora son inmutables, las manipulaciones devuelven nuevas instancias del tipo que representa el valor alterado. (Como Java Strings). Esto hace que sea más fácil razonar sobre la reutilización de objetos de fecha / hora.
  • Por diseño, evita mezclar DST y valores / operaciones dependientes de la zona horaria con DST y los agnósticos de zona horaria. Esto hace que sea mucho más fácil escribir código que funcione de manera consistente y correcta y que no tenga casos de esquina que dependan de la zona horaria / locale / date / time-of-day.
  • Se puede esperar que los valores predeterminados para cosas como toString() permitan serializar / deserializar correctamente con un mínimo esfuerzo.
  • Casos de esquina dependientes de la cultura / localidad y cosas de las que aún no tenía conocimiento (por ejemplo, ¿sabía algo sobre el calendario coreano tradicional?) Que le ahorra una gran cantidad de problemas al convertir sus fechas entre sistemas locales y de calendario. Además: una gran cantidad de opciones de formato.
  • El concepto de Instant s para representar sellos de tiempo “absolutos” que es útil cuando se trabaja con sistemas distribuidos geográficamente (cuando los relojes predeterminados del sistema / zonas horarias y las reglas de horario de verano pueden diferir) o para la interoperación porque usa UTC.

EDITAR para agregar:

Según algunas opiniones que he leído, algunas personas dicen enfáticamente que el almacenamiento de System.currentTimeMillis() definitivamente no es la mejor práctica, sin embargo, por alguna razón, todos olvidan decir POR QUÉ no es recomendable. ¿Me estoy perdiendo de algo?

System.currentTimeMillis() tiene algunas desventajas. El gran inconveniente es que el tipo de reloj está mal definido. Podría ser un reloj monotónico, podría ser un reloj sujeto a horario de verano y zona horaria o podría ser una hora UTC. Tampoco es necesariamente un reloj preciso, en realidad no se garantiza que sea preciso hasta el milisegundo. Pase lo que pase a mano para algo que funcionará como una apariencia de la hora actual en el momento actual, básicamente.

Esto significa que si desea usar varios servidores para manejar solicitudes entrantes, por ejemplo, se vuelve complicado cuando debe considerar trabajar con la salida de System.currentTimeMillis() desde el servidor A en el contexto de su progtwig que se ejecuta en un servidor diferente B al día siguiente, por ejemplo.