jdk/src/share/classes/java/time/ZoneId.java
changeset 16852 60207b2b4b42
parent 15658 55b829ca2334
child 17474 8c100beabcc0
equal deleted inserted replaced
16851:3bbdae468b05 16852:60207b2b4b42
    64 import java.io.DataOutput;
    64 import java.io.DataOutput;
    65 import java.io.IOException;
    65 import java.io.IOException;
    66 import java.io.Serializable;
    66 import java.io.Serializable;
    67 import java.time.format.DateTimeFormatterBuilder;
    67 import java.time.format.DateTimeFormatterBuilder;
    68 import java.time.format.TextStyle;
    68 import java.time.format.TextStyle;
    69 import java.time.temporal.Queries;
       
    70 import java.time.temporal.TemporalAccessor;
    69 import java.time.temporal.TemporalAccessor;
    71 import java.time.temporal.TemporalField;
    70 import java.time.temporal.TemporalField;
    72 import java.time.temporal.TemporalQuery;
    71 import java.time.temporal.TemporalQuery;
       
    72 import java.time.temporal.UnsupportedTemporalTypeException;
    73 import java.time.zone.ZoneRules;
    73 import java.time.zone.ZoneRules;
    74 import java.time.zone.ZoneRulesException;
    74 import java.time.zone.ZoneRulesException;
    75 import java.time.zone.ZoneRulesProvider;
    75 import java.time.zone.ZoneRulesProvider;
    76 import java.util.Collections;
    76 import java.util.Collections;
    77 import java.util.HashMap;
    77 import java.util.HashMap;
    78 import java.util.Locale;
    78 import java.util.Locale;
    79 import java.util.Map;
    79 import java.util.Map;
    80 import java.util.Objects;
    80 import java.util.Objects;
       
    81 import java.util.Set;
    81 import java.util.TimeZone;
    82 import java.util.TimeZone;
    82 
    83 
    83 /**
    84 /**
    84  * A time-zone ID, such as {@code Europe/Paris}.
    85  * A time-zone ID, such as {@code Europe/Paris}.
    85  * <p>
    86  * <p>
    91  *  the same offset for all local date-times
    92  *  the same offset for all local date-times
    92  * <li>Geographical regions - an area where a specific set of rules for finding
    93  * <li>Geographical regions - an area where a specific set of rules for finding
    93  *  the offset from UTC/Greenwich apply
    94  *  the offset from UTC/Greenwich apply
    94  * </ul><p>
    95  * </ul><p>
    95  * Most fixed offsets are represented by {@link ZoneOffset}.
    96  * Most fixed offsets are represented by {@link ZoneOffset}.
       
    97  * Calling {@link #normalized()} on any {@code ZoneId} will ensure that a
       
    98  * fixed offset ID will be represented as a {@code ZoneOffset}.
    96  * <p>
    99  * <p>
    97  * The actual rules, describing when and how the offset changes, are defined by {@link ZoneRules}.
   100  * The actual rules, describing when and how the offset changes, are defined by {@link ZoneRules}.
    98  * This class is simply an ID used to obtain the underlying rules.
   101  * This class is simply an ID used to obtain the underlying rules.
    99  * This approach is taken because rules are defined by governments and change
   102  * This approach is taken because rules are defined by governments and change
   100  * frequently, whereas the ID is stable.
   103  * frequently, whereas the ID is stable.
   101  * <p>
   104  * <p>
   102  * The distinction has other effects. Serializing the {@code ZoneId} will only send
   105  * The distinction has other effects. Serializing the {@code ZoneId} will only send
   103  * the ID, whereas serializing the rules sends the entire data set.
   106  * the ID, whereas serializing the rules sends the entire data set.
   104  * Similarly, a comparison of two IDs only examines the ID, whereas
   107  * Similarly, a comparison of two IDs only examines the ID, whereas
   105  * a comparison of two rules examines the entire data set.
   108  * a comparison of two rules examines the entire data set.
   106  * <p>
       
   107  * The code supports loading a {@code ZoneId} on a JVM which does not have available rules
       
   108  * for that ID. This allows the date-time object, such as {@link ZonedDateTime},
       
   109  * to still be queried.
       
   110  *
   109  *
   111  * <h3>Time-zone IDs</h3>
   110  * <h3>Time-zone IDs</h3>
   112  * The ID is unique within the system.
   111  * The ID is unique within the system.
   113  * The formats for offset and region IDs differ.
   112  * There are three types of ID.
   114  * <p>
   113  * <p>
   115  * An ID is parsed as an offset ID if it starts with 'UTC', 'GMT', 'UT' '+' or '-', or
   114  * The simplest type of ID is that from {@code ZoneOffset}.
   116  * is a single letter. For example, 'Z', '+02:00', '-05:00', 'UTC+05', 'GMT-6' and
   115  * This consists of 'Z' and IDs starting with '+' or '-'.
   117  * 'UT+01:00' are all valid offset IDs.
   116  * <p>
   118  * Note that some IDs, such as 'D' or '+ABC' meet the criteria to be parsed as offset IDs,
   117  * The next type of ID are offset-style IDs with some form of prefix,
   119  * but have an invalid offset.
   118  * such as 'GMT+2' or 'UTC+01:00'.
   120  * <p>
   119  * The recognised prefixes are 'UTC', 'GMT' and 'UT'.
   121  * All other IDs are considered to be region IDs.
   120  * The offset is the suffix and will be normalized during creation.
   122  * <p>
   121  * These IDs can be normalized to a {@code ZoneOffset} using {@code normalized()}.
   123  * Region IDs are defined by configuration, which can be thought of as a {@code Map}
   122  * <p>
   124  * from region ID to {@code ZoneRules}, see {@link ZoneRulesProvider}.
   123  * The third type of ID are region-based IDs. A region-based ID must be of
   125  * <p>
   124  * two or more characters, and not start with 'UTC', 'GMT', 'UT' '+' or '-'.
   126  * Time-zones are defined by governments and change frequently. There are a number of
   125  * Region-based IDs are defined by configuration, see {@link ZoneRulesProvider}.
   127  * organizations, known here as groups, that monitor time-zone changes and collate them.
   126  * The configuration focuses on providing the lookup from the ID to the
       
   127  * underlying {@code ZoneRules}.
       
   128  * <p>
       
   129  * Time-zone rules are defined by governments and change frequently.
       
   130  * There are a number of organizations, known here as groups, that monitor
       
   131  * time-zone changes and collate them.
   128  * The default group is the IANA Time Zone Database (TZDB).
   132  * The default group is the IANA Time Zone Database (TZDB).
   129  * Other organizations include IATA (the airline industry body) and Microsoft.
   133  * Other organizations include IATA (the airline industry body) and Microsoft.
   130  * <p>
   134  * <p>
   131  * Each group defines its own format for the region ID it provides.
   135  * Each group defines its own format for the region ID it provides.
   132  * The TZDB group defines IDs such as 'Europe/London' or 'America/New_York'.
   136  * The TZDB group defines IDs such as 'Europe/London' or 'America/New_York'.
   137  * region IDs are typically the same as the three letter airport code.
   141  * region IDs are typically the same as the three letter airport code.
   138  * However, the airport of Utrecht has the code 'UTC', which is obviously a conflict.
   142  * However, the airport of Utrecht has the code 'UTC', which is obviously a conflict.
   139  * The recommended format for region IDs from groups other than TZDB is 'group~region'.
   143  * The recommended format for region IDs from groups other than TZDB is 'group~region'.
   140  * Thus if IATA data were defined, Utrecht airport would be 'IATA~UTC'.
   144  * Thus if IATA data were defined, Utrecht airport would be 'IATA~UTC'.
   141  *
   145  *
       
   146  * <h3>Serialization</h3>
       
   147  * This class can be serialized and stores the string zone ID in the external form.
       
   148  * The {@code ZoneOffset} subclass uses a dedicated format that only stores the
       
   149  * offset from UTC/Greenwich.
       
   150  * <p>
       
   151  * A {@code ZoneId} can be deserialized in a Java Runtime where the ID is unknown.
       
   152  * For example, if a server-side Java Runtime has been updated with a new zone ID, but
       
   153  * the client-side Java Runtime has not been updated. In this case, the {@code ZoneId}
       
   154  * object will exist, and can be queried using {@code getId}, {@code equals},
       
   155  * {@code hashCode}, {@code toString}, {@code getDisplayName} and {@code normalized}.
       
   156  * However, any call to {@code getRules} will fail with {@code ZoneRulesException}.
       
   157  * This approach is designed to allow a {@link ZonedDateTime} to be loaded and
       
   158  * queried, but not modified, on a Java Runtime with incomplete time-zone information.
       
   159  *
   142  * <h3>Specification for implementors</h3>
   160  * <h3>Specification for implementors</h3>
   143  * This abstract class has two implementations, both of which are immutable and thread-safe.
   161  * This abstract class has two implementations, both of which are immutable and thread-safe.
   144  * One implementation models region-based IDs, the other is {@code ZoneOffset} modelling
   162  * One implementation models region-based IDs, the other is {@code ZoneOffset} modelling
   145  * offset-based IDs. This difference is visible in serialization.
   163  * offset-based IDs. This difference is visible in serialization.
   146  *
   164  *
   147  * @since 1.8
   165  * @since 1.8
   148  */
   166  */
   149 public abstract class ZoneId implements Serializable {
   167 public abstract class ZoneId implements Serializable {
   150 
   168 
   151     /**
   169     /**
   152      * A map of zone overrides to enable the older US time-zone names to be used.
   170      * A map of zone overrides to enable the older short time-zone names to be used.
       
   171      * <p>
       
   172      * Use of short zone IDs has been deprecated in {@code java.util.TimeZone}.
       
   173      * This map allows the IDs to continue to be used via the
       
   174      * {@link #of(String, Map)} factory method.
       
   175      * <p>
       
   176      * This map contains an older mapping of the IDs, where 'EST', 'MST' and 'HST'
       
   177      * map to IDs which include daylight savings.
       
   178      * This is in line with versions of TZDB before 2005r.
   153      * <p>
   179      * <p>
   154      * This maps as follows:
   180      * This maps as follows:
   155      * <p><ul>
   181      * <p><ul>
   156      * <li>EST - America/New_York</li>
   182      * <li>EST - America/New_York</li>
   157      * <li>MST - America/Denver</li>
   183      * <li>MST - America/Denver</li>
   182      * <li>SST - Pacific/Guadalcanal</li>
   208      * <li>SST - Pacific/Guadalcanal</li>
   183      * <li>VST - Asia/Ho_Chi_Minh</li>
   209      * <li>VST - Asia/Ho_Chi_Minh</li>
   184      * </ul><p>
   210      * </ul><p>
   185      * The map is unmodifiable.
   211      * The map is unmodifiable.
   186      */
   212      */
   187     public static final Map<String, String> OLD_IDS_PRE_2005;
   213     public static final Map<String, String> OLD_SHORT_IDS;
   188     /**
   214     /**
   189      * A map of zone overrides to enable the older US time-zone names to be used.
   215      * A map of zone overrides to enable the short time-zone names to be used.
       
   216      * <p>
       
   217      * Use of short zone IDs has been deprecated in {@code java.util.TimeZone}.
       
   218      * This map allows the IDs to continue to be used via the
       
   219      * {@link #of(String, Map)} factory method.
       
   220      * <p>
       
   221      * This map contains a newer mapping of the IDs, where 'EST', 'MST' and 'HST'
       
   222      * map to IDs which do not include daylight savings
       
   223      * This is in line with TZDB 2005r and later.
   190      * <p>
   224      * <p>
   191      * This maps as follows:
   225      * This maps as follows:
   192      * <p><ul>
   226      * <p><ul>
   193      * <li>EST - -05:00</li>
   227      * <li>EST - -05:00</li>
   194      * <li>HST - -10:00</li>
   228      * <li>HST - -10:00</li>
   219      * <li>SST - Pacific/Guadalcanal</li>
   253      * <li>SST - Pacific/Guadalcanal</li>
   220      * <li>VST - Asia/Ho_Chi_Minh</li>
   254      * <li>VST - Asia/Ho_Chi_Minh</li>
   221      * </ul><p>
   255      * </ul><p>
   222      * The map is unmodifiable.
   256      * The map is unmodifiable.
   223      */
   257      */
   224     public static final Map<String, String> OLD_IDS_POST_2005;
   258     public static final Map<String, String> SHORT_IDS;
   225     static {
   259     static {
   226         Map<String, String> base = new HashMap<>();
   260         Map<String, String> base = new HashMap<>();
   227         base.put("ACT", "Australia/Darwin");
   261         base.put("ACT", "Australia/Darwin");
   228         base.put("AET", "Australia/Sydney");
   262         base.put("AET", "Australia/Sydney");
   229         base.put("AGT", "America/Argentina/Buenos_Aires");
   263         base.put("AGT", "America/Argentina/Buenos_Aires");
   251         base.put("VST", "Asia/Ho_Chi_Minh");
   285         base.put("VST", "Asia/Ho_Chi_Minh");
   252         Map<String, String> pre = new HashMap<>(base);
   286         Map<String, String> pre = new HashMap<>(base);
   253         pre.put("EST", "America/New_York");
   287         pre.put("EST", "America/New_York");
   254         pre.put("MST", "America/Denver");
   288         pre.put("MST", "America/Denver");
   255         pre.put("HST", "Pacific/Honolulu");
   289         pre.put("HST", "Pacific/Honolulu");
   256         OLD_IDS_PRE_2005 = Collections.unmodifiableMap(pre);
   290         OLD_SHORT_IDS = Collections.unmodifiableMap(pre);
   257         Map<String, String> post = new HashMap<>(base);
   291         Map<String, String> post = new HashMap<>(base);
   258         post.put("EST", "-05:00");
   292         post.put("EST", "-05:00");
   259         post.put("MST", "-07:00");
   293         post.put("MST", "-07:00");
   260         post.put("HST", "-10:00");
   294         post.put("HST", "-10:00");
   261         OLD_IDS_POST_2005 = Collections.unmodifiableMap(post);
   295         SHORT_IDS = Collections.unmodifiableMap(post);
   262     }
   296     }
   263     /**
   297     /**
   264      * Serialization version.
   298      * Serialization version.
   265      */
   299      */
   266     private static final long serialVersionUID = 8352817235686L;
   300     private static final long serialVersionUID = 8352817235686L;
   276      * @return the zone ID, not null
   310      * @return the zone ID, not null
   277      * @throws DateTimeException if the converted zone ID has an invalid format
   311      * @throws DateTimeException if the converted zone ID has an invalid format
   278      * @throws ZoneRulesException if the converted zone region ID cannot be found
   312      * @throws ZoneRulesException if the converted zone region ID cannot be found
   279      */
   313      */
   280     public static ZoneId systemDefault() {
   314     public static ZoneId systemDefault() {
   281         return ZoneId.of(TimeZone.getDefault().getID(), OLD_IDS_POST_2005);
   315         return ZoneId.of(TimeZone.getDefault().getID(), SHORT_IDS);
       
   316     }
       
   317 
       
   318     /**
       
   319      * Gets the set of available zone IDs.
       
   320      * <p>
       
   321      * This set includes the string form of all available region-based IDs.
       
   322      * Offset-based zone IDs are not included in the returned set.
       
   323      * The ID can be passed to {@link #of(String)} to create a {@code ZoneId}.
       
   324      * <p>
       
   325      * The set of zone IDs can increase over time, although in a typical application
       
   326      * the set of IDs is fixed. Each call to this method is thread-safe.
       
   327      *
       
   328      * @return a modifiable copy of the set of zone IDs, not null
       
   329      */
       
   330     public static Set<String> getAvailableZoneIds() {
       
   331         return ZoneRulesProvider.getAvailableZoneIds();
   282     }
   332     }
   283 
   333 
   284     //-----------------------------------------------------------------------
   334     //-----------------------------------------------------------------------
   285     /**
   335     /**
   286      * Obtains an instance of {@code ZoneId} using its ID using a map
   336      * Obtains an instance of {@code ZoneId} using its ID using a map
   308 
   358 
   309     /**
   359     /**
   310      * Obtains an instance of {@code ZoneId} from an ID ensuring that the
   360      * Obtains an instance of {@code ZoneId} from an ID ensuring that the
   311      * ID is valid and available for use.
   361      * ID is valid and available for use.
   312      * <p>
   362      * <p>
   313      * This method parses the ID, applies any appropriate normalization, and validates it
   363      * This method parses the ID producing a {@code ZoneId} or {@code ZoneOffset}.
   314      * against the known set of IDs for which rules are available.
   364      * A {@code ZoneOffset} is returned if the ID is 'Z', or starts with '+' or '-'.
   315      * <p>
   365      * The result will always be a valid ID for which {@link ZoneRules} can be obtained.
   316      * An ID is parsed as though it is an offset ID if it starts with 'UTC', 'GMT', 'UT', '+'
   366      * <p>
   317      * or '-', or if it has less then two letters.
   367      * Parsing matches the zone ID step by step as follows.
   318      * The offset of {@linkplain ZoneOffset#UTC zero} may be represented in multiple ways,
   368      * <ul>
   319      * including 'Z', 'UTC', 'GMT', 'UT', 'UTC0', 'GMT0', 'UT0', '+00:00', '-00:00' and 'UTC+00:00'.
   369      * <li>If the zone ID equals 'Z', the result is {@code ZoneOffset.UTC}.
   320      * <p>
   370      * <li>If the zone ID consists of a single letter, the zone ID is invalid
   321      * Six forms of ID are recognized:
   371      *  and {@code DateTimeException} is thrown.
   322      * <p><ul>
   372      * <li>If the zone ID starts with '+' or '-', the ID is parsed as a
   323      * <li><code>Z</code> - an offset of zero, which is {@code ZoneOffset.UTC}
   373      *  {@code ZoneOffset} using {@link ZoneOffset#of(String)}.
   324      * <li><code>{offset}</code> - a {@code ZoneOffset} ID, such as '+02:00'
   374      * <li>If the zone ID equals 'GMT', 'UTC' or 'UT' then the result is a {@code ZoneId}
   325      * <li><code>{utcPrefix}</code> - a {@code ZoneOffset} ID equal to 'Z'
   375      *  with the same ID and rules equivalent to {@code ZoneOffset.UTC}.
   326      * <li><code>{utcPrefix}0</code> - a {@code ZoneOffset} ID equal to 'Z'
   376      * <li>If the zone ID starts with 'UTC+', 'UTC-', 'GMT+', 'GMT-', 'UT+' or 'UT-'
   327      * <li><code>{utcPrefix}{offset}</code> - a {@code ZoneOffset} ID equal to '{offset}'
   377      *  then the ID is a prefixed offset-based ID. The ID is split in two, with
   328      * <li><code>{regionID}</code> - full region ID, loaded from configuration
   378      *  a two or three letter prefix and a suffix starting with the sign.
   329      * </ul><p>
   379      *  The suffix is parsed as a {@link ZoneOffset#of(String) ZoneOffset}.
   330      * The {offset} is a valid format for {@link ZoneOffset#of(String)}, excluding 'Z'.
   380      *  The result will be a {@code ZoneId} with the specified UTC/GMT/UT prefix
   331      * The {utcPrefix} is 'UTC', 'GMT' or 'UT'.
   381      *  and the normalized offset ID as per {@link ZoneOffset#getId()}.
   332      * Region IDs must match the regular expression <code>[A-Za-z][A-Za-z0-9~/._+-]+</code>.
   382      *  The rules of the returned {@code ZoneId} will be equivalent to the
   333      * <p>
   383      *  parsed {@code ZoneOffset}.
   334      * The detailed format of the region ID depends on the group supplying the data.
   384      * <li>All other IDs are parsed as region-based zone IDs. Region IDs must
   335      * The default set of data is supplied by the IANA Time Zone Database (TZDB)
   385      *  match the regular expression <code>[A-Za-z][A-Za-z0-9~/._+-]+</code>
   336      * This has region IDs of the form '{area}/{city}', such as 'Europe/Paris' or 'America/New_York'.
   386      *  otherwise a {@code DateTimeException} is thrown. If the zone ID is not
   337      * This is compatible with most IDs from {@link java.util.TimeZone}.
   387      *  in the configured set of IDs, {@code ZoneRulesException} is thrown.
       
   388      *  The detailed format of the region ID depends on the group supplying the data.
       
   389      *  The default set of data is supplied by the IANA Time Zone Database (TZDB).
       
   390      *  This has region IDs of the form '{area}/{city}', such as 'Europe/Paris' or 'America/New_York'.
       
   391      *  This is compatible with most IDs from {@link java.util.TimeZone}.
       
   392      * </ul>
   338      *
   393      *
   339      * @param zoneId  the time-zone ID, not null
   394      * @param zoneId  the time-zone ID, not null
   340      * @return the zone ID, not null
   395      * @return the zone ID, not null
   341      * @throws DateTimeException if the zone ID has an invalid format
   396      * @throws DateTimeException if the zone ID has an invalid format
   342      * @throws ZoneRulesException if the zone ID is a region ID that cannot be found
   397      * @throws ZoneRulesException if the zone ID is a region ID that cannot be found
   343      */
   398      */
   344     public static ZoneId of(String zoneId) {
   399     public static ZoneId of(String zoneId) {
       
   400         return of(zoneId, true);
       
   401     }
       
   402 
       
   403     /**
       
   404      * Parses the ID, taking a flag to indicate whether {@code ZoneRulesException}
       
   405      * should be thrown or not, used in deserialization.
       
   406      *
       
   407      * @param zoneId  the time-zone ID, not null
       
   408      * @param checkAvailable  whether to check if the zone ID is available
       
   409      * @return the zone ID, not null
       
   410      * @throws DateTimeException if the ID format is invalid
       
   411      * @throws ZoneRulesException if checking availability and the ID cannot be found
       
   412      */
       
   413     static ZoneId of(String zoneId, boolean checkAvailable) {
   345         Objects.requireNonNull(zoneId, "zoneId");
   414         Objects.requireNonNull(zoneId, "zoneId");
   346         if (zoneId.length() <= 1 || zoneId.startsWith("+") || zoneId.startsWith("-")) {
   415         if (zoneId.length() <= 1 || zoneId.startsWith("+") || zoneId.startsWith("-")) {
   347             return ZoneOffset.of(zoneId);
   416             return ZoneOffset.of(zoneId);
   348         } else if (zoneId.startsWith("UTC") || zoneId.startsWith("GMT")) {
   417         } else if (zoneId.startsWith("UTC") || zoneId.startsWith("GMT")) {
   349             return ofWithPrefix(zoneId, 3);
   418             return ofWithPrefix(zoneId, 3, checkAvailable);
   350         } else if (zoneId.startsWith("UT")) {
   419         } else if (zoneId.startsWith("UT")) {
   351             return ofWithPrefix(zoneId, 2);
   420             return ofWithPrefix(zoneId, 2, checkAvailable);
   352         }
   421         }
   353         return ZoneRegion.ofId(zoneId, true);
   422         return ZoneRegion.ofId(zoneId, checkAvailable);
   354     }
   423     }
   355 
   424 
   356     /**
   425     /**
   357      * Parse once a prefix is established.
   426      * Parse once a prefix is established.
   358      *
   427      *
   359      * @param zoneId  the time-zone ID, not null
   428      * @param zoneId  the time-zone ID, not null
   360      * @param prefixLength  the length of the prefix, 2 or 3
   429      * @param prefixLength  the length of the prefix, 2 or 3
   361      * @return the zone ID, not null
   430      * @return the zone ID, not null
   362      * @return the zone ID, not null
       
   363      * @throws DateTimeException if the zone ID has an invalid format
   431      * @throws DateTimeException if the zone ID has an invalid format
   364      */
   432      */
   365     private static ZoneId ofWithPrefix(String zoneId, int prefixLength) {
   433     private static ZoneId ofWithPrefix(String zoneId, int prefixLength, boolean checkAvailable) {
   366         if (zoneId.length() == prefixLength ||
   434         String prefix = zoneId.substring(0, prefixLength);
   367                 (zoneId.length() == prefixLength + 1 && zoneId.charAt(prefixLength) == '0')) {
   435         if (zoneId.length() == prefixLength) {
   368             return ZoneOffset.UTC;
   436             return ZoneRegion.ofPrefixedOffset(prefix, ZoneOffset.UTC);
   369         }
   437         }
   370         if (zoneId.charAt(prefixLength) == '+' || zoneId.charAt(prefixLength) == '-') {
   438         if (zoneId.charAt(prefixLength) != '+' && zoneId.charAt(prefixLength) != '-') {
   371             try {
   439             return ZoneRegion.ofId(zoneId, checkAvailable);  // drop through to ZoneRulesProvider
   372                 return ZoneOffset.of(zoneId.substring(prefixLength));
   440         }
   373             } catch (DateTimeException ex) {
   441         try {
   374                 throw new DateTimeException("Invalid ID for offset-based ZoneId: " + zoneId, ex);
   442             ZoneOffset offset = ZoneOffset.of(zoneId.substring(prefixLength));
       
   443             if (offset == ZoneOffset.UTC) {
       
   444                 return ZoneRegion.ofPrefixedOffset(prefix, offset);
   375             }
   445             }
   376         }
   446             return ZoneRegion.ofPrefixedOffset(prefix + offset.toString(), offset);
   377         throw new DateTimeException("Invalid ID for offset-based ZoneId: " + zoneId);
   447         } catch (DateTimeException ex) {
       
   448             throw new DateTimeException("Invalid ID for offset-based ZoneId: " + zoneId, ex);
       
   449         }
   378     }
   450     }
   379 
   451 
   380     //-----------------------------------------------------------------------
   452     //-----------------------------------------------------------------------
   381     /**
   453     /**
   382      * Obtains an instance of {@code ZoneId} from a temporal object.
   454      * Obtains an instance of {@code ZoneId} from a temporal object.
   387      * <p>
   459      * <p>
   388      * A {@code TemporalAccessor} represents some form of date and time information.
   460      * A {@code TemporalAccessor} represents some form of date and time information.
   389      * This factory converts the arbitrary temporal object to an instance of {@code ZoneId}.
   461      * This factory converts the arbitrary temporal object to an instance of {@code ZoneId}.
   390      * <p>
   462      * <p>
   391      * The conversion will try to obtain the zone in a way that favours region-based
   463      * The conversion will try to obtain the zone in a way that favours region-based
   392      * zones over offset-based zones using {@link Queries#zone()}.
   464      * zones over offset-based zones using {@link TemporalQuery#zone()}.
   393      * <p>
   465      * <p>
   394      * This method matches the signature of the functional interface {@link TemporalQuery}
   466      * This method matches the signature of the functional interface {@link TemporalQuery}
   395      * allowing it to be used in queries via method reference, {@code ZoneId::from}.
   467      * allowing it to be used in queries via method reference, {@code ZoneId::from}.
   396      *
   468      *
   397      * @param temporal  the temporal object to convert, not null
   469      * @param temporal  the temporal object to convert, not null
   398      * @return the zone ID, not null
   470      * @return the zone ID, not null
   399      * @throws DateTimeException if unable to convert to a {@code ZoneId}
   471      * @throws DateTimeException if unable to convert to a {@code ZoneId}
   400      */
   472      */
   401     public static ZoneId from(TemporalAccessor temporal) {
   473     public static ZoneId from(TemporalAccessor temporal) {
   402         ZoneId obj = temporal.query(Queries.zone());
   474         ZoneId obj = temporal.query(TemporalQuery.zone());
   403         if (obj == null) {
   475         if (obj == null) {
   404             throw new DateTimeException("Unable to obtain ZoneId from TemporalAccessor: " + temporal.getClass());
   476             throw new DateTimeException("Unable to obtain ZoneId from TemporalAccessor: " + temporal.getClass());
   405         }
   477         }
   406         return obj;
   478         return obj;
   407     }
   479     }
   427      */
   499      */
   428     public abstract String getId();
   500     public abstract String getId();
   429 
   501 
   430     //-----------------------------------------------------------------------
   502     //-----------------------------------------------------------------------
   431     /**
   503     /**
   432      * Gets the time-zone rules for this ID allowing calculations to be performed.
       
   433      * <p>
       
   434      * The rules provide the functionality associated with a time-zone,
       
   435      * such as finding the offset for a given instant or local date-time.
       
   436      * <p>
       
   437      * A time-zone can be invalid if it is deserialized in a JVM which does not
       
   438      * have the same rules loaded as the JVM that stored it. In this case, calling
       
   439      * this method will throw an exception.
       
   440      * <p>
       
   441      * The rules are supplied by {@link ZoneRulesProvider}. An advanced provider may
       
   442      * support dynamic updates to the rules without restarting the JVM.
       
   443      * If so, then the result of this method may change over time.
       
   444      * Each individual call will be still remain thread-safe.
       
   445      * <p>
       
   446      * {@link ZoneOffset} will always return a set of rules where the offset never changes.
       
   447      *
       
   448      * @return the rules, not null
       
   449      * @throws ZoneRulesException if no rules are available for this ID
       
   450      */
       
   451     public abstract ZoneRules getRules();
       
   452 
       
   453     //-----------------------------------------------------------------------
       
   454     /**
       
   455      * Gets the textual representation of the zone, such as 'British Time' or
   504      * Gets the textual representation of the zone, such as 'British Time' or
   456      * '+02:00'.
   505      * '+02:00'.
   457      * <p>
   506      * <p>
   458      * This returns the textual name used to identify the time-zone ID,
   507      * This returns the textual name used to identify the time-zone ID,
   459      * suitable for presentation to the user.
   508      * suitable for presentation to the user.
   464      * @param style  the length of the text required, not null
   513      * @param style  the length of the text required, not null
   465      * @param locale  the locale to use, not null
   514      * @param locale  the locale to use, not null
   466      * @return the text value of the zone, not null
   515      * @return the text value of the zone, not null
   467      */
   516      */
   468     public String getDisplayName(TextStyle style, Locale locale) {
   517     public String getDisplayName(TextStyle style, Locale locale) {
   469         return new DateTimeFormatterBuilder().appendZoneText(style).toFormatter(locale).format(new TemporalAccessor() {
   518         return new DateTimeFormatterBuilder().appendZoneText(style).toFormatter(locale).format(toTemporal());
       
   519     }
       
   520 
       
   521     /**
       
   522      * Converts this zone to a {@code TemporalAccessor}.
       
   523      * <p>
       
   524      * A {@code ZoneId} can be fully represented as a {@code TemporalAccessor}.
       
   525      * However, the interface is not implemented by this class as most of the
       
   526      * methods on the interface have no meaning to {@code ZoneId}.
       
   527      * <p>
       
   528      * The returned temporal has no supported fields, with the query method
       
   529      * supporting the return of the zone using {@link TemporalQuery#zoneId()}.
       
   530      *
       
   531      * @return a temporal equivalent to this zone, not null
       
   532      */
       
   533     private TemporalAccessor toTemporal() {
       
   534         return new TemporalAccessor() {
   470             @Override
   535             @Override
   471             public boolean isSupported(TemporalField field) {
   536             public boolean isSupported(TemporalField field) {
   472                 return false;
   537                 return false;
   473             }
   538             }
   474             @Override
   539             @Override
   475             public long getLong(TemporalField field) {
   540             public long getLong(TemporalField field) {
   476                 throw new DateTimeException("Unsupported field: " + field);
   541                 throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
   477             }
   542             }
   478             @SuppressWarnings("unchecked")
   543             @SuppressWarnings("unchecked")
   479             @Override
   544             @Override
   480             public <R> R query(TemporalQuery<R> query) {
   545             public <R> R query(TemporalQuery<R> query) {
   481                 if (query == Queries.zoneId()) {
   546                 if (query == TemporalQuery.zoneId()) {
   482                     return (R) ZoneId.this;
   547                     return (R) ZoneId.this;
   483                 }
   548                 }
   484                 return TemporalAccessor.super.query(query);
   549                 return TemporalAccessor.super.query(query);
   485             }
   550             }
   486         });
   551         };
       
   552     }
       
   553 
       
   554     //-----------------------------------------------------------------------
       
   555     /**
       
   556      * Gets the time-zone rules for this ID allowing calculations to be performed.
       
   557      * <p>
       
   558      * The rules provide the functionality associated with a time-zone,
       
   559      * such as finding the offset for a given instant or local date-time.
       
   560      * <p>
       
   561      * A time-zone can be invalid if it is deserialized in a Java Runtime which
       
   562      * does not have the same rules loaded as the Java Runtime that stored it.
       
   563      * In this case, calling this method will throw a {@code ZoneRulesException}.
       
   564      * <p>
       
   565      * The rules are supplied by {@link ZoneRulesProvider}. An advanced provider may
       
   566      * support dynamic updates to the rules without restarting the Java Runtime.
       
   567      * If so, then the result of this method may change over time.
       
   568      * Each individual call will be still remain thread-safe.
       
   569      * <p>
       
   570      * {@link ZoneOffset} will always return a set of rules where the offset never changes.
       
   571      *
       
   572      * @return the rules, not null
       
   573      * @throws ZoneRulesException if no rules are available for this ID
       
   574      */
       
   575     public abstract ZoneRules getRules();
       
   576 
       
   577     /**
       
   578      * Normalizes the time-zone ID, returning a {@code ZoneOffset} where possible.
       
   579      * <p>
       
   580      * The returns a normalized {@code ZoneId} that can be used in place of this ID.
       
   581      * The result will have {@code ZoneRules} equivalent to those returned by this object,
       
   582      * however the ID returned by {@code getId()} may be different.
       
   583      * <p>
       
   584      * The normalization checks if the rules of this {@code ZoneId} have a fixed offset.
       
   585      * If they do, then the {@code ZoneOffset} equal to that offset is returned.
       
   586      * Otherwise {@code this} is returned.
       
   587      *
       
   588      * @return the time-zone unique ID, not null
       
   589      */
       
   590     public ZoneId normalized() {
       
   591         try {
       
   592             ZoneRules rules = getRules();
       
   593             if (rules.isFixedOffset()) {
       
   594                 return rules.getOffset(Instant.EPOCH);
       
   595             }
       
   596         } catch (ZoneRulesException ex) {
       
   597             // invalid ZoneRegion is not important to this method
       
   598         }
       
   599         return this;
   487     }
   600     }
   488 
   601 
   489     //-----------------------------------------------------------------------
   602     //-----------------------------------------------------------------------
   490     /**
   603     /**
   491      * Checks if this time-zone ID is equal to another time-zone ID.
   604      * Checks if this time-zone ID is equal to another time-zone ID.
   534      * <a href="../../serialized-form.html#java.time.Ser">dedicated serialized form</a>.
   647      * <a href="../../serialized-form.html#java.time.Ser">dedicated serialized form</a>.
   535      * <pre>
   648      * <pre>
   536      *  out.writeByte(7);  // identifies this as a ZoneId (not ZoneOffset)
   649      *  out.writeByte(7);  // identifies this as a ZoneId (not ZoneOffset)
   537      *  out.writeUTF(zoneId);
   650      *  out.writeUTF(zoneId);
   538      * </pre>
   651      * </pre>
       
   652      * <p>
       
   653      * When read back in, the {@code ZoneId} will be created as though using
       
   654      * {@link #of(String)}, but without any exception in the case where the
       
   655      * ID has a valid format, but is not in the known set of region-based IDs.
   539      *
   656      *
   540      * @return the instance of {@code Ser}, not null
   657      * @return the instance of {@code Ser}, not null
   541      */
   658      */
   542     // this is here for serialization Javadoc
   659     // this is here for serialization Javadoc
   543     private Object writeReplace() {
   660     private Object writeReplace() {