Java 1.7无法识别夏令时的变化

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 1.7无法识别夏令时的变化相关的知识,希望对你有一定的参考价值。

Java:ver 1.7 update 71 DB:mysql ver 5.6.x.

我们正在运行访问相同数据库的多个应用程序Tomcat 7(版本7.0.52)上运行了两个应用程序,两个应用程序正在使用Netty 3.10。服务器位于EST(美国)时区。

Web应用程序技术堆栈:Spring 3.1.x Struts MVC 2.3.14 Hibernate 3.3.x

Netty应用技术栈:Netty 3.10 Hibernate 3.3.x

我们在应用程序中使用了许多Apache log4j,commons库。

在最近的夏令时变化期间,在tomcat下运行的应用程序能够在DST之后获得正确的时间。根据DST的时区,Linux和DB显示正确的时间。然而,在Netty上运行的人继续跑了一个小时。不仅仅是为应用程序插入的行设置的日期,所有日期值(包括日志文件中的时间戳)都显示不正确的时间。

重新启动后,他们开始显示正确的时间。

Java使用底层操作系统的时区详细信息。作为启动的一部分,没有给出与时区相关的特定设置。

我认为这个问题在JDK 1.5 / 1.6期间得到解决,并在1.7中自动处理。

是否有一些需要应用于Java 1.7的补丁?是否有明确需要在应用程序中完成识别DST变化的任何事情? Tomcat如何处理这个问题?

答案

tzdata

时区数据几乎总是由tz database定义,以前称为Olson数据库,现在由IANA处理。此数据库包含全球每个地区(时区)人员使用的UTC偏移的过去,现在和将来的变化历史记录。

Zone definition changes

这个数据库经常变化,因为世界各地的政治家都莫名其妙地习惯于经常变化。这些变化通常只需要几个星期的预警即可完成。

当不安分的政治家做出改变时,如果你关心受影响的区域,你需要更新操作系统(对于本机应用程序)和Java实现(对于Java应用程序)的“tzdata”。 Oracle提供Timezone Updater Tool来为其实现安装新的tzdata。 Oracle的更新版本通常包括最新的tzdata,但并非总是如此,因此您应该阅读发行说明以获取此类详细信息。通过针对Oracle和OpenJDK实现的新计划季度发布节奏,如果您选择保持与Java版本保持同步,则需要手动安装tzdata。

因此,当您关心的时区的定义发生变化时,您可能需要至少在三个位置更改tzdata

  • 你的电脑操作系统。
  • 你的JVM。
  • 您的数据库服务器,如果该数据库具有复杂的日期时间处理,例如Postgres(请参阅example history)。

Java使用底层操作系统的时区详细信息。

不是。时区详细信息的来源是每个Java实现的实现细节。

在我所知道的JVM实现中,JVM通过将主机操作系统作为JVM启动的一部分来确定当前的默认时区。作为启动的一部分,可能已将默认时区作为启动参数传递给JVM。第三,在运行时期间的任何时刻任何应用程序的任何线程中的任何代码都可以更改默认时区(这几乎不是一个好主意)。

但在所有这些情况下,区域的定义都存储在JVM本身中。因此,如果感兴趣的区域发生了变化,您需要使JVM的tzdata保持最新状态。

Possible cause of problem: Caching offset rather than zone

在Netty上运行的人继续跑了一个小时......

重新启动后,他们开始显示正确的时间。

如果没有看到您的应用程序的编程,并且没有看到Netty的编程,我们只能猜测具体问题。我怀疑应用程序可能无法正确处理日期时间。

捕获片刻的正确方法是在UTC中这样做。在Java中,这意味着Instant类。 Instant类代表UTC时间轴上的一个时刻,分辨率为nanoseconds(小数部分最多九(9)位)。

Instant instant = Instant.now() ;  // Capture current moment in UTC.

或者,您可以捕获特定时区的时刻。

ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.now( z );

当应用程序启动时,您的应用可能只使用了从UTC缓存的偏移量。

ZoneOffset offset = ZoneOffset.systemDefault() ; // Do NOT cache this, as it will be invalid after a DST cut-over.

