tzdata
时区数据通常由tz数据库定义,以前被称为Olson数据库,现在由IANA处理。该数据库记录了全球各地区(时区)居民使用的UTC偏移量的过去、现在和未来变化历史。
区域定义更改
政治家们经常无法解释地进行更改,因此这个数据库经常会发生变化。这些更改通常很少提前警告,有时只有几周的时间。
当不安定的政治局势带来变化时,如果你关心受影响的时区,你需要更新操作系统(原生应用)和Java实现(Java应用)的“tzdata”。Oracle提供了
时区更新工具,以便为其实现安装最新的tzdata。Oracle的更新版本通常包含最新的tzdata,但并非总是如此,因此您应该阅读发布说明以获取相关详细信息。随着Oracle和OpenJDK实现计划中每季度发布的频率,如果您选择保持与Java版本的同步,手动安装tzdata的需求可能会减少。
因此,当你关心的时区发生定义更改时,你可能需要在至少三个地方更改tzdata
:
底层操作系统的Java使用时区详细信息。
不是的。每个Java实现的时区详细信息来源都是实现细节。
在我所知道的JVM实现中,JVM通过在启动JVM时询问主机操作系统来确定当前默认时区。作为启动参数,可以向JVM传递默认时区。第三,在运行时的任何时刻的任何应用程序的任何线程中的任何代码都可以更改默认时区(这几乎从不是一个好主意)。
但在所有这些情况下,时区的定义都存储在JVM内部。因此,如果任何感兴趣的时区发生了更改,则需要保持JVM的tzdata最新。
问题可能的原因:缓存偏移而不是时区
在Netty上运行的应用程序继续落后一个小时...
重新启动后,它们开始显示正确的时间。
没有看到您的应用程序编程,也没有看到Netty的编程,我们只能猜测具体问题。我怀疑该应用程序可能未能正确处理日期时间。
捕捉时刻的正确方式是在UTC中进行。在Java中,这意味着使用Instant
类。Instant
类表示以UTC为时间轴上的一瞬间,精度为纳秒(最多九位小数)。
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.
时区总是优于仅有的
UTC偏移量。偏移量只是小时、分钟和秒数的数字,没有更多,也没有更少。相比之下,
时区是一个特定地区人民过去、现在和未来使用的偏移变化历史记录。
如果一个应用程序在启动时检测到当前使用的偏移量并永久缓存该偏移量,则在
夏令时(DST)切换后,其使用将不再正确。
例如,目前在
America/Los_Angeles
区,偏移量与UTC相差8个小时,
-08:00
。但在几天后,即三月第二个星期日,DST荒谬行为开始生效,当每个人将时钟拨快一小时时,使用的UTC偏移量将改变为相对UTC落后7个小时,
-07:00
。如果那个
-08:00
被错误地缓存,捕获当前时刻的尝试将是不正确的。
OffsetDateTime odt = OffsetDateTime.now( inappropriatelyCachedOffset )
再次提醒,预防措施很简单...使用时区而非UTC偏移量。
以continent/region
的格式指定正确的时区名称,例如America/Montreal
、Africa/Casablanca
或Pacific/Auckland
。永远不要使用三到四个字母的缩写,例如EST
或IST
,因为它们不是真正的时区,没有标准化,甚至不唯一(!)。
问题可能的原因:未更新数据库tzdata
您的应用程序表现出的另一个可能原因是,它从数据库服务器获取当前带有时区的日期时间,而该数据库自己的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
将年月日与小时分钟秒分开。结尾处的
Z
缩写为
Zulu
,表示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" ) ;
使用 java.time 类
另一个重要的点:永远不要使用早期版本的 Java 中捆绑的令人头疼的旧日期时间类。它们是一团糟,应该避免使用。它们现在已经过时,完全被 java.time 类所取代。以上所有代码都使用了现代的 java.time 类。
Java 1.7 需要应用程序补丁吗?
据我所知,不需要。但是,您可能需要像上面讨论的那样更新您的 tzdata
,同时在您的 JVM 和数据库服务器中更新。
应用程序中是否需要明确做出 DST 更改的识别?
不需要。如果您的 tzdata
在 (a) 您的 JVM 和 (b) 您的数据库中是最新的,并且您使用时区而不仅仅是偏移量,则在 DST 切换期间无需进行任何操作。
Tomcat 如何处理这个问题?
不,Apache Tomcat与您的日期时间问题无关。Tomcat对时区和偏移量一无所知。这是您的JVM的业务,而不是Tomcat。
关于java.time
java.time框架内置于Java 8及更高版本中。这些类替代了旧的遗留日期时间类,例如java.util.Date
、Calendar
和SimpleDateFormat
等。
Joda-Time 项目现在处于 维护模式,建议迁移到 java.time 类。
欲了解更多信息,请参阅 Oracle 教程。并在 Stack Overflow 上搜索许多示例和解释。规范为 JSR 310。
您可以直接与数据库交换 java.time 对象。使用符合 JDBC 4.2 或更高版本的 JDBC 驱动程序。无需字符串,也无需 java.sql.*
类。
如何获取 java.time 类?
ThreeTen-Extra 项目扩展了 java.time 的附加类。该项目是 java.time 可能未来增加的一个试验场。您可能会在这里找到一些有用的类,例如 Interval
、YearWeek
、YearQuarter
,以及 更多。