0

I'm attempting to populate a UTC timestamp into a SQL table, but when I use Instant.now() to get the current UTC time, a conversion of Timestamp.from(instant) is writing local time zones into the table. Is there a way to write UTC into the table?

PreparedStatement ps = connection.prepareStatement(...)
ps.setString(1, Timestamp.from(Instant.now())

This results in local timezone opposed to UTC.

The JDBC driver is net.sourceforge.jtds.jdbc.Driver.

7
  • 1
    ps.setObject(1, OffsetDateTime.now(ZoneOffset.UTC)) (or LocalDateTime.now(ZoneOffset.UTC) if you’re so unfortunate that the database column doesn’t carry the information that it’s in UTC). Commented Feb 9, 2023 at 19:44
  • 1
    Thanks for the responses! The issue was my JVM was defaulting to local time. I set a VM option -Duser.timzone= "UTC" and it is now storing UTC. Database is UTC as well, so VM was the issue. Commented Feb 9, 2023 at 21:30
  • 2
    Afaik Instant.now will be unaffected by user.timezone Commented Feb 9, 2023 at 22:46
  • 1
    You should not se the Timestamp class at all. In the old days it was used with SQL databases, but it was always poorly deigned, a true hack on top of the already bad Date class. Since JDBC 4.2 instead use OffsetDateTime, Instant or LocalDateTime. Stick to the modern API. Timestamp is but a troublesome detour. Commented Feb 10, 2023 at 4:56
  • 2
    Add a mention of (a) what database you are using, (b) exactly what data type is your column. Commented Feb 10, 2023 at 5:08

2 Answers 2

5

OffsetDateTime

when I use Instant.now() to get the current UTC time,

Don’t use Instant for SQL database work.

In JDBC 4.2+, the specification maps OffsetDateTime class to columns of a type akin to the SQL standard type TIMESTAMP WITH TIME ZONE.

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

Neither Instant nor ZonedDateTime are mapped in JDBC. The SQL standard defines no such types equivalent to those classes.

By the way, for columns of a type akin to the SQL standard type TIMESTAMP WITHOUT TIME ZONE, use the LocalDateTime class.

Avoid legacy date-time classes

a conversion of Timestamp.from(instant)

Never use the terrible legacy date-time classes such as Timestamp. Use only their replacement: the modern java.time classes defined in JSR 310.

Write to the database:

myPreparedStatement.setObject … , odt ) ;

Retrieve:

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

Do not depend on default zone

You commented:

The issue was my JVM was defaulting to local time

You should write your Java code in such a way as to not care about the JVM’s current default time zone.

The code shown above is unaffected by the JVM’s current default time zone.

Example code

Here is a complete example.

package work.basil.example.db;

import javax.sql.DataSource;
import java.sql.*;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.UUID;

public class DemoH2InMem
{
    public static void main ( String[] args )
    {
        DemoH2InMem app = new DemoH2InMem();
        app.demo();
    }

    private void demo ( )
    {
        DataSource dataSource = this.fetchDataSource();
        this.createTable( dataSource );
        this.insertDummyData( dataSource );
        this.dump( dataSource );

//        Scanner scanner = new Scanner( System.in );
//        System.out.print( "Type anything to end program: " );
//        String anything = scanner.nextLine();
        System.out.println( "Demo done at " + Instant.now() );
    }

    private DataSource fetchDataSource ( )
    {
        org.h2.jdbcx.JdbcDataSource dataSource = new org.h2.jdbcx.JdbcDataSource();
        dataSource.setURL( "jdbc:h2:mem:demo_db;DB_CLOSE_DELAY=-1" );
        return dataSource;
    }

    private void createTable ( final DataSource dataSource )
    {
        String sql =
                """
                CREATE TABLE bogus_ (
                    id_ UUID PRIMARY KEY  ,
                    when_ TIMESTAMP WITH TIME ZONE
                )
                ;
                """;
        try (
                Connection conn = dataSource.getConnection() ;
                Statement stmt = conn.createStatement() ;
        )
        {
            stmt.execute( sql );
        }
        catch ( SQLException e ) { throw new RuntimeException( e ); }
    }

