简短版本: 我可以强迫Java无时区吗?
更长的版本: 我有一个较旧的,基于Java 6的三层系统,带有Java GUI前端(在用户PC上运行),一个Java / OSGi中间件服务器和一个Oracle 11g数据库后端。这两台服务器位于群集内的同一机架中。存储在数据库中的主实体(检查)具有Date列,该列存储检查发生时的日期和时间。日期和时间在GUI中作为字符串输入,并按如下方式存储在DB中:
(theInspection.getInspectionDate() returns a java.util.Date object)
inspectionDate = new java.sql.Timestamp(theInspection.getInspectionDate().getTime());
...
statementHandler.openPreparedStatement(C_INSERT_INTO_INSPECTIONS);
...
statementHandler.setTimestamp(4, inspectionDate);
然后将检查日期和时间读回中间件服务器,以识别和定位其文件名中包含日期和时间的原始数据文件。从DB读取如下:
resultSet.getDate("inspection_date");
if (resultSet.wasNull()) {
inspection.setInspectionDate(null);
} else {
date = new java.util.Date(resultSet.getDate("inspection_date").getTime()
+ resultSet.getTime("inspection_date").getTime() + (3600 * 1000L) );
inspection.setInspectionDate(date);
}
现在,这在CET / CEST(UTC + 1)时区中运行良好多年,但将其移至MSK(UTC + 3)打破了它。当它存储到DB时,时间会移动+2小时,并在读回时再移动+2小时。我已经确定Java是这样做的,Oracle在此设置中是无时区的。
我正在考虑更改此设置,以便中间件服务器在存储时转换为UTC,并在读取时返回到本地时区。我知道java.util.Date已被弃用,还有其他表示时间和日期的方法。但是,我担心如果GUI客户端和服务器位于不同的时区,或者服务器或数据跨时区移动会发生什么。
最后,迫使Java成为时区无知也许是最有意义的。那可能吗?
谢谢!
答案 0 :(得分:4)
Date
正在使用隐含的时区
Date
定义为自“1970-01-01T00:00:00”UTC以来的毫秒数。因此,如果将日期(+时间)字符串转换为应用了不同时区的Date
实例,则ms的数量(对于内部值)将不同。
未使用String-to-Date转换指定显式时区,并且转换为DB值并不意味着值不会与时区相关。
只要这个隐含的时区在没有代码知道的情况下“默默地”改变,这种变化就会被观察为“扭曲”。
这样的“无声”时区变化将例如发生在以下场合(不完整清单!):
Date
对象实例,从一个时区到另一个时区为了克服这种麻烦,基本上有两种策略:
严格执行整个应用程序的唯一时区。 (鉴于Date
使用 UTC 的隐式语义将是一个不错的选择。)
在这种情况下,您会立即将从“外部”输入的任何值转换为您的公共应用程序时区(来自已知或隐含的本地时区)。并且仅将(内部)日期/时间值转换为本地时区以便显示(尽可能晚)。应用程序“内部”(并与数据库一起存储)的任何值都将位于应用程序时区中
这对于更改服务器和数据库的时区是不变的,只需要正确的检测日期I / O位置。您还需要处理与其他外部实体(例如您提到的文件名)相关的日期/时间值。这些值最好根据应用时区生成。
你放弃尝试在没有时区概念的情况下过关,并改为使用带有任何日期/时间值的显式时区。
第一种方法的好处是可以在不同时区提供一致的值(例如,使用生成的文件名使用日期/时间值 - 然后需要将其用作UTC值)。
第二个很容易提供来自不同时区的事件的一致排序。
在存储/读取值时执行时区转换 来自DB的潜在风险是客户端可能存在错误解释,可能存在于完全不同的时区或更改存储和检索此类值之间的时区(正如您已经怀疑的那样)。
明确地解决主题中的问题:
不,只要您使用java Date
类,您将始终在从/到String的转换时引用隐式时区,而内部值基于 UTC 。
答案 1 :(得分:1)
JDBC提供了在读取或写入日期期间设置时区的方法。
对于时间戳,以下是方法。
ResultSet getTimeStamp
PreparedStatement setTimeStamp
将这些方法与下面构造的Calendar实例一起使用。
Calendar gmtCalendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
编写代码时将成为
statementHandler.setTimestamp(4, inspectionDate, gmtCalendar);
阅读时会是
date = new java.util.Date(resultSet.getDate("inspection_date", gmtCalendar).getTime()
+ resultSet.getTime("inspection_date", gmtCalendar).getTime());
当您使用getTime()
(毫秒数)时,您应该需要解决此问题所需的时间GMT/UTC
。由于时间戳(毫秒数)默认为GMT/UTC
。
唯一剩下的就是指示JDBC
考虑该时区。