parse localdatetime how convertir cast java spring-data-jpa postgresql-9.3 java-time

java - how - LocalDateTime to ZonedDateTime



set date to localdate (1)

Tengo la aplicación web Java 8 Spring que soporta múltiples regiones. Necesito hacer eventos de calendario para la ubicación de un cliente. Así que digamos que mi servidor web y Postgres está alojado en la zona horaria MST (pero supongo que podría estar en cualquier lugar si nos vamos a la nube). Pero el cliente está en EST. Entonces, siguiendo algunas de las mejores prácticas que leí, pensé que almacenaría todas las fechas en formato UTC. Todos los campos de fecha y hora en la base de datos se declaran como TIMESTAMP.

Así que aquí es cómo tomo un LocalDateTime y me convierto a UTC:

ZonedDateTime startZonedDT = ZonedDateTime.ofLocal(dto.getStartDateTime(), ZoneOffset.UTC, null); //appointment startDateTime is a LocalDateTime appointment.setStartDateTime( startZonedDT.toLocalDateTime() );

Ahora, por ejemplo, cuando entra una búsqueda de una fecha, tengo que convertir la hora de la fecha solicitada a UTC, obtener los resultados y convertir a la zona horaria del usuario (almacenada en la db):

ZoneId userTimeZone = ZoneId.of(timeZone.getID()); ZonedDateTime startZonedDT = ZonedDateTime.ofLocal(appointment.getStartDateTime(), userTimeZone, null); dto.setStartDateTime( startZonedDT.toLocalDateTime() );

Ahora, no estoy seguro de que este sea el enfoque correcto. También me pregunto si porque voy de LocalDateTime a ZonedDateTime y viceversa, podría estar perdiendo cualquier información de la zona horaria.

Esto es lo que estoy viendo y no me parece correcto. Cuando recibo LocalDateTime desde la interfaz de usuario, obtengo esto:

2016-04-04T08:00

El ZonedDateTime =

dateTime=2016-04-04T08:00 offset="Z" zone="Z"

Y luego, cuando asigno ese valor convertido a mi cita, LocalDateTime almaceno:

2016-04-04T08:00

Siento que porque estoy almacenando en LocalDateTime, estoy perdiendo la zona horaria que convertí en ZonedDateTime.

¿Debo hacer que mi entidad (cita) use ZonedDateTime en lugar de LocalDateTime para que Postgres no pierda esa información?

---------------- Editar ----------------

Después de la excelente respuesta de Basils, me di cuenta de que tengo el lujo de no preocuparme por la zona horaria de los usuarios: todas las citas van en contra de una ubicación específica, por lo que puedo almacenar todos los horarios de fecha como UTC y luego convertirlos a la zona horaria de ubicación cuando se recuperan. Hice la siguiente question seguimiento


Postgres no tiene un tipo de datos como TIMESTAMP . Postgres tiene dos tipos para la fecha más la hora del día: TIMESTAMP WITH TIME ZONE y TIMESTAMP WITHOUT TIME ZONE . Estos tipos tienen un comportamiento muy diferente con respecto a la información de zona horaria.

  • El tipo WITH utiliza cualquier información de desplazamiento o zona horaria para ajustar la fecha y la hora a UTC , luego elimina ese desplazamiento o zona horaria; Postgres nunca guarda la información de desplazamiento / zona.
    • Este tipo representa un momento, un punto específico en la línea de tiempo.
  • El tipo WITHOUT ignora cualquier desplazamiento o información de zona que pueda estar presente.
    • Este tipo no representa un momento. Representa una vaga idea de momentos potenciales en un rango de aproximadamente 26 a 27 horas (el rango de zonas horarias en todo el mundo).

Prácticamente siempre desea el tipo WITH , como se explica aquí por el experto David E. Wheeler. El WITHOUT solo tiene sentido cuando tiene la vaga idea de una fecha y hora en lugar de un punto fijo en la línea de tiempo. Por ejemplo, "La Navidad este año comienza el 2016-12-25T00: 00: 00" se almacenará en la WITHOUT ya que se aplica a cualquier zona horaria, aún no se ha aplicado a una sola zona horaria para obtener un momento real en la línea de tiempo. Si los elfos de Papá Noel estuvieran siguiendo la hora de inicio de Eugene Oregon EE. UU., WITH tipo WITH y una entrada que incluía un desplazamiento o una zona horaria como 2016-12-25T00:00:00-08:00 que se guarda en Postgres como 2016-12-25T08:00.00Z (donde Z significa Zulu o UTC).

