Joda-Time 到 Java 8 的转换

Joda-Time to Java 8 conversion

如何在 Java 8 java.time 中实现以下目标:

DateTime dt = new DateTime(year, month, date, hours, min, sec, milli);

但是我找到了这个选项:

OffsetDateTime.of(year, month, dayOfMonth, hour, minute, second, nanoOfSecond, offset)

但是只有没有毫秒的方法

如何实现与 Joda-Time 相同的效果?

要将毫秒调整为纳秒,只需将毫秒值乘以 1000000(一百万)即可。


不过,要获得正确的偏移量,有点棘手。

Joda 的 DateTime 在您未指定时区时使用系统的默认时区。因此,在 java.time 中,很自然地认为这只是使用 ZoneId.systemDefault()(系统的默认时区)来获取该日期的有效偏移量的问题——这在大多数情况下都有效,但是是一些您必须注意的极端情况。

时区在历史上可能有不同的偏移量 - 现在,大多数这些变化是由于 夏令时 (DST) 而发生的,但也可能因为某些 government/administration 决定改了。

示例:1985 年,巴西阿卡州的标准偏移量为 UTC-05:00(夏令时期间为 UTC-04:00),然后在 1988 年为 UTC-05:00 没有 DST,然后在 2008 年标准更改为 UTC-04:00(并且没有 DST),并且自 2013 年起又回到 UTC-05:00 并且没有 DST。 这听起来像是一个极端的案例,但这样的变化是可能发生的 at anytime/anywhere

所有这些只是为了表明 偏移量取决于时区和日期。在遇到极端情况时,您必须做出一些决定。

对于这个例子,我将使用我的默认时区,即 America/Sao_Paulo(这里我们每年都有夏令时)。目前,我们的偏移量是 -03:00,但在 2017 年 10 月 15 日th 午夜,时钟将向前移动 1 小时(从午夜到凌晨 1 点),偏移量将为改为 -02:00.

在这种情况下,午夜和凌晨 1 点之间的时间段是 "skipped",它们之间的任何当地时间都是无效的(这称为 时间间隔 )。实际上,如果您尝试在 Joda-Time 中创建这样的日期和时间,它会抛出异常:

// reminding that my system's default timezone is America/Sao_Paulo
DateTime d = new DateTime(2017, 10, 15, 0, 10, 0, 0);

这抛出:

org.joda.time.IllegalInstantException: Illegal instant due to time zone offset transition (daylight savings time 'gap'): 2017-10-15T00:10:00.000 (America/Sao_Paulo)

但在java.time中,ZonedDateTime class将其调整为下一个有效偏移量:

// reminding that my system's default timezone is America/Sao_Paulo
ZonedDateTime z = ZonedDateTime.of(2017, 10, 15, 0, 10, 0, 0, ZoneId.systemDefault());

z的值为:

2017-10-15T01:10-02:00[America/Sao_Paulo]

请注意,它将00:10调整为下一个有效偏移量(-02:00),时间也相应调整为01:10。然后你可以这样做:

OffsetDateTime odt = z.toOffsetDateTime()

获取您的OffsetDateTime(在本例中,该值为2017-10-15T01:10-02:00)。


当 DST 结束时,它也很复杂。在圣保罗,夏令时将于 2018 年 2 月 18 日结束。午夜时分,时钟将倒退 1 小时(到 2 月 17 日晚上 11 点),偏移量将回到 -03:00

所以,从2月17日下午23点到2月18日午夜这段时间会存在两次(第一次在-02:00 偏移量,然后在 -03:00 偏移量中 - 这称为 重叠 )。对于当地时间晚上 11 点到午夜之间,将有 2 个有效偏移量,因此您必须决定使用哪一个。

幸运的是,ZonedDateTime 提供了 2 个选项:您可以使用 withEarlierOffsetAtOverlap() 获取 DST 更改前的偏移量(在我的例子中,它将是 -02:00)和 withLaterOffsetAtOverlap() 获取 DST 更改后的偏移量(在我的例子中,它将是 -03:00):

