Saltar al contenido

Java: convierta java.time.Instant en java.sql.Timestamp sin desplazamiento de zona

Después de de una larga recopilación de datos resolvimos esta traba que tienen muchos de nuestros lectores. Te regalamos la solución y nuestro objetivo es que resulte de mucha ayuda.

Solución:

Cambié la zona horaria de mi computadora a Europa / Bucarest para un experimento. Esto es UTC + 2 horas como su zona horaria.

Ahora, cuando copio 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, continúo:

    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 el Timestamp realmente está de acuerdo con el Instant comenzamos desde (solo los milisegundos no se imprimen, pero confío en que también estén allí). Así que ha hecho todo correctamente y solo se confundió porque cuando imprimimos el Timestamp estábamos llamando implícitamente a su toString método, y este método a su vez toma la configuración de la zona horaria de la computadora 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 el Timestamp usando nuestro UTC DateFormat, podemos ver que es 2 horas antes, 04:16:32 UTC cuando el Instant son las 06:16:32 UTC. Así que este método es engañoso, parece que funciona, pero no es así.

Esto muestra el problema que condujo al diseño de las clases de fecha y hora de Java 8 para reemplazar las antiguas. Entonces, la verdadera y buena solución a su problema probablemente sería conseguir un controlador JDBC 4.2 que pueda aceptar un Instant objeto fácilmente para que pueda evitar la conversión a Timestamp en total. No sé si eso está disponible para usted todavía, pero estoy convencido de que lo estará.

Clases incorrectas para 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 lo moderno java.time clasesLocalDateTime aquí) con las terribles clases antiguas de fecha y hora heredadas (java.sql.Timestamp aquí). los java.time framework suplanta por completo las terribles clases antiguas, a partir de la adopción de JSR 310. Nunca necesitas usar Timestamp de nuevo: A partir de JDBC 4.2 podemos intercambiar directamente java.time objetos con la base de datos.

El otro problema es que el LocalDateTime la clase no puede, por definición, representar un momento. A propósito, carece de una zona horaria o un desplazamiento de UTC. Usar LocalDateTime solo cuando te refieres a una fecha con la hora del día En todas partes o en cualquier sitio, en otras palabras, cualquiera / todos de muchos más momentos en un rango de aproximadamente 26-27 horas (los extremos actuales de las zonas horarias alrededor del mundo).

Hacer no usar LocalDateTime cuando te refieres a un momento específico, un punto específico en la línea de tiempo. En su lugar use:

  • Instant (siempre en UTC)
  • OffsetDateTime (una fecha con hora del día y con compensación de UTC)
  • ZonedDateTime (una fecha con la hora del día y con una zona horaria).

Luego trato de crear un objeto Timestamp

No lo hagas.

Nunca usar java.sql.Timestamp. Reemplazado por java.time.Instant. Siga leyendo para obtener más información.

Tabla de tipos de fecha y hora en Java (tanto moderno como heredado) y en SQL estándar.

Momento actual

Para capturar el momento actual en UTC, use cualquiera de estos:

Instant instant = Instant.now() ;

…o…

OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC );

Ambos representan exactamente 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 usa el motor de base de datos H2, construido 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 que usa 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 NULLn" +
                    ") ; ";
            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() )  when: " + odt );
                
            
         catch ( SQLException e ) 
            e.printStackTrace();
        
    

Analizando string

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 un string.

La entrada string carece de cualquier indicador de zona horaria o desplazamiento de UTC. Así que analizamos como LocalDateTime, teniendo en cuenta que esto no representar un momento, es no 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

Pero se nos ha informado string estaba destinado a representar un momento en UTC, pero el remitente cometió un error y no incluyó esa información (como un Z o +00:00 al final para significar UTC). Entonces, podemos aplicar un desplazamiento de UTC de cero horas-minutos-segundos para determinar un momento real, un punto específico en la línea de tiempo. El resultado como OffsetDateTime objeto.

OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC );

odt.toString (): 2018-11-22T00: 00Z

los Z al final 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 SQL TIMESTAMP WITH TIME ZONE.

preparedStatement.setObject( 2 , odt );

Cuando recupere ese valor almacenado.

 OffsetDateTime odt = rs.getObject( "when_" , OffsetDateTime.class );

2018-11-22T00: 00Z

Aquí está el completo 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 NULLn" +
                    ") ; ";
            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() )  name: " + name + " 
            
         catch ( SQLException e ) 
            e.printStackTrace();
        
    

Mudado

Si debe interoperar con un código antiguo que aún no se ha actualizado para java.time, puede convertir de un lado a otro. Busque nuevos métodos to…/from… añadido a las clases antiguas.

Para conseguir un legado java.sql.Timestamp objeto, llamar Timestamp.from( Instant ). Para conseguir un Instant De nuestros OffsetDateTime objeto visto arriba, simplemente llame OffsetDateTime::toInstant.

java.sql.Timestamp ts = Timestamp.from( odt.toInstant() ) ;

Yendo en la otra dirección.

OffsetDateTime odt = OffsetDateTime.ofInstant( ts.toInstant() , ZoneOffset.UTC ) ;

Si usa el ThreeTen-Backport biblioteca para proyectos de Java 6 y 7, consulte la DateTimeUtils clase para el to…/from… métodos de conversión.


Sobre java.time

los java.time framework está integrado en Java 8 y versiones posteriores. Estas clases suplantan a las antiguas y problemáticas clases de fecha y hora heredadas, como java.util.Date, CalendarY SimpleDateFormat.

los Joda-Time proyecto, 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 busque Stack Overflow para obtener muchos ejemplos y explicaciones. La especificación es JSR 310.

Puedes intercambiar java.time objetos directamente con su base de datos. Utilice un controlador JDBC compatible con JDBC 4.2 o posterior. Sin necesidad de cuerdas, sin necesidad de java.sql.* clases.

¿Dónde obtener las clases java.time?

  • Java SE 8, Java SE 9, Java SE 10, Java SE 11y posterior: parte de la API estándar de Java con una implementación integrada.
    • Java 9 agrega algunas características y correcciones menores.
  • Java SE 6 y Java SE 7
    • La mayoría de java.time la funcionalidad está respaldada a Java 6 y 7 en ThreeTen-Backport.
  • Androide
    • Versiones posteriores de implementaciones de paquetes de Android del java.time clases.
    • Para Android anterior (<26), el ThreeTenABP el proyecto se adapta ThreeTen-Backport (mencionado anteriormente). Ver Cómo usar ThreeTenABP….

Tabla de qué biblioteca java.time usar con qué versión de Java o Android

los ThreeTen-Extra el proyecto extiende java.time con clases adicionales. Este proyecto es un campo de pruebas para posibles adiciones futuras a java.time. Puede encontrar algunas clases útiles aquí, como Interval, YearWeek, YearQuarter, y más.

Acuérdate de que tienes autorización de agregar una reseña .

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *