如何从JDBC + postgreSql时间戳获取UTC时间戳?

2022-09-02 21:23:52

我在PostgreSQL中创建了一个这样的表:

create table myTable (
    dateAdded timestamp(0) without time zone null default (current_timestamp at time zone 'UTC');
)

我选择“无时区”,因为我知道我的应用程序使用的所有时间戳始终是 UTC。就我获得的文档而言,与“带时间戳”的唯一区别是,我可以提供其他时区的值,然后将其转换为UTC。但是,我想避免这种自动转换,因为如果我知道我的值是UTC,它们几乎不会有任何好处。

当我在测试表中添加新记录并使用pgAdmin查看表的内容时,我可以看到插入日期已正确保存为UTC格式。

但是,当我尝试使用JDBC选择值时,该值会减去2小时。我位于 UTC+2,因此看起来 JDBC 假设表中的日期不是 UTC 时间戳,而是 UTC+2 时间戳,并尝试转换为 UTC。

一些谷歌搜索显示,JDBC标准规定了与当前时区的转换,但这可以通过提供Calander来防止getTimestamp/setTimestamp调用。然而,提供日历根本没有任何区别。这是我的MyBatis / Jodatime转换器:

@MappedTypes(DateTime.class)
public class DateTimeTypeHandler extends BaseTypeHandler<DateTime> {
    private static final Calendar UTC_CALENDAR = Calendar.getInstance(DateTimeZone.UTC.toTimeZone());

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i,
            DateTime parameter, JdbcType jdbcType) throws SQLException {
        ps.setTimestamp(i, new Timestamp(parameter.getMillis()), UTC_CALENDAR);
    }

    @Override
    public DateTime getNullableResult(ResultSet rs, String columnName)
            throws SQLException {
        return fromSQLTimestamp(rs.getTimestamp(columnName, UTC_CALENDAR));
    }
    /* further get methods with pretty much same content as above */

    private static DateTime fromSQLTimestamp(final Timestamp ts) {
        if (ts == null) {
            return null;
        }
        return new DateTime(ts.getTime(), DateTimeZone.UTC);
    }
}

从 JDBC+PostgreSQL 时间戳源获取 UTC 时间戳的正确方法是什么?


答案 1

溶液

将 UTC 设置为 JVM 的默认时区,或将整个操作系统设置为 UTC。-Duser.timezone=UTC

背景

在Postgres中,两者都以相同的方式存储 - 自Postgres epoch(2000-01-01)以来的秒数。主要区别在于Postgres在保存时间戳值时执行的操作,例如:TIMESTAMPTIMESTAMP WITH TIMEZONE2004-10-19 10:23:54+02

  • 没有TZ,只是被剥离了+02
  • 使用TZ进行校正以使其成为UTC-02

现在有趣的是,当 JDBC 驱动程序加载值时:

  • 没有TZ,存储的值由用户的(JVM / OS)TZ转移
  • 对于 TZ,该值被视为 UTC

在这两种情况下,您最终都会得到具有用户默认TZ的对象。java.sql.Timestamp

时区

没有TZ的时间戳非常有限。如果有两个系统连接到数据库,并且这两个系统都具有不同的 TZ,则它们将以不同的方式解释时间戳。因此,我强烈建议您使用.TIMESTAMP WITH TIMEZONE


更新

你可以告诉JDBC在通过ResultSet#getTimestamp(String,Calendar)读取时间戳时应该使用哪种TZ。摘自 JavaDoc:

如果基础数据库不存储时区信息,则此方法使用给定的日历为时间戳构造适当的毫秒值。


答案 2

有一些特定于Postgres JDBC驱动程序的技巧

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

所以在阅读时你可以做

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

如果您使用 Hikari 等连接池,还可以通过设置 connectionInitSql=set 时区'UTC'来指定每个连接使用的时区