Path: blob/master/src/java.base/share/classes/java/time/ZoneId.java
41152 views
/*1* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26* This file is available under and governed by the GNU General Public27* License version 2 only, as published by the Free Software Foundation.28* However, the following notice accompanied the original version of this29* file:30*31* Copyright (c) 2007-2012, Stephen Colebourne & Michael Nascimento Santos32*33* All rights reserved.34*35* Redistribution and use in source and binary forms, with or without36* modification, are permitted provided that the following conditions are met:37*38* * Redistributions of source code must retain the above copyright notice,39* this list of conditions and the following disclaimer.40*41* * Redistributions in binary form must reproduce the above copyright notice,42* this list of conditions and the following disclaimer in the documentation43* and/or other materials provided with the distribution.44*45* * Neither the name of JSR-310 nor the names of its contributors46* may be used to endorse or promote products derived from this software47* without specific prior written permission.48*49* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS50* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT51* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR52* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR53* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,54* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,55* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR56* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF57* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING58* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS59* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.60*/61package java.time;6263import java.io.DataOutput;64import java.io.IOException;65import java.io.InvalidObjectException;66import java.io.ObjectInputStream;67import java.io.Serializable;68import java.time.format.DateTimeFormatterBuilder;69import java.time.format.TextStyle;70import java.time.temporal.TemporalAccessor;71import java.time.temporal.TemporalField;72import java.time.temporal.TemporalQueries;73import java.time.temporal.TemporalQuery;74import java.time.temporal.UnsupportedTemporalTypeException;75import java.time.zone.ZoneRules;76import java.time.zone.ZoneRulesException;77import java.time.zone.ZoneRulesProvider;78import java.util.HashSet;79import java.util.Locale;80import java.util.Map;81import java.util.Objects;82import java.util.Set;83import java.util.TimeZone;8485import static java.util.Map.entry;8687/**88* A time-zone ID, such as {@code Europe/Paris}.89* <p>90* A {@code ZoneId} is used to identify the rules used to convert between91* an {@link Instant} and a {@link LocalDateTime}.92* There are two distinct types of ID:93* <ul>94* <li>Fixed offsets - a fully resolved offset from UTC/Greenwich, that uses95* the same offset for all local date-times96* <li>Geographical regions - an area where a specific set of rules for finding97* the offset from UTC/Greenwich apply98* </ul>99* Most fixed offsets are represented by {@link ZoneOffset}.100* Calling {@link #normalized()} on any {@code ZoneId} will ensure that a101* fixed offset ID will be represented as a {@code ZoneOffset}.102* <p>103* The actual rules, describing when and how the offset changes, are defined by {@link ZoneRules}.104* This class is simply an ID used to obtain the underlying rules.105* This approach is taken because rules are defined by governments and change106* frequently, whereas the ID is stable.107* <p>108* The distinction has other effects. Serializing the {@code ZoneId} will only send109* the ID, whereas serializing the rules sends the entire data set.110* Similarly, a comparison of two IDs only examines the ID, whereas111* a comparison of two rules examines the entire data set.112*113* <h2>Time-zone IDs</h2>114* The ID is unique within the system.115* There are three types of ID.116* <p>117* The simplest type of ID is that from {@code ZoneOffset}.118* This consists of 'Z' and IDs starting with '+' or '-'.119* <p>120* The next type of ID are offset-style IDs with some form of prefix,121* such as 'GMT+2' or 'UTC+01:00'.122* The recognised prefixes are 'UTC', 'GMT' and 'UT'.123* The offset is the suffix and will be normalized during creation.124* These IDs can be normalized to a {@code ZoneOffset} using {@code normalized()}.125* <p>126* The third type of ID are region-based IDs. A region-based ID must be of127* two or more characters, and not start with 'UTC', 'GMT', 'UT' '+' or '-'.128* Region-based IDs are defined by configuration, see {@link ZoneRulesProvider}.129* The configuration focuses on providing the lookup from the ID to the130* underlying {@code ZoneRules}.131* <p>132* Time-zone rules are defined by governments and change frequently.133* There are a number of organizations, known here as groups, that monitor134* time-zone changes and collate them.135* The default group is the IANA Time Zone Database (TZDB).136* Other organizations include IATA (the airline industry body) and Microsoft.137* <p>138* Each group defines its own format for the region ID it provides.139* The TZDB group defines IDs such as 'Europe/London' or 'America/New_York'.140* TZDB IDs take precedence over other groups.141* <p>142* It is strongly recommended that the group name is included in all IDs supplied by143* groups other than TZDB to avoid conflicts. For example, IATA airline time-zone144* region IDs are typically the same as the three letter airport code.145* However, the airport of Utrecht has the code 'UTC', which is obviously a conflict.146* The recommended format for region IDs from groups other than TZDB is 'group~region'.147* Thus if IATA data were defined, Utrecht airport would be 'IATA~UTC'.148*149* <h2>Serialization</h2>150* This class can be serialized and stores the string zone ID in the external form.151* The {@code ZoneOffset} subclass uses a dedicated format that only stores the152* offset from UTC/Greenwich.153* <p>154* A {@code ZoneId} can be deserialized in a Java Runtime where the ID is unknown.155* For example, if a server-side Java Runtime has been updated with a new zone ID, but156* the client-side Java Runtime has not been updated. In this case, the {@code ZoneId}157* object will exist, and can be queried using {@code getId}, {@code equals},158* {@code hashCode}, {@code toString}, {@code getDisplayName} and {@code normalized}.159* However, any call to {@code getRules} will fail with {@code ZoneRulesException}.160* This approach is designed to allow a {@link ZonedDateTime} to be loaded and161* queried, but not modified, on a Java Runtime with incomplete time-zone information.162* <p>163* This is a <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>164* class; programmers should treat instances that are165* {@linkplain #equals(Object) equal} as interchangeable and should not166* use instances for synchronization, or unpredictable behavior may167* occur. For example, in a future release, synchronization may fail.168* The {@code equals} method should be used for comparisons.169*170* @implSpec171* This abstract class has two implementations, both of which are immutable and thread-safe.172* One implementation models region-based IDs, the other is {@code ZoneOffset} modelling173* offset-based IDs. This difference is visible in serialization.174*175* @since 1.8176*/177@jdk.internal.ValueBased178public abstract class ZoneId implements Serializable {179180/**181* A map of zone overrides to enable the short time-zone names to be used.182* <p>183* Use of short zone IDs has been deprecated in {@code java.util.TimeZone}.184* This map allows the IDs to continue to be used via the185* {@link #of(String, Map)} factory method.186* <p>187* This map contains a mapping of the IDs that is in line with TZDB 2005r and188* later, where 'EST', 'MST' and 'HST' map to IDs which do not include daylight189* savings.190* <p>191* This maps as follows:192* <ul>193* <li>EST - -05:00</li>194* <li>HST - -10:00</li>195* <li>MST - -07:00</li>196* <li>ACT - Australia/Darwin</li>197* <li>AET - Australia/Sydney</li>198* <li>AGT - America/Argentina/Buenos_Aires</li>199* <li>ART - Africa/Cairo</li>200* <li>AST - America/Anchorage</li>201* <li>BET - America/Sao_Paulo</li>202* <li>BST - Asia/Dhaka</li>203* <li>CAT - Africa/Harare</li>204* <li>CNT - America/St_Johns</li>205* <li>CST - America/Chicago</li>206* <li>CTT - Asia/Shanghai</li>207* <li>EAT - Africa/Addis_Ababa</li>208* <li>ECT - Europe/Paris</li>209* <li>IET - America/Indiana/Indianapolis</li>210* <li>IST - Asia/Kolkata</li>211* <li>JST - Asia/Tokyo</li>212* <li>MIT - Pacific/Apia</li>213* <li>NET - Asia/Yerevan</li>214* <li>NST - Pacific/Auckland</li>215* <li>PLT - Asia/Karachi</li>216* <li>PNT - America/Phoenix</li>217* <li>PRT - America/Puerto_Rico</li>218* <li>PST - America/Los_Angeles</li>219* <li>SST - Pacific/Guadalcanal</li>220* <li>VST - Asia/Ho_Chi_Minh</li>221* </ul>222* The map is unmodifiable.223*/224public static final Map<String, String> SHORT_IDS = Map.ofEntries(225entry("ACT", "Australia/Darwin"),226entry("AET", "Australia/Sydney"),227entry("AGT", "America/Argentina/Buenos_Aires"),228entry("ART", "Africa/Cairo"),229entry("AST", "America/Anchorage"),230entry("BET", "America/Sao_Paulo"),231entry("BST", "Asia/Dhaka"),232entry("CAT", "Africa/Harare"),233entry("CNT", "America/St_Johns"),234entry("CST", "America/Chicago"),235entry("CTT", "Asia/Shanghai"),236entry("EAT", "Africa/Addis_Ababa"),237entry("ECT", "Europe/Paris"),238entry("IET", "America/Indiana/Indianapolis"),239entry("IST", "Asia/Kolkata"),240entry("JST", "Asia/Tokyo"),241entry("MIT", "Pacific/Apia"),242entry("NET", "Asia/Yerevan"),243entry("NST", "Pacific/Auckland"),244entry("PLT", "Asia/Karachi"),245entry("PNT", "America/Phoenix"),246entry("PRT", "America/Puerto_Rico"),247entry("PST", "America/Los_Angeles"),248entry("SST", "Pacific/Guadalcanal"),249entry("VST", "Asia/Ho_Chi_Minh"),250entry("EST", "-05:00"),251entry("MST", "-07:00"),252entry("HST", "-10:00")253);254/**255* Serialization version.256*/257@java.io.Serial258private static final long serialVersionUID = 8352817235686L;259260//-----------------------------------------------------------------------261/**262* Gets the system default time-zone.263* <p>264* This queries {@link TimeZone#getDefault()} to find the default time-zone265* and converts it to a {@code ZoneId}. If the system default time-zone is changed,266* then the result of this method will also change.267*268* @return the zone ID, not null269* @throws DateTimeException if the converted zone ID has an invalid format270* @throws ZoneRulesException if the converted zone region ID cannot be found271*/272public static ZoneId systemDefault() {273return TimeZone.getDefault().toZoneId();274}275276/**277* Gets the set of available zone IDs.278* <p>279* This set includes the string form of all available region-based IDs.280* Offset-based zone IDs are not included in the returned set.281* The ID can be passed to {@link #of(String)} to create a {@code ZoneId}.282* <p>283* The set of zone IDs can increase over time, although in a typical application284* the set of IDs is fixed. Each call to this method is thread-safe.285*286* @return a modifiable copy of the set of zone IDs, not null287*/288public static Set<String> getAvailableZoneIds() {289return new HashSet<String>(ZoneRulesProvider.getAvailableZoneIds());290}291292//-----------------------------------------------------------------------293/**294* Obtains an instance of {@code ZoneId} using its ID using a map295* of aliases to supplement the standard zone IDs.296* <p>297* Many users of time-zones use short abbreviations, such as PST for298* 'Pacific Standard Time' and PDT for 'Pacific Daylight Time'.299* These abbreviations are not unique, and so cannot be used as IDs.300* This method allows a map of string to time-zone to be setup and reused301* within an application.302*303* @param zoneId the time-zone ID, not null304* @param aliasMap a map of alias zone IDs (typically abbreviations) to real zone IDs, not null305* @return the zone ID, not null306* @throws DateTimeException if the zone ID has an invalid format307* @throws ZoneRulesException if the zone ID is a region ID that cannot be found308*/309public static ZoneId of(String zoneId, Map<String, String> aliasMap) {310Objects.requireNonNull(zoneId, "zoneId");311Objects.requireNonNull(aliasMap, "aliasMap");312String id = Objects.requireNonNullElse(aliasMap.get(zoneId), zoneId);313return of(id);314}315316/**317* Obtains an instance of {@code ZoneId} from an ID ensuring that the318* ID is valid and available for use.319* <p>320* This method parses the ID producing a {@code ZoneId} or {@code ZoneOffset}.321* A {@code ZoneOffset} is returned if the ID is 'Z', or starts with '+' or '-'.322* The result will always be a valid ID for which {@link ZoneRules} can be obtained.323* <p>324* Parsing matches the zone ID step by step as follows.325* <ul>326* <li>If the zone ID equals 'Z', the result is {@code ZoneOffset.UTC}.327* <li>If the zone ID consists of a single letter, the zone ID is invalid328* and {@code DateTimeException} is thrown.329* <li>If the zone ID starts with '+' or '-', the ID is parsed as a330* {@code ZoneOffset} using {@link ZoneOffset#of(String)}.331* <li>If the zone ID equals 'GMT', 'UTC' or 'UT' then the result is a {@code ZoneId}332* with the same ID and rules equivalent to {@code ZoneOffset.UTC}.333* <li>If the zone ID starts with 'UTC+', 'UTC-', 'GMT+', 'GMT-', 'UT+' or 'UT-'334* then the ID is a prefixed offset-based ID. The ID is split in two, with335* a two or three letter prefix and a suffix starting with the sign.336* The suffix is parsed as a {@link ZoneOffset#of(String) ZoneOffset}.337* The result will be a {@code ZoneId} with the specified UTC/GMT/UT prefix338* and the normalized offset ID as per {@link ZoneOffset#getId()}.339* The rules of the returned {@code ZoneId} will be equivalent to the340* parsed {@code ZoneOffset}.341* <li>All other IDs are parsed as region-based zone IDs. Region IDs must342* match the regular expression {@code [A-Za-z][A-Za-z0-9~/._+-]+}343* otherwise a {@code DateTimeException} is thrown. If the zone ID is not344* in the configured set of IDs, {@code ZoneRulesException} is thrown.345* The detailed format of the region ID depends on the group supplying the data.346* The default set of data is supplied by the IANA Time Zone Database (TZDB).347* This has region IDs of the form '{area}/{city}', such as 'Europe/Paris' or 'America/New_York'.348* This is compatible with most IDs from {@link java.util.TimeZone}.349* </ul>350*351* @param zoneId the time-zone ID, not null352* @return the zone ID, not null353* @throws DateTimeException if the zone ID has an invalid format354* @throws ZoneRulesException if the zone ID is a region ID that cannot be found355*/356public static ZoneId of(String zoneId) {357return of(zoneId, true);358}359360/**361* Obtains an instance of {@code ZoneId} wrapping an offset.362* <p>363* If the prefix is "GMT", "UTC", or "UT" a {@code ZoneId}364* with the prefix and the non-zero offset is returned.365* If the prefix is empty {@code ""} the {@code ZoneOffset} is returned.366*367* @param prefix the time-zone ID, not null368* @param offset the offset, not null369* @return the zone ID, not null370* @throws IllegalArgumentException if the prefix is not one of371* "GMT", "UTC", or "UT", or ""372*/373public static ZoneId ofOffset(String prefix, ZoneOffset offset) {374Objects.requireNonNull(prefix, "prefix");375Objects.requireNonNull(offset, "offset");376if (prefix.isEmpty()) {377return offset;378}379380if (!prefix.equals("GMT") && !prefix.equals("UTC") && !prefix.equals("UT")) {381throw new IllegalArgumentException("prefix should be GMT, UTC or UT, is: " + prefix);382}383384if (offset.getTotalSeconds() != 0) {385prefix = prefix.concat(offset.getId());386}387return new ZoneRegion(prefix, offset.getRules());388}389390/**391* Parses the ID, taking a flag to indicate whether {@code ZoneRulesException}392* should be thrown or not, used in deserialization.393*394* @param zoneId the time-zone ID, not null395* @param checkAvailable whether to check if the zone ID is available396* @return the zone ID, not null397* @throws DateTimeException if the ID format is invalid398* @throws ZoneRulesException if checking availability and the ID cannot be found399*/400static ZoneId of(String zoneId, boolean checkAvailable) {401Objects.requireNonNull(zoneId, "zoneId");402if (zoneId.length() <= 1 || zoneId.startsWith("+") || zoneId.startsWith("-")) {403return ZoneOffset.of(zoneId);404} else if (zoneId.startsWith("UTC") || zoneId.startsWith("GMT")) {405return ofWithPrefix(zoneId, 3, checkAvailable);406} else if (zoneId.startsWith("UT")) {407return ofWithPrefix(zoneId, 2, checkAvailable);408}409return ZoneRegion.ofId(zoneId, checkAvailable);410}411412/**413* Parse once a prefix is established.414*415* @param zoneId the time-zone ID, not null416* @param prefixLength the length of the prefix, 2 or 3417* @return the zone ID, not null418* @throws DateTimeException if the zone ID has an invalid format419*/420private static ZoneId ofWithPrefix(String zoneId, int prefixLength, boolean checkAvailable) {421String prefix = zoneId.substring(0, prefixLength);422if (zoneId.length() == prefixLength) {423return ofOffset(prefix, ZoneOffset.UTC);424}425if (zoneId.charAt(prefixLength) != '+' && zoneId.charAt(prefixLength) != '-') {426return ZoneRegion.ofId(zoneId, checkAvailable); // drop through to ZoneRulesProvider427}428try {429ZoneOffset offset = ZoneOffset.of(zoneId.substring(prefixLength));430if (offset == ZoneOffset.UTC) {431return ofOffset(prefix, offset);432}433return ofOffset(prefix, offset);434} catch (DateTimeException ex) {435throw new DateTimeException("Invalid ID for offset-based ZoneId: " + zoneId, ex);436}437}438439//-----------------------------------------------------------------------440/**441* Obtains an instance of {@code ZoneId} from a temporal object.442* <p>443* This obtains a zone based on the specified temporal.444* A {@code TemporalAccessor} represents an arbitrary set of date and time information,445* which this factory converts to an instance of {@code ZoneId}.446* <p>447* A {@code TemporalAccessor} represents some form of date and time information.448* This factory converts the arbitrary temporal object to an instance of {@code ZoneId}.449* <p>450* The conversion will try to obtain the zone in a way that favours region-based451* zones over offset-based zones using {@link TemporalQueries#zone()}.452* <p>453* This method matches the signature of the functional interface {@link TemporalQuery}454* allowing it to be used as a query via method reference, {@code ZoneId::from}.455*456* @param temporal the temporal object to convert, not null457* @return the zone ID, not null458* @throws DateTimeException if unable to convert to a {@code ZoneId}459*/460public static ZoneId from(TemporalAccessor temporal) {461ZoneId obj = temporal.query(TemporalQueries.zone());462if (obj == null) {463throw new DateTimeException("Unable to obtain ZoneId from TemporalAccessor: " +464temporal + " of type " + temporal.getClass().getName());465}466return obj;467}468469//-----------------------------------------------------------------------470/**471* Constructor only accessible within the package.472*/473ZoneId() {474if (getClass() != ZoneOffset.class && getClass() != ZoneRegion.class) {475throw new AssertionError("Invalid subclass");476}477}478479//-----------------------------------------------------------------------480/**481* Gets the unique time-zone ID.482* <p>483* This ID uniquely defines this object.484* The format of an offset based ID is defined by {@link ZoneOffset#getId()}.485*486* @return the time-zone unique ID, not null487*/488public abstract String getId();489490//-----------------------------------------------------------------------491/**492* Gets the textual representation of the zone, such as 'British Time' or493* '+02:00'.494* <p>495* This returns the textual name used to identify the time-zone ID,496* suitable for presentation to the user.497* The parameters control the style of the returned text and the locale.498* <p>499* If no textual mapping is found then the {@link #getId() full ID} is returned.500*501* @param style the length of the text required, not null502* @param locale the locale to use, not null503* @return the text value of the zone, not null504*/505public String getDisplayName(TextStyle style, Locale locale) {506return new DateTimeFormatterBuilder().appendZoneText(style).toFormatter(locale).format(toTemporal());507}508509/**510* Converts this zone to a {@code TemporalAccessor}.511* <p>512* A {@code ZoneId} can be fully represented as a {@code TemporalAccessor}.513* However, the interface is not implemented by this class as most of the514* methods on the interface have no meaning to {@code ZoneId}.515* <p>516* The returned temporal has no supported fields, with the query method517* supporting the return of the zone using {@link TemporalQueries#zoneId()}.518*519* @return a temporal equivalent to this zone, not null520*/521private TemporalAccessor toTemporal() {522return new TemporalAccessor() {523@Override524public boolean isSupported(TemporalField field) {525return false;526}527@Override528public long getLong(TemporalField field) {529throw new UnsupportedTemporalTypeException("Unsupported field: " + field);530}531@SuppressWarnings("unchecked")532@Override533public <R> R query(TemporalQuery<R> query) {534if (query == TemporalQueries.zoneId()) {535return (R) ZoneId.this;536}537return TemporalAccessor.super.query(query);538}539};540}541542//-----------------------------------------------------------------------543/**544* Gets the time-zone rules for this ID allowing calculations to be performed.545* <p>546* The rules provide the functionality associated with a time-zone,547* such as finding the offset for a given instant or local date-time.548* <p>549* A time-zone can be invalid if it is deserialized in a Java Runtime which550* does not have the same rules loaded as the Java Runtime that stored it.551* In this case, calling this method will throw a {@code ZoneRulesException}.552* <p>553* The rules are supplied by {@link ZoneRulesProvider}. An advanced provider may554* support dynamic updates to the rules without restarting the Java Runtime.555* If so, then the result of this method may change over time.556* Each individual call will be still remain thread-safe.557* <p>558* {@link ZoneOffset} will always return a set of rules where the offset never changes.559*560* @return the rules, not null561* @throws ZoneRulesException if no rules are available for this ID562*/563public abstract ZoneRules getRules();564565/**566* Normalizes the time-zone ID, returning a {@code ZoneOffset} where possible.567* <p>568* The returns a normalized {@code ZoneId} that can be used in place of this ID.569* The result will have {@code ZoneRules} equivalent to those returned by this object,570* however the ID returned by {@code getId()} may be different.571* <p>572* The normalization checks if the rules of this {@code ZoneId} have a fixed offset.573* If they do, then the {@code ZoneOffset} equal to that offset is returned.574* Otherwise {@code this} is returned.575*576* @return the time-zone unique ID, not null577*/578public ZoneId normalized() {579try {580ZoneRules rules = getRules();581if (rules.isFixedOffset()) {582return rules.getOffset(Instant.EPOCH);583}584} catch (ZoneRulesException ex) {585// invalid ZoneRegion is not important to this method586}587return this;588}589590//-----------------------------------------------------------------------591/**592* Checks if this time-zone ID is equal to another time-zone ID.593* <p>594* The comparison is based on the ID.595*596* @param obj the object to check, null returns false597* @return true if this is equal to the other time-zone ID598*/599@Override600public boolean equals(Object obj) {601if (this == obj) {602return true;603}604return (obj instanceof ZoneId other)605&& getId().equals(other.getId());606}607608/**609* A hash code for this time-zone ID.610*611* @return a suitable hash code612*/613@Override614public int hashCode() {615return getId().hashCode();616}617618//-----------------------------------------------------------------------619/**620* Defend against malicious streams.621*622* @param s the stream to read623* @throws InvalidObjectException always624*/625@java.io.Serial626private void readObject(ObjectInputStream s) throws InvalidObjectException {627throw new InvalidObjectException("Deserialization via serialization delegate");628}629630/**631* Outputs this zone as a {@code String}, using the ID.632*633* @return a string representation of this time-zone ID, not null634*/635@Override636public String toString() {637return getId();638}639640//-----------------------------------------------------------------------641/**642* Writes the object using a643* <a href="{@docRoot}/serialized-form.html#java.time.Ser">dedicated serialized form</a>.644* @serialData645* <pre>646* out.writeByte(7); // identifies a ZoneId (not ZoneOffset)647* out.writeUTF(getId());648* </pre>649* <p>650* When read back in, the {@code ZoneId} will be created as though using651* {@link #of(String)}, but without any exception in the case where the652* ID has a valid format, but is not in the known set of region-based IDs.653*654* @return the instance of {@code Ser}, not null655*/656// this is here for serialization Javadoc657@java.io.Serial658private Object writeReplace() {659return new Ser(Ser.ZONE_REGION_TYPE, this);660}661662abstract void write(DataOutput out) throws IOException;663664}665666667