// reminding that my system's default timezone is America/Sao_Paulo
z = ZonedDateTime.of(2018, 2, 17, 23, 30, 0, 0, ZoneId.systemDefault());
System.out.println(z.withEarlierOffsetAtOverlap()); // 2018-02-17T23:30-02:00[America/Sao_Paulo]
System.out.println(z.withLaterOffsetAtOverlap()); // 2018-02-17T23:30-03:00[America/Sao_Paulo]

输出将是:

2018-02-17T23:30-02:00[America/Sao_Paulo]
2018-02-17T23:30-03:00[America/Sao_Paulo]

注意方法 with...OffsetAtOverlap 如何获取 DST 更改前后的偏移量 - 还要注意本地时间 23:30 存在两次,在 2 个不同的偏移量中(-02:00-03:00).选择要使用的实例后,您可以调用 toOffsetDateTime() 来获取 OffsetDateTime 实例:

OffsetDateTime beforeDSTChange = z.withEarlierOffsetAtOverlap().toOffsetDateTime();
OffsetDateTime afterDSTChange = z.withLaterOffsetAtOverlap().toOffsetDateTime();

beforeDSTChange 将是 2018-02-17T23:30-02:00afterDSTChange 将是 2018-02-17T23:30-03:00.

默认情况下,ZonedDateTime 使用较早的偏移量创建 date/time,但我认为最好通过调用相应的方法。在只有一个偏移量有效的情况下,两种方法 return 相同。

顺便说一句,我在 Joda-Time 2.9.9 中测试了相同的日期,它得到了更早的偏移量 -02:00:

// reminding that my system's default timezone is America/Sao_Paulo
System.out.println(new DateTime(2018, 2, 17, 23, 30, 0, 0));

2018-02-17T23:30:00.000-02:00

并且 DateTime class 也有方法 withEarlierOffsetAtOverlap()withLaterOffsetAtOverlap(),其工作方式与 ZonedDateTime 类似。


当然,在大多数情况下,您可以放心地使用 ZonedDateTime - DST 和任何其他偏移量更改不会一直发生。但重要的是要注意这些极端情况并相应地进行调整。

一个额外的警告:除了使用 ZoneId.systemDefault(),您可以使用 ZoneId.of("zonename") 明确指定您想要的时区。 API 使用 IANA timezones names(格式总是 Region/City,如 America/Sao_PauloEurope/Berlin)。 避免使用 3 个字母的缩写(如 CSTPST),因为它们是 ambiguous and not standard.

您可以通过调用 ZoneId.getAvailableZoneIds() 获得可用时区列表(并选择最适合您的系统的时区)。最好明确说明您使用的时区,因为系统的默认设置可以在不通知的情况下更改,即使在运行时也是如此。


如果您有 Joda 的DateTimejava.time,您还可以从一个转换为另一个。

您可以从 Joda 的 DateTime 中获取纪元毫秒值来创建一个 java.time.Instant。然后创建一个与 DateTime 的区域具有相同 ID 的 ZoneId,并获取 Instant 的相应偏移量并加入这些片段以创建 OffsetDateTime:

DateTime d = new DateTime(2018, 2, 17, 23, 30, 0, 0);

// convert to java.time.Instant, using the same epoch millis value
Instant instant = Instant.ofEpochMilli(d.getMillis());

// get the offset for that instant, in the same zone of the Joda's DateTime instance
ZoneOffset offset = ZoneId.of(d.getZone().getID()).getRules().getOffset(instant);

// get the OffsetDateTime in the offset above
OffsetDateTime odt = instant.atOffset(offset); // 2018-02-17T23:30-02:00

结果将是2018-02-17T23:30-02:00。请注意,DateTime 对象使用较早的偏移量(如上所述),但如果您想使用较晚的偏移量,则可以选择使用 d.withLaterOffsetAtOverlap().getMillis()

提醒一下,如果出现夏令时间隔,Joda-Time 会抛出异常,如前所述。