如何在Java 8中获取默认的ZoneOffset?

128

在Java 8中,我们知道可以使用ZoneId.default()获取系统默认的ZoneId,但是如何获取默认的ZoneOffset呢?

我看到一个ZoneId有一些“规则”,每个规则都有一个ZoneOffset,这是否意味着一个ZoneId可能拥有多个ZoneOffset


尝试使用ZoneOffset.systemDefault() - Shubham Chaurasia
2
许多时区由于夏令时而具有不同的偏移量。 - Marc Philipp
29
ZoneOffset.systemDefault() 返回的是 ZoneId - Marc Philipp
4
当前的ZoneOffset是由ZoneId和Instant函数决定的。ZoneOffset current = zone.getRules().getOffset(instant) - Alexander Yanyshin
4
我认为yanys的意思是,在没有指定时区和时间点(日期时间值)的情况下要求偏移量没有意义。例如:美国洛杉矶的夏令时导致当前偏移量为“-08:00”,但在夏季变为“-07:00”。因此,不能在不说明何时生效哪个时区的情况下要求偏移量。有关更多讨论,请参见我的答案 - Basil Bourque
显示剩余3条评论
2个回答

294

太长不读

OffsetDateTime.now().getOffset()

但你应该使用时区而不是简单的与协调世界时的偏移量。

ZoneId.systemDefault()

偏移量与时区

一个偏移量(距离协调世界时)只是一个小时、分钟和秒数的数字,没有其他含义。例如,-08:00表示比协调世界时晚八小时,+05:45表示比协调世界时提前五小时四十五分钟。

一个时区是一个特定地区人民过去、现在和未来偏移量变化的历史记录。诸如夏令时(DST)等异常情况会导致偏移量在特定时间段内发生改变,并且这些变化被追踪记录,包括过去的变化以及政府官员公布计划变更的未来变化。

因此,当已知时最好使用时区

任何地区的偏移量随时间而变化。例如,美国的夏令时在一年中的大约一半时间将偏移量提前一个小时,然后在另一半时间将这个小时重新加回到偏移量中。时区的整个目的就是记录这些偏移量的变化。
因此,没有日期和时间的情况下询问偏移量真的没有意义。例如,在洛杉矶,今年的某个时候偏移量为-08:00,但在夏令时期间的另一段时间为-07:00。
OffsetDateTime 所以让我们将一个时刻指定为OffsetDateTime,并提取ZoneOffset。
OffsetDateTime odt = OffsetDateTime.now ();
ZoneOffset zoneOffset = odt.getOffset ();

odt.toString(): 2017-01-02T15:19:47.162-08:00
zoneOffset.toString(): -08:00
那个 `now` 方法实际上隐式地应用了JVM的当前默认时区。我建议您始终通过指定所需/预期的时区来明确表示这一点。即使您想要当前的默认时区,也要明确说明以清楚表达您的意图。消除关于您是打算使用默认时区还是忽略考虑时区的不确定性,这在程序员中经常发生。调用 ZoneId.systemDefault
OffsetDateTime odt = OffsetDateTime.now ( ZoneId.systemDefault () );
ZoneOffset zoneOffset = odt.getOffset ();

ZoneId.systemDefault().toString(): America/Los_Angeles
odt: 2017-01-02T15:19:47.162-08:00
zoneOffsetOfOdt: -08:00
A caution about depending on the default zone: This default can be changed at any moment by any code in any thread within the JVM. If important, ask the user for their intended time zone.
You can ask the offset for its amount of time as a total number of seconds.
int offsetSeconds = zoneOffset.getTotalSeconds ();

偏移秒数:-28800

ZonedDateTime

另一个例子:也许你想知道今年圣诞节在魁北克的偏移量是多少。指定时区为America/Montreal,获取一个ZonedDateTime对象,然后将其偏移量作为ZoneOffset对象来查询。