El equivalente de TIMESTAMP WITHOUT TIME ZONE de Postgres en java.time es java.time.LocalDateTime . Como su intención era trabajar en UTC (algo bueno), no debería usar LocalDateTime (algo malo). Ese puede ser el principal punto de confusión y problemas para ti. Sigues pensando en usar LocalDateTime o ZonedDateTime pero no deberías usar ninguno; en su lugar, debe utilizar Instant (que se explica a continuación).

También me pregunto si porque voy de LocalDateTime a ZonedDateTime y viceversa, podría estar perdiendo cualquier información de la zona horaria.

De hecho son. El punto completo de LocalDateTime es perder información de zona horaria. Así que rara vez usamos esta clase en la mayoría de las aplicaciones. De nuevo, el ejemplo de navidad. O otro ejemplo, "Política de la compañía: todas nuestras fábricas alrededor del mundo almuerzan a las 12:30 PM". Eso sería LocalTime , y para una fecha en particular, LocalDateTime . Pero eso no tiene un significado real, no es un punto real en la línea de tiempo, hasta que se aplica una zona horaria para obtener un ZonedDateTime . Esa pausa para el almuerzo se realizará en diferentes puntos de la línea de tiempo en la fábrica de Delhi que en la fábrica de Düsseldorf y de nuevo en la fábrica de Detroit.

La palabra "Local" en LocalDateTime puede ser contraintuitiva ya que no significa una localidad en particular . Cuando lea "Local" en el nombre de una clase, piense "No por un momento ... no en la línea de tiempo ... solo una idea confusa acerca de una fecha y hora un poco".

Sus servidores casi siempre deben configurarse en UTC en la zona horaria de su sistema operativo. Pero su programación nunca debe depender de esta externalidad, ya que es muy fácil para un administrador del sistema cambiarla o para que cualquier otra aplicación Java cambie la zona horaria predeterminada actual dentro de la JVM. Así que siempre especifique su zona horaria deseada / esperada. (Lo mismo ocurre con Locale , por cierto).

Resultado:

  • Estás trabajando demasiado duro.
  • Los programadores / administradores de sistemas deben aprender a "Pensar globalmente, Presentar localmente".

Durante el día de trabajo mientras usa su casco geek, piense en UTC. Solo al final del día, cuando haya cambiado a la casa de su laico, debería volver a pensar en la hora local de su ciudad.

Su lógica de negocio debe centrarse en UTC. El almacenamiento de la base de datos, la lógica de negocios, el intercambio de datos, la serialización, el registro y su propio pensamiento deben realizarse en la zona horaria UTC (y, por cierto, en el reloj de 24 horas). Cuando presente datos a los usuarios, solo aplique una zona horaria en particular. Piense en la fecha y hora de la zona como una cosa externa, no como una parte funcional de las aplicaciones internas.

En el lado de Java, use java.time.Instant (un momento en la línea de tiempo en UTC) en gran parte de su lógica empresarial.

Instant now = Instant.now();

Con suerte, los controladores JDBC se actualizarán para tratar con tipos de java.time como Instant directamente. Hasta entonces debemos usar los tipos java.sql. La antigua clase java.sql tiene nuevos métodos para la conversión a / desde java.time.

java.sql.TimeStamp ts = java.sql.TimeStamp.valueOf( instant );

Ahora pase ese objeto java.sql.TimeStamp través de setTimestamp en PreparedStatement para guardarlo en una columna definida como TIMESTAMP WITH TIME ZONE en Postgres.

Para ir en la otra dirección:

Instant instant = ts.toInstant();