    private void insertDummyData ( final DataSource dataSource )
    {
        String sql =
                """
                INSERT INTO bogus_ ( id_ , when_ )
                VALUES ( ? , ? )
                ;
                """;
        try (
                Connection conn = dataSource.getConnection() ;
                PreparedStatement pstmt = conn.prepareStatement( sql ) ;
        )
        {
            pstmt.setObject( 1 , UUID.fromString( "97a9e379-4d8f-4d06-8bea-43560a72120b" ) );
            pstmt.setObject( 2 , OffsetDateTime.now( ZoneOffset.UTC ) );
            pstmt.executeUpdate();

            pstmt.setObject( 1 , UUID.fromString( "052ae129-d0ca-4fdf-9a06-c87d20a2d3f2" ) );
            pstmt.setObject( 2 , OffsetDateTime.now( ZoneOffset.UTC ) );
            pstmt.executeUpdate();
        }
        catch ( SQLException e ) { throw new RuntimeException( e ); }
    }

    private void dump ( final DataSource dataSource )
    {
        String sql =
                """
                SELECT * FROM bogus_
                ;
                """;
        try (
                Connection conn = dataSource.getConnection() ;
                Statement stmt = conn.createStatement() ;
                ResultSet resultSet = stmt.executeQuery( sql ) ;
        )
        {

            System.out.println( "-------------|  table start |---------------" );
            while ( resultSet.next() )
            {
                UUID uuid = resultSet.getObject( "id_" , UUID.class );
                OffsetDateTime when = resultSet.getObject( "when_" , OffsetDateTime.class );
                System.out.println( uuid + " | " + when );
            }
            System.out.println( "-------------|  table end  |---------------" );
        }
        catch ( SQLException e ) { throw new RuntimeException( e ); }
    }
}

When run:

-------------|  table start |---------------
97a9e379-4d8f-4d06-8bea-43560a72120b | 2023-02-10T20:32:57.074979Z
052ae129-d0ca-4fdf-9a06-c87d20a2d3f2 | 2023-02-10T20:32:57.080153Z
-------------|  table end  |---------------
Demo done at 2023-02-10T20:32:57.092230Z
Sign up to request clarification or add additional context in comments.

2 Comments

"You should write your Java code in such a way as to not care about the JVM’s current default time zone." - Agreed. This solution threw an exception for me: java.sql.SQLException: Unable to convert between java.time.OffsetDateTime and JAVA_OBJECT. I've proposed a solution that works (although the comments suggest Timestamp is 'bad' to use, its' the only solution that has worked for me outside of changing the JVM timezone (which I can't do in my situation)
@RandyB I could guess that you have defined your column with an incorrect data type. But I cannot say for sure as you ignored my Comment asking for you to edit your Question with (a) what database you are using, (b) exactly what data type is your column. So I added a complete example app using the H2 Database Engine to demonstrate that my suggested code runs correctly.
0

Changing the JVM timezone to UTC solved the issue of Timestamp, however, in this case I cannot update the server in which this code is running.

The solution that worked for me without needing to adjust JVM timezone is

statement.setTimestamp(1,
        Timestamp.valueOf(DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss.SSS")
                .format(Instant.now().atZone(ZoneId.of("UTC")))));

This forces Timestamp to use UTC, although it seems rather robust and redundant. The other solutions proposed did not work for me.

9 Comments

Thanks for reporting back and answering your own question. Your solutions have several issues, though, sorry. Setting the JVM time zone is fragile since someone else may set it differently (and need to do so). Your code still uses Timestamp, a hack of a class that you can easily avoid using, which you should. You format patterns is wrong. Your resulting Timestamp will be wrong too, and there will probably be times that it cannot represent at all. And you are not picking up the full precision of Instant.now(), which is usually finer than milliseconds.
(docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/…) will query the system UTC clock to obtain the current instant." The fact that you're having such trouble with this suggests to me that your system clock is not set to UTC. This of course is a weakness. It should be set to UTC and you should leave the OS to do the local time. There should be absolutely no need to be converting to and from strings
I tried your suggestions but kept getting exceptions for setObject. The PreparedStatement setTimestamp takes in a Timestamp object otherwise I would avoid it entirely. My goal was to update existing code to put the Timestamp object corresponding to the UTC timezone. I would have to do a larger change it seems to support and different paramter (setObject)
@g00se - That is true but i don't have permissions to update the system clock as the server supports multiple processes so I have to localize the timezone control to this command.
You mean true in the sense that your system clock is not set to UTC?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.