如何在Java中手动设置夏令时(DST)转换日期

7

我的国家将夏令时转移日期从“10月21日”更改为“11月4日”,我们需要在后端应用此更改。

适当的解决方案是更新操作系统配置,但我们有限制无法这样做(遗留依赖项)。 我们正在寻找解决方法。

是否可以使用代码并通过编程方式更改DST转移日期?

GregorianCalendar gc = new GregorianCalendar();
gc.setTimeInMillis(0);
gc.set(2018, Calendar.OCTOBER, 21, 0, 0, 0);
gc.setTimeZone(TimeZone.getTimeZone("Brazil/East"));
XMLGregorianCalendar xml = DatatypeFactory.newInstance().newXMLGregorianCalendar(gc);
System.out.println("XML Date: " + xml.toString());

输出必须为-03:00

XML Date: 2018-10-21T01:00:00.000-02:00

如果所有其他方法都失败了,你可以编写自己的逻辑来解决这个问题。 - Code-Apprentice
2
“OS配置”是指服务器或JRE的时区数据(tzdata:https://www.oracle.com/technetwork/java/javase/tzdata-versions-138805.html)。 - everton
1
@everton 我猜想Java会从操作系统中获取DST转换日期的理解是错误的? - Evandro Pomatti
@BonanzaOne 是的,它是不正确的... 控制这个的是 tzdata,你需要随着新变化保持更新,正如 BasilBourque 的回答所展示的那样。 - everton
1个回答

10

操作系统无关

您的操作系统配置是无关紧要的。大多数Java实现在启动时默认从主机操作系统中选择其初始默认时区。但是,时区的定义存储在Java实现内部。

Java时区更新程序

因此,您需要更新Java实现中的时区定义。大多数实现使用tz数据库,也称为tzdata

对于Oracle品牌的Java实现,Oracle提供Timezone Updater Tool。该页面的截止日期为2018-08,因此可能已包含您所在时区的更改。但我建议您进行更详细的调查以进行验证。

对于其他实现,请与供应商联系。他们可能已经提供了包含新tzdata的JVM的更新版本。或者他们也提供更新程序工具。或者您可以手动替换tzdata文件。

避免使用代码混淆区域

我强烈建议您避免尝试在代码中自行进行人为调整。您很可能会出错。日期时间工作令人惊讶地棘手和令人困惑。

但如果你坚持使用日期时间类,首先避免可怕的旧遗留日期时间类,例如GregorianCalendar & Calendar & Date。多年前,它们已被JSR 310所取代。如果你必须与尚未更新为java.time的旧代码进行交互,请在现代类中完成工作,然后最后通过添加到旧类的新方法进行转换。
使用现代的java.time类,具体包括:
  • Instant(UTC中的某一时刻)
  • OffsetDateTime (带有时区偏移量的某一时刻,但没有时区)
  • ZonedDateTime (特定时区中的某一时刻)
你可以在Stack Overflow上搜索许多使用这些类的现有示例和解释。你应该专注于OffsetDateTime, ZoneOffset (而不是ZoneId),以及Instant,因为如果你知道你的tzdata文件已过时,则必须避免使用ZonedDateTime

同一时刻,不同的挂钟时间

enter image description here

OffsetDateTime::withOffsetSameInstant​

OffsetDateTime odt = OffsetDateTime.parse( "2018-10-21T01:00:00.000-02:00" ) ;
ZoneOffset offset = ZoneOffset.ofHours( -3 ) ;
OffsetDateTime odt2 = odt.withOffsetSameInstant​( offset ) ;  // Same moment, same point on the timeline, different wall-clock time.

在这个例子中,odtodt2都代表同一时刻,时间线上的同一点。如果你提取一个Instant(UTC值),你的结果将是相同的时刻。只有它们的挂钟时间不同。
Instant instant1 = odt.toInstant() ;  // Adjust to UTC.
Instant instant2 = odt2.toInstant() ;
boolean sameMoment = instant1.equals( instant2 ) ; 

instant1.toString(): 2018-10-21T03:00:00Z
instant2.toString(): 2018-10-21T03:00:00Z
sameMoment = true “Z”代表UTC,偏移量为零,即+00:00。 “Z”发音为“Zulu”。由ISO 8601标准定义。
相同的墙上时间,不同的时刻。

enter image description here

OffsetDateTime::withOffsetSameLocal

相比之下,您可能希望强制指定时间,并因此表示一个不同的时刻。为此,请使用withOffsetSameLocal方法。请非常注意,您正在改变数据的含义,您正在移动到时间线上的另一点。

OffsetDateTime differentMomentButSameTimeOfDay = odt. withOffsetSameLocal( offset ) ;

不同的时刻,但时间相同:2018-10-21T01:00-03:00。提取瞬间以查看我们是否有不同的时刻。
Instant differentInstant = differentMomentButSameTimeOfDay.toInstant() ;

differentInstant.toString(): 2018-10-21T04:00:00Z
注意上面看到的UTC时间4点和3点之间的差异。这一刻发生在上面那一刻的一个小时后,是时间轴上的两个不同点。
在完全理解时间轴上的点和改变点之间的转换与调整偏移量之间的完全不同之前,请勿尝试此工作。在进行真正的工作之前,请进行广泛的实践。半心半意的猜测只会让你陷入困境和头痛之中。
而且,正如我上面建议的那样,你最好花时间安装更新的tzdata文件,而不是修改这些偏移量。
实时代码
查看上面所有代码在IdeOne.com上运行
更新各地的tzdata
为了获得最佳效果,您应该在以下所有各种地方更新tzdata(或等效物):
- 操作系统 - JVM - 数据库引擎,例如Postgres - 任何捆绑自己的时区信息的库(例如:Joda-Time

关于java.time

java.time框架是Java 8及以上版本内置的。这些类取代了老旧的legacy日期时间类,如java.util.DateCalendarSimpleDateFormat等。

欲了解更多信息,请参见Oracle教程。在Stack Overflow上搜索许多示例和解释。规范为JSR 310

Joda-Time项目现在处于维护模式,建议迁移到java.time类。

您可以直接与数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC驱动程序。不需要字符串,也不需要java.sql.*类。Hibernate 5和JPA 2.2支持java.time

如何获取java.time类?

ThreeTen-Extra 项目使用额外的类扩展了 java.time。该项目是 java.time 可能未来添加内容的试验场。您可能会在这里找到一些有用的类,例如 IntervalYearWeekYearQuartermore


Basil,这是一个很好的答案。不幸的是,我们正在运行非常陈旧的遗留代码,使用Java 6,我会尽力在您的帮助下解决这个问题。 - Evandro Pomatti
@BonanzaOne 请再阅读我的回答,其中包括Java 6和ThreeTen-Backport的要点。 - Basil Bourque
我只想看到问题得到按照所问的方式回答,而不是建议把一切都扔掉重新开始。 - user1644002

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接