ZoneId z = ZoneId.of( "America/Montreal" );
LocalDate ld = LocalDate.of( 2017 , 12 , 25 );
ZonedDateTime zdtXmas = ld.atStartOfDay( z );
ZoneOffset zoneOffsetXmas = zdtXmas.getOffset();

zdtXmas.toString(): 2017-12-25T00:00-05:00[America/Montreal]
zoneOffsetXmas.toString(): -05:00
zoneOffsetXmas.getTotalSeconds(): -18000

Table of date-time types in Java, both modern and legacy.

ZoneId

如yanys的评论所建议,您可以通过将时间作为Instant传递给ZoneId来查询特定ZoneOffsetInstant类表示UTC上的时间点,其分辨率为纳秒(最多九位小数)。

这只是达到相同目标的另一种方法。就像上面讨论的OffsetDateTimeZonedDateTime一样,我们指定了(a)时区和(b)时间点。

Instant instant = zdtXmas.toInstant();
ZoneOffset zo = z.getRules().getOffset( instant );

对于ZoneId: America/Montreal在时刻:2017-12-25T05:00:00Z,ZoneOffset为:-05:00
查看所有这些示例的代码在线链接于IdeOne.com

ZoneOffset.systemDefault

ZoneOffset类是ZoneId的子类,继承了systemDefault方法。然而,我们不能使用该方法来确定当前的偏移量。继承的方法返回的是超类ZoneId的对象,而不是子类ZoneOffset的对象。
ZoneOffset zoneOffset = ZoneOffset.systemDefault() ;  // Error.
// Fails to compile. 
// Every ZoneOffset is a ZoneId, but not every ZoneId is a ZoneOffset. 
// We cannot assign a superclass object to a reference variable of the subclass type.

错误:不兼容的类型:ZoneId 无法转换为 ZoneOffset
但是还有另一种方法。
时区规则
要获取当前偏移量,我们必须走另一条路。我们需要通过默认时区的规则来进行。
ZoneId zoneId = ZoneId.systemDefault ( );
ZoneRules rules = zoneId.getRules ( );

规则随着时间的推移而改变 — 正如上面所讨论的那样。因此,我们必须指定我们想要了解有效偏移量的时刻。我们将该时刻指定为一个Instant对象,即以零小时-分钟-秒的偏移量从UTC看到的日期和时间。
Instant now = Instant.now ( );
ZoneOffset offset = rules.getOffset ( now );

控制台输出。
System.out.println ( "For zone " + zoneId + " at " + now + ", the offset is " + offset );

对于2023-07-07T23:19:09.084624Z的America/Los_Angeles时区,偏移量为-07:00。
太阳时间与政治时间
关于偏移量和时区的更多信息...
自史前时代以来一直使用太阳时间,通过观察太阳何时位于正上方来跟踪每一天。将一根棍子插入地面,观察它的影子。当影子最短、开始变长而不是缩短时,你就知道现在是中午。通过使用日晷来形式化跟踪过去的小时。
随着你从西向东旅行时,太阳时间会稍微晚些到达中午。而向东移动时,中午会稍早到达。因此,每个城镇都有自己的中午,只与同一经度上的北部和南部城镇共享。
太阳时间在现代时代基本被废弃。随着火车、电报和电话的出现,协调时间变得必要。于是,在一个离正午太阳时间较近的地点被选定,并宣布西边和东边相距数英里的一大片土地共享同样的12:00钟点,与格林威治本初子午线相比,有相同数量的小时偏移。于是开始了每个火车站都醒目地展示钟表的传统,以便让该镇了解其所在地区的标准时间,而不是自己镇上的太阳时间。通常情况下,该时区地区的西部边缘城镇的火车站钟表会在太阳正上方之前读到12:00。而该地区东部边缘城镇的钟表读取的时间会稍稍晚于太阳正上方。

