jdbc时区
在jOOQ邮件列表上,最近有一个有趣的讨论,关于jOOQ当前缺乏对TIMESTAMP WITH TIME ZONE
数据类型的现成支持。
没有人说日期,时间和时区很容易! 这里有一个有趣的文章,我建议阅读: 虚假的程序员相信时间
当那还不够时,还请阅读: 更多的虚假程序员相信时间
我个人喜欢程序员错误地认为“ Unix时间是自1970年1月1日以来的秒数”。 …Unix时间无法代表leap秒;)
返回JDBC
这是Jaybird开发人员(Firebird JDBC驱动程序) Mark Rotteveel提出的一个有趣的Stack Overflow答案: java.sql.Timestamp时区是否特定?
可以按照以下方式观察Mark的解释(我在这里使用PostgreSQL):
Connection c = getConnection();
Calendar utc = Calendar.getInstance(
TimeZone.getTimeZone("UTC"));
try (PreparedStatement ps = c.prepareStatement(
"select"
+ " ?::timestamp,"
+ " ?::timestamp,"
+ " ?::timestamp with time zone,"
+ " ?::timestamp with time zone"
)) {
ps.setTimestamp(1, new Timestamp(0));
ps.setTimestamp(2, new Timestamp(0), utc);
ps.setTimestamp(3, new Timestamp(0));
ps.setTimestamp(4, new Timestamp(0), utc);
try (ResultSet rs = ps.executeQuery()) {
rs.next();
System.out.println(rs.getTimestamp(1)
+ " / " + rs.getTimestamp(1).getTime());
System.out.println(rs.getTimestamp(2, utc)
+ " / " + rs.getTimestamp(2, utc).getTime());
System.out.println(rs.getTimestamp(3)
+ " / " + rs.getTimestamp(3).getTime());
System.out.println(rs.getTimestamp(4, utc)
+ " / " + rs.getTimestamp(4, utc).getTime());
}
}
上面的程序使用Java和DB中使用时区而不使用时区的所有排列,并且输出始终相同:
1970-01-01 01:00:00.0 / 0
1970-01-01 01:00:00.0 / 0
1970-01-01 01:00:00.0 / 0
1970-01-01 01:00:00.0 / 0
如您所见,在每种情况下,UTC时间戳0均已正确存储并从数据库中检索到。 我自己的语言环境是瑞士,即CET / CEST,在Epoch是UTC + 1,这是在Timestamp.toString()
上获得的输出。
当您在SQL和/或Java中使用时间戳文字时,事情会变得很有趣。 如果这样替换绑定变量:
Timestamp almostEpoch = Timestamp.valueOf("1970-01-01 00:00:00");
ps.setTimestamp(1, almostEpoch);
ps.setTimestamp(2, almostEpoch, utc);
ps.setTimestamp(3, almostEpoch);
ps.setTimestamp(4, almostEpoch, utc);
这又是我在CET / CEST上获得的机器上的东西
1970-01-01 00:00:00.0 / -3600000
1970-01-01 00:00:00.0 / -3600000
1970-01-01 00:00:00.0 / -3600000
1970-01-01 00:00:00.0 / -3600000
即不是Epoch,而是我首先发送到服务器的时间戳文字。 请注意,绑定/获取的四种组合仍然始终产生相同的时间戳。
让我们看看如果写入数据库的会话使用与从数据库获取会话不同的时区(假设您在PST中)(我再次使用CET或UTC),会发生什么。 我正在运行此程序:
Calendar utc = Calendar.getInstance(
TimeZone.getTimeZone("UTC"));
Calendar pst = Calendar.getInstance(
TimeZone.getTimeZone("PST"));
try (PreparedStatement ps = c.prepareStatement(
"select"
+ " ?::timestamp,"
+ " ?::timestamp,"
+ " ?::timestamp with time zone,"
+ " ?::timestamp with time zone"
)) {
ps.setTimestamp(1, new Timestamp(0), pst);
ps.setTimestamp(2, new Timestamp(0), pst);
ps.setTimestamp(3, new Timestamp(0), pst);
ps.setTimestamp(4, new Timestamp(0), pst);
try (ResultSet rs = ps.executeQuery()) {
rs.next();
System.out.println(rs.getTimestamp(1)
+ " / " + rs.getTimestamp(1).getTime());
System.out.println(rs.getTimestamp(2, utc)
+ " / " + rs.getTimestamp(2, utc).getTime());
System.out.println(rs.getTimestamp(3)
+ " / " + rs.getTimestamp(3).getTime());
System.out.println(rs.getTimestamp(4, utc)
+ " / " + rs.getTimestamp(4, utc).getTime());
}
}
它产生以下输出:
1969-12-31 16:00:00.0 / -32400000
1969-12-31 17:00:00.0 / -28800000
1970-01-01 01:00:00.0 / 0
1970-01-01 01:00:00.0 / 0
第一个时间戳记是将Epoch存储为PST(16:00),然后数据库中删除了时区信息,这将Epoch转换为您在Epoch处的本地时间(-28800秒/ -8h),即真的存储了。
现在,当我从自己的时区CET获取此时间时,我仍然想要获取当地时间(16:00)。 但是在我的时区,这不再是-28800秒,而是-32400秒(-9h)。 够古怪吗?
当我获取存储的本地时间(16:00)时,事情反过来了,但是我强迫获取发生在UTC中,这将产生您存储的时间戳,最初是PST(-28800)秒)。 但是,在我的时区CET中打印此时间戳(-28800秒)时,现在是17:00。
当我们在数据库中使用TIMESTAMP WITH TIME ZONE数据类型时,将保持时区(PST),并且当我获取Timestamp值时,无论使用CET还是UTC,我仍然会得到Epoch,它已安全地存储到了数据库,在CET中打印为01:00。
ew。
TL; DR:
使用jOOQ时 ,如果正确的UTC时间戳对您很重要,请使用TIMESTAMP WITH TIMEZONE,但是您必须实现自己的数据类型Binding ,因为jOOQ当前不支持该数据类型。 一旦使用了自己的数据类型Binding,就可以使用Java 8的time API,它比java.sql.Timestamp +丑陋的Calendar更好地表示了这些不同的类型。
如果当地时间对您很重要,或者您不在跨时区工作,则可以使用TIMESTAMP和jOOQ的Field <Timestamp>。
幸运的是,如果您像我一样,可以在一个只有一个时区的很小的国家/地区进行操作,而大多数本地软件都不会遇到此问题。
jdbc时区