• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved.
4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5  *
6  * This code is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License version 2 only, as
8  * published by the Free Software Foundation.  Oracle designates this
9  * particular file as subject to the "Classpath" exception as provided
10  * by Oracle in the LICENSE file that accompanied this code.
11  *
12  * This code is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15  * version 2 for more details (a copy is included in the LICENSE file that
16  * accompanied this code).
17  *
18  * You should have received a copy of the GNU General Public License version
19  * 2 along with this work; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21  *
22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23  * or visit www.oracle.com if you need additional information or have any
24  * questions.
25  */
26 
27 /*
28  * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved
29  * (C) Copyright IBM Corp. 1996 - All Rights Reserved
30  *
31  *   The original version of this source code and documentation is copyrighted
32  * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
33  * materials are provided under terms of a License Agreement between Taligent
34  * and Sun. This technology is protected by multiple US and International
35  * patents. This notice and attribution to Taligent may not be removed.
36  *   Taligent is a registered trademark of Taligent, Inc.
37  *
38  */
39 
40 package java.util;
41 
42 import android.icu.text.TimeZoneNames;
43 import com.android.i18n.timezone.ZoneInfoData;
44 import com.android.i18n.timezone.ZoneInfoDb;
45 import com.android.icu.util.ExtendedTimeZone;
46 
47 import java.io.IOException;
48 import java.io.Serializable;
49 import java.time.ZoneId;
50 import java.util.function.Supplier;
51 import java.util.regex.Matcher;
52 import java.util.regex.Pattern;
53 import libcore.io.IoUtils;
54 import libcore.util.ZoneInfo;
55 import java.time.ZoneOffset;
56 
57 import dalvik.system.RuntimeHooks;
58 
59 /**
60  * {@code TimeZone} represents a time zone offset, and also figures out daylight
61  * savings.
62  *
63  * <p>
64  * Typically, you get a {@code TimeZone} using {@code getDefault}
65  * which creates a {@code TimeZone} based on the time zone where the program
66  * is running. For example, for a program running in Japan, {@code getDefault}
67  * creates a {@code TimeZone} object based on Japanese Standard Time.
68  *
69  * <p>
70  * You can also get a {@code TimeZone} using {@code getTimeZone}
71  * along with a time zone ID. For instance, the time zone ID for the
72  * U.S. Pacific Time zone is "America/Los_Angeles". So, you can get a
73  * U.S. Pacific Time {@code TimeZone} object with:
74  * <blockquote>
75  * {@snippet lang=java :
76  * TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
77  * }
78  * </blockquote>
79  * You can use the {@code getAvailableIDs} method to iterate through
80  * all the supported time zone IDs. You can then choose a
81  * supported ID to get a {@code TimeZone}.
82  * If the time zone you want is not represented by one of the
83  * supported IDs, then a custom time zone ID can be specified to
84  * produce a TimeZone. The syntax of a custom time zone ID is:
85  *
86  * <blockquote><pre>
87  * <a id="CustomID"><i>CustomID:</i></a>
88  *         {@code GMT} <i>Sign</i> <i>Hours</i> {@code :} <i>Minutes</i> {@code :} <i>Seconds</i>
89  *         {@code GMT} <i>Sign</i> <i>Hours</i> {@code :} <i>Minutes</i>
90  *         {@code GMT} <i>Sign</i> <i>Hours</i> <i>Minutes</i>
91  *         {@code GMT} <i>Sign</i> <i>Hours</i>
92  * <i>Sign:</i> one of
93  *         {@code + -}
94  * <i>Hours:</i>
95  *         <i>Digit</i>
96  *         <i>Digit</i> <i>Digit</i>
97  * <i>Minutes:</i>
98  *         <i>Digit</i> <i>Digit</i>
99  * <i>Seconds:</i>
100  *         <i>Digit</i> <i>Digit</i>
101  * <i>Digit:</i> one of
102  *         {@code 0 1 2 3 4 5 6 7 8 9}
103  * </pre></blockquote>
104  *
105  * <i>Hours</i> must be between 0 to 23 and <i>Minutes</i>/<i>Seconds</i> must be
106  * between 00 to 59.  For example, "GMT+10" and "GMT+0010" mean ten
107  * hours and ten minutes ahead of GMT, respectively.
108  * <p>
109  * The format is locale independent and digits must be taken from the
110  * Basic Latin block of the Unicode standard. No daylight saving time
111  * transition schedule can be specified with a custom time zone ID. If
112  * the specified string doesn't match the syntax, {@code "GMT"}
113  * is used.
114  * <p>
115  * When creating a {@code TimeZone}, the specified custom time
116  * zone ID is normalized in the following syntax:
117  * <blockquote><pre>
118  * <a id="NormalizedCustomID"><i>NormalizedCustomID:</i></a>
119  *         {@code GMT} <i>Sign</i> <i>TwoDigitHours</i> {@code :} <i>Minutes</i> [<i>ColonSeconds</i>]
120  * <i>Sign:</i> one of
121  *         {@code + -}
122  * <i>TwoDigitHours:</i>
123  *         <i>Digit</i> <i>Digit</i>
124  * <i>Minutes:</i>
125  *         <i>Digit</i> <i>Digit</i>
126  * <i>ColonSeconds:</i>
127  *         {@code :} <i>Digit</i> <i>Digit</i>
128  * <i>Digit:</i> one of
129  *         {@code 0 1 2 3 4 5 6 7 8 9}
130  * </pre></blockquote>
131  * For example, TimeZone.getTimeZone("GMT-8").getID() returns "GMT-08:00".
132  * <i>ColonSeconds</i> part only appears if the seconds value is non-zero.
133  *
134  * <h2>Three-letter time zone IDs</h2>
135  *
136  * For compatibility with JDK 1.1.x, some other three-letter time zone IDs
137  * (such as "PST", "CTT", "AST") are also supported. However, <strong>their
138  * use is deprecated</strong> because the same abbreviation is often used
139  * for multiple time zones (for example, "CST" could be U.S. "Central Standard
140  * Time" and "China Standard Time"), and the Java platform can then only
141  * recognize one of them.
142  *
143  *
144  * @see          Calendar
145  * @see          GregorianCalendar
146  * @see          SimpleTimeZone
147  * @author       Mark Davis, David Goldsmith, Chen-Lieh Huang, Alan Liu
148  * @since        1.1
149  */
150 public abstract class TimeZone implements Serializable, Cloneable {
151     /**
152      * Sole constructor.  (For invocation by subclass constructors, typically
153      * implicit.)
154      */
TimeZone()155     public TimeZone() {
156     }
157 
158     /**
159      * A style specifier for {@code getDisplayName()} indicating
160      * a short name, such as "PST."
161      * @see #LONG
162      * @since 1.2
163      */
164     public static final int SHORT = 0;
165 
166     /**
167      * A style specifier for {@code getDisplayName()} indicating
168      * a long name, such as "Pacific Standard Time."
169      * @see #SHORT
170      * @since 1.2
171      */
172     public static final int LONG  = 1;
173 
174     // Android-changed: Use a preload holder to allow compile-time initialization of TimeZone and
175     // dependents.
176     private static class NoImagePreloadHolder {
177         // Custom time zone ID can only have one of the following forms:
178         // 1. GMT Sign Hours
179         // 2. GMT Sign Hours Minutes
180         // 3. GMT Sign Hours : Minutes
181         // 4. GMT Sign Hours : Minutes : Seconds
182         // The second capturing group is either:
183         // * Absent (then input matches the 1st form).
184         // * Consists of only 2 digits (matches the 2nd form).
185         // * Consists of colon and 2 digits (matches the 3rd form).
186         // * Or has the 4th form: colon followed by 2 digits, followed by colon and 2 digits.
187         public static final Pattern CUSTOM_ZONE_ID_PATTERN =
188                 Pattern.compile("^GMT[-+](\\d{1,2})((\\d\\d)|:((\\d\\d)(:(\\d\\d))?))?");
189     }
190 
191     // Proclaim serialization compatibility with JDK 1.1
192     @java.io.Serial
193     static final long serialVersionUID = 3581463369166924961L;
194 
195     // Android-changed: common timezone instances.
196     private static final TimeZone GMT = new SimpleTimeZone(0, "GMT");
197     private static final TimeZone UTC = new SimpleTimeZone(0, "UTC");
198 
199     /**
200      * Gets the time zone offset, for current date, modified in case of
201      * daylight savings. This is the offset to add to UTC to get local time.
202      * <p>
203      * This method returns a historically correct offset if an
204      * underlying {@code TimeZone} implementation subclass
205      * supports historical Daylight Saving Time schedule and GMT
206      * offset changes.
207      *
208      * @param era the era of the given date.
209      * @param year the year in the given date.
210      * @param month the month in the given date.
211      * Month is 0-based. e.g., 0 for January.
212      * @param day the day-in-month of the given date.
213      * @param dayOfWeek the day-of-week of the given date.
214      * @param milliseconds the milliseconds in day in <em>standard</em>
215      * local time.
216      *
217      * @return the offset in milliseconds to add to GMT to get local time.
218      *
219      * @see Calendar#ZONE_OFFSET
220      * @see Calendar#DST_OFFSET
221      */
getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds)222     public abstract int getOffset(int era, int year, int month, int day,
223                                   int dayOfWeek, int milliseconds);
224 
225     /**
226      * Returns the offset of this time zone from UTC at the specified
227      * date. If Daylight Saving Time is in effect at the specified
228      * date, the offset value is adjusted with the amount of daylight
229      * saving.
230      * <p>
231      * This method returns a historically correct offset value if an
232      * underlying TimeZone implementation subclass supports historical
233      * Daylight Saving Time schedule and GMT offset changes.
234      *
235      * @param date the date represented in milliseconds since January 1, 1970 00:00:00 GMT
236      * @return the amount of time in milliseconds to add to UTC to get local time.
237      *
238      * @see Calendar#ZONE_OFFSET
239      * @see Calendar#DST_OFFSET
240      * @since 1.4
241      */
getOffset(long date)242     public int getOffset(long date) {
243         if (inDaylightTime(new Date(date))) {
244             return getRawOffset() + getDSTSavings();
245         }
246         return getRawOffset();
247     }
248 
249     /**
250      * Gets the raw GMT offset and the amount of daylight saving of this
251      * time zone at the given time.
252      * @param date the milliseconds (since January 1, 1970,
253      * 00:00:00.000 GMT) at which the time zone offset and daylight
254      * saving amount are found
255      * @param offsets an array of int where the raw GMT offset
256      * (offset[0]) and daylight saving amount (offset[1]) are stored,
257      * or null if those values are not needed. The method assumes that
258      * the length of the given array is two or larger.
259      * @return the total amount of the raw GMT offset and daylight
260      * saving at the specified date.
261      *
262      * @see Calendar#ZONE_OFFSET
263      * @see Calendar#DST_OFFSET
264      */
getOffsets(long date, int[] offsets)265     int getOffsets(long date, int[] offsets) {
266         int rawoffset = getRawOffset();
267         int dstoffset = 0;
268         if (inDaylightTime(new Date(date))) {
269             dstoffset = getDSTSavings();
270         }
271         if (offsets != null) {
272             offsets[0] = rawoffset;
273             offsets[1] = dstoffset;
274         }
275         return rawoffset + dstoffset;
276     }
277 
278     /**
279      * Sets the base time zone offset to GMT.
280      * This is the offset to add to UTC to get local time.
281      * <p>
282      * If an underlying {@code TimeZone} implementation subclass
283      * supports historical GMT offset changes, the specified GMT
284      * offset is set as the latest GMT offset and the difference from
285      * the known latest GMT offset value is used to adjust all
286      * historical GMT offset values.
287      *
288      * @param offsetMillis the given base time zone offset to GMT.
289      */
setRawOffset(int offsetMillis)290     public abstract void setRawOffset(int offsetMillis);
291 
292     /**
293      * Returns the amount of time in milliseconds to add to UTC to get
294      * standard time in this time zone. Because this value is not
295      * affected by daylight saving time, it is called <I>raw
296      * offset</I>.
297      * <p>
298      * If an underlying {@code TimeZone} implementation subclass
299      * supports historical GMT offset changes, the method returns the
300      * raw offset value of the current date. In Honolulu, for example,
301      * its raw offset changed from GMT-10:30 to GMT-10:00 in 1947, and
302      * this method always returns -36000000 milliseconds (i.e., -10
303      * hours).
304      *
305      * @return the amount of raw offset time in milliseconds to add to UTC.
306      * @see Calendar#ZONE_OFFSET
307      */
getRawOffset()308     public abstract int getRawOffset();
309 
310     /**
311      * Gets the ID of this time zone.
312      * @return the ID of this time zone.
313      */
getID()314     public String getID()
315     {
316         return ID;
317     }
318 
319     /**
320      * Sets the time zone ID. This does not change any other data in
321      * the time zone object.
322      * @implSpec The default implementation throws a
323      * {@code NullPointerException} if {@code ID} is {@code null}
324      * @param ID the new time zone ID.
325      * @throws NullPointerException This method may throw a
326      * {@code NullPointerException} if {@code ID} is {@code null}
327      */
setID(String ID)328     public void setID(String ID)
329     {
330         if (ID == null) {
331             throw new NullPointerException();
332         }
333         this.ID = ID;
334     }
335 
336     /**
337      * Returns a long standard time name of this {@code TimeZone} suitable for
338      * presentation to the user in the default locale.
339      *
340      * <p>This method is equivalent to:
341      * <blockquote>
342      * {@snippet lang=java :
343      * // @link substring="LONG" target="#LONG" :
344      * getDisplayName(false, LONG,
345      *                // @link substring="Locale.Category.DISPLAY" target="Locale.Category#DISPLAY" :
346      *                Locale.getDefault(Locale.Category.DISPLAY));
347      * }
348      * </blockquote>
349      *
350      * @return the human-readable name of this time zone in the default locale.
351      * @since 1.2
352      * @see #getDisplayName(boolean, int, Locale)
353      * @see Locale#getDefault(Locale.Category)
354      * @see Locale.Category
355      */
getDisplayName()356     public final String getDisplayName() {
357         return getDisplayName(false, LONG,
358                               Locale.getDefault(Locale.Category.DISPLAY));
359     }
360 
361     /**
362      * Returns a long standard time name of this {@code TimeZone} suitable for
363      * presentation to the user in the specified {@code locale}.
364      *
365      * <p>This method is equivalent to:
366      * <blockquote>
367      * {@snippet lang=java :
368      * // @link substring="LONG" target="#LONG" :
369      * getDisplayName(false, LONG, locale);
370      * }
371      * </blockquote>
372      *
373      * @param locale the locale in which to supply the display name.
374      * @return the human-readable name of this time zone in the given locale.
375      * @throws    NullPointerException if {@code locale} is {@code null}.
376      * @since 1.2
377      * @see #getDisplayName(boolean, int, Locale)
378      */
getDisplayName(Locale locale)379     public final String getDisplayName(Locale locale) {
380         return getDisplayName(false, LONG, locale);
381     }
382 
383     /**
384      * Returns a name in the specified {@code style} of this {@code TimeZone}
385      * suitable for presentation to the user in the default locale. If the
386      * specified {@code daylight} is {@code true}, a Daylight Saving Time name
387      * is returned (even if this {@code TimeZone} doesn't observe Daylight Saving
388      * Time). Otherwise, a Standard Time name is returned.
389      *
390      * <p>This method is equivalent to:
391      * <blockquote>
392      * {@snippet lang=java :
393      * getDisplayName(daylight, style,
394      *                // @link substring="Locale.Category.DISPLAY" target="Locale.Category#DISPLAY" :
395      *                Locale.getDefault(Locale.Category.DISPLAY));
396      * }
397      * </blockquote>
398      *
399      * @param daylight {@code true} specifying a Daylight Saving Time name, or
400      *                 {@code false} specifying a Standard Time name
401      * @param style either {@link #LONG} or {@link #SHORT}
402      * @return the human-readable name of this time zone in the default locale.
403      * @throws    IllegalArgumentException if {@code style} is invalid.
404      * @since 1.2
405      * @see #getDisplayName(boolean, int, Locale)
406      * @see Locale#getDefault(Locale.Category)
407      * @see Locale.Category
408      * @see java.text.DateFormatSymbols#getZoneStrings()
409      */
getDisplayName(boolean daylight, int style)410     public final String getDisplayName(boolean daylight, int style) {
411         return getDisplayName(daylight, style,
412                               Locale.getDefault(Locale.Category.DISPLAY));
413     }
414 
415     // Android-changed: ResourceBundle section removed.
416     /**
417      * Returns the {@link #SHORT short} or {@link #LONG long} name of this time
418      * zone with either standard or daylight time, as written in {@code locale}.
419      * If the name is not available, the result is in the format
420      * {@code GMT[+-]hh:mm}.
421      *
422      * @implSpec The default implementation throws an
423      * {@code IllegalArgumentException} if {@code style} is invalid or a
424      * {@code NullPointerException} if {@code ID} is {@code null}.
425      * @param daylightTime {@code true} specifying a Daylight Saving Time name, or
426      *                 {@code false} specifying a Standard Time name
427      * @param style either {@link #LONG} or {@link #SHORT}
428      * @param locale   the locale in which to supply the display name.
429      * @return the human-readable name of this time zone in the given locale.
430      * @throws IllegalArgumentException This method may throw an
431      * {@code IllegalArgumentException} if {@code style} is invalid.
432      * @throws NullPointerException This method may throw a
433      * {@code NullPointerException} if {@code ID} is {@code null}
434      * @since 1.2
435      * @see java.text.DateFormatSymbols#getZoneStrings()
436      */
437     // Android-changed: daylight -> daylightTime.
getDisplayName(boolean daylightTime, int style, Locale locale)438     public String getDisplayName(boolean daylightTime, int style, Locale locale) {
439         // BEGIN Android-changed: implement using android.icu.text.TimeZoneNames
440         TimeZoneNames.NameType nameType;
441         switch (style) {
442             case SHORT:
443                 nameType = daylightTime
444                         ? TimeZoneNames.NameType.SHORT_DAYLIGHT
445                         : TimeZoneNames.NameType.SHORT_STANDARD;
446                 break;
447             case LONG:
448                 nameType = daylightTime
449                         ? TimeZoneNames.NameType.LONG_DAYLIGHT
450                         : TimeZoneNames.NameType.LONG_STANDARD;
451                 break;
452             default:
453                 throw new IllegalArgumentException("Illegal style: " + style);
454         }
455         String canonicalID = android.icu.util.TimeZone.getCanonicalID(getID());
456         if (canonicalID != null) {
457             TimeZoneNames names = TimeZoneNames.getInstance(locale);
458             long now = System.currentTimeMillis();
459             String displayName = names.getDisplayName(canonicalID, nameType, now);
460             if (displayName != null) {
461                 return displayName;
462             }
463         }
464 
465         // We get here if this is a custom timezone or ICU doesn't have name data for the specific
466         // style and locale.
467         int offsetMillis = getRawOffset();
468         if (daylightTime) {
469             offsetMillis += getDSTSavings();
470         }
471         return createGmtOffsetString(true /* includeGmt */, true /* includeMinuteSeparator */,
472                 offsetMillis);
473         // END Android-changed: implement using android.icu.text.TimeZoneNames
474     }
475 
476     // BEGIN Android-added: utility method to format an offset as a GMT offset string.
477     /**
478      * Returns a string representation of an offset from UTC.
479      *
480      * <p>The format is "[GMT](+|-)HH[:]MM". The output is not localized.
481      *
482      * @param includeGmt true to include "GMT", false to exclude
483      * @param includeMinuteSeparator true to include the separator between hours and minutes, false
484      *     to exclude.
485      * @param offsetMillis the offset from UTC
486      *
487      * @hide used internally by SimpleDateFormat
488      */
createGmtOffsetString(boolean includeGmt, boolean includeMinuteSeparator, int offsetMillis)489     public static String createGmtOffsetString(boolean includeGmt,
490             boolean includeMinuteSeparator, int offsetMillis) {
491         int offsetMinutes = offsetMillis / 60000;
492         char sign = '+';
493         if (offsetMinutes < 0) {
494             sign = '-';
495             offsetMinutes = -offsetMinutes;
496         }
497         StringBuilder builder = new StringBuilder(9);
498         if (includeGmt) {
499             builder.append("GMT");
500         }
501         builder.append(sign);
502         appendNumber(builder, 2, offsetMinutes / 60);
503         if (includeMinuteSeparator) {
504             builder.append(':');
505         }
506         appendNumber(builder, 2, offsetMinutes % 60);
507         return builder.toString();
508     }
509 
appendNumber(StringBuilder builder, int count, int value)510     private static void appendNumber(StringBuilder builder, int count, int value) {
511         String string = Integer.toString(value);
512         for (int i = 0; i < count - string.length(); i++) {
513             builder.append('0');
514         }
515         builder.append(string);
516     }
517     // END Android-added: utility method to format an offset as a GMT offset string.
518 
519     /**
520      * Returns the amount of time to be added to local standard time
521      * to get local wall clock time.
522      *
523      * <p>The default implementation returns 3600000 milliseconds
524      * (i.e., one hour) if a call to {@link #useDaylightTime()}
525      * returns {@code true}. Otherwise, 0 (zero) is returned.
526      *
527      * <p>If an underlying {@code TimeZone} implementation subclass
528      * supports historical and future Daylight Saving Time schedule
529      * changes, this method returns the amount of saving time of the
530      * last known Daylight Saving Time rule that can be a future
531      * prediction.
532      *
533      * <p>If the amount of saving time at any given time stamp is
534      * required, construct a {@link Calendar} with this {@code
535      * TimeZone} and the time stamp, and call {@link Calendar#get(int)
536      * Calendar.get}{@code (}{@link Calendar#DST_OFFSET}{@code )}.
537      *
538      * @return the amount of saving time in milliseconds
539      * @since 1.4
540      * @see #inDaylightTime(Date)
541      * @see #getOffset(long)
542      * @see #getOffset(int,int,int,int,int,int)
543      * @see Calendar#ZONE_OFFSET
544      */
getDSTSavings()545     public int getDSTSavings() {
546         if (useDaylightTime()) {
547             return 3600000;
548         }
549         return 0;
550     }
551 
552     /**
553      * Queries if this {@code TimeZone} uses Daylight Saving Time.
554      *
555      * <p>If an underlying {@code TimeZone} implementation subclass
556      * supports historical and future Daylight Saving Time schedule
557      * changes, this method refers to the last known Daylight Saving Time
558      * rule that can be a future prediction and may not be the same as
559      * the current rule. Consider calling {@link #observesDaylightTime()}
560      * if the current rule should also be taken into account.
561      *
562      * @return {@code true} if this {@code TimeZone} uses Daylight Saving Time,
563      *         {@code false}, otherwise.
564      * @see #inDaylightTime(Date)
565      * @see Calendar#DST_OFFSET
566      */
useDaylightTime()567     public abstract boolean useDaylightTime();
568 
569     /**
570      * Returns {@code true} if this {@code TimeZone} is currently in
571      * Daylight Saving Time, or if a transition from Standard Time to
572      * Daylight Saving Time occurs at any future time.
573      *
574      * <p>The default implementation returns {@code true} if
575      * {@code useDaylightTime()} or {@code inDaylightTime(new Date())}
576      * returns {@code true}.
577      *
578      * @return {@code true} if this {@code TimeZone} is currently in
579      * Daylight Saving Time, or if a transition from Standard Time to
580      * Daylight Saving Time occurs at any future time; {@code false}
581      * otherwise.
582      * @since 1.7
583      * @see #useDaylightTime()
584      * @see #inDaylightTime(Date)
585      * @see Calendar#DST_OFFSET
586      */
observesDaylightTime()587     public boolean observesDaylightTime() {
588         return useDaylightTime() || inDaylightTime(new Date());
589     }
590 
591     /**
592      * Queries if the given {@code date} is in Daylight Saving Time in
593      * this time zone.
594      *
595      * @param date the given Date.
596      * @return {@code true} if the given date is in Daylight Saving Time,
597      *         {@code false}, otherwise.
598      * @throws NullPointerException This method may throw a
599      * {@code NullPointerException} if {@code date} is {@code null}
600      */
inDaylightTime(Date date)601     public abstract boolean inDaylightTime(Date date);
602 
603     /**
604      * Gets the {@code TimeZone} for the given ID.
605      *
606      * @param id the ID for a <code>TimeZone</code>, either an abbreviation
607      * such as "PST", a full name such as "America/Los_Angeles", or a custom
608      * ID such as "GMT-8:00". Note that the support of abbreviations is
609      * for JDK 1.1.x compatibility only and full names should be used.
610      *
611      * @return the specified {@code TimeZone}, or the GMT zone if the given ID
612      * cannot be understood.
613      * @throws NullPointerException if {@code ID} is {@code null}
614      */
615     // Android-changed: param s/ID/id; use ZoneInfoDb instead of ZoneInfo class.
getTimeZone(String id)616     public static synchronized TimeZone getTimeZone(String id) {
617         if (id == null) {
618             throw new NullPointerException("id == null");
619         }
620 
621         // Special cases? These can clone an existing instance.
622         if (id.length() == 3) {
623             if (id.equals("GMT")) {
624                 return (TimeZone) GMT.clone();
625             }
626             if (id.equals("UTC")) {
627                 return (TimeZone) UTC.clone();
628             }
629         }
630 
631         // In the database?
632 
633         ZoneInfoData zoneInfoData = ZoneInfoDb.getInstance().makeZoneInfoData(id);
634         TimeZone zone = zoneInfoData == null ? null : ZoneInfo.createZoneInfo(zoneInfoData);
635 
636         // Custom time zone?
637         if (zone == null && id.length() > 3 && id.startsWith("GMT")) {
638             zone = getCustomTimeZone(id);
639         }
640 
641         // We never return null; on failure we return the equivalent of "GMT".
642         return (zone != null) ? zone : (TimeZone) GMT.clone();
643     }
644 
645     /**
646      * Gets the {@code TimeZone} for the given {@code zoneId}.
647      *
648      * @param zoneId a {@link ZoneId} from which the time zone ID is obtained
649      * @return the specified {@code TimeZone}, or the GMT zone if the given ID
650      *         cannot be understood.
651      * @throws NullPointerException if {@code zoneId} is {@code null}
652      * @since 1.8
653      */
getTimeZone(ZoneId zoneId)654     public static TimeZone getTimeZone(ZoneId zoneId) {
655         String tzid = zoneId.getId(); // throws an NPE if null
656         // BEGIN Android-removed: sun.util.calendar.ZoneInfo is not available.
657         /*
658         if (zoneId instanceof ZoneOffset zo) {
659             var totalMillis = zo.getTotalSeconds() * 1_000;
660             return new ZoneInfo(totalMillis == 0 ? "UTC" : GMT_ID + tzid, totalMillis);
661         } else if (tzid.startsWith("UT") && !tzid.equals("UTC")) {
662             tzid = tzid.replaceFirst("(UTC|UT)(.*)", "GMT$2");
663         }
664         */
665         // END Android-removed: sun.util.calendar.ZoneInfo is not available.
666         char c = tzid.charAt(0);
667         if (c == '+' || c == '-') {
668             tzid = "GMT" + tzid;
669         } else if (c == 'Z' && tzid.length() == 1) {
670             tzid = "UTC";
671         }
672         return getTimeZone(tzid);
673     }
674 
675     /**
676      * Converts this {@code TimeZone} object to a {@code ZoneId}.
677      *
678      * @return a {@code ZoneId} representing the same time zone as this
679      *         {@code TimeZone}
680      * @since 1.8
681      */
toZoneId()682     public ZoneId toZoneId() {
683         // Android-changed: don't support "old mapping"
684         return ZoneId.of(getID(), ZoneId.SHORT_IDS);
685     }
686 
687     /**
688      * Returns a new SimpleTimeZone for an ID of the form either "GMT[+|-]hh[[:]mm]" or
689      * GMT[+|-]hh:mm:SS, or null.
690      */
getCustomTimeZone(String id)691     private static TimeZone getCustomTimeZone(String id) {
692         Matcher m = NoImagePreloadHolder.CUSTOM_ZONE_ID_PATTERN.matcher(id);
693         if (!m.matches()) {
694             return null;
695         }
696 
697         int hour;
698         int minute = 0;
699         int second = 0;
700         try {
701             hour = Integer.parseInt(m.group(1));
702             if (m.group(3) != null) {
703                 minute = Integer.parseInt(m.group(3));
704             }
705             if (m.group(5) != null) {
706                 minute = Integer.parseInt(m.group(5));
707             }
708             if (m.group(7) != null) {
709                 second = Integer.parseInt(m.group(7));
710             }
711         } catch (NumberFormatException impossible) {
712             throw new AssertionError(impossible);
713         }
714 
715         if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59) {
716             return null;
717         }
718 
719         char sign = id.charAt(3);
720         int raw = (hour * 3600000) + (minute * 60000) + second * 1000;
721         if (sign == '-') {
722             raw = -raw;
723         }
724 
725         String cleanId;
726         if (second == 0) {
727             cleanId = String.format(Locale.ROOT, "GMT%c%02d:%02d", sign, hour, minute);
728         } else {
729             cleanId = String.format(Locale.ROOT, "GMT%c%02d:%02d:%02d", sign, hour, minute, second);
730         }
731 
732         return new SimpleTimeZone(raw, cleanId);
733     }
734 
735     /**
736      * Gets the available IDs according to the given time zone offset in milliseconds.
737      *
738      * @param rawOffset the given time zone GMT offset in milliseconds.
739      * @return an array of IDs, where the time zone for that ID has
740      * the specified GMT offset. For example, "America/Phoenix" and "America/Denver"
741      * both have GMT-07:00, but differ in daylight saving behavior.
742      * @see #getRawOffset()
743      */
getAvailableIDs(int rawOffset)744     public static synchronized String[] getAvailableIDs(int rawOffset) {
745         return ZoneInfoDb.getInstance().getAvailableIDs(rawOffset);
746     }
747 
748     /**
749      * Gets all the available IDs supported.
750      * @return an array of IDs.
751      */
getAvailableIDs()752     public static synchronized String[] getAvailableIDs() {
753         return ZoneInfoDb.getInstance().getAvailableIDs();
754     }
755 
756     /**
757      * Gets the platform defined TimeZone ID.
758      **/
getSystemTimeZoneID(String javaHome, String country)759     private static native String getSystemTimeZoneID(String javaHome,
760                                                      String country);
761 
762     /**
763      * Gets the custom time zone ID based on the GMT offset of the
764      * platform. (e.g., "GMT+08:00")
765      */
getSystemGMTOffsetID()766     private static native String getSystemGMTOffsetID();
767 
768     // Android-changed: removed section about the way default time zone ID is stored / fetched.
769     /**
770      * Gets the default <code>TimeZone</code> for this host.
771      * The source of the default <code>TimeZone</code>
772      * may vary with implementation.
773      * @return a default <code>TimeZone</code>.
774      * @see #setDefault(TimeZone)
775      */
getDefault()776     public static TimeZone getDefault() {
777         return (TimeZone) getDefaultRef().clone();
778     }
779 
780     /**
781      * Returns the reference to the default TimeZone object. This
782      * method doesn't create a clone.
783      */
getDefaultRef()784     static synchronized TimeZone getDefaultRef() {
785         if (defaultTimeZone == null) {
786             Supplier<String> tzGetter = RuntimeHooks.getTimeZoneIdSupplier();
787             String zoneName = (tzGetter != null) ? tzGetter.get() : null;
788             if (zoneName != null) {
789                 zoneName = zoneName.trim();
790             }
791             if (zoneName == null || zoneName.isEmpty()) {
792                 try {
793                     // On the host, we can find the configured timezone here.
794                     zoneName = IoUtils.readFileAsString("/etc/timezone");
795                 } catch (IOException ex) {
796                     // "vogar --mode device" can end up here.
797                     // TODO: give libcore access to Android system properties and read "persist.sys.timezone".
798                     zoneName = GMT_ID;
799                 }
800             }
801             defaultTimeZone = TimeZone.getTimeZone(zoneName);
802         }
803         return defaultTimeZone;
804     }
805 
806     // BEGIN Android-removed: different getDefault / setDefault implementation.
807     /*
808      * Returns the reference to the default TimeZone object. This
809      * method doesn't create a clone.
810      *
811     static TimeZone getDefaultRef() {
812         TimeZone defaultZone = defaultTimeZone;
813         if (defaultZone == null) {
814             // Need to initialize the default time zone.
815             defaultZone = setDefaultZone();
816             assert defaultZone != null;
817         }
818         // Don't clone here.
819         return defaultZone;
820     }
821 
822     private static synchronized TimeZone setDefaultZone() {
823         TimeZone tz;
824         // get the time zone ID from the system properties
825         Properties props = GetPropertyAction.privilegedGetProperties();
826         String zoneID = props.getProperty("user.timezone");
827 
828         // if the time zone ID is not set (yet), perform the
829         // platform to Java time zone ID mapping.
830         if (zoneID == null || zoneID.isEmpty()) {
831             zoneID = getSystemTimeZoneID(StaticProperty.javaHome());
832             if (zoneID == null) {
833                 zoneID = GMT_ID;
834             }
835         }
836 
837         // Get the time zone for zoneID. But not fall back to
838         // "GMT" here.
839         tz = getTimeZone(zoneID, false);
840 
841         if (tz == null) {
842             // If the given zone ID is unknown in Java, try to
843             // get the GMT-offset-based time zone ID,
844             // a.k.a. custom time zone ID (e.g., "GMT-08:00").
845             String gmtOffsetID = getSystemGMTOffsetID();
846             if (gmtOffsetID != null) {
847                 zoneID = gmtOffsetID;
848             }
849             tz = getTimeZone(zoneID, true);
850         }
851         assert tz != null;
852 
853         final String id = zoneID;
854         props.setProperty("user.timezone", id);
855 
856         defaultTimeZone = tz;
857         return tz;
858     }
859     */
860     // END Android-removed: different getDefault / setDefault implementation.
861 
862     /**
863      * Sets the {@code TimeZone} that is returned by the {@code getDefault}
864      * method. {@code zone} is cached. If {@code zone} is null, the cached
865      * default {@code TimeZone} is cleared. This method doesn't change the value
866      * of the {@code user.timezone} property.
867      *
868      * @param timeZone the new default {@code TimeZone}, or null
869      * @see #getDefault
870      */
871     // Android-changed: s/zone/timeZone, synchronized, removed mention of SecurityException
setDefault(TimeZone timeZone)872     public synchronized static void setDefault(TimeZone timeZone)
873     {
874         @SuppressWarnings("removal")
875         SecurityManager sm = System.getSecurityManager();
876         if (sm != null) {
877             sm.checkPermission(new PropertyPermission
878                     ("user.timezone", "write"));
879         }
880         // by saving a defensive clone and returning a clone in getDefault() too,
881         // the defaultTimeZone instance is isolated from user code which makes it
882         // effectively immutable. This is important to avoid races when the
883         // following is evaluated in ZoneId.systemDefault():
884         // TimeZone.getDefault().toZoneId().
885         defaultTimeZone = (timeZone == null) ? null : (TimeZone) timeZone.clone();
886         // Android-changed: notify ICU4J of changed default TimeZone.
887         ExtendedTimeZone.clearDefaultTimeZone();
888     }
889 
890     /**
891      * Returns true if this zone has the same rule and offset as another zone.
892      * That is, if this zone differs only in ID, if at all.  Returns false
893      * if the other zone is null.
894      * @param other the {@code TimeZone} object to be compared with
895      * @return true if the other zone is not null and is the same as this one,
896      * with the possible exception of the ID
897      * @since 1.2
898      */
hasSameRules(TimeZone other)899     public boolean hasSameRules(TimeZone other) {
900         return other != null && getRawOffset() == other.getRawOffset() &&
901             useDaylightTime() == other.useDaylightTime();
902     }
903 
904     /**
905      * Creates a copy of this {@code TimeZone}.
906      *
907      * @return a clone of this {@code TimeZone}
908      */
clone()909     public Object clone()
910     {
911         try {
912             return super.clone();
913         } catch (CloneNotSupportedException e) {
914             throw new InternalError(e);
915         }
916     }
917 
918     /**
919      * The null constant as a TimeZone.
920      */
921     static final TimeZone NO_TIMEZONE = null;
922 
923     // =======================privates===============================
924 
925     /**
926      * The string identifier of this {@code TimeZone}.  This is a
927      * programmatic identifier used internally to look up {@code TimeZone}
928      * objects from the system table and also to map them to their localized
929      * display names.  {@code ID} values are unique in the system
930      * table but may not be for dynamically created zones.
931      * @serial
932      */
933     private String           ID;
934     private static volatile TimeZone defaultTimeZone;
935 
936     static final String         GMT_ID        = "GMT";
937     // Android-removed: different setDefault / getDefault implementation.
938     /*
939     private static final int    GMT_ID_LENGTH = 3;
940 
941      *
942      * Parses a custom time zone identifier and returns a corresponding zone.
943      * This method doesn't support the RFC 822 time zone format. (e.g., +hhmm)
944      *
945      * @param id a string of the <a href="#CustomID">custom ID form</a>.
946      * @return a newly created TimeZone with the given offset and
947      * no daylight saving time, or null if the id cannot be parsed.
948      *
949     private static final TimeZone parseCustomTimeZone(String id) {
950         int length;
951 
952         // Error if the length of id isn't long enough or id doesn't
953         // start with "GMT".
954         if ((length = id.length()) < (GMT_ID_LENGTH + 2) ||
955             id.indexOf(GMT_ID) != 0) {
956             return null;
957         }
958 
959         ZoneInfo zi;
960 
961         // First, we try to find it in the cache with the given
962         // id. Even the id is not normalized, the returned ZoneInfo
963         // should have its normalized id.
964         zi = ZoneInfoFile.getZoneInfo(id);
965         if (zi != null) {
966             return zi;
967         }
968 
969         int index = GMT_ID_LENGTH;
970         boolean negative = false;
971         char c = id.charAt(index++);
972         if (c == '-') {
973             negative = true;
974         } else if (c != '+') {
975             return null;
976         }
977 
978         int hours = 0;
979         int minutes = 0;
980         int num = 0;
981         int countDelim = 0;
982         int len = 0;
983         while (index < length) {
984             c = id.charAt(index++);
985             if (c == ':') {
986                 if (countDelim > 1) {
987                     return null;
988                 }
989                 if (len == 0 || len > 2) {
990                     return null;
991                 }
992                 if (countDelim == 0) {
993                     hours = num;
994                 } else if (countDelim == 1){
995                     minutes = num;
996                 }
997                 countDelim++;
998                 num = 0;
999                 len = 0;
1000                 continue;
1001             }
1002             if (c < '0' || c > '9') {
1003                 return null;
1004             }
1005             num = num * 10 + (c - '0');
1006             len++;
1007         }
1008         if (index != length) {
1009             return null;
1010         }
1011         if (countDelim == 0) {
1012             if (len <= 2) {
1013                 hours = num;
1014                 minutes = 0;
1015                 num = 0;
1016             } else if (len <= 4) {
1017                 hours = num / 100;
1018                 minutes = num % 100;
1019                 num = 0;
1020             } else {
1021                 return null;
1022             }
1023         } else if (countDelim == 1){
1024             if (len == 2) {
1025                 minutes = num;
1026                 num = 0;
1027             } else {
1028                 return null;
1029             }
1030         } else {
1031             if (len != 2) {
1032                 return null;
1033             }
1034         }
1035         if (hours > 23 || minutes > 59 || num > 59) {
1036             return null;
1037         }
1038         int gmtOffset =  (hours * 3_600 + minutes * 60 + num) * 1_000;
1039 
1040         if (gmtOffset == 0) {
1041             zi = ZoneInfoFile.getZoneInfo(GMT_ID);
1042             if (negative) {
1043                 zi.setID("GMT-00:00");
1044             } else {
1045                 zi.setID("GMT+00:00");
1046             }
1047         } else {
1048             zi = ZoneInfoFile.getCustomTimeZone(id, negative ? -gmtOffset : gmtOffset);
1049         }
1050         return zi;
1051     }
1052     */
1053 }
1054