我在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标准规定了与当前时区的转换,但是可以通过向getTimestamp / setTimestamp调用提供Calander来防止这种情况。但是提供日历并没有任何差别。这是我的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时间戳的正确方法是什么?
答案 0 :(得分:20)
将UTC设置为JVM -Duser.timezone=UTC
的默认时区,或将整个操作系统设置为UTC。
在Postgres中TIMESTAMP
和TIMESTAMP WITH TIMEZONE
的存储方式相同 - 自Postgres纪元(2000-01-01)以来的秒数。主要区别在于Postgres在保存时间戳值时所做的事情,例如2004-10-19 10:23:54+02
:
+02
就被剥夺了-02
校正以使其成为UTC 现在有趣的是JDBC驱动程序加载值:
在这两种情况下,您最终都会使用用户默认TZ的java.sql.Timestamp
对象。
没有TZ的时间戳非常有限。如果有两个系统连接到数据库,两个系统都有不同的TZ,它们将以不同的方式解释时间戳。因此,我强烈建议您使用TIMESTAMP WITH TIMEZONE
。
您可以tell JDBC在通过ResultSet#getTimestamp(String, Calendar)
阅读时间戳时应该使用哪种TZ。摘自JavaDoc:
如果基础数据库不存储时区信息,则此方法使用给定的日历为时间戳构造适当的毫秒值。
答案 1 :(得分:0)
Pavel提出的解决方案(添加jvm参数'-Duser.timezone = UTC')肯定是最佳选择,如果您没有系统访问权限,这并不总是可行。
我找到的唯一方法是在查询中将时间戳转换为epoch,并将其作为long
读取。
SELECT extract(epoch from my_timestamp_colum at time zone 'utc')::bigint * 1000
AS my_timestamp_as_epoc
FROM my_table
然后使用
在纯JDBC中读取它ResultSet rs = ...
long myTimestampAsEpoc = rs.getLong("my_timestamp_as_epoc");
Date myTimestamp = new Date(myTimestampAsEpoc);
使用iBatis / MyBatis,您需要将列作为Long处理,然后手动将其转换为Date / Timestamp。不方便和丑陋。
PostgreSQL中的反向操作可以通过以下方式完成:
SELECT TIMESTAMP WITHOUT TIME ZONE 'epoch' +
1421855729000 * INTERVAL '1 millisecond'
也就是说,JDBC规范并没有说返回的时间戳应该或不应该将时间戳转移到用户时区;但是,由于您将表格列定义为“没有时区的时间戳”,我预计不会有时间变化,但记录的时期只包含在java.sql.Timestamp
中。我认为,这是PostgreSQL JDBC驱动程序中的一个错误。因此,如果没有系统访问,那么在这个级别上可能没有更多的问题可以解决。
答案 2 :(得分:0)
Postgres JDBC驱动程序有一些特殊的技巧
请参见https://jdbc.postgresql.org/documentation/head/java8-date-time.html
所以阅读时可以做
Instant utc =resultSet.getObject("dateAdded",LocalDateTime.class).toInstant(ZoneOffset.UTC);
如果使用连接池(例如Hikari),则还可以通过设置 connectionInitSql = 设置时区'UTC'来指定每个连接使用的时区