Java - Convert java.time.Instant to java.sql.Timestamp without Zone offset

前端 未结 4 1626
遥遥无期
遥遥无期 2020-11-30 03:42

In the application I am developing, I need to convert java.time.Instant object to java.sql.Timestamp. When I create Instant object lik

4条回答
  •  失恋的感觉
    2020-11-30 04:22

    Wrong classes to use

    LocalDateTime ldt = LocalDateTime.ofInstant(Instnant.now(), ZoneOffset.UTC);
    Timestamp current = Timestamp.valueOf(ldt);
    

    Two problems with that code.

    Firstly, never mix the modern java.time classes (LocalDateTime here) with the terrible old legacy date-time classes (java.sql.Timestamp here). The java.time framework supplants entirely the terrible old classes, as of the adoption of JSR 310. You need never use Timestamp again: As of JDBC 4.2 we can directly exchange java.time objects with the database.

    The other problem, is that the LocalDateTime class cannot, by definition, represent a moment. It purposely lacks a time zone or offset-from-UTC. Use LocalDateTime only when you mean a date with time-of-day everywhere or anywhere, in other words, any/all of many more moments across a range of about 26-27 hours (the current extremes of time zones around the globe).

    Do not use LocalDateTime when you mean a specific moment, a specific point on the timeline. Instead use:

    • Instant (always in UTC)
    • OffsetDateTime (a date with time-of-day and with offset-from-UTC)
    • ZonedDateTime (a date with time-of-day and with a time zone).

    Then I try to create Timestamp object

    Don’t.

    Never use java.sql.Timestamp. Replaced by java.time.Instant. Read on for more info.

    Current moment

    To capture the current moment in UTC use either of these:

    Instant instant = Instant.now() ;
    

    …or…

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

    The both represent the very same thing, a moment in UTC.

    Database

    Here is some example SQL and the Java code to pass the current moment into the database.

    The example uses the H2 Database Engine, built in 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();
    }
    

    Here is a complete example app using that code.

    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();
            }
        }
    }
    

    Parsing string

    Regarding a related comment by Melnyk, here is another example based on the example code above. Rather than capturing the current moment, this code parses a string.

    The input string lacks any indicator of time zone or offset-from-UTC. So we parse as a LocalDateTime, keeping in mind that this does not represent a moment, is not a point on the timeline.

    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

    But we have been informed the string was meant to represent a moment in UTC, but the sender screwed up and failed to include that information (such as a Z or +00:00 on the end to mean UTC). So we can apply an offset-from-UTC of zero hours-minutes-seconds to determine an actual moment, a specific point on the timeline. The result as a OffsetDateTime object.

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

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

    The Z on the end means UTC and is pronounced “Zulu”. Defined in ISO 8601 standard.

    Now that we have a moment in hand, we can send it to the database in a column of SQL-standard type TIMESTAMP WITH TIME ZONE.

    preparedStatement.setObject( 2 , odt );
    

    When then retrieve that stored value.

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

    2018-11-22T00:00Z

    Here is the complete for this example app.

    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();
            }
        }
    }
    

    Converting

    If you must interoperate with old code not yet updated for java.time, you can convert back-and-forth. Look to new methods to…/from… added to the old classes.

    To get a legacy java.sql.Timestamp object, call Timestamp.from( Instant ). To get an Instant from our OffsetDateTime object seen above, simply call OffsetDateTime::toInstant.

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

    Going the other direction.

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

    If using the ThreeTen-Backport library for Java 6 & 7 projects, look to the DateTimeUtils class for the to…/from… conversion methods.


    About java.time

    The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

    The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

    To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

    You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

    Where to obtain the java.time classes?

    • Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.
      • Java 9 adds some minor features and fixes.
    • Java SE 6 and Java SE 7
      • Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
    • Android
      • Later versions of Android bundle implementations of the java.time classes.
      • For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….

    The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

提交回复
热议问题