java 8 中的 ZoneOffset 与 ZoneId

5,386 阅读4分钟

ZoneOffset 表示 UTC 时区的偏移量,而 ZoneId 表示 UTC 的时区

ZoneOffset 继承自 ZoneId

偏移量 VS 时区

一个 UTC 偏移量 仅仅只记录了时分秒而已,除此之外没有任何其他信息。举个例子 ,+08:00 的意思时超前于 UTC 八个小时,而 -05:45 意思是落后于 UTC 五小时四十五分钟

对于特定地区的人来说,时区是过去、现在以及未来的偏移量的历史集合。像夏令时会导致特定时间段内的偏移量会随时间变化。

比如美国的夏令时,半年左右的时间会再原来的基础上偏移一个小时,然后下半年再调整回这一个小时的偏移量。时区的意义就是记录这些所有的会造成偏移的情况。

因此只看偏移量没有意义的,同一个时区下,不同的时间,偏移量可能不一样,而且未来还会随着政府的法令进行更改。

举个例子,巴黎时间 (Europe/Paris),在一年的一小部分时间内偏移量为 +01:00,而由于夏令时的缘故,大部分时间 (三月末到十月末)的偏移量都是 +02:00。

// 2020年2月,Europe/Paris(欧洲/巴黎)时区 偏移量为 +01:00
LocalDateTime dateTime1 = LocalDateTime.of(LocalDate.of(2020, 2, 01), LocalTime.now());
ZonedDateTime zonedDateTime1 = ZonedDateTime.of(dateTime1, ZoneId.of("Europe/Paris"));
zonedDateTime1.getOffset(); // +01:00

// 2020年5月,Europe/Paris(欧洲/巴黎)时区 偏移量为 +02:00
LocalDateTime dateTime2 = LocalDateTime.of(LocalDate.of(2020, 5, 01), LocalTime.now());
ZonedDateTime zonedDateTime2 = ZonedDateTime.of(dateTime2, ZoneId.of("Europe/Paris"));
zonedDateTime2.getOffset(); // +02:00

java 中如何处理时区和偏移量

由于同一个时区下,不同的时间,偏移量可能不一样,而且未来还会随着政府的法令进行更改,因此处理起来特别麻烦。好在有一个名为 IANA 时区数据库的国际组织在维护全球各时区的规则,包括历史规则。

在 java 中,时区偏移量规则是由 java.time.zone.ZoneRulesProvider 来处理的。

/**
 * Provider of time-zone rules to the system.
 * <p>
 * This class manages the configuration of time-zone rules.
 * The static methods provide the public API that can be used to manage the providers.
 * The abstract methods provide the SPI that allows rules to be provided.
 * <p>
 * ZoneRulesProvider may be installed in an instance of the Java Platform as
 * extension classes, that is, jar files placed into any of the usual extension
 * directories. Installed providers are loaded using the service-provider loading
 * facility defined by the {@link ServiceLoader} class. A ZoneRulesProvider
 * identifies itself with a provider configuration file named
 * {@code java.time.zone.ZoneRulesProvider} in the resource directory
 * {@code META-INF/services}. The file should contain a line that specifies the
 * fully qualified concrete zonerules-provider class name.
 * Providers may also be made available by adding them to the class path or by
 * registering themselves via {@link #registerProvider} method.
 * <p>
 * The Java virtual machine has a default provider that provides zone rules
 * for the time-zones defined by IANA Time Zone Database (TZDB). If the system
 * property {@code java.time.zone.DefaultZoneRulesProvider} is defined then
 * it is taken to be the fully-qualified name of a concrete ZoneRulesProvider
 * class to be loaded as the default provider, using the system class loader.
 * If this system property is not defined, a system-default provider will be
 * loaded to serve as the default provider.
 * <p>
 * Rules are looked up primarily by zone ID, as used by {@link ZoneId}.
 * Only zone region IDs may be used, zone offset IDs are not used here.
 * <p>
 * Time-zone rules are political, thus the data can change at any time.
 * Each provider will provide the latest rules for each zone ID, but they
 * may also provide the history of how the rules changed.
 *
 * @implSpec
 * This interface is a service provider that can be called by multiple threads.
 * Implementations must be immutable and thread-safe.
 * <p>
 * Providers must ensure that once a rule has been seen by the application, the
 * rule must continue to be available.
 * <p>
*  Providers are encouraged to implement a meaningful {@code toString} method.
 * <p>
 * Many systems would like to update time-zone rules dynamically without stopping the JVM.
 * When examined in detail, this is a complex problem.
 * Providers may choose to handle dynamic updates, however the default provider does not.
 *
 * @since 1.8
 */
public abstract class ZoneRulesProvider {
	...
}    

Java虚拟机有一个默认提供程序,为IANA时区数据库(TZDB)定义的时区提供区域规则。如果系统属性{@code java.time.zone.DefaultZoneRulesProvider} 那么它将被视为具体ZoneRulesProvider类的完全限定名,使用系统类加载器作为默认提供程序加载。如果未定义此系统属性,则将加载系统默认提供程序作为默认提供程序。

许多系统都希望动态更新时区规则,而不停止JVM,详细分析一下,这是一个复杂的问题,提供程序可以选择处理动态更新,但是默认提供程序不会这样做。