如果 DST 格式不正确,如何获取时区偏移量?

How can I get the timezone offset without the DST formatted correctly?

我还没有找到不受夏时制干扰的时区偏移量的方法。

LocalDateTime dt = LocalDateTime.now();
...
ZoneId zone = ZoneId.of(s);
ZonedDateTime zdt = dt.atZone(zone);
ZoneOffset offset = zdt.getOffset();

这看起来可行,但初始数据时间可能会受到 DST 的影响。

还有这个:

int rawOffset = tZone.getRawOffset();

但是,这始终是一个正数,因此我们永远不会得到 -05:00

所以我想要的是一种在不受 DST 干扰的情况下获得偏移量的方法。有什么想法吗?

使用ZoneRules.getStandardOffset(Instant).

ZoneId zone = ZoneId.of(s);
ZoneRules rules = zone.getRules();
ZoneOffset standardOffset = rules.getStandardOffset(Instant.now());
根据 Java API 文档,

TimeZone.getRawOffset() 将 return 正确的历史偏移量。

public abstract int getRawOffset()

Returns the amount of time in milliseconds to add to UTC to get standard time in this time zone. Because this value is not affected by daylight saving time, it is called raw offset.

If an underlying TimeZone implementation subclass supports historical GMT offset changes, the method returns the raw offset value of the current date. In Honolulu, for example, its raw offset changed from GMT-10:30 to GMT-10:00 in 1947, and this method always returns -36000000 milliseconds (i.e., -10 hours).

Returns: the amount of raw offset time in milliseconds to add to UTC. See Also: Calendar.ZONE_OFFSET

来自:https://docs.oracle.com/javase/7/docs/api/java/util/TimeZone.html#getRawOffset()

首先,请记住,偏移随时间而变化(即使是标准偏移),主要是因为政府、法律和其他外部因素。

这就是 API 需要 Instant 作为参考的原因:知道那个特定时刻的标准偏移量是多少(无论 Instant 是否过去,现在或将来)。你不能在不知道时间的情况下说出偏移量是多少。这就是为什么 是正确的。

但是正如您在评论中所说的那样您不想依赖 Instant.now(),我已经完成了下面的代码 - 请记住该解决方案并非 100% 理想,因为以上原因。这是我按照你的标准得到的最好的。


您可以使用 java.time.zone.ZoneRules class 来做到这一点,您可以直接从 ZoneId 实例中获取:

// pick a timezone with DST
ZoneId zone = ZoneId.of("America/Sao_Paulo");
// get rules
ZoneRules rules = zone.getRules();
// check if offset changes over time (if it returns false, it means is not fixed, so it has DST)
System.out.println(rules.isFixedOffset()); // false

在上面的代码中,rules.isFixedOffset() returns false,这意味着相应的时区的偏移量随时间变化(因此,它有 DST 变化)。

要知道这些 DST 更改何时发生,您必须获取所有转换:

// get all transitions (all dates where DST starts or ends)
List<ZoneOffsetTransition> transitions = rules.getTransitions();
transitions.stream().forEach(System.out::println);

这会打印很多行(因为这个时区几乎每年都有夏令时)。这是最后两行的示例:

Transition[Overlap at 2039-02-20T00:00-02:00 to -03:00
Transition[Gap at 2039-10-16T00:00-03:00 to -02:00]

这意味着在 2039-02-20 的午夜,偏移量将从 -02:00 变为 -03:00(DST 结束 - 时钟向后移动1 小时),并且在 2039-10-16 的午夜,偏移量将从 -03:00 变为 -02:00(DST 开始 - 时钟向前移动 1 小时)。

所以,如果你想知道不是 DST 时的偏移量,你只需获取最后一个转换,检查它是间隙还是重叠并获取之后或之前的偏移量:

// get the last transition
ZoneOffsetTransition last = transitions.get(transitions.size() - 1);

// find the offset without DST interference
ZoneOffset offsetWithoutDST;

// overlap means that clock moves back 1 hour (when DST ends) 
if(last.isOverlap()) {
    // DST ends, get the offset after it
    offsetWithoutDST = last.getOffsetAfter();
} else {
    // DST starts, get the offset before it
    offsetWithoutDST = last.getOffsetBefore();
}
System.out.println(offsetWithoutDST);

在这种情况下,它打印 -03:00,这是 America/Sao_Paulo 时区的 "normal" 偏移量(当它不是 DST 时)。


PS: 请注意,DST 甚至标准偏移量都会随时间变化很大。

仅仅因为今天的偏移量是一个,并不意味着它总是相同的:政府可以随时更改偏移量,并且在过去的日期 - 比如 1900 年之前 - 偏移量比今天(每个城市都有自己的时间,偏移量如 -03:06:28 等)。

上面的代码为当前时区的规则获取标准(非 DST)偏移量。如果你想知道30年前的非DST偏移量是多少,你可以使用(传递一个30年前的Instant作为参数)。