时区总是优于offset-from-UTC。偏移只是一个小时,分钟和秒的数量 - 仅此而已,仅此而已。相比之下,time zone是特定地区人民使用的偏移的过去,现在和未来变化的历史。

如果应用程序在启动时检测到当前正在使用的偏移量,并且无限期地缓存该偏移量,则在Daylight Saving Time (DST)切换后其使用将不再正确。

例如,目前在America/Los_Angeles区域,UTC的偏移量是UTC的八小时,-08:00。但是在几天内,在3月的第二个星期天,当每个人都将他们的时钟提前一小时时,DST废话生效,从而将使用中的UTC偏移量改为UTC后七个小时,-07:00。如果-08:00被不恰当地缓存,捕获当前时刻的尝试将是不正确的。

OffsetDateTime odt = OffsetDateTime.now( inappropriatelyCachedOffset ) ;  // Capturing the wrong wall-clock time. 

同样,预防措施很简单......使用时区,而不是从UTC偏移。

proper time zone name的格式指定continent/region,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用诸如ESTIST之类的3-4字母缩写,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

Possible cause of problem: Database tzdata not updated

您的表现行为的另一个可能原因可能是您的应用程序从数据库服务器获取当前的分区日期时间,其中数据库自己的tzdata现已过时,尚未更新以反映该区域定义中的更改。

如上所述,一些复杂的数据库(如Postgres)有自己内部存储的tzdata信息。预防措施是使数据库服务器与其版本更新保持同步,或者手动安装更新的tzdata

专注于UTC

这也是以UTC思考,工作,记录,存储和交换日期时间值的另一个原因。在整个问题讨论中,时钟可能已正确设置为当前时间。它是通过一个可能出错的区域对那段时间的解释。如果以UTC格式捕获电流,则不会调整时区,因此没有问题。

所以学会用UTC思考。在作为程序员或管理员的工作中,忘掉自己的狭隘时区。将桌面上的第二个时钟设置为UTC。以UTC格式记录您的所有记录和记录。将UTC视为唯一真实时间,其他所有划分时间仅仅是变化。

再次,Instant.now()是你的朋友。

ISO 8601

如果需要序列化日期时间值,请仅使用标准ISO 8601格式。该标准设计巧妙,实用且合理。这些格式很容易通过机器解析,但也很容易被不同文化的人阅读。

对于UTC的片刻,格式为YYYY-MM-DDTHH:MM:SS.SZ。中间的T将年 - 月 - 日与小时 - 分 - 秒分开。最后的ZZulu的缩写,意思是UTC。

2018-01-23T12:34:56.123456789Z

在解析/生成字符串时,java.time类默认使用ISO 8601格式。

String output = instant.toString() ;

…和…

Instant instant = Instant.parse( "2018-01-23T12:34:56.123456789Z" ) ;

Use java.time classes

另一个要点:永远不要使用与最早版本的Java捆绑在一起的麻烦的旧日期时间类。他们是一个可怕的可怜的混乱,要避免。它们现在是遗留的,完全由java.time类取代。上面看到的所有代码都使用了现代的java.time类。

是否有一些需要应用于Java 1.7的补丁?

不是我知道的。但您可能需要在JVM和数据库服务器中更新上面讨论过的tzdata

是否有明确需要在应用程序中完成识别DST变化的任何事情?

不可以。在DST切换过程中,如果您的Qazxswpoi在您的JVM中是最新的(a),并且(b)在您的数据库中,您不需要做任何事情,并且您使用时区而不仅仅是偏移。

Tomcat如何处理这个问题?

不,Apache Tomcat与您的日期时间问题无关。 Tomcat对时区和偏移一无所知。这是您的JVM的业务,而不是Tomcat。


About java.time

tzdata框架内置于Java 8及更高版本中。这些类取代了麻烦的旧java.time日期时间类,如legacyjava.util.DateCalendar

现在在SimpleDateFormatJoda-Time项目建议迁移到maintenance mode班。

要了解更多信息,请参阅java.time。并搜索Stack Overflow以获取许多示例和解释。规格是Oracle Tutorial

您可以直接与数据库交换java.time对象。使用符合JSR 310或更高版本的JDBC driver。不需要字符串,不需要JDBC 4.2类。

从哪里获取java.time类?