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

How to manually set the Daylight Saving (DST) shift date in Java

我的国家/地区将夏令时日期从 "October 21" 更改为 "November 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

OS无关紧要

您的操作系统配置无关紧要。默认情况下,大多数 Java 实现会在启动时从主机 OS 中获取其初始默认时区。但是时区的定义存储在Java实现中。

Java 时区更新器

因此您需要在 Java 实施中更新时区定义。大多数实现使用 tz database 也称为 tzdata.

对于 Oracle 品牌的 Java 实施,Oracle 提供 Timezone Updater Tool。该着陆页的截止日期为 2018-08,因此您的时区更改可能已包含在内。但我建议你更仔细地调查以验证。

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

使用代码

避免混淆区

我强烈建议您避免尝试自己在代码中对偏移量进行人为调整。 您可能会弄错。 日期时间工作非常棘手和令人困惑。

但如果你坚持,首先要避免可怕的旧遗留日期时间 类,例如 GregorianCalendar & Calendar & Date。这些在几年前被 JSR 310 取代。如果您必须与尚未更新到 java.time 的旧代码进行互操作,请在现代 类 中进行工作,然后在最后通过添加到旧 类.

的新方法进行转换

使用现代的java.time类,具体来说:

  • Instant(UTC 时间)
  • OffsetDateTime(与 UTC 的偏移量为小时-分钟-秒但没有时区)
  • ZonedDateTime(特定时区的片刻)

您可以在 Stack Overflow 中搜索许多使用这些 类 的现有示例和解释。您应该关注 OffsetDateTime, ZoneOffset (rather than ZoneId), and Instant,因为如果您知道 tzdata 文件已过时,则必须避免 ZonedDateTime

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

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.

odt.toString(): 2018-10-21T01:00-02:00

odt2.toString(): 2018-10-21T00:00-03:00

在该示例中,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,与 UTC 的偏移量为零,+00:00Z 发音为“Zulu”。由 ISO 8601 标准定义。

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

OffsetDateTime::withOffsetSameLocal​

相比之下,您可能想要强制显示一天中的时间,从而代表 不同的 时刻。为此,请使用 withOffsetSameLocal 方法。请注意,您正在 改变数据的含义 ,您正在移动到时间轴上的另一点。

OffsetDateTime differentMomentButSameTimeOfDay = odt. withOffsetSameLocal( offset ) ;

differentMomentButSameTimeOfDay.toString(): 2018-10-21T01:00-03:00

提取瞬间以查看我们有不同的时刻。

Instant differentInstant = differentMomentButSameTimeOfDay.toInstant() ;

differentInstant.toString(): 2018-10-21T04:00:00Z

注意上面看到的世界标准时间凌晨 4 点与世界标准时间凌晨 3 点。这一刻发生在上述时刻之后的一个小时。时间轴上的两个不同点。

在完全理解时间轴上点的概念之前不要尝试这项工作,并且在点之间进行更改与调整偏移量完全不同。在开始真正的工作之前进行广泛的练习。三心二意的猜测会让你陷入痛苦和头痛的世界。

并且,正如我上面所建议的,您最好将时间花在安装更新的 tzdata 文件上,而不是破解这些偏移量。

活码

查看以上所有代码run live at IdeOne.com

到处更新tzdata

为了获得最佳结果,您应该在所有这些不同的地方更新 tzdata(或等效的):

  • 您的操作系统
  • 您的 JVM
  • 你的数据库引擎,比如Postgres
  • 任何捆绑自己时区信息的图书馆(例如:Joda-Time

关于java.time

java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

Joda-Time project, now in maintenance mode, advises migration to the java.time 类.

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

在哪里获取 java.time 类?

ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.