夏令时 世界上许多国家都采用所谓的夏令时 (DST),即在夏季将时钟提前一小时的做法(好吧,并非所有国家都在夏季,但让我们忍一忍吧)节省时间开始。
当白天时间结束时,时钟将拨回一小时。这样做是为了更好地利用自然光。
ZonedDateTime 完全了解 DST。
例如,让我们以一个完全遵守 DST 的国家为例,例如意大利 (UTC/GMT +2)。
2015年,意大利夏令时于3月29日开始,10月25日结束。这意味着在:
March, 29 2015 at 2:00:00 A.M. clocks were turned forward 1 hour to
March, 29 2015 at 3:00:00 A.M. local daylight time instead
(So a time like March, 29 2015 2:30:00 A.M. didn't actually exist!)
October, 25 2015 at 3:00:00 A.M. clocks were turned backward 1 hour to
October, 25 2015 at 2:00:00 A.M. local daylight time instead
(So a time like October, 25 2015 2:30:00 A.M. actually existed twice!)
如果我们用这个日期/时间创建一个 LocalDateTime 的实例并打印它:
LocalDateTime ldt = LocalDateTime.of(2015, 3, 29, 2, 30);
System.out.println(ldt);
结果将是:
2015-03-29T02:30
但是如果我们为意大利创建一个 ZonedDateTime 的实例(注意格式使用的是城市,而不是国家)并打印:
ZonedDateTime zdt = ZonedDateTime.of(
2015, 3, 29, 2, 30, 0, 0, ZoneId.of("Europe/Rome"));
System.out.println(zdt);
使用 DST 时,结果将与现实世界中的一样:
2015-03-29T03:30+02:00[Europe/Rome]
但小心点。我们必须使用区域 ZoneId,ZoneOffset 无法解决问题,因为此类没有区域规则信息来解释 DST:
ZonedDateTime zdt1 = ZonedDateTime.of(
2015, 3, 29, 2, 30, 0, 0, ZoneOffset.ofHours(2));
ZonedDateTime zdt2 = ZonedDateTime.of(
2015, 3, 29, 2, 30, 0, 0, ZoneId.of("UTC+2"));
System.out.println(zdt1); System.out.println(zdt2);
结果将是:
2015-03-29T02:30+02:00[UTC+02:00] 2015-03-29T02:30+02:00
当我们为意大利创建 ZonedDateTime 的实例时,我们必须添加一个小时才能看到效果(否则我们将在新时间的 3:00 创建 ZonedDateTime):
ZonedDateTime zdt = ZonedDateTime.of(
2015, 10, 25, 2, 30, 0, 0, ZoneId.of("Europe/Rome"));
ZonedDateTime zdt2 = zdt.plusHours(1);
System.out.println(zdt);
System.out.println(zdt2);
结果将是:
2015-10-25T02:30+02:00[Europe/Rome] 2015-10-25T02:30+01:00[Europe/Rome]
在使用采用 TemporalAmount 实现的方法 plus() 和 minus() 的版本(换句话说,Period 或 Duration)调整跨 DST 边界的时间时,我们还需要小心。这是因为两者对夏令时的处理方式不同。
考虑在意大利夏令时开始前一小时:
ZonedDateTime zdt = ZonedDateTime.of(
2015, 3, 29, 1, 0, 0, 0, ZoneId.of("Europe/Rome"));
当我们添加一天的 Duration 时:
System.out.println(zdt.plus(Duration.ofDays(1)));
结果是:
2015-03-30T02:00+02:00[Europe/Rome]
当我们添加一天的时间段时:
System.out.println(zdt.plus(Period.ofDays(1)));
结果是:
2015-03-30T01:00+02:00[Europe/Rome]
原因是 Period 添加了一个概念日期,而 Duration 恰好添加了一天(24 小时或 86、400 秒),当它越过 DST 边界时,添加了一小时,最终时间是 02:00 而不是 01: 00.
==================================================== ===================== Java 8引入java.time.LocalDateTime:
LocalDateTime localDateTimeBeforeDST = LocalDateTime
.of(2018, 3, 25, 1, 55);
assertThat(localDateTimeBeforeDST.toString())
.isEqualTo("2018-03-25T01:55");
我们可以观察到 LocalDateTime 是如何符合 ISO-8601 规则的。
但是,它完全不知道 Zones 和 Offsets,这就是为什么我们需要将其转换为完全 DST 感知的 java.time.ZonedDateTime:
ZoneId italianZoneId = ZoneId.of("Europe/Rome");
ZonedDateTime zonedDateTimeBeforeDST = localDateTimeBeforeDST
.atZone(italianZoneId);
assertThat(zonedDateTimeBeforeDST.toString())
.isEqualTo("2018-03-25T01:55+01:00[Europe/Rome]");
正如我们所见,现在日期包含两个基本的尾随信息:+01:00 是 ZoneOffset,而 [Europe/Rome] 是 ZoneId。
让我们通过增加十分钟来触发夏令时:
ZonedDateTime zonedDateTimeAfterDST = zonedDateTimeBeforeDST
.plus(10, ChronoUnit.MINUTES);
assertThat(zonedDateTimeAfterDST.toString())
.isEqualTo("2018-03-25T03:05+02:00[Europe/Rome]");
再次,我们看到时间和区域偏移量是如何向前移动的,并且仍然保持相同的距离:
Long deltaBetweenDatesInMinutes = ChronoUnit.MINUTES
.between(zonedDateTimeBeforeDST,zonedDateTimeAfterDST);
assertThat(deltaBetweenDatesInMinutes)
.isEqualTo(10);