java.time.ZoneId 是java8的新时区类
ZoneId.systemDefault()
TimeZone.getDefault().toZoneId()
据说是新标准, 等同GMT+8 , 可能取代GMT+8 ? 一个+8 一个-8
ZoneId.of("Etc/GMT-8")
ZoneId.of("GMT+8")
ZoneId.of("GMT+08")
ZoneId.of("GMT+08:00")
用"8:00"会报错
ZoneId.of("UTC+8")
ZoneId.of("UTC+08")
ZoneId.of("UTC+08:00")
ZoneId.of("UT+8")
ZoneId.of("UT+08")
ZoneId.of("UT+08:00")
ZoneId.of("+8")
ZoneId.of("+08")
ZoneId.of("+08:00")
ZoneId.of("Asia/Shanghai")
重庆1
ZoneId.of("Asia/Chongqing")
重庆2
ZoneId.of("Asia/Chungking")
香港1
ZoneId.of("Asia/Hong_Kong")
香港2
ZoneId.of("Hongkong")
System.out.println(ZoneId.getAvailableZoneIds());
UTC : 世界协调时
GMT : 格林威治平时
不是格林威治本地时 , 格林尼治本地的时间是GMT+1
UTC与GMT目前相差小于0.9秒(理论上) 受天体运动影响
UTC更精准(使用了原子钟)
我的理解是,GMT是时区,UTC不是时区,是一个基准,只是他们表示的时间近乎相同
ZoneId用于标识用于在Instant和LocalDateTime之间进行转换的规则。ID有两种不同的类型:
•固定的偏移量-相对于UTC /格林威治标准时间的完全解析的偏移量,它对所有本地日期时间都使用相同的偏移量
•地理区域-适用于查找与UTC /格林威治的偏移量的一组特定规则的区域
大多数固定偏移量由ZoneOffset表示。在任何ZoneId上调用normalized()将确保将固定的偏移量ID表示为ZoneOffset。
ZoneRules定义了描述偏移量何时更改以及如何更改的实际规则。此类仅是用于获取基础规则的ID。之所以采用这种方法,是因为规则是由政府定义且频繁更改的,而该ID是稳定的。
区别还有其他影响。序列化ZoneId仅将发送ID,而序列化规则将发送整个数据集。类似地,两个ID的比较仅检查ID,而两个规则的比较则检查整个数据集。
时区ID
该ID在系统内是唯一的.ID有三种类型。
最简单的ID类型是ZoneOffset中的ID,它由’Z’和以’+‘或’-'开头的ID组成。
下一类ID是带有某种形式的前缀的偏移样式ID,例如’GMT + 2’或’UTC + 01:00’。可识别的前缀为’UTC’,‘GMT’和’UT’。是后缀,将在创建过程中进行规范化。可以使用normalized()将这些ID规范化为ZoneOffset。
ID的第三种类型是基于区域的ID。基于区域的ID必须包含两个或多个字符,并且不能以’UTC’,‘GMT’,‘UT’,’+‘或’-'开头。基于区域的ID由配置定义,请参见ZoneRulesProvider。提供从ID到底层ZoneRules的查找。
时区规则由政府定义并经常更改。有许多组织(在此称为组)来监视时区更改并进行整理。默认组是IANA时区数据库(TZDB)。其他组织包括IATA (航空业团体)和Microsoft。
每个组为其提供的区域ID定义其自己的格式.TZDB组定义诸如欧洲/伦敦’‘或美国/纽约’'的ID.TZDB ID优先于其他组。
强烈建议将组名包括在TZDB以外的其他组提供的所有ID中,以免发生冲突。例如,国际航空运输协会(IATA)航空公司的时区区域ID通常与三个字母的机场代码相同,但是乌得勒支机场的代码为’UTC’,这显然是冲突的.TZDB以外的其他组的区域ID的推荐格式是’group〜region’。因此,如果定义了IATA数据,乌得勒支机场将是’IATA〜UTC’。
序列化
此类可以序列化并以外部格式存储字符串区域ID。ZoneOffset子类使用专用格式,该格式仅存储UTC /格林威治的偏移量。
可以在ID不明的Java运行时中反序列化ZoneId。例如,如果服务器端Java运行时已使用新的区域ID更新,但客户端Java运行时尚未更新。在这种情况下,ZoneId对象将存在,并且可以使用getId,equals,hashCode,toString,getDisplayName和normalized进行查询。但是,对getRules的任何调用都将失败并显示ZoneRulesException。此方法旨在允许加载和查询ZonedDateTime,但在具有不完整时区信息的Java Runtime上未进行修改。
这是一个基于值的类;在ZoneId实例上使用标识敏感的操作(包括引用等于(==),标识哈希码或同步)可能会产生不可预测的结果,应避免使用equals方法进行比较。
/**
* A time-zone ID, such as {@code Europe/Paris}.
*
* 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:
*
* - Fixed offsets - a fully resolved offset from UTC/Greenwich, that uses
* the same offset for all local date-times
*
- Geographical regions - an area where a specific set of rules for finding
* the offset from UTC/Greenwich apply
*
* 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}.
*
* 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.
*
* 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.
*
*
Time-zone IDs
* The ID is unique within the system.
* There are three types of ID.
*
* The simplest type of ID is that from {@code ZoneOffset}.
* This consists of 'Z' and IDs starting with '+' or '-'.
*
* 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()}.
*
* 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}.
*
* 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.
*
* 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.
*
* 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'.
*
*
Serialization
* 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.
*
* 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.
*
*
* This is a value-based
* 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的使用已在java.util.TimeZone中弃用。
加粗样式此映射允许通过of(String,Map)工厂方法继续使用这些ID。
此映射包含与TZDB 2005r和更高版本一致的ID的映射,其中“ EST”,“ MST”和“ HST”映射到不包括夏时制的ID。
/**
* A map of zone overrides to enable the short time-zone names to be used.
*
* Use of short zone IDs has been deprecated in {@code java.util.TimeZone}.
* This map allows the IDs to continue to be used via the
* {@link #of(String, Map)} factory method.
*
* This map contains a mapping of the IDs that is in line with TZDB 2005r and
* later, where 'EST', 'MST' and 'HST' map to IDs which do not include daylight savings.
*
*/
public static final Map<String, String> SHORT_IDS = Map.ofEntries(
entry("ACT", "Australia/Darwin"),
entry("AET", "Australia/Sydney"),
entry("AGT", "America/Argentina/Buenos_Aires"),
entry("ART", "Africa/Cairo"),
entry("AST", "America/Anchorage"),
entry("BET", "America/Sao_Paulo"),
entry("BST", "Asia/Dhaka"),
entry("CAT", "Africa/Harare"),
entry("CNT", "America/St_Johns"),
entry("CST", "America/Chicago"),
entry("CTT", "Asia/Shanghai"),
entry("EAT", "Africa/Addis_Ababa"),
entry("ECT", "Europe/Paris"),
entry("IET", "America/Indiana/Indianapolis"),
entry("IST", "Asia/Kolkata"),
entry("JST", "Asia/Tokyo"),
entry("MIT", "Pacific/Apia"),
entry("NET", "Asia/Yerevan"),
entry("NST", "Pacific/Auckland"),
entry("PLT", "Asia/Karachi"),
entry("PNT", "America/Phoenix"),
entry("PRT", "America/Puerto_Rico"),
entry("PST", "America/Los_Angeles"),
entry("SST", "Pacific/Guadalcanal"),
entry("VST", "Asia/Ho_Chi_Minh"),
entry("EST", "-05:00"),
entry("MST", "-07:00"),
entry("HST", "-10:00")
);
public static ZoneId systemDefault() {
return TimeZone.getDefault().toZoneId();
}
ZoneId of(String zoneId, Map
public static ZoneId of(String zoneId, Map<String, String> aliasMap) {
Objects.requireNonNull(zoneId, "zoneId");
Objects.requireNonNull(aliasMap, "aliasMap");
String id = Objects.requireNonNullElse(aliasMap.get(zoneId), zoneId);
return of(id);
}
ZoneId of(String zoneId)
public static ZoneId of(String zoneId) {
return of(zoneId, true);
}
ZoneId ofOffset(String prefix, ZoneOffset offset)
public static ZoneId ofOffset(String prefix, ZoneOffset offset) {
Objects.requireNonNull(prefix, "prefix");
Objects.requireNonNull(offset, "offset");
if (prefix.isEmpty()) {
return offset;
}
if (!prefix.equals("GMT") && !prefix.equals("UTC") && !prefix.equals("UT")) {
throw new IllegalArgumentException("prefix should be GMT, UTC or UT, is: " + prefix);
}
if (offset.getTotalSeconds() != 0) {
prefix = prefix.concat(offset.getId());
}
return new ZoneRegion(prefix, offset.getRules());
}
ZoneId of(String zoneId, boolean checkAvailable)
static ZoneId of(String zoneId, boolean checkAvailable) {
Objects.requireNonNull(zoneId, "zoneId");
if (zoneId.length() <= 1 || zoneId.startsWith("+") || zoneId.startsWith("-")) {
return ZoneOffset.of(zoneId);
} else if (zoneId.startsWith("UTC") || zoneId.startsWith("GMT")) {
return ofWithPrefix(zoneId, 3, checkAvailable);
} else if (zoneId.startsWith("UT")) {
return ofWithPrefix(zoneId, 2, checkAvailable);
}
return ZoneRegion.ofId(zoneId, checkAvailable);
}
可以看出:
ZoneId与TimeZone 可以相互转换
ZoneId zoneId = TimeZone.getTimeZone(ZoneId.of("GMT+8")).toZoneId();
有意思的是ZoneId.systemDefault()就是通过TimeZone.getDefault().toZoneId()来获得
源码:
/**
* Gets the system default time-zone.
*
* This queries {@link TimeZone#getDefault()} to find the default time-zone
* and converts it to a {@code ZoneId}. If the system default time-zone is changed,
* then the result of this method will also change.
*
* @return the zone ID, not null
* @throws DateTimeException if the converted zone ID has an invalid format
* @throws ZoneRulesException if the converted zone region ID cannot be found
*/
public static ZoneId systemDefault() {
return TimeZone.getDefault().toZoneId();
}
systemDefault()通过调用TimeZone的toZoneId获得, 但后来还是调用ZoneId.of()方法
TimeZone的部门源码
private transient ZoneId zoneId;
public ZoneId toZoneId() {
ZoneId zId = zoneId;
if (zId == null) {
zoneId = zId = toZoneId0();
}
return zId;
}
private ZoneId toZoneId0() {
String id = getID();
TimeZone defaultZone = defaultTimeZone;
// are we not defaultTimeZone but our id is equal to default's?
if (defaultZone != this &&
defaultZone != null && id.equals(defaultZone.getID())) {
// delegate to default TZ which is effectively immutable
return defaultZone.toZoneId();
}
// derive it ourselves
if (ZoneInfoFile.useOldMapping() && id.length() == 3) {
if ("EST".equals(id))
return ZoneId.of("America/New_York");
if ("MST".equals(id))
return ZoneId.of("America/Denver");
if ("HST".equals(id))
return ZoneId.of("America/Honolulu");
}
return ZoneId.of(id, ZoneId.SHORT_IDS);
}
TimeZone.getTimeZone("GMT+8").toZoneId()
TimeZone.getTimeZone("GMT+8:00").toZoneId()
TimeZone.getTimeZone("GMT+08:00").toZoneId()
TimeZone.getTimeZone("Asia/Shanghai").toZoneId()
TimeZone.getTimeZone("Asia/Chongqing").toZoneId()
TimeZone.getTimeZone("Asia/Chungking").toZoneId()
TimeZone.getTimeZone("Asia/Hong_Kong").toZoneId()
TimeZone.getTimeZone("CTT").toZoneId()
TimeZone可以用"CTT" , ZoneId不可以
TimeZone.getTimeZone(ZoneId.of("GMT+8"))
public final class ZoneOffset
extends ZoneId
implements TemporalAccessor, TemporalAdjuster, Comparable<ZoneOffset>, Serializable {
现成实例 UTC MIN(负18小时) MAX(正18小时)
/**
* The time-zone offset for UTC, with an ID of 'Z'.
*/
public static final ZoneOffset UTC = ZoneOffset.ofTotalSeconds(0);
/**
* Constant for the minimum supported offset.
*/
public static final ZoneOffset MIN = ZoneOffset.ofTotalSeconds(-MAX_SECONDS);
/**
* Constant for the maximum supported offset.
*/
public static final ZoneOffset MAX = ZoneOffset.ofTotalSeconds(MAX_SECONDS);
MAX_SECONDS 取值范围 (正负18小时)
/**
* The abs maximum seconds.
*/
private static final int MAX_SECONDS = 18 * SECONDS_PER_HOUR;
ZoneOffset的取值范围为正负18小时
两个ConcurrentMap作为秒缓存和id缓存
/** Cache of time-zone offset by offset in seconds. */
private static final ConcurrentMap<Integer, ZoneOffset> SECONDS_CACHE = new ConcurrentHashMap<>(16, 0.75f, 4);
/** Cache of time-zone offset by ID. */
private static final ConcurrentMap<String, ZoneOffset> ID_CACHE = new ConcurrentHashMap<>(16, 0.75f, 4);
ZoneOffset与ZoneId字符串格式的区别
ZoneId.of("UTC+8");
ZoneOffset.of("+08:00");
/**
* Obtains an instance of {@code ZoneOffset} using the ID.
*
* This method parses the string ID of a {@code ZoneOffset} to
* return an instance. The parsing accepts all the formats generated by
* {@link #getId()}, plus some additional formats:
*
* - {@code Z} - for UTC
*
- {@code +h}
*
- {@code +hh}
*
- {@code +hh:mm}
*
- {@code -hh:mm}
*
- {@code +hhmm}
*
- {@code -hhmm}
*
- {@code +hh:mm:ss}
*
- {@code -hh:mm:ss}
*
- {@code +hhmmss}
*
- {@code -hhmmss}
*
* Note that ± means either the plus or minus symbol.
*
* The ID of the returned offset will be normalized to one of the formats
* described by {@link #getId()}.
*
* The maximum supported range is from +18:00 to -18:00 inclusive.
*
* @param offsetId the offset ID, not null
* @return the zone-offset, not null
* @throws DateTimeException if the offset ID is invalid
*/
@SuppressWarnings("fallthrough")
public static ZoneOffset of(String offsetId) {
Objects.requireNonNull(offsetId, "offsetId");
// "Z" is always in the cache
ZoneOffset offset = ID_CACHE.get(offsetId);
if (offset != null) {
return offset;
}
// parse - +h, +hh, +hhmm, +hh:mm, +hhmmss, +hh:mm:ss
final int hours, minutes, seconds;
switch (offsetId.length()) {
case 2: //"+8"变为"+08"
offsetId = offsetId.charAt(0) + "0" + offsetId.charAt(1); // fallthru
case 3:
hours = parseNumber(offsetId, 1, false);
minutes = 0;
seconds = 0;
break;
case 5:
hours = parseNumber(offsetId, 1, false);
minutes = parseNumber(offsetId, 3, false);
seconds = 0;
break;
case 6:
hours = parseNumber(offsetId, 1, false);
minutes = parseNumber(offsetId, 4, true);
seconds = 0;
break;
case 7:
hours = parseNumber(offsetId, 1, false);
minutes = parseNumber(offsetId, 3, false);
seconds = parseNumber(offsetId, 5, false);
break;
case 9:
hours = parseNumber(offsetId, 1, false);
minutes = parseNumber(offsetId, 4, true);
seconds = parseNumber(offsetId, 7, true);
break;
default:
throw new DateTimeException("Invalid ID for ZoneOffset, invalid format: " + offsetId);
}
char first = offsetId.charAt(0);
if (first != '+' && first != '-') {
throw new DateTimeException("Invalid ID for ZoneOffset, plus/minus not found when expected: " + offsetId);
}
if (first == '-') {
return ofHoursMinutesSeconds(-hours, -minutes, -seconds);
} else {
return ofHoursMinutesSeconds(hours, minutes, seconds);
}
}
可看出开头必须为正或负号(+或-)
ZoneRegion不是公开类 没有public
/**
* A geographical region where the same time-zone rules apply.
*
* Time-zone information is categorized as a set of rules defining when and
* how the offset from UTC/Greenwich changes. These rules are accessed using
* identifiers based on geographical regions, such as countries or states.
* The most common region classification is the Time Zone Database (TZDB),
* which defines regions such as 'Europe/Paris' and 'Asia/Tokyo'.
*
* The region identifier, modeled by this class, is distinct from the
* underlying rules, modeled by {@link ZoneRules}.
* The rules are defined by governments and change frequently.
* By contrast, the region identifier is well-defined and long-lived.
* This separation also allows rules to be shared between regions if appropriate.
*
*适用于相同时区规则的地理区域。
时区信息归类为一组规则,这些规则定义了何时以及如何更改与UTC /格林威治的偏移量。
可使用基于地理区域(例如国家或州)的标识符来访问这些规则。
最常见的区域分类是时区数据库(TZDB),时区数据库定义了“欧洲/巴黎”和“亚洲/东京”等区域。
由此类建模的区域标识符与由ZoneRules建模的基础规则不同,这些规则由政府定义且经常更改,相比之下,区域标识符定义明确且寿命长,这种分隔也使规则能够 适当时在区域之间共享。
* @implSpec
* This class is immutable and thread-safe.
* 此类不可变,且线程安全
* @since 1.8
*/
final class ZoneRegion extends ZoneId implements Serializable {
没有public
只能由同包下的类调用
ZoneRegion(String id, ZoneRules rules) {
this.id = id;
this.rules = rules;
}
/**
* The time-zone ID, not null.
*/
private final String id;
/**
* The time-zone rules, null if zone ID was loaded leniently.
*/
private final transient ZoneRules rules;
可以看到ZoneRegion保存一个String id 和 ZoneRules ,
这里又牵扯到了ZoneRules(Zone规则)
/**
* 定义区域偏移量在单个时区中如何变化的规则。
该规则为时区建模了所有历史和未来的过渡。 ZoneOffsetTransition用于已知的过渡,通常是历史性的。
ZoneOffsetTransitionRule用于基于算法结果的将来过渡。
这些规则通过ZoneRulesProvider使用ZoneId加载。
相同的规则可以在多个区域ID之间内部共享。
序列化ZoneRules实例将存储整个规则集。
它不存储区域ID,因为它不是该对象状态的一部分。
规则实现可能存储或不存储有关历史和未来过渡的完整信息,并且存储的信息仅与规则提供者提供给实现的信息一样准确。
应用程序应将提供的数据视为代表可用于实现此规则的最佳信息。 。
*
* @implSpec
* This class is immutable and thread-safe.
*
* @since 1.8
*/
public final class ZoneRules implements Serializable {
/**
* Obtains an instance of {@code ZoneId} from an identifier.
*
* @param zoneId the time-zone ID, not null
* @param checkAvailable whether to check if the zone ID is available
* @return the zone ID, not null
* @throws DateTimeException if the ID format is invalid
* @throws ZoneRulesException if checking availability and the ID cannot be found
*/
static ZoneRegion ofId(String zoneId, boolean checkAvailable) {
Objects.requireNonNull(zoneId, "zoneId");
checkName(zoneId);
ZoneRules rules = null;
try {
// always attempt load for better behavior after deserialization
rules = ZoneRulesProvider.getRules(zoneId, true);
} catch (ZoneRulesException ex) {
if (checkAvailable) {
throw ex;
}
}
return new ZoneRegion(zoneId, rules);
}