Estoy trabajando en un proyecto de Scala y necesito asignar el tipo OffsetDateTime al tipo de Timestamp de tiempo de SQL. En DB me gustaría tener tiempos UTC.
La conversión de OffsetDateTime a Timestamp es sencilla (sugerencia de esta pregunta ) y funciona como se esperaba:
import java.time._ import java.sql.Timestamp val ofsdatetime = OffsetDateTime.now() // ofsdatetime: java.time.OffsetDateTime = 2017-04-04T21:46:33.567+02:00 val tstamp = Timestamp.valueOf(ofsdatetime.atZoneSameInstant(ZoneOffset.UTC).toLocalDateTime()) // tstamp: java.sql.Timestamp = 2017-04-04 19:46:33.567Como puede ver, se elimina la zona horaria y la marca de tiempo retrocede dos horas en el tiempo (UTC), ¡excelente!
Volver a convertir la marca de OffsetDateTime Timestamp funciona como se esperaba :
OffsetDateTime.ofInstant(Instant.ofEpochMilli(tstamp.getTime), ZoneId.systemDefault()) // java.time.OffsetDateTime = 2017-04-04T19:46:33.567+02:00 La zona horaria se ha agregado al OffsetDateTime recién creado, pero la hora no es correcta (sigue siendo UTC, necesito que esté adaptada a la zona horaria real).
¿Por qué? ¿Qué estoy haciendo mal?
Aunque java.sql.Timestamp almacena la época en milisegundos, el método .toString usa la zona horaria predeterminada para representar la cadena. Además, .valueOf interpreta LocalDateTime utilizando su zona horaria predeterminada.
La combinación de ambas cosas hace que la primera conversión "parezca" correcta, pero en realidad es incorrecta. El valor "2017-04-04 19:46:33.567" se muestra en su TZ predeterminado, no en UTC.
Porque pasó el método valueOf a LocalDateTime (UTC), pero lo interpretó como LocalDateTime (Su TZ predeterminado).
Aquí hay una prueba de que la primera conversión es incorrecta:
scala> val now = OffsetDateTime.now now: java.time.OffsetDateTime = 2017-04-04T14:50:12.534-06:00 scala> Timestamp.valueOf(now.atZoneSameInstant(ZoneId.of("UTC")).toLocalDateTime).getTime == now.toInstant.toEpochMilli res54: Boolean = false Ahora con el .atZoneSameInstant eliminado:
scala> Timestamp.valueOf(now.toLocalDateTime).getTime == now.toInstant.toEpochMilli res53: Boolean = trueLa respuesta aceptada a la pregunta de stackoverflow a la que se hace referencia es incorrecta.
Una vez que corrija la primera conversión (elimine .atZoneSameInstant ), entonces su segunda conversión debería funcionar bien.
java.sql.Timestamp es un envoltorio delgado alrededor de un valor long que representa milisegundos desde la época ( 1970-01-01T00:00:00.000 UTC ), por lo que la zona horaria UTC está implícita en java.sql.Timestamp . No puede almacenar ninguna información de zona horaria, pero implícitamente está en UTC, y mientras todos lo sepan, todo funciona. No hay forma de almacenar información de zona horaria en java.sql.Timestamp . Si necesita recordar qué zona horaria recibió en sus datos de entrada, guárdelo como una columna separada en la base de datos. Puede guardar un momento correcto en el tiempo en java.sql.Timestamp , pero no la zona horaria recibida en los datos de entrada. Para eso necesitas un campo extra.
Como le gusta que las fechas de su base de datos estén en UTC, puede recuperar los datos de la base de datos de esta manera: OffsetDateTime.ofInstant(Instant.ofEpochMilli(tstamp.getTime), ZoneId.of("UTC")) . Este será el momento correcto, pero en la zona horaria UTC. No puede recuperar de la base de datos el hecho de que OffsetDateTime estaba en la zona horaria +0200 antes de guardarlo en la base de datos, porque java.sql.Timestamp no almacena un componente de zona horaria. Si necesita esa información, debe almacenarla en una columna separada en la base de datos.