除了时钟回滚一小时的情况外,在给定国家代码和时区更改时间列表的情况下,可以通过编程方式确定时区。
例如,如果您知道英国的时钟在每年 3 月的最后一个星期日凌晨 1 点和 10 月的最后一个星期日凌晨 2 点发生变化,那么给定历史或 future 的“本地”时间,您就可以确定 UTC 时间。
我的问题是,Java 中是否有任何内置方法可以确定此信息 - 不仅可以确定当前时间,还可以确定历史时间?
最佳答案
事情没那么简单,因为时区的规则(可能/可以/做)一直在变化。
... if you know that on the last Sunday of every March at 1am and October at 2am the clocks change in the UK
并非所有历史日期都是如此。 In the 70's您会注意到,一些 DST 更改发生在 3 月中旬,而不是最后一个星期日,例如 1974 年和 1975 年,DST 分别于 3 月 17 日和 16 日开始。另请注意,它从凌晨 2 点开始,而不是凌晨 1 点 ( only in 1981 it was changed to start at 1 AM )。
无论如何,Java 包含来自 IANA 的所有时区数据(包括历史数据),因此您只需使用内置类,API 即可完成所有数学运算:
// London timezone
ZoneId londonTimezone = ZoneId.of("Europe/London");
// the historical local date/time I want to check (March 16th 1975, 3 AM)
LocalDateTime localDt = LocalDateTime.of(1975, 3, 16, 3, 0, 0);
// convert to UK timezone (1975-03-16T03:00+01:00)
ZonedDateTime londonDt = localDt.atZone(londonTimezone);
// convert to UTC (1975-03-16T02:00:00Z)
Instant instant = londonDt.toInstant();
在上面的代码中,我创建了本地时间 1975 年 3 月 16 日凌晨 3 点(即 after the DST transition, that occurred at 2 AM) 。
当我将其转换为伦敦时区时,结果为 1975-03-16T03:00+01:00
- 偏移量为 +01:00(比 UTC 早一小时),因为 DST 是有效。将其转换为 UTC,结果为 1975-03-16T02:00:00Z
。
With the exception of when clocks roll back an hour...
其实,有一个办法可以解决这个问题。使用London's DST transition in 1975 ,夏令时于 10 月 26 日结束:凌晨 3 点,时钟被调回到凌晨 2 点,因此凌晨 2 点到 2:59 之间的所有本地时间都存在两次。如何解决这种歧义?
// London timezone
ZoneId londonTimezone = ZoneId.of("Europe/London");
// October 26th 1975, 2 AM - the ambiguous local time in London, due to DST end
LocalDateTime localDt = LocalDateTime.of(1975, 10, 26, 2, 0, 0);
// convert to UK timezone (default is the local time in DST: 1975-10-26T02:00+01:00)
ZonedDateTime londonDt = localDt.atZone(londonTimezone);
// get the date in DST (1975-10-26T02:00+01:00)
System.out.println(londonDt.withEarlierOffsetAtOverlap());
// get the date after DST ends (1975-10-26T02:00Z)
System.out.println(londonDt.withLaterOffsetAtOverlap());
方法withEarlierOffsetAtOverlap()
和withLaterOffsetAtOverlap()
分别返回DST结束之前和之后对应的日期/时间,解决了歧义。
这些方法中的每一个都会返回一个新的 ZonedDateTime
实例(每个实例对应于不同的 UTC 时刻),您可以对它们调用 toInstant()
来获取对应的UTC 即时。
始终使用这两种方法来获取转换之前和之后的日期/时间。不要尝试增加或减少一小时,因为并非所有转换都会将时钟更改 1 小时:
- 在豪勋爵群岛(澳大利亚),clocks change 30 minutes during DST
- 2011年,Samoa changed from -10:00 to +14:00 (他们切换到国际日期变更线的另一边, skipping a whole local day ) - 这与 DST 无关,这是一个“永久”更改(永久更改,直到再次更改 - 时区规则由政府定义,因此它们是始终可能发生变化)
It is possible to programmatically determine the timezone, given a country-code and a list of times at which the timezone changes
这是可能的,但请注意,同一个国家可以有多个时区,特别是像美国(非洲大陆有 4 个时区,加上阿拉斯加和夏威夷)、俄罗斯(超过 10 个)、巴西(4 - 实际上是有更多,因为有些州有夏令时,但其他州没有),等等。
无论如何,给定国家代码,您可以检查 this file并获取该国家/地区的所有时区。然后,对于每个时区,您运行与此类似的代码来比较发生转换时的所有日期:
ZoneId londonTimezone = ZoneId.of("Europe/London");
ZoneRules rules = londonTimezone.getRules();
// get all the transitions (dates when the offset changes)
rules.getTransitions().forEach(t -> {
// UTC instant when the change occurs
Instant instant = t.getInstant();
// local date/time before the transition
LocalDateTime before = t.getDateTimeBefore();
// UTC offset before the transition
ZoneOffset offsetBefore = t.getOffsetBefore();
// local date/time after the transition
LocalDateTime after = t.getDateTimeAfter();
// UTC offset afger the transition
ZoneOffset offsetAfter = t.getOffsetAfter();
// is GAP (clock shifts forward - local times are skipped)
boolean gap = t.isGap();
// is overlap (clock shifts backwards - local times can happen twice)
boolean overlap = t.isOverlap();
// *** You can use the data above to check if the transition date matches your list***
});
有些区域有转换规则而不仅仅是转换,因此您还必须检查转换规则列表:
rules.getTransitionRules().forEach(rule -> {
// get a transition for specific year
ZoneOffsetTransition transition = rule.createTransition(2018);
// use the transition the same as above (getInstant(), getDateTimeBefore(), etc)
});
关于Java - 从本地时间转换为 UTC 历史时间,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49671454/