Java-Convertir java.time.Instant a java.sql.Timestamp sin desplazamiento de zona
date (4)
Clases equivocadas de usar
LocalDateTime ldt = LocalDateTime.ofInstant(Instnant.now(), ZoneOffset.UTC);
Timestamp current = Timestamp.valueOf(ldt);
Dos problemas con ese código.
En primer lugar, nunca mezcle las clases modernas de java.time ( LocalDateTime
aquí) con las antiguas y antiguas clases de fecha y hora ( java.sql.Timestamp
aquí). El marco java.time reemplaza por completo a las terribles clases anteriores, a partir de la adopción de JSR 310 . Nunca más debe volver a utilizar la Timestamp
: a partir de JDBC 4.2 podemos intercambiar directamente objetos java.time con la base de datos.
El otro problema, es que la clase LocalDateTime
no puede, por definición, representar un momento. A propósito carece de una zona horaria o desplazamiento desde UTC. Use LocalDateTime
solo cuando se LocalDateTime
a una fecha con hora del día en cualquier lugar o en cualquier lugar , en otras palabras, cualquiera o todos los momentos más en un rango de aproximadamente 26 a 27 horas (los extremos actuales de las zonas horarias de todo el mundo).
No use LocalDateTime
cuando se LocalDateTime
a un momento específico, un punto específico en la línea de tiempo. En su lugar, utilice:
-
Instant
(siempre en UTC) -
OffsetDateTime
(una fecha con hora del día y con offset-from-UTC) -
ZonedDateTime
(una fecha con hora del día y con una zona horaria).
Entonces trato de crear el objeto Timestamp
No lo hagas
Nunca uses java.sql.Timestamp
. Sustituido por java.time.Instant
. Ver más abajo para más información.
Momento actual
Para capturar el momento actual en UTC utiliza cualquiera de estos:
Instant instant = Instant.now() ;
…o…
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC );
Los dos representan lo mismo, un momento en UTC.
Base de datos
Aquí hay un ejemplo de SQL y el código Java para pasar el momento actual a la base de datos.
El ejemplo utiliza el motor de base de datos H2 , integrado en Java.
sql = "INSERT INTO event_ ( name_ , when_ ) " + "VALUES ( ? , ? ) ;";
try ( PreparedStatement preparedStatement = conn.prepareStatement( sql ) ; ) {
String name = "whatever";
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC );
preparedStatement.setString( 1 , name );
preparedStatement.setObject( 2 , odt );
preparedStatement.executeUpdate();
}
Aquí hay una aplicación de ejemplo completa usando ese código.
package com.basilbourque.example;
import java.sql.*;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.UUID;
public class MomentIntoDatabase {
public static void main ( String[] args ) {
MomentIntoDatabase app = new MomentIntoDatabase();
app.doIt();
}
private void doIt ( ) {
try {
Class.forName( "org.h2.Driver" );
} catch ( ClassNotFoundException e ) {
e.printStackTrace();
}
try (
Connection conn = DriverManager.getConnection( "jdbc:h2:mem:moment_into_db_example_" ) ;
Statement stmt = conn.createStatement() ;
) {
String sql = "CREATE TABLE event_ (/n" +
" id_ UUID DEFAULT random_uuid() PRIMARY KEY ,/n" +
" name_ VARCHAR NOT NULL ,/n" +
" when_ TIMESTAMP WITH TIME ZONE NOT NULL/n" +
") ; ";
System.out.println( sql );
stmt.execute( sql );
// Insert row.
sql = "INSERT INTO event_ ( name_ , when_ ) " + "VALUES ( ? , ? ) ;";
try ( PreparedStatement preparedStatement = conn.prepareStatement( sql ) ; ) {
String name = "whatever";
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC );
preparedStatement.setString( 1 , name );
preparedStatement.setObject( 2 , odt );
preparedStatement.executeUpdate();
}
// Query all.
sql = "SELECT * FROM event_ ;";
try ( ResultSet rs = stmt.executeQuery( sql ) ; ) {
while ( rs.next() ) {
//Retrieve by column name
UUID id = ( UUID ) rs.getObject( "id_" ); // Cast the `Object` object to UUID if your driver does not support JDBC 4.2 and its ability to pass the expected return type for type-safety.
String name = rs.getString( "name_" );
OffsetDateTime odt = rs.getObject( "when_" , OffsetDateTime.class );
//Display values
System.out.println( "id: " + id + " | name: " + name + " | when: " + odt );
}
}
} catch ( SQLException e ) {
e.printStackTrace();
}
}
}
Cadena de análisis
Con respecto a un comentario relacionado de Melnyk, aquí hay otro ejemplo basado en el código de ejemplo anterior. En lugar de capturar el momento actual, este código analiza una cadena.
La cadena de entrada carece de cualquier indicador de zona horaria o offset-from-UTC . Entonces, analizamos como LocalDateTime
, teniendo en cuenta que esto no representa un momento, no es un punto en la línea de tiempo.
String input = "22.11.2018 00:00:00";
DateTimeFormatter f = DateTimeFormatter.ofPattern( "dd.MM.uuuu HH:mm:ss" );
LocalDateTime ldt = LocalDateTime.parse( input , f );
ldt.toString (): 2018-11-22T00: 00
Sin embargo, se nos informó que la cadena debía representar un momento en UTC, pero el remitente cometió un error y no pudo incluir esa información (como una Z
o +00:00
en el extremo para indicar UTC). Así que podemos aplicar un desplazamiento desde UTC de cero horas-minutos-segundos para determinar un momento real, un punto específico en la línea de tiempo. El resultado como un objeto OffsetDateTime
.
OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC );
odt.toString (): 2018-11-22T00: 00Z
La Z
en el extremo significa UTC y se pronuncia "Zulu". Definido en la norma ISO 8601.
Ahora que tenemos un momento en la mano, podemos enviarlo a la base de datos en una columna de tipo estándar de SQL TIMESTAMP WITH TIME ZONE
.
preparedStatement.setObject( 2 , odt );
Cuando luego recupere ese valor almacenado.
OffsetDateTime odt = rs.getObject( "when_" , OffsetDateTime.class );
2018-11-22T00: 00Z
Aquí está la completa para esta aplicación de ejemplo.
package com.basilbourque.example;
import java.sql.*;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
public class MomentIntoDatabase {
public static void main ( String[] args ) {
MomentIntoDatabase app = new MomentIntoDatabase();
app.doIt();
}
private void doIt ( ) {
try {
Class.forName( "org.h2.Driver" );
} catch ( ClassNotFoundException e ) {
e.printStackTrace();
}
try (
Connection conn = DriverManager.getConnection( "jdbc:h2:mem:moment_into_db_example_" ) ;
Statement stmt = conn.createStatement() ;
) {
String sql = "CREATE TABLE event_ (/n" +
" id_ UUID DEFAULT random_uuid() PRIMARY KEY ,/n" +
" name_ VARCHAR NOT NULL ,/n" +
" when_ TIMESTAMP WITH TIME ZONE NOT NULL/n" +
") ; ";
System.out.println( sql );
stmt.execute( sql );
// Insert row.
sql = "INSERT INTO event_ ( name_ , when_ ) " + "VALUES ( ? , ? ) ;";
try ( PreparedStatement preparedStatement = conn.prepareStatement( sql ) ; ) {
String name = "whatever";
String input = "22.11.2018 00:00:00";
DateTimeFormatter f = DateTimeFormatter.ofPattern( "dd.MM.uuuu HH:mm:ss" );
LocalDateTime ldt = LocalDateTime.parse( input , f );
System.out.println( "ldt.toString(): " + ldt );
OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC );
System.out.println( "odt.toString(): " + odt );
preparedStatement.setString( 1 , name );
preparedStatement.setObject( 2 , odt );
preparedStatement.executeUpdate();
}
// Query all.
sql = "SELECT * FROM event_ ;";
try ( ResultSet rs = stmt.executeQuery( sql ) ; ) {
while ( rs.next() ) {
//Retrieve by column name
UUID id = ( UUID ) rs.getObject( "id_" ); // Cast the `Object` object to UUID if your driver does not support JDBC 4.2 and its ability to pass the expected return type for type-safety.
String name = rs.getString( "name_" );
OffsetDateTime odt = rs.getObject( "when_" , OffsetDateTime.class );
//Display values
System.out.println( "id: " + id + " | name: " + name + " | when: " + odt );
}
}
} catch ( SQLException e ) {
e.printStackTrace();
}
}
}
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 Stack Overflow 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 , Java SE 10 , Java SE 11 y versiones posteriores: parte de la API estándar de Java con una implementación integrada.
- Java 9 agrega algunas características menores y correcciones.
- Java SE 6 y Java SE 7
- La mayor 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 ThreeTen-Backport 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 .
En la aplicación que estoy desarrollando necesito convertir el objeto java.time.Instant a java.sql.Timestamp. Cuando creo un objeto instantáneo, por ejemplo
Instant now = Instant.now();
e inspeccionar el elemento. Recibo algo como 2017-03-13T14:28:59.970Z
Luego trato de crear un objeto de marca de tiempo como este:
Timestamp current = Timestamp.from(now);
Y cuando inspecciono el elemento. Recibo algo así como 2017-03-13T16:28:59.970Z
El mismo resultado pero con 2 horas de desplazamiento adicionales. ¿Alguien puede explicar que esto suceda y brindarme una respuesta sobre cómo resolverlo sin compensación?
Cuando creé así:
LocalDateTime ldt = LocalDateTime.ofInstant(Instnant.now(), ZoneOffset.UTC);
Timestamp current = Timestamp.valueOf(ldt);
Todo funciona bien. Pero trato de evitar las conversiones. ¿Hay alguna manera de hacer esto solo usando el objeto instantáneo?
Cambié la zona horaria de mi computadora a Europa / Bucarest para un experimento. Esto es UTC + 2 horas como su zona horaria.
Ahora cuando copie tu código obtengo un resultado similar al tuyo:
Instant now = Instant.now();
System.out.println(now); // prints 2017-03-14T06:16:32.621Z
Timestamp current = Timestamp.from(now);
System.out.println(current); // 2017-03-14 08:16:32.621
La salida se da en los comentarios. Sin embargo, sigo:
DateFormat df = DateFormat.getDateTimeInstance();
df.setTimeZone(TimeZone.getTimeZone("UTC"));
// the following prints: Timestamp in UTC: 14-03-2017 06:16:32
System.out.println("Timestamp in UTC: " + df.format(current));
Ahora puedes ver que la Timestamp
realmente está de acuerdo con el Instant
que comenzamos (solo no se imprimen los milisegundos, pero confío en que también estén allí). Así que has hecho todo correctamente y solo te confundiste porque cuando Timestamp
la Timestamp
estábamos invocando implícitamente su método toString
, y este método, a su vez, toma la configuración de zona horaria del equipo y muestra la hora en esta zona. Solo por esto, las pantallas son diferentes.
La otra cosa que intentaste, usando LocalDateTime
, parece funcionar, pero realmente no te da lo que quieres:
LocalDateTime ldt = LocalDateTime.ofInstant(Instant.now(), ZoneOffset.UTC);
System.out.println(ldt); // 2017-03-14T06:16:32.819
current = Timestamp.valueOf(ldt);
System.out.println(current); // 2017-03-14 06:16:32.819
System.out.println("Timestamp in UTC: " + df.format(current)); // 14-03-2017 04:16:32
Ahora, cuando imprimimos la Timestamp
utilizando nuestro DateFormat
UTC, podemos ver que es 2 horas antes, 04:16:32 UTC cuando el Instant
es 06:16:32 UTC. Entonces, este método es engañoso, parece que está funcionando, pero no funciona.
Esto muestra el problema que condujo al diseño de las clases de fecha y hora de Java 8 para reemplazar las antiguas. Por lo tanto, la solución real y buena a su problema probablemente sería obtener un controlador JDBC 4.2 que pueda aceptar un objeto Instant
fácilmente para que pueda evitar la conversión a la Timestamp
de Timestamp
completo. No sé si eso está disponible para usted todavía, pero estoy convencido de que lo estará.
Durante el guardado de un registro en SQL Server DB, me encontré con el mismo problema. He usado java.sql.Timestamp.valueOf(String s)
para obtener la marca de tiempo en UTC :
import java.time.Instant; import java.time.format.DateTimeFormatter; .... .... DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(UTC); String dateTime = dateTimeFormatter.format(Instant date); Timestamp timestamp = Timestamp.valueOf(dateTime);
Esto funciona para mi.
Si desea la marca de tiempo actual, ¿por qué no usa la siguiente función? Lo he usado en varios proyectos y funciona perfectamente:
public static Timestamp getTimeStamp()
{
// Calendar information
Calendar calendar = Calendar.getInstance();
java.util.Date now = calendar.getTime();
Timestamp dbStamp = new Timestamp(now.getTime());
return dbStamp;
}
Ejemplo:
System.out.println( getTimeStamp() );
Salida: 2017-03-13 15: 01: 34.027
EDITAR
Usando Java 8 LocalDateTime:
public static Timestamp getTimeStamp()
{
return Timestamp.valueOf(LocalDateTime.now());
}