ZonedDateTime 会在夏季夏令时使用与冬季相同的时区吗?
ZonedDateTime would use same timezone of winter when summer daylight saving?
ZonedDateTime zdt = ZonedDateTime.of(2015, 10, 18, 0, 30, 0, 0,
ZoneId.of("America/Sao_Paulo"));
System.out.println(zdt); // 2015-10-18T01:30-02:00[America/Sao_Paulo]
你可以看到小时是1
,而我们设置的是0
,时区是UTC-02:00
,而夏令时时区应该是UTC-03:00
。
但这里有一个不同的例子:
ZonedDateTime zdt = ZonedDateTime.of(2015, 10, 18, 0, 30, 0, 0,
ZoneId.of("America/Los_Angeles"));
System.out.println(zdt); //2015-10-18T00:30-07:00[America/Los_Angeles]
你可以看到我们设置的夏令时是UTC-07:00
,小时是0
。
为什么不同?
发生这种情况是因为您选择的时间恰好在午夜和 01:00 巴西切换到夏令时的那个晚上之间。那个时间实际上是不可能的,所以你会得到 documentation:
中描述的行为
In the case of a gap, when clocks jump forward, there is no valid offset. Instead, the local date-time is adjusted to be later by the length of the gap. For a typical one hour daylight savings change, the local date-time will be moved one hour later into the offset typically corresponding to "summer".
您可以在 Los_Angeles 区域观察相同的行为,方法是在三月的相应夜晚选择 02:00 和 03:00 之间的时间:
zdt = ZonedDateTime.of(2015, 3, 8, 2, 30, 0, 0,
ZoneId.of("America/Los_Angeles"));
System.out.println(zdt);
如 中所述,这是由于夏令时规则而发生的。
在圣保罗,夏令时从 2015-10-18 午夜开始:时钟向前移动 1 小时,因此 "jumps" 从 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
method returns 指定 date/time 的所有有效偏移量。如果列表为空,则表示 date/time 在该时区不 "exist"(通常是因为 DST,时钟向前跳转)。
当date/time存在时区时,返回偏移量:
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 和补偿由政府和法律定义,它们可以随时更改。所以,有效偏移量的差距也可能意味着发生了这种变化(一些政客决定改变国家的标准偏移量,所以差距可能不一定与夏令时有关)。
您还可以查看变化发生的时间,以及变化前后的偏移量:
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);
输出为:
Transition[Gap at 2015-10-18T00:00-03:00 to -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));
输出为:
Transition[Gap at 2015-03-08T02:00-08:00 to -07:00]
Transition[Overlap at 2015-11-01T02:00-07:00 to -08:00]
因此,在 Los_Angeles
时区,夏令时从 2015-03-08
开始,在 2015-11-01
结束。这就是为什么在 2015-10-18
,所有时间都有效(没有调整,因为它发生在 Sao_Paulo
时区)。
一些时区有转换规则(如"DST starts at the third Sunday of October")而不只是转换(如"DST starts at this specific date and time") ,您也可以使用它们(如果可用):
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
第一种情况没问题,因为对于给定的 date/time,-7
的偏移量对洛杉矶有效。第二种情况抛出异常,因为在给定的 date/time.
处,-3
的偏移量对于圣保罗无效
ZonedDateTime zdt = ZonedDateTime.of(2015, 10, 18, 0, 30, 0, 0,
ZoneId.of("America/Sao_Paulo"));
System.out.println(zdt); // 2015-10-18T01:30-02:00[America/Sao_Paulo]
你可以看到小时是1
,而我们设置的是0
,时区是UTC-02:00
,而夏令时时区应该是UTC-03:00
。
但这里有一个不同的例子:
ZonedDateTime zdt = ZonedDateTime.of(2015, 10, 18, 0, 30, 0, 0,
ZoneId.of("America/Los_Angeles"));
System.out.println(zdt); //2015-10-18T00:30-07:00[America/Los_Angeles]
你可以看到我们设置的夏令时是UTC-07:00
,小时是0
。
为什么不同?
发生这种情况是因为您选择的时间恰好在午夜和 01:00 巴西切换到夏令时的那个晚上之间。那个时间实际上是不可能的,所以你会得到 documentation:
中描述的行为In the case of a gap, when clocks jump forward, there is no valid offset. Instead, the local date-time is adjusted to be later by the length of the gap. For a typical one hour daylight savings change, the local date-time will be moved one hour later into the offset typically corresponding to "summer".
您可以在 Los_Angeles 区域观察相同的行为,方法是在三月的相应夜晚选择 02:00 和 03:00 之间的时间:
zdt = ZonedDateTime.of(2015, 3, 8, 2, 30, 0, 0,
ZoneId.of("America/Los_Angeles"));
System.out.println(zdt);
如
在圣保罗,夏令时从 2015-10-18 午夜开始:时钟向前移动 1 小时,因此 "jumps" 从 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
method returns 指定 date/time 的所有有效偏移量。如果列表为空,则表示 date/time 在该时区不 "exist"(通常是因为 DST,时钟向前跳转)。
当date/time存在时区时,返回偏移量:
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 和补偿由政府和法律定义,它们可以随时更改。所以,有效偏移量的差距也可能意味着发生了这种变化(一些政客决定改变国家的标准偏移量,所以差距可能不一定与夏令时有关)。
您还可以查看变化发生的时间,以及变化前后的偏移量:
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);
输出为:
Transition[Gap at 2015-10-18T00:00-03:00 to -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));
输出为:
Transition[Gap at 2015-03-08T02:00-08:00 to -07:00]
Transition[Overlap at 2015-11-01T02:00-07:00 to -08:00]
因此,在 Los_Angeles
时区,夏令时从 2015-03-08
开始,在 2015-11-01
结束。这就是为什么在 2015-10-18
,所有时间都有效(没有调整,因为它发生在 Sao_Paulo
时区)。
一些时区有转换规则(如"DST starts at the third Sunday of October")而不只是转换(如"DST starts at this specific date and time") ,您也可以使用它们(如果可用):
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
第一种情况没问题,因为对于给定的 date/time,-7
的偏移量对洛杉矶有效。第二种情况抛出异常,因为在给定的 date/time.
-3
的偏移量对于圣保罗无效