我可以强制Java忽略时区吗?

时间:2016-05-26 10:58:29

标签: java oracle date datetime timezone

简短版本: 我可以强迫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成为时区无知也许是最有意义的。那可能吗?

谢谢!

2 个答案:

答案 0 :(得分:4)

Date正在使用隐含的时区 Date定义为自“1970-01-01T00:00:00”UTC以来的毫秒数。因此,如果将日期(+时间)字符串转换为应用了不同时区的Date实例,则ms的数量(对于内部值)将不同。

未使用String-to-Date转换指定显式时区,并且转换为DB值并不意味着值不会与时区相关。

只要这个隐含的时区在没有代码知道的情况下“默默地”改变,这种变化就会被观察为“扭曲”。

这样的“无声”时区变化将例如发生在以下场合(不完整清单!):

  • 通过“wire”发送Date对象实例,从一个时区到另一个时区
  • 解析时区的日期,该时区不是与值
  • 关联的隐含的日期
  • (错误)对隐式时区的代码假设 日期/时间值并尝试应用其错误的“更正”。 (例如,当无时区的DB值被归因于DB的隐含时区,读取应用程序代码尝试将其调整为应用程序假定的(不同的)本地时区。)

为了克服这种麻烦,基本上有两种策略:

  1. 严格执行整个应用程序的唯一时区。 (鉴于Date使用 UTC 的隐式语义将是一个不错的选择。)
    在这种情况下,您会立即将从“外部”输入的任何值转换为您的公共应用程序时区(来自已知或隐含的本地时区)。并且仅将(内部)日期/时间值转换为本地时区以便显示(尽可能晚)。应用程序“内部”(并与数据库一起存储)的任何值都将位于应用程序时区中 这对于更改服务器和数据库的时区是不变的,只需要正确的检测日期I / O位置。您还需要处理与其他外部实体(例如您提到的文件名)相关的日期/时间值。这些值最好根据应用时区生成。

  2. 你放弃尝试在没有时区概念的情况下过关,并改为使用带有任何日期/时间值的显式时区。

  3. 第一种方法的好处是可以在不同时区提供一致的值(例如,使用生成的文件名使用日期/时间值 - 然后需要将其用作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考虑该时区。

有关主题https://stackoverflow.com/a/37103310/5343269

的详细信息,请参阅此SO条目