世界各地的政治家们表现出一种改变其管辖范围偏移量的倾向。原因各不相同,比如外交、战争和占领,以及夏令时的荒谬之处。这些原因各不相同,但他们的改变频率让人惊讶。时区是一个名称,用来追踪该地区的历史变化。因此,“与UTC的偏移量”只是一个小时-分钟-秒数,超前或落后于本初子午线。时区远远不止如此:它是对特定地区偏移量在过去、现在和未来变化的历史记录。尽管今天两个相邻地区可能共享相同的与UTC的偏移量,但根据其政治家们不同的心思或逻辑,它们在过去或未来可能有所不同。

这意味着现代政治家所定义的时间跟地理关系很小。例如,如今印度这个庞大的国家只有一个时区(与世界标准时间偏差为+05:30)。因此,在这个广阔的次大陆上,各个地方的太阳正午(太阳直射头顶的时刻)相差数小时。印度的政治家决定这样做是为了统一多元化的民主国家。在世界其他地方的例子中,我们看到一些地区将自己的时区作为国际关系的象征,例如与其冲突的邻国选择不同的时区,或者与邻国选择相同的时区以改善关系,就像最近朝鲜改变时区以与韩国保持一致一样。因此,如今,太阳时间只是时间跟踪中的几个考虑因素之一。

关于java.time

java.time框架是内置于Java 8及更高版本中的。这些类取代了旧的麻烦的遗留日期时间类,如java.util.DateCalendarSimpleDateFormat

要了解更多信息,请参阅Oracle教程。并在Stack Overflow上搜索许多示例和解释。规范为JSR 310

现在处于维护模式Joda-Time项目建议迁移到java.time类。

您可以直接与数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC驱动程序。无需字符串,无需java.sql.*类。Hibernate 5和JPA 2.2支持java.time

如何获取java.time类?

Table of which java.time library to use with which version of Java or Android


2
@FredSuvn 这正是夏令时(DST)的定义,一年中的一半时间偏移量不同。而且DST并不是唯一的原因;其他异常情况也会导致特定时区的偏移量发生变化。例如,在2016年秋季,土耳其决定永久调整他们的偏移量提前一个小时,进一步超前于UTC(+03:00)。政治家经常做出这样的改变,通常很少提前通知。这让人们匆忙更新计算机的“tzdata”数据库列表中的区域定义。 - Basil Bourque
5
我会尽力做到最好的翻译,以下是您要求的内容:在Stack Overflow上很久没见过这么好的答案了。@FredSuvn,你应该点击✅接受这个答案。 - earcam
对于任何被方程式 时区 = (历史偏移量 + 异常规则) 所困惑的人,这并不意味着在使用 zone.getRules().getOffset(instant) 时,需要手动添加任何其他异常情况到获取的偏移量中,例如夏令时。**getOffset(instant) 返回的偏移量已经是该时间点与 GMT 的确切小时、分钟和秒数偏移量。**(我一直以为偏移量只是地理偏移量,描述了该地区太阳升起的早晚程度,但实际上它考虑了所有规则。) - Snackoverflow
1
我怀疑你无法使用ZoneOffset.systemDefault()调用进行编译的原因是返回类型是ZoneId(ZoneOffset的父类)。 - user3347392
@user3347392 是的,那很有道理。这个方法是直接从ZoneId继承到ZoneOffset的。谢谢,我已经更新了答案以体现你的观点。 - Basil Bourque
显示剩余2条评论

3

根据您的目标,您可能能够完全绕过ZoneOffset

假设您只需要一个ZoneOffset用于例如LocalDateTime.ofEpochSecond(),您可以替换:

ZoneOffset offset = OffsetDateTime.now().getOffset();
LocalDateTime dt1 = LocalDateTime.ofEpochSecond(seconds, 0, offset);

使用

LocalDateTime dt2 = LocalDateTime.ofInstant(
    Instant.ofEpochSecond(seconds), 
    ZoneId.systemDefault());

dt1.equals(dt2)true 时。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接