How to get UTC timestamps from JDBC+postgreSql timestamp?

前端 未结 3 1423
别那么骄傲
别那么骄傲 2021-02-02 14:06

I created a table like like this in PostgreSQL:

create table myTable (
    dateAdded timestamp(0) without time zone null          


        
相关标签:
3条回答
  • 2021-02-02 14:47

    The solution proposed by Pavel (adding the jvm param '-Duser.timezone=UTC') is for sure the best option, if you don't have system access, this isn't always possible to do.

    The only way I found is to convert the timestamp to epoch in the query and read it as a long.

    SELECT  extract(epoch from my_timestamp_colum at time zone 'utc')::bigint * 1000 
                AS my_timestamp_as_epoc
    FROM    my_table
    

    Then read it in plain JDBC with

    ResultSet rs = ...
    long myTimestampAsEpoc = rs.getLong("my_timestamp_as_epoc");
    Date myTimestamp = new Date(myTimestampAsEpoc);
    

    With iBatis/MyBatis, you need to handle the column as a Long, then convert it manually to Date/Timestamp. Inconvenient and ugly.

    The reverse operation in PostgreSQL can be done with:

    SELECT TIMESTAMP WITHOUT TIME ZONE 'epoch' + 
           1421855729000 * INTERVAL '1 millisecond'
    

    That said, the JDBC specification doesn't say that the returned timestamp shall or shall not shift the timestamp to the user time zone; BUT since you defined the table column as 'timestamp without time zone' I would expect no time shifts, but the recorded epoch being just wrapped in a java.sql.Timestamp. For my opinion, this is a bug in the PostgreSQL JDBC driver. Being the problem at this level, then, probably there is not much more that can be done, without system access.

    0 讨论(0)
  • 2021-02-02 14:55

    There are a few tricks specific for the Postgres JDBC driver

    See https://jdbc.postgresql.org/documentation/head/java8-date-time.html

    So when reading you can do

        Instant utc =resultSet.getObject("dateAdded",LocalDateTime.class).toInstant(ZoneOffset.UTC);
    

    If you use a connection pool such as Hikari, you can also specify the time time-zone used by each connection by setting connectionInitSql=set time zone 'UTC'

    0 讨论(0)
  • 2021-02-02 14:56

    Solution

    Set UTC as default timezone of your JVM -Duser.timezone=UTC or set your whole OS to UTC.

    Background

    In Postgres both TIMESTAMP and TIMESTAMP WITH TIMEZONE are stored the same way - number of seconds since Postgres epoch (2000-01-01). The main difference is what Postgres do when it saves timestamp value such as 2004-10-19 10:23:54+02:

    • without TZ the +02 is just stripped away
    • with TZ a -02 correction is performed to make it UTC

    Now the interesting thing is when JDBC driver loads the value:

    • without TZ the stored value is shifted by the user's (JVM / OS) TZ
    • with TZ the value is considered to be UTC

    In both cases you will end up with java.sql.Timestamp object with user's default TZ.

    Time Zones

    Timestamps without TZ are pretty limited. If you have two systems connected to your database, both with different TZ, they will interpret timestamps differently. Therefore, I strongly advice you to use TIMESTAMP WITH TIMEZONE.


    Update

    You can tell JDBC what kind of TZ it should use when reading timestamp via ResultSet#getTimestamp(String, Calendar). Excerpt from JavaDoc:

    This method uses the given calendar to construct an appropriate millisecond value for the timestamp if the underlying database does not store timezone information.

    0 讨论(0)
提交回复
热议问题