Así que es fácil, pasando de Instant a java.sql.Timestamp a TIMESTAMP WITH TIME ZONE , todo en UTC. No hay zonas horarias involucradas. La zona horaria predeterminada actual de su servidor OS, su JVM y sus clientes es irrelevante.

Para presentar al usuario, aplicar una zona horaria. Use nombres de zona horaria adecuados , nunca los códigos de 3 a 4 letras, como EST o IST .

ZoneId zoneId = ZoneId.of( "America/Montreal" ); ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );

Puede ajustarse en una zona diferente según sea necesario.

ZonedDateTime zdtKolkata = zdt.withZoneSameInstant( ZoneId.of( "Asia/Kolkata" ) );

Para volver a un Instant , un momento en la línea de tiempo en UTC, puede extraer de ZonedDateTime .

Instant instant = zdt.toInstant();

No donde nosotros usamos LocalDateTime .

Si obtiene una parte de los datos sin ningún desplazamiento desde UTC o zona horaria, como 2016-04-04T08:00 , esos datos son totalmente inútiles para usted (suponiendo que no estemos hablando de los escenarios de tipo de Navidad o Almuerzo de empresa). discutido anteriormente). Una fecha y hora sin información de compensación / zona es como una cantidad monetaria sin indicar la moneda: 142.70 o incluso $142.70 - inútil. Pero USD 142.70 , o CAD 142.70 , o MXN 142.70 ... son útiles.

Si obtiene ese valor 2016-04-04T08:00 , y está absolutamente seguro del contexto de desplazamiento / zona previsto, entonces:

  1. Analizar esa cadena como un LocalDateTime .
  2. Aplique un offset-from-UTC para obtener un OffsetDateTime , o (mejor) aplique una zona horaria para obtener un ZonedDateTime .

Me gusta este código.

LocalDateTime ldt = LocalDateTime.parse( "2016-04-04T08:00" ); ZoneId zoneId = ZoneId.of( "Asia/Kolkata" ); // Or "America/Montreal" etc. ZonedDateTime zdt = ldt.atZone( zoneId ); // Or atOffset( myZoneOffset ) if only an offset is known rather than a full time zone.

Su pregunta realmente es un duplicado de muchos otros. Estos problemas se han discutido muchas veces en otras preguntas y respuestas. Le insto a que busque y estudie para obtener más información sobre este tema.

JDBC 4.2

A partir de JDBC 4.2, podemos intercambiar directamente objetos java.time con la base de datos. No es necesario volver a utilizar java.sql.Timestamp , ni sus clases relacionadas.

Almacenamiento, utilizando OffsetDateTime como se define en la especificación JDBC.

myPreparedStatement.setObject( … , instant.atOffset( ZoneOffset.UTC ) ) ; // The JDBC spec requires support for `OffsetDateTime`.

... o posiblemente use Instant directamente, si es compatible con su controlador JDBC.

myPreparedStatement.setObject( … , instant ) ; // Your JDBC driver may or may not support `Instant` directly, as it is not required by the JDBC spec.

Recuperando

OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;

Acerca de java.time

El marco java.time está integrado en Java 8 y java.time posteriores. Estas clases sustituyen a las antiguas y problemáticas clases de fecha y hora como java.util.Date , Calendar , y SimpleDateFormat .

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

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

Puede intercambiar objetos java.time directamente con su base de datos. Utilice un controlador JDBC compatible con JDBC 4.2 o posterior. No se necesitan cadenas, no se necesitan clases java.sql.* .

¿Dónde obtener las clases java.time?

  • Java SE 8 , Java SE 9 y posteriores
    • Incorporado.
    • Parte de la API de Java estándar con una implementación en paquete.
    • Java 9 agrega algunas características menores y correcciones.
  • Java SE 6 y Java SE 7
    • Gran parte de la funcionalidad de java.time está respaldada a Java 6 y 7 en ThreeTen-Backport .
  • Android
    • Versiones posteriores de las implementaciones de paquetes de Android de las clases java.time.
    • Para Android anterior (<26), el proyecto ThreeTenABP adapta ThreeTen-Backport (mencionado anteriormente). Vea Cómo usar ThreeTenABP… .

El proyecto ThreeTen-Extra extiende 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 more .