Home | History | Annotate | Download | only in util
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 package java.util;
     19 
     20 import java.io.IOException;
     21 import java.io.Serializable;
     22 import java.util.regex.Matcher;
     23 import java.util.regex.Pattern;
     24 import libcore.icu.TimeZoneNames;
     25 import libcore.io.IoUtils;
     26 import libcore.util.ZoneInfoDB;
     27 
     28 // TODO: repackage this class, used by frameworks/base.
     29 import org.apache.harmony.luni.internal.util.TimezoneGetter;
     30 
     31 /**
     32  * {@code TimeZone} represents a time zone, primarily used for configuring a {@link Calendar} or
     33  * {@link java.text.SimpleDateFormat} instance.
     34  *
     35  * <p>Most applications will use {@link #getDefault} which returns a {@code TimeZone} based on
     36  * the time zone where the program is running.
     37  *
     38  * <p>You can also get a specific {@code TimeZone} {@link #getTimeZone by Olson ID}.
     39  *
     40  * <p>It is highly unlikely you'll ever want to use anything but the factory methods yourself.
     41  * Let classes like {@link Calendar} and {@link java.text.SimpleDateFormat} do the date
     42  * computations for you.
     43  *
     44  * <p>If you do need to do date computations manually, there are two common cases to take into
     45  * account:
     46  * <ul>
     47  * <li>Somewhere like California, where daylight time is used.
     48  * The {@link #useDaylightTime} method will always return true, and {@link #inDaylightTime}
     49  * must be used to determine whether or not daylight time applies to a given {@code Date}.
     50  * The {@link #getRawOffset} method will return a raw offset of (in this case) -8 hours from UTC,
     51  * which isn't usually very useful. More usefully, the {@link #getOffset} methods return the
     52  * actual offset from UTC <i>for a given point in time</i>; this is the raw offset plus (if the
     53  * point in time is {@link #inDaylightTime in daylight time}) the applicable
     54  * {@link #getDSTSavings DST savings} (usually, but not necessarily, 1 hour).
     55  * <li>Somewhere like Japan, where daylight time is not used.
     56  * The {@link #useDaylightTime} and {@link #inDaylightTime} methods both always return false,
     57  * and the raw and actual offsets will always be the same.
     58  * </ul>
     59  *
     60  * <p>Note the type returned by the factory methods {@link #getDefault} and {@link #getTimeZone} is
     61  * implementation dependent. This may introduce serialization incompatibility issues between
     62  * different implementations, or different versions of Android.
     63  *
     64  * @see Calendar
     65  * @see GregorianCalendar
     66  * @see java.text.SimpleDateFormat
     67  */
     68 public abstract class TimeZone implements Serializable, Cloneable {
     69     private static final long serialVersionUID = 3581463369166924961L;
     70 
     71     private static final Pattern CUSTOM_ZONE_ID_PATTERN = Pattern.compile("^GMT[-+](\\d{1,2})(:?(\\d\\d))?$");
     72 
     73     /**
     74      * The short display name style, such as {@code PDT}. Requests for this
     75      * style may yield GMT offsets like {@code GMT-08:00}.
     76      */
     77     public static final int SHORT = 0;
     78 
     79     /**
     80      * The long display name style, such as {@code Pacific Daylight Time}.
     81      * Requests for this style may yield GMT offsets like {@code GMT-08:00}.
     82      */
     83     public static final int LONG = 1;
     84 
     85     private static final TimeZone GMT = new SimpleTimeZone(0, "GMT");
     86     private static final TimeZone UTC = new SimpleTimeZone(0, "UTC");
     87 
     88     private static TimeZone defaultTimeZone;
     89 
     90     private String ID;
     91 
     92     public TimeZone() {}
     93 
     94     /**
     95      * Returns a new time zone with the same ID, raw offset, and daylight
     96      * savings time rules as this time zone.
     97      */
     98     @Override public Object clone() {
     99         try {
    100             return super.clone();
    101         } catch (CloneNotSupportedException e) {
    102             throw new AssertionError(e);
    103         }
    104     }
    105 
    106     /**
    107      * Returns the system's installed time zone IDs. Any of these IDs can be
    108      * passed to {@link #getTimeZone} to lookup the corresponding time zone
    109      * instance.
    110      */
    111     public static synchronized String[] getAvailableIDs() {
    112         return ZoneInfoDB.getInstance().getAvailableIDs();
    113     }
    114 
    115     /**
    116      * Returns the IDs of the time zones whose offset from UTC is {@code
    117      * offsetMillis}. Any of these IDs can be passed to {@link #getTimeZone} to
    118      * lookup the corresponding time zone instance.
    119      *
    120      * @return a possibly-empty array.
    121      */
    122     public static synchronized String[] getAvailableIDs(int offsetMillis) {
    123         return ZoneInfoDB.getInstance().getAvailableIDs(offsetMillis);
    124     }
    125 
    126     /**
    127      * Returns the user's preferred time zone. This may have been overridden for
    128      * this process with {@link #setDefault}.
    129      *
    130      * <p>Since the user's time zone changes dynamically, avoid caching this
    131      * value. Instead, use this method to look it up for each use.
    132      */
    133     public static synchronized TimeZone getDefault() {
    134         if (defaultTimeZone == null) {
    135             TimezoneGetter tzGetter = TimezoneGetter.getInstance();
    136             String zoneName = (tzGetter != null) ? tzGetter.getId() : null;
    137             if (zoneName != null) {
    138                 zoneName = zoneName.trim();
    139             }
    140             if (zoneName == null || zoneName.isEmpty()) {
    141                 try {
    142                     // On the host, we can find the configured timezone here.
    143                     zoneName = IoUtils.readFileAsString("/etc/timezone");
    144                 } catch (IOException ex) {
    145                     // "vogar --mode device" can end up here.
    146                     // TODO: give libcore access to Android system properties and read "persist.sys.timezone".
    147                     zoneName = "GMT";
    148                 }
    149             }
    150             defaultTimeZone = TimeZone.getTimeZone(zoneName);
    151         }
    152         return (TimeZone) defaultTimeZone.clone();
    153     }
    154 
    155     /**
    156      * Equivalent to {@code getDisplayName(false, TimeZone.LONG, Locale.getDefault())}.
    157      * <a href="../util/Locale.html#default_locale">Be wary of the default locale</a>.
    158      */
    159     public final String getDisplayName() {
    160         return getDisplayName(false, LONG, Locale.getDefault());
    161     }
    162 
    163     /**
    164      * Equivalent to {@code getDisplayName(false, TimeZone.LONG, locale)}.
    165      */
    166     public final String getDisplayName(Locale locale) {
    167         return getDisplayName(false, LONG, locale);
    168     }
    169 
    170     /**
    171      * Equivalent to {@code getDisplayName(daylightTime, style, Locale.getDefault())}.
    172      * <a href="../util/Locale.html#default_locale">Be wary of the default locale</a>.
    173      */
    174     public final String getDisplayName(boolean daylightTime, int style) {
    175         return getDisplayName(daylightTime, style, Locale.getDefault());
    176     }
    177 
    178     /**
    179      * Returns the {@link #SHORT short} or {@link #LONG long} name of this time
    180      * zone with either standard or daylight time, as written in {@code locale}.
    181      * If the name is not available, the result is in the format
    182      * {@code GMT[+-]hh:mm}.
    183      *
    184      * @param daylightTime true for daylight time, false for standard time.
    185      * @param style either {@link TimeZone#LONG} or {@link TimeZone#SHORT}.
    186      * @param locale the display locale.
    187      */
    188     public String getDisplayName(boolean daylightTime, int style, Locale locale) {
    189         if (style != SHORT && style != LONG) {
    190             throw new IllegalArgumentException("Bad style: " + style);
    191         }
    192 
    193         String[][] zoneStrings = TimeZoneNames.getZoneStrings(locale);
    194         String result = TimeZoneNames.getDisplayName(zoneStrings, getID(), daylightTime, style);
    195         if (result != null) {
    196             return result;
    197         }
    198 
    199         // If we get here, it's because icu4c has nothing for us. Most commonly, this is in the
    200         // case of short names. For Pacific/Fiji, for example, icu4c has nothing better to offer
    201         // than "GMT+12:00". Why do we re-do this work ourselves? Because we have up-to-date
    202         // time zone transition data, which icu4c _doesn't_ use --- it uses its own baked-in copy,
    203         // which only gets updated when we update icu4c. http://b/7955614 and http://b/8026776.
    204 
    205         // TODO: should we generate these once, in TimeZoneNames.getDisplayName? Revisit when we
    206         // upgrade to icu4c 50 and rewrite the underlying native code. See also the
    207         // "element[j] != null" check in SimpleDateFormat.parseTimeZone, and the extra work in
    208         // DateFormatSymbols.getZoneStrings.
    209         int offsetMillis = getRawOffset();
    210         if (daylightTime) {
    211             offsetMillis += getDSTSavings();
    212         }
    213         return createGmtOffsetString(true /* includeGmt */, true /* includeMinuteSeparator */,
    214                 offsetMillis);
    215     }
    216 
    217     /**
    218      * Returns a string representation of an offset from UTC.
    219      *
    220      * <p>The format is "[GMT](+|-)HH[:]MM". The output is not localized.
    221      *
    222      * @param includeGmt true to include "GMT", false to exclude
    223      * @param includeMinuteSeparator true to include the separator between hours and minutes, false
    224      *     to exclude.
    225      * @param offsetMillis the offset from UTC
    226      *
    227      * @hide used internally by SimpleDateFormat
    228      */
    229     public static String createGmtOffsetString(boolean includeGmt,
    230             boolean includeMinuteSeparator, int offsetMillis) {
    231         int offsetMinutes = offsetMillis / 60000;
    232         char sign = '+';
    233         if (offsetMinutes < 0) {
    234             sign = '-';
    235             offsetMinutes = -offsetMinutes;
    236         }
    237         StringBuilder builder = new StringBuilder(9);
    238         if (includeGmt) {
    239             builder.append("GMT");
    240         }
    241         builder.append(sign);
    242         appendNumber(builder, 2, offsetMinutes / 60);
    243         if (includeMinuteSeparator) {
    244             builder.append(':');
    245         }
    246         appendNumber(builder, 2, offsetMinutes % 60);
    247         return builder.toString();
    248     }
    249 
    250     private static void appendNumber(StringBuilder builder, int count, int value) {
    251         String string = Integer.toString(value);
    252         for (int i = 0; i < count - string.length(); i++) {
    253             builder.append('0');
    254         }
    255         builder.append(string);
    256     }
    257 
    258     /**
    259      * Returns the ID of this {@code TimeZone}, such as
    260      * {@code America/Los_Angeles}, {@code GMT-08:00} or {@code UTC}.
    261      */
    262     public String getID() {
    263         return ID;
    264     }
    265 
    266     /**
    267      * Returns the latest daylight savings in milliseconds for this time zone, relative
    268      * to this time zone's regular UTC offset (as returned by {@link #getRawOffset}).
    269      *
    270      * <p>This class returns {@code 3600000} (1 hour) for time zones
    271      * that use daylight savings time and {@code 0} for timezones that do not,
    272      * leaving it to subclasses to override this method for other daylight savings
    273      * offsets. (There are time zones, such as {@code Australia/Lord_Howe},
    274      * that use other values.)
    275      *
    276      * <p>Note that this method doesn't tell you whether or not to <i>apply</i> the
    277      * offset: you need to call {@code inDaylightTime} for the specific time
    278      * you're interested in. If this method returns a non-zero offset, that only
    279      * tells you that this {@code TimeZone} sometimes observes daylight savings.
    280      *
    281      * <p>Note also that this method doesn't necessarily return the value you need
    282      * to apply to the time you're working with. This value can and does change over
    283      * time for a given time zone.
    284      *
    285      * <p>It's highly unlikely that you should ever call this method. You
    286      * probably want {@link #getOffset} instead, which tells you the offset
    287      * for a specific point in time, and takes daylight savings into account for you.
    288      */
    289     public int getDSTSavings() {
    290         return useDaylightTime() ? 3600000 : 0;
    291     }
    292 
    293     /**
    294      * Returns the offset in milliseconds from UTC for this time zone at {@code
    295      * time}. The offset includes daylight savings time if the specified
    296      * date is within the daylight savings time period.
    297      *
    298      * @param time the date in milliseconds since January 1, 1970 00:00:00 UTC
    299      */
    300     public int getOffset(long time) {
    301         if (inDaylightTime(new Date(time))) {
    302             return getRawOffset() + getDSTSavings();
    303         }
    304         return getRawOffset();
    305     }
    306 
    307     /**
    308      * Returns this time zone's offset in milliseconds from UTC at the specified
    309      * date and time. The offset includes daylight savings time if the date
    310      * and time is within the daylight savings time period.
    311      *
    312      * <p>This method is intended to be used by {@link Calendar} to compute
    313      * {@link Calendar#DST_OFFSET} and {@link Calendar#ZONE_OFFSET}. Application
    314      * code should have no reason to call this method directly. Each parameter
    315      * is interpreted in the same way as the corresponding {@code Calendar}
    316      * field. Refer to {@link Calendar} for specific definitions of this
    317      * method's parameters.
    318      */
    319     public abstract int getOffset(int era, int year, int month, int day,
    320             int dayOfWeek, int timeOfDayMillis);
    321 
    322     /**
    323      * Returns the offset in milliseconds from UTC of this time zone's standard
    324      * time.
    325      */
    326     public abstract int getRawOffset();
    327 
    328     /**
    329      * Returns a {@code TimeZone} corresponding to the given {@code id}, or {@code GMT}
    330      * for unknown ids.
    331      *
    332      * <p>An ID can be an Olson name of the form <i>Area</i>/<i>Location</i>, such
    333      * as {@code America/Los_Angeles}. The {@link #getAvailableIDs} method returns
    334      * the supported names.
    335      *
    336      * <p>This method can also create a custom {@code TimeZone} given an ID with the following
    337      * syntax: {@code GMT[+|-]hh[[:]mm]}. For example, {@code "GMT+05:00"}, {@code "GMT+0500"},
    338      * {@code "GMT+5:00"}, {@code "GMT+500"}, {@code "GMT+05"}, and {@code "GMT+5"} all return
    339      * an object with a raw offset of +5 hours from UTC, and which does <i>not</i> use daylight
    340      * savings. These are rarely useful, because they don't correspond to time zones actually
    341      * in use by humans.
    342      *
    343      * <p>Other than the special cases "UTC" and "GMT" (which are synonymous in this context,
    344      * both corresponding to UTC), Android does not support the deprecated three-letter time
    345      * zone IDs used in Java 1.1.
    346      */
    347     public static synchronized TimeZone getTimeZone(String id) {
    348         if (id == null) {
    349             throw new NullPointerException("id == null");
    350         }
    351 
    352         // Special cases? These can clone an existing instance.
    353         if (id.length() == 3) {
    354             if (id.equals("GMT")) {
    355                 return (TimeZone) GMT.clone();
    356             }
    357             if (id.equals("UTC")) {
    358                 return (TimeZone) UTC.clone();
    359             }
    360         }
    361 
    362         // In the database?
    363         TimeZone zone = null;
    364         try {
    365             zone = ZoneInfoDB.getInstance().makeTimeZone(id);
    366         } catch (IOException ignored) {
    367         }
    368 
    369         // Custom time zone?
    370         if (zone == null && id.length() > 3 && id.startsWith("GMT")) {
    371             zone = getCustomTimeZone(id);
    372         }
    373 
    374         // We never return null; on failure we return the equivalent of "GMT".
    375         return (zone != null) ? zone : (TimeZone) GMT.clone();
    376     }
    377 
    378     /**
    379      * Returns a new SimpleTimeZone for an ID of the form "GMT[+|-]hh[[:]mm]", or null.
    380      */
    381     private static TimeZone getCustomTimeZone(String id) {
    382         Matcher m = CUSTOM_ZONE_ID_PATTERN.matcher(id);
    383         if (!m.matches()) {
    384             return null;
    385         }
    386 
    387         int hour;
    388         int minute = 0;
    389         try {
    390             hour = Integer.parseInt(m.group(1));
    391             if (m.group(3) != null) {
    392                 minute = Integer.parseInt(m.group(3));
    393             }
    394         } catch (NumberFormatException impossible) {
    395             throw new AssertionError(impossible);
    396         }
    397 
    398         if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
    399             return null;
    400         }
    401 
    402         char sign = id.charAt(3);
    403         int raw = (hour * 3600000) + (minute * 60000);
    404         if (sign == '-') {
    405             raw = -raw;
    406         }
    407 
    408         String cleanId = String.format("GMT%c%02d:%02d", sign, hour, minute);
    409         return new SimpleTimeZone(raw, cleanId);
    410     }
    411 
    412     /**
    413      * Returns true if {@code timeZone} has the same rules as this time zone.
    414      *
    415      * <p>The base implementation returns true if both time zones have the same
    416      * raw offset.
    417      */
    418     public boolean hasSameRules(TimeZone timeZone) {
    419         if (timeZone == null) {
    420             return false;
    421         }
    422         return getRawOffset() == timeZone.getRawOffset();
    423     }
    424 
    425     /**
    426      * Returns true if {@code time} is in a daylight savings time period for
    427      * this time zone.
    428      */
    429     public abstract boolean inDaylightTime(Date time);
    430 
    431     /**
    432      * Overrides the default time zone for the current process only.
    433      *
    434      * <p><strong>Warning</strong>: avoid using this method to use a custom time
    435      * zone in your process. This value may be cleared or overwritten at any
    436      * time, which can cause unexpected behavior. Instead, manually supply a
    437      * custom time zone as needed.
    438      *
    439      * @param timeZone a custom time zone, or {@code null} to set the default to
    440      *     the user's preferred value.
    441      */
    442     public static synchronized void setDefault(TimeZone timeZone) {
    443         defaultTimeZone = timeZone != null ? (TimeZone) timeZone.clone() : null;
    444     }
    445 
    446     /**
    447      * Sets the ID of this {@code TimeZone}.
    448      */
    449     public void setID(String id) {
    450         if (id == null) {
    451             throw new NullPointerException("id == null");
    452         }
    453         ID = id;
    454     }
    455 
    456     /**
    457      * Sets the offset in milliseconds from UTC of this time zone's standard
    458      * time.
    459      */
    460     public abstract void setRawOffset(int offsetMillis);
    461 
    462     /**
    463      * Returns true if this time zone has a future transition to or from
    464      * daylight savings time.
    465      *
    466      * <p><strong>Warning:</strong> this returns false for time zones like
    467      * {@code Asia/Kuala_Lumpur} that have previously used DST but do not
    468      * currently. A hypothetical country that has never observed daylight
    469      * savings before but plans to start next year would return true.
    470      *
    471      * <p><strong>Warning:</strong> this returns true for time zones that use
    472      * DST, even when it is not active.
    473      *
    474      * <p>Use {@link #inDaylightTime} to find out whether daylight savings is
    475      * in effect at a specific time.
    476      *
    477      * <p>Most applications should not use this method.
    478      */
    479     public abstract boolean useDaylightTime();
    480 }
    481