Java - 历史时间从本地时间转换为UTC

时间:2018-04-05 11:38:55

标签: java time timezone

除了时钟回滚一小时之外,可以通过编程方式确定时区,给定国家/地区代码和时区更改的时间列表。

例如,如果您知道在每个3月的最后一个星期日凌晨1点和10月凌晨2点,英国的时钟会发生变化,那么根据历史或未来的“本地”时间,您可以确定UTC时间。

我的问题是,Java中是否有任何可以确定此信息的内置方法 - 不仅是当前时间,还有历史时期?

1 个答案:

答案 0 :(得分:2)

这并不是那么简单,因为时区的规则(可能/可以/做)一直在变化。

  

...如果您知道在每个三月的最后一个星期日凌晨1点和10月凌晨2点,英国的时钟会发生变化

所有历史日期都不是这样。 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日 th 凌晨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

  

除了时钟回滚一小时......

实际上,有办法解决这个问题。使用London's DST transition in 1975,DST于10月26日结束 th :凌晨3点,时钟设置为1小时回到凌晨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小时:

  

可以以编程方式确定时区,给定国家/地区代码和时区更改的时间列表

这是可能的,但提醒一下,同一个国家可以有多个时区,特别是大国,如美国(大陆有4个区,加上阿拉斯加和夏威夷),俄罗斯(超过10个),巴西(4个 - 实际上是还有更多,因为有些州有DST但其他州没有),等等。

无论如何,根据国家/地区代码,您可以查看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)
});