理解 Java8 的时间API(一)时区
理解 Java8 的时间API:java.time
由于Java的时间API:java.util.Date
、java.util.Calendar
、java.util.TimeZone
使用起来非常混乱,因此 Java8 重新设计了一套时间API,放在java.time.*
包下。
如果需要熟练使用新的LocalDateTime
,LocalDate
,LocalTime
类,最好是先了解时区的概念。因此本文先梳理一下 Java8 里新的时区API。
一、时区与时间
首先是时区
,time zone,定义这里不多介绍,只需要了解地球上划分了很多时区,不同时区当前的日期时间是不一样的。比如北京时间、伦敦时间、洛杉矶时间等。常见的格式是:
其次是时间
,这个太熟悉了,甚至不需要定义。时间通常有两种表示法:
- 时间戳(timestamp):计算机常用的表示方法。定义是以自1970年1月1日经过的秒数(忽略闰秒)来表示时间。也就是说表示某一个“瞬间”,并且不受时区影响,在任何一个时区获取当前的时间戳都是一个相同的值。在类Unix系统里是一个有正负号的32位整数(signed int32),因此存在2038年问题。在Java里则通常用一个long类型的整数表示。
- 日期时间(date time):日常生活中最常见的表示方法。例如“2023年4月22日15时23分08秒”,格式有很多种,但是通常由两部分组成:date(年+月+日)与 time(时+分+秒)组成。受时区影响,在当前这个“瞬间”,不同时区的日期时间是不同的。
由上面的定义可以知道:时间戳与日期时间之间相互转化是需要时区信息的。这个作为大前提。
二、Java8中的时区
2.1 介绍
对时区和时间有了一个大概的概念之后,这里介绍一下Java中怎么描述这两种概念。
首先是Java中时区类java.time.ZoneId
,这里贴一下注释:
/**
* A time-zone ID, such as {@code Europe/Paris}.
* <p>
* A {@code ZoneId} is used to identify the rules used to convert between
* an {@link Instant} and a {@link LocalDateTime}.
* There are two distinct types of ID:
* <ul>
* <li>Fixed offsets - a fully resolved offset from UTC/Greenwich, that uses
* the same offset for all local date-times
* <li>Geographical regions - an area where a specific set of rules for finding
* the offset from UTC/Greenwich apply
* </ul>
* Most fixed offsets are represented by {@link ZoneOffset}.
* Calling {@link #normalized()} on any {@code ZoneId} will ensure that a
* fixed offset ID will be represented as a {@code ZoneOffset}.
* <p>
* The actual rules, describing when and how the offset changes, are defined by {@link ZoneRules}.
* This class is simply an ID used to obtain the underlying rules.
* This approach is taken because rules are defined by governments and change
* frequently, whereas the ID is stable.
* <p>
* The distinction has other effects. Serializing the {@code ZoneId} will only send
* the ID, whereas serializing the rules sends the entire data set.
* Similarly, a comparison of two IDs only examines the ID, whereas
* a comparison of two rules examines the entire data set.
*
* <h3>Time-zone IDs</h3>
* The ID is unique within the system.
* There are three types of ID.
* <p>
* The simplest type of ID is that from {@code ZoneOffset}.
* This consists of 'Z' and IDs starting with '+' or '-'.
* <p>
* The next type of ID are offset-style IDs with some form of prefix,
* such as 'GMT+2' or 'UTC+01:00'.
* The recognised prefixes are 'UTC', 'GMT' and 'UT'.
* The offset is the suffix and will be normalized during creation.
* These IDs can be normalized to a {@code ZoneOffset} using {@code normalized()}.
* <p>
* The third type of ID are region-based IDs. A region-based ID must be of
* two or more characters, and not start with 'UTC', 'GMT', 'UT' '+' or '-'.
* Region-based IDs are defined by configuration, see {@link ZoneRulesProvider}.
* The configuration focuses on providing the lookup from the ID to the
* underlying {@code ZoneRules}.
* <p>
* Time-zone rules are defined by governments and change frequently.
* There are a number of organizations, known here as groups, that monitor
* time-zone changes and collate them.
* The default group is the IANA Time Zone Database (TZDB).
* Other organizations include IATA (the airline industry body) and Microsoft.
* <p>
* Each group defines its own format for the region ID it provides.
* The TZDB group defines IDs such as 'Europe/London' or 'America/New_York'.
* TZDB IDs take precedence over other groups.
* <p>
* It is strongly recommended that the group name is included in all IDs supplied by
* groups other than TZDB to avoid conflicts. For example, IATA airline time-zone
* region IDs are typically the same as the three letter airport code.
* However, the airport of Utrecht has the code 'UTC', which is obviously a conflict.
* The recommended format for region IDs from groups other than TZDB is 'group~region'.
* Thus if IATA data were defined, Utrecht airport would be 'IATA~UTC'.
*
* <h3>Serialization</h3>
* This class can be serialized and stores the string zone ID in the external form.
* The {@code ZoneOffset} subclass uses a dedicated format that only stores the
* offset from UTC/Greenwich.
* <p>
* A {@code ZoneId} can be deserialized in a Java Runtime where the ID is unknown.
* For example, if a server-side Java Runtime has been updated with a new zone ID, but
* the client-side Java Runtime has not been updated. In this case, the {@code ZoneId}
* object will exist, and can be queried using {@code getId}, {@code equals},
* {@code hashCode}, {@code toString}, {@code getDisplayName} and {@code normalized}.
* However, any call to {@code getRules} will fail with {@code ZoneRulesException}.
* This approach is designed to allow a {@link ZonedDateTime} to be loaded and
* queried, but not modified, on a Java Runtime with incomplete time-zone information.
*
* <p>
* This is a <a href="{@docRoot}/java/lang/doc-files/ValueBased.html">value-based</a>
* class; use of identity-sensitive operations (including reference equality
* ({@code ==}), identity hash code, or synchronization) on instances of
* {@code ZoneId} may have unpredictable results and should be avoided.
* The {@code equals} method should be used for comparisons.
*
* @implSpec
* This abstract class has two implementations, both of which are immutable and thread-safe.
* One implementation models region-based IDs, the other is {@code ZoneOffset} modelling
* offset-based IDs. This difference is visible in serialization.
*
* @since 1.8
*/
public abstract class ZoneId implements Serializable {
}
从注释中可以总结一下要点:
- 时区ID 定义了
Instant
和LocalDateTime
之间相互转化的规则; - 时区ID 有两种不同的类型:
- 固定偏移:以与某个标准时间(如UTC、GMT、UT等)的偏移量来定义时区;例如“UTC+08:00”、“UTC+8”、“GMT+8”等。(UTC、GMT、UT的定义可以自行搜索,这里只需要知道UTC和GMT可以简单理解成一个东西。)
- 地理区域:以具体的地理位置来表示当地的时区。通常是定义在TZDB中的,例如“America/Los_Angeles”、“Asia/Shanghai”等。
此外,还有时区缩写(time-zone abbreviations),也叫短时区ID:例如"PST"(太平洋标准时间,Pacific Standard Time,代表城市洛杉矶,因此等效于America/Los_Angeles
),但是由于很多时区的缩写相同,容易引起歧义,因此不太建议用这种。
固定偏移类型的时区ID通常是根据地理经度来划分的,日常生活中使用这种类型的时区会带来诸多不便,例如中国在地理上横跨很多时区,但是全国使用同一的北京时间("UTC+8",国际上定义"Asia/Shanghai")。并且,欧美很多国家会实行夏时制(又称夏令时、日光节约时间),例如2023年,美国在3月12日启用夏时制,11月5日结束夏时制(这里日期用美国东部时间,"EST"),因此洛杉矶时区"America/Los_Angeles"在夏时制等效于UTC-7
,不在夏时制等效于UTC-8
。
2.2 ZoneId使用
经过前面的介绍,正式介绍Java里的时区类:java.time.ZoneId
,这是一个抽象类,有两个具体子类:public的java.time.ZoneOffset
和 package的java.time.ZoneRegion
。
ZoneId
可以使用其静态方法来构造:public static ZoneId of(String zoneId)
,参数中"zoneId"合法的格式:
- 只包含与UTC/GMT的偏移量:"Z"(即UTC/GMT),"+8","+08:00",之所以精确到分,是因为还有一些"+04:30"之类的时区;
- 包含前缀以及与其的偏移量:例如 "UTC" / "UTC+0","UTC+8","UTC+08:00";(合法的前缀只有 'UTC', 'GMT' and 'UT')
- 包含定义在TZDB中的地区格式:例如:"Asia/Shanghai","America/Los_Angeles"。
其中,格式1初始化出来的时区类是ZoneOffset
,格式2和格式3初始化出来的是ZoneRegion
。
几个额外的点:
- 如果需要使用缩写:"EST","PST",只能使用重载的
public static ZoneId of(String zoneId, Map<String, String> aliasMap)
方法,为了消除歧义,需要指定缩写与全称的映射关系。 public ZoneId normalized()
方法:如果一个ZoneId
实例实质上是一个UTC/GMT的固定偏移,那么调用其normalized()
方法会转化为ZoneOffset
实例返回;否则返回自身。public abstract ZoneRules getRules()
方法:获取当前ZoneId
实例对应的java.time.zone.ZoneRules
。ZoneRules
包含了Instant
和LocalDateTime
互相转化的时区规则,并且已经考虑了夏时制等其他规则,了解即可。并且可以使用java.time.zone.ZoneRules#getOffset(java.time.Instant)
或java.time.zone.ZoneRules#getOffset(java.time.LocalDateTime)
方法获取当前时间的偏移量(因为夏时制的存在,不同时间的偏移量可能不同)。
代码验证:
public static void main(String[] args) {
System.out.println(ZoneId.of("Z").getClass().getSimpleName()); // ZoneOffset
System.out.println(ZoneId.of("+8").getClass().getSimpleName()); // ZoneOffset
System.out.println(ZoneId.of("UTC").getClass().getSimpleName()); // ZoneRegion
System.out.println(ZoneId.of("UTC+8").getClass().getSimpleName()); // ZoneRegion
System.out.println(ZoneId.of("Asia/Shanghai").getClass().getSimpleName()); // ZoneRegion
System.out.println(ZoneId.of("PST", ZoneId.SHORT_IDS).getClass().getSimpleName()); // ZoneRegion
System.out.println(ZoneId.of("UTC+8").normalized().getClass().getSimpleName()); // ZoneOffset
ZoneId los = ZoneId.of("America/Los_Angeles");
ZoneRules rules = los.getRules();
ZoneOffset offset1 = rules.getOffset(Instant.now()); // 现在是2023/4/22 17:29
ZoneOffset offset2 = rules.getOffset(LocalDateTime.of(2023, 11, 10, 0, 0, 0));
System.out.println(offset1); // -07:00
System.out.println(offset2); // -08:00
}
https://blog.csdn.net/u012107143/article/details/78790378
https://www.baeldung.com/java-zone-offset