正如@Misha 的回答中已经解释的那样,这是由于夏令时规则而发生的。
在圣保罗,夏令时从 2015 年 10 月 18 日午夜开始:时钟向前移动 1 小时,因此它从23:59:59
到01:00:00
. 00:00:00
和之间有间隔00:59:59
,所以时间00:30
相应调整。
ZoneRules
您可以使用和类检查日期和时间是否对时区有效ZoneOffsetTransition
:
ZoneId sp = ZoneId.of("America/Sao_Paulo");
ZoneRules rules = sp.getRules();
// check if 2015-10-18 00:30 is valid for this timezone
LocalDateTime dt = LocalDateTime.of(2015, 10, 18, 0, 30);
List<ZoneOffset> validOffsets = rules.getValidOffsets(dt);
System.out.println(validOffsets.size()); // size is zero, no valid offsets at 00:30
该getValidOffsets
方法返回指定日期/时间的所有有效偏移量。如果列表为空,则表示该时区中不“存在”日期/时间(通常是因为 DST 时钟向前跳)。
当日期/时间存在于时区中时,将返回偏移量:
ZoneId la = ZoneId.of("America/Los_Angeles");
rules = la.getRules();
validOffsets = rules.getValidOffsets(dt);
System.out.println(validOffsets.size()); // 1 - date/time valid for this timezone
System.out.println(validOffsets.get(0)); // -07:00
对于Los_Angeles
时区,返回 1 个有效偏移量:-07:00
.
PS:偏移量变化通常是由于 DST 而发生的,但情况并非总是如此。DST 和偏移量由政府和法律定义,它们可以随时更改。因此,有效偏移量的差距也可能意味着发生了这种变化(一些政客决定改变国家的标准偏移量,因此差距可能不一定与 DST 相关)。
您还可以检查更改发生的时间,以及更改前后的偏移量:
ZoneId sp = ZoneId.of("America/Sao_Paulo");
ZoneRules rules = sp.getRules();
// get the previous transition (the last one that occurred before 2015-10-18 00:30 in Sao_Paulo timezone
ZoneOffsetTransition t = rules.previousTransition(dt.atZone(sp).toInstant());
System.out.println(t);
输出是:
过渡[差距在 2015-10-18T00:00-03:00 到 -02:00]
这意味着在 处存在间隙(时钟向前移动)2015-10-18T00:00
,并且偏移量将从-03:00
变为-02:00
(因此,时钟向前移动 1 小时)。
您还可以单独获取所有这些信息:
System.out.println(t.getDateTimeBefore() + " -> " + t.getDateTimeAfter());
System.out.println(t.getOffsetBefore() + " -> " + t.getOffsetAfter());
输出是:
2015-10-18T00:00 -> 2015-10-18T01:00
-03:00 -> -02:00
它表明,00:00
时钟直接移动到01:00
(所以00:30
不能存在)。在第二行中,更改前后的偏移量。
如果你检查Los_Angeles
时区的转换,你会看到它的 DST 在不同的日期开始和结束:
ZoneId la = ZoneId.of("America/Los_Angeles");
rules = la.getRules();
// 2015-10-18 00:30 in Los Angeles
Instant instant = dt.atZone(la).toInstant();
System.out.println(rules.previousTransition(instant));
System.out.println(rules.nextTransition(instant));
输出是:
过渡[在 2015-03-08T02:00-08:00 到 -07:00 的差距]
过渡[在 2015-11-01T02:00-07:00 到 -08:00 重叠]
因此,在Los_Angeles
时区中,DST 开始于2015-03-08
并结束于2015-11-01
. 这就是为什么在2015-10-18
,所有时间都是有效的(没有调整,因为它发生在Sao_Paulo
时区)。
一些时区有转换规则(例如“DST 在十月的第三个星期日开始”),而不仅仅是转换(例如“DST 在这个特定的日期和时间开始”),如果可用,您也可以使用它们:
ZoneId sp = ZoneId.of("America/Sao_Paulo");
ZoneRules rules = sp.getRules();
// hardcoded: Sao_Paulo timezone has 2 transition rules, the second one is relative to October
// but you should always check if the list is not empty
ZoneOffsetTransitionRule tr = rules.getTransitionRules().get(1);
// get the transition for year 2015
ZoneOffsetTransition t = tr.createTransition(2015);
// use t the same way as above (the output will be the same)
检查日期和时间是否对某个时区有效的另一种方法是使用该ZonedDateTime.ofStrict
方法,如果日期和时间对某个时区无效,则会引发异常:
ZoneId sp = ZoneId.of("America/Sao_Paulo");
ZoneId la = ZoneId.of("America/Los_Angeles");
LocalDateTime dt = LocalDateTime.of(2015, 10, 18, 0, 30);
System.out.println(ZonedDateTime.ofStrict(dt, ZoneOffset.ofHours(-7), la)); // OK
System.out.println(ZonedDateTime.ofStrict(dt, ZoneOffset.ofHours(-3), sp)); // throws java.time.DateTimeException
第一种情况是可以的,因为-7
对于给定的日期/时间,洛杉矶的偏移量是有效的。第二种情况引发异常,因为-3
在给定日期/时间,偏移量对圣保罗无效。