TimeZone.java revision 1c422fc0ab0692e10a05af6f48c6276c4dad4bea
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
18package java.util;
19
20import java.io.Serializable;
21
22// BEGIN android-added
23import org.apache.harmony.luni.internal.util.ZoneInfo;
24import org.apache.harmony.luni.internal.util.ZoneInfoDB;
25import com.ibm.icu4jni.util.Resources;
26// END android-added
27
28/**
29 * {@code TimeZone} represents a time zone offset, taking into account
30 * daylight savings.
31 * <p>
32 * Typically, you get a {@code TimeZone} using {@code getDefault}
33 * which creates a {@code TimeZone} based on the time zone where the
34 * program is running. For example, for a program running in Japan,
35 * {@code getDefault} creates a {@code TimeZone} object based on
36 * Japanese Standard Time.
37 * <p>
38 * You can also get a {@code TimeZone} using {@code getTimeZone}
39 * along with a time zone ID. For instance, the time zone ID for the U.S.
40 * Pacific Time zone is "America/Los_Angeles". So, you can get a U.S. Pacific
41 * Time {@code TimeZone} object with the following: <blockquote>
42 *
43 * <pre>
44 * TimeZone tz = TimeZone.getTimeZone(&quot;America/Los_Angeles&quot;);
45 * </pre>
46 *
47 * </blockquote> You can use the {@code getAvailableIDs} method to iterate
48 * through all the supported time zone IDs. You can then choose a supported ID
49 * to get a {@code TimeZone}. If the time zone you want is not
50 * represented by one of the supported IDs, then you can create a custom time
51 * zone ID with the following syntax: <blockquote>
52 *
53 * <pre>
54 * GMT[+|-]hh[[:]mm]
55 * </pre>
56 *
57 * </blockquote> For example, you might specify GMT+14:00 as a custom time zone
58 * ID. The {@code TimeZone} that is returned when you specify a custom
59 * time zone ID does not include daylight savings time.
60 * <p>
61 * For compatibility with JDK 1.1.x, some other three-letter time zone IDs (such
62 * as "PST", "CTT", "AST") are also supported. However, <strong>their use is
63 * deprecated</strong> because the same abbreviation is often used for multiple
64 * time zones (for example, "CST" could be U.S. "Central Standard Time" and
65 * "China Standard Time"), and the Java platform can then only recognize one of
66 * them.
67 * <p>
68 * Please note the type returned by factory methods, i.e. {@code getDefault()}
69 * and {@code getTimeZone(String)}, is implementation dependent, so it may
70 * introduce serialization incompatibility issues between different
71 * implementations. Android returns instances of {@link SimpleTimeZone} so that
72 * the bytes serialized by Android can be deserialized successfully on other
73 * implementations, but the reverse compatibility cannot be guaranteed.
74 *
75 * @see GregorianCalendar
76 * @see SimpleTimeZone
77 */
78public abstract class TimeZone implements Serializable, Cloneable {
79    private static final long serialVersionUID = 3581463369166924961L;
80
81    /**
82     * The SHORT display name style.
83     */
84    public static final int SHORT = 0;
85
86    /**
87     * The LONG display name style.
88     */
89    public static final int LONG = 1;
90
91    // BEGIN android-removed
92    // private static HashMap<String, TimeZone> AvailableZones;
93    // END android-removed
94
95    private static TimeZone Default;
96
97    static TimeZone GMT = new SimpleTimeZone(0, "GMT"); // Greenwich Mean Time
98
99    private String ID;
100
101    // BEGIN android-removed
102    // private com.ibm.icu.util.TimeZone icuTimeZone = null;
103    //
104    // private static void initializeAvailable() {
105    //     TimeZone[] zones = TimeZones.getTimeZones();
106    //     AvailableZones = new HashMap<String, TimeZone>(
107    //             (zones.length + 1) * 4 / 3);
108    //     AvailableZones.put(GMT.getID(), GMT);
109    //     for (int i = 0; i < zones.length; i++) {
110    //         AvailableZones.put(zones[i].getID(), zones[i]);
111    //     }
112    //}
113    //
114    // private static boolean isAvailableIDInICU(String name) {
115    //     String[] availableIDs = com.ibm.icu.util.TimeZone.getAvailableIDs();
116    //     for (int i = 0; i < availableIDs.length; i++) {
117    //         if (availableIDs[i].equals(name)) {
118    //             return true;
119    //         }
120    //     }
121    //     return false;
122    // }
123    //
124    // private static void appendAvailableZones(String name) {
125    //     com.ibm.icu.util.TimeZone icuTZ = com.ibm.icu.util.TimeZone
126    //             .getTimeZone(name);
127    //     int raw = icuTZ.getRawOffset();
128    //     TimeZone zone = new SimpleTimeZone(raw, name);
129    //     AvailableZones.put(name, zone);
130    // }
131    // END android-removed
132
133    /**
134     * Constructs a new instance of this class.
135     */
136    public TimeZone() {
137    }
138
139    /**
140     * Returns a new {@code TimeZone} with the same ID, {@code rawOffset} and daylight savings
141     * time rules as this {@code TimeZone}.
142     *
143     * @return a shallow copy of this {@code TimeZone}.
144     * @see java.lang.Cloneable
145     */
146    @Override
147    public Object clone() {
148        try {
149            TimeZone zone = (TimeZone) super.clone();
150            return zone;
151        } catch (CloneNotSupportedException e) {
152            throw new AssertionError(e); // android-changed
153        }
154    }
155
156    /**
157     * Gets the available time zone IDs. Any one of these IDs can be passed to
158     * {@code get()} to create the corresponding {@code TimeZone} instance.
159     *
160     * @return an array of time zone ID strings.
161     */
162    public static synchronized String[] getAvailableIDs() {
163        // BEGIN android-changed
164        // return com.ibm.icu.util.TimeZone.getAvailableIDs();
165        return ZoneInfoDB.getAvailableIDs();
166        // END android-changed
167    }
168
169    /**
170     * Gets the available time zone IDs which match the specified offset from
171     * GMT. Any one of these IDs can be passed to {@code get()} to create the corresponding
172     * {@code TimeZone} instance.
173     *
174     * @param offset
175     *            the offset from GMT in milliseconds.
176     * @return an array of time zone ID strings.
177     */
178    public static synchronized String[] getAvailableIDs(int offset) {
179        // BEGIN android-changed
180        // String[] availableIDs = com.ibm.icu.util.TimeZone.getAvailableIDs();
181        // int count = 0;
182        // int length = availableIDs.length;
183        // String[] all = new String[length];
184        // for (int i = 0; i < length; i++) {
185        //     com.ibm.icu.util.TimeZone tz = com.ibm.icu.util.TimeZone
186        //             .getTimeZone(availableIDs[i]);
187        //     if (tz.getRawOffset() == offset) {
188        //         all[count++] = tz.getID();
189        //     }
190        // }
191        // String[] answer = new String[count];
192        // System.arraycopy(all, 0, answer, 0, count);
193        // return answer;
194        return ZoneInfoDB.getAvailableIDs(offset);
195        // END android-changed
196    }
197
198    /**
199     * Gets the default time zone.
200     *
201     * @return the default time zone.
202     */
203    public static synchronized TimeZone getDefault() {
204        if (Default == null) {
205            // BEGIN android-changed
206            // setDefault(null);
207            Default = ZoneInfoDB.getDefault();
208            // END android-changed
209        }
210        return (TimeZone) Default.clone();
211    }
212
213    /**
214     * Gets the LONG name for this {@code TimeZone} for the default {@code Locale} in standard
215     * time. If the name is not available, the result is in the format
216     * {@code GMT[+-]hh:mm}.
217     *
218     * @return the {@code TimeZone} name.
219     */
220    public final String getDisplayName() {
221        return getDisplayName(false, LONG, Locale.getDefault());
222    }
223
224    /**
225     * Gets the LONG name for this {@code TimeZone} for the specified {@code Locale} in standard
226     * time. If the name is not available, the result is in the format
227     * {@code GMT[+-]hh:mm}.
228     *
229     * @param locale
230     *            the {@code Locale}.
231     * @return the {@code TimeZone} name.
232     */
233    public final String getDisplayName(Locale locale) {
234        return getDisplayName(false, LONG, locale);
235    }
236
237    /**
238     * Gets the specified style of name ({@code LONG} or {@code SHORT}) for this {@code TimeZone} for
239     * the default {@code Locale} in either standard or daylight time as specified. If
240     * the name is not available, the result is in the format {@code GMT[+-]hh:mm}.
241     *
242     * @param daylightTime
243     *            {@code true} for daylight time, {@code false} for standard
244     *            time.
245     * @param style
246     *            either {@code LONG} or {@code SHORT}.
247     * @return the {@code TimeZone} name.
248     */
249    public final String getDisplayName(boolean daylightTime, int style) {
250        return getDisplayName(daylightTime, style, Locale.getDefault());
251    }
252
253    /**
254     * Gets the specified style of name ({@code LONG} or {@code SHORT}) for this {@code TimeZone} for
255     * the specified {@code Locale} in either standard or daylight time as specified. If
256     * the name is not available, the result is in the format {@code GMT[+-]hh:mm}.
257     *
258     * @param daylightTime
259     *            {@code true} for daylight time, {@code false} for standard
260     *            time.
261     * @param style
262     *            either LONG or SHORT.
263     * @param locale
264     *            either {@code LONG} or {@code SHORT}.
265     * @return the {@code TimeZone} name.
266     */
267    public String getDisplayName(boolean daylightTime, int style, Locale locale) {
268        // BEGIN android-changed
269        // if(icuTimeZone == null || !ID.equals(icuTimeZone.getID())){
270        //     icuTimeZone = com.ibm.icu.util.TimeZone.getTimeZone(ID);
271        // }
272        // return icuTimeZone.getDisplayName(
273        //         daylightTime, style, locale);
274        if (style == SHORT || style == LONG) {
275            boolean useDaylight = daylightTime && useDaylightTime();
276
277            String result = Resources.getDisplayTimeZone(getID(), daylightTime, style, locale.toString());
278            if (result != null) {
279                return result;
280            }
281
282            int offset = getRawOffset();
283            if (useDaylight && this instanceof SimpleTimeZone) {
284                offset += ((SimpleTimeZone) this).getDSTSavings();
285            }
286            offset /= 60000;
287            char sign = '+';
288            if (offset < 0) {
289                sign = '-';
290                offset = -offset;
291            }
292            StringBuffer buffer = new StringBuffer(9);
293            buffer.append("GMT");
294            buffer.append(sign);
295            appendNumber(buffer, 2, offset / 60);
296            buffer.append(':');
297            appendNumber(buffer, 2, offset % 60);
298            return buffer.toString();
299        }
300        throw new IllegalArgumentException();
301        // END android-changed
302    }
303
304    // BEGIN android-added
305    private void appendNumber(StringBuffer buffer, int count, int value) {
306        String string = Integer.toString(value);
307        if (count > string.length()) {
308            for (int i = 0; i < count - string.length(); i++) {
309                buffer.append('0');
310            }
311        }
312        buffer.append(string);
313    }
314    // END android-added
315
316    /**
317     * Gets the ID of this {@code TimeZone}.
318     *
319     * @return the time zone ID string.
320     */
321    public String getID() {
322        return ID;
323    }
324
325    /**
326     * Gets the daylight savings offset in milliseconds for this {@code TimeZone}.
327     * <p>
328     * This implementation returns 3600000 (1 hour), or 0 if the time zone does
329     * not observe daylight savings.
330     * <p>
331     * Subclasses may override to return daylight savings values other than 1
332     * hour.
333     * <p>
334     *
335     * @return the daylight savings offset in milliseconds if this {@code TimeZone}
336     *         observes daylight savings, zero otherwise.
337     */
338    public int getDSTSavings() {
339        if (useDaylightTime()) {
340            return 3600000;
341        }
342        return 0;
343    }
344
345    /**
346     * Gets the offset from GMT of this {@code TimeZone} for the specified date. The
347     * offset includes daylight savings time if the specified date is within the
348     * daylight savings time period.
349     *
350     * @param time
351     *            the date in milliseconds since January 1, 1970 00:00:00 GMT
352     * @return the offset from GMT in milliseconds.
353     */
354    public int getOffset(long time) {
355        if (inDaylightTime(new Date(time))) {
356            return getRawOffset() + getDSTSavings();
357        }
358        return getRawOffset();
359    }
360
361    /**
362     * Gets the offset from GMT of this {@code TimeZone} for the specified date and
363     * time. The offset includes daylight savings time if the specified date and
364     * time are within the daylight savings time period.
365     *
366     * @param era
367     *            the {@code GregorianCalendar} era, either {@code GregorianCalendar.BC} or
368     *            {@code GregorianCalendar.AD}.
369     * @param year
370     *            the year.
371     * @param month
372     *            the {@code Calendar} month.
373     * @param day
374     *            the day of the month.
375     * @param dayOfWeek
376     *            the {@code Calendar} day of the week.
377     * @param time
378     *            the time of day in milliseconds.
379     * @return the offset from GMT in milliseconds.
380     */
381    abstract public int getOffset(int era, int year, int month, int day,
382            int dayOfWeek, int time);
383
384    /**
385     * Gets the offset for standard time from GMT for this {@code TimeZone}.
386     *
387     * @return the offset from GMT in milliseconds.
388     */
389    abstract public int getRawOffset();
390
391    /**
392     * Gets the {@code TimeZone} with the specified ID.
393     *
394     * @param name
395     *            a time zone string ID.
396     * @return the {@code TimeZone} with the specified ID or null if no {@code TimeZone} with
397     *         the specified ID exists.
398     */
399    public static synchronized TimeZone getTimeZone(String name) {
400        // BEGIN android-changed
401        // if (AvailableZones == null) {
402        //     initializeAvailable();
403        // }
404        //
405        // TimeZone zone = AvailableZones.get(name);
406        // if(zone == null && isAvailableIDInICU(name)){
407        //     appendAvailableZones(name);
408        //     zone = AvailableZones.get(name);
409        // }
410        TimeZone zone = ZoneInfo.getTimeZone(name);
411        // END android-changed
412        if (zone == null) {
413            if (name.startsWith("GMT") && name.length() > 3) {
414                char sign = name.charAt(3);
415                if (sign == '+' || sign == '-') {
416                    int[] position = new int[1];
417                    String formattedName = formatTimeZoneName(name, 4);
418                    int hour = parseNumber(formattedName, 4, position);
419                    if (hour < 0 || hour > 23) {
420                        return (TimeZone) GMT.clone();
421                    }
422                    int index = position[0];
423                    if (index != -1) {
424                        int raw = hour * 3600000;
425                        if (index < formattedName.length()
426                                && formattedName.charAt(index) == ':') {
427                            int minute = parseNumber(formattedName, index + 1,
428                                    position);
429                            if (position[0] == -1 || minute < 0 || minute > 59) {
430                                return (TimeZone) GMT.clone();
431                            }
432                            raw += minute * 60000;
433                        } else if (hour >= 30 || index > 6) {
434                            raw = (hour / 100 * 3600000) + (hour % 100 * 60000);
435                        }
436                        if (sign == '-') {
437                            raw = -raw;
438                        }
439                        return new SimpleTimeZone(raw, formattedName);
440                    }
441                }
442            }
443            zone = GMT;
444        }
445        return (TimeZone) zone.clone();
446    }
447
448    private static String formatTimeZoneName(String name, int offset) {
449        StringBuilder buf = new StringBuilder();
450        int index = offset, length = name.length();
451        buf.append(name.substring(0, offset));
452
453        while (index < length) {
454            if (Character.digit(name.charAt(index), 10) != -1) {
455                buf.append(name.charAt(index));
456                if ((length - (index + 1)) == 2) {
457                    buf.append(':');
458                }
459            } else if (name.charAt(index) == ':') {
460                buf.append(':');
461            }
462            index++;
463        }
464
465        if (buf.toString().indexOf(":") == -1) {
466            buf.append(':');
467            buf.append("00");
468        }
469
470        if (buf.toString().indexOf(":") == 5) {
471            buf.insert(4, '0');
472        }
473
474        return buf.toString();
475    }
476
477    /**
478     * Returns whether the specified {@code TimeZone} has the same raw offset as this
479     * {@code TimeZone}.
480     *
481     * @param zone
482     *            a {@code TimeZone}.
483     * @return {@code true} when the {@code TimeZone} have the same raw offset, {@code false}
484     *         otherwise.
485     */
486    public boolean hasSameRules(TimeZone zone) {
487        if (zone == null) {
488            return false;
489        }
490        return getRawOffset() == zone.getRawOffset();
491    }
492
493    /**
494     * Returns whether the specified {@code Date} is in the daylight savings time period for
495     * this {@code TimeZone}.
496     *
497     * @param time
498     *            a {@code Date}.
499     * @return {@code true} when the {@code Date} is in the daylight savings time period, {@code false}
500     *         otherwise.
501     */
502    abstract public boolean inDaylightTime(Date time);
503
504    private static int parseNumber(String string, int offset, int[] position) {
505        int index = offset, length = string.length(), digit, result = 0;
506        while (index < length
507                && (digit = Character.digit(string.charAt(index), 10)) != -1) {
508            index++;
509            result = result * 10 + digit;
510        }
511        position[0] = index == offset ? -1 : index;
512        return result;
513    }
514
515    /**
516     * Sets the default time zone. If passed {@code null}, then the next
517     * time {@link #getDefault} is called, the default time zone will be
518     * determined. This behavior is slightly different than the canonical
519     * description of this method, but it follows the spirit of it.
520     *
521     * @param timezone
522     *            a {@code TimeZone} object.
523     */
524    public static synchronized void setDefault(TimeZone timezone) {
525        // BEGIN android-removed
526        // if (timezone != null) {
527        //     setICUDefaultTimeZone(timezone);
528        //     Default = timezone;
529        //     return;
530        // }
531        //
532        // String zone = AccessController.doPrivileged(new PriviAction<String>(
533        //         "user.timezone"));
534        //
535        // // sometimes DRLVM incorrectly adds "\n" to the end of timezone ID
536        // if (zone != null && zone.contains("\n")) {
537        //     zone = zone.substring(0, zone.indexOf("\n"));
538        // }
539        //
540        // // if property user.timezone is not set, we call the native method
541        // // getCustomTimeZone
542        // if (zone == null || zone.length() == 0) {
543        //     int[] tzinfo = new int[10];
544        //     boolean[] isCustomTimeZone = new boolean[1];
545        //
546        //     String zoneId = getCustomTimeZone(tzinfo, isCustomTimeZone);
547        //
548        //     // if returned TimeZone is a user customized TimeZone
549        //     if (isCustomTimeZone[0]) {
550        //         // build a new SimpleTimeZone
551        //         switch (tzinfo[1]) {
552        //         case 0:
553        //             // does not observe DST
554        //             Default = new SimpleTimeZone(tzinfo[0], zoneId);
555        //             break;
556        //         default:
557        //             // observes DST
558        //             Default = new SimpleTimeZone(tzinfo[0], zoneId, tzinfo[5],
559        //                     tzinfo[4], tzinfo[3], tzinfo[2], tzinfo[9],
560        //                     tzinfo[8], tzinfo[7], tzinfo[6], tzinfo[1]);
561        //         }
562        //     } else {
563        //         // get TimeZone
564        //         Default = getTimeZone(zoneId);
565        //     }
566        // } else {
567        //     // if property user.timezone is set in command line (with -D option)
568        //     Default = getTimeZone(zone);
569        // }
570        // setICUDefaultTimeZone(Default);
571        // END android-removed
572
573        // BEGIN android-added
574        Default = timezone;
575
576        // TODO Not sure if this is spec-compliant. Shouldn't be persistent.
577        ZoneInfoDB.setDefault(timezone);
578        // END android-added
579    }
580
581    // BEGIN android-removed
582    // private static void setICUDefaultTimeZone(TimeZone timezone) {
583    //     final com.ibm.icu.util.TimeZone icuTZ = com.ibm.icu.util.TimeZone
584    //             .getTimeZone(timezone.getID());
585    //
586    //     AccessController
587    //             .doPrivileged(new PrivilegedAction<java.lang.reflect.Field>() {
588    //                 public java.lang.reflect.Field run() {
589    //                     java.lang.reflect.Field field = null;
590    //                     try {
591    //                         field = com.ibm.icu.util.TimeZone.class
592    //                                 .getDeclaredField("defaultZone");
593    //                         field.setAccessible(true);
594    //                         field.set("defaultZone", icuTZ);
595    //                     } catch (Exception e) {
596    //                         return null;
597    //                     }
598    //                     return field;
599    //                 }
600    //             });
601    // }
602    // END android-removed
603
604    /**
605     * Sets the ID of this {@code TimeZone}.
606     *
607     * @param name
608     *            a string which is the time zone ID.
609     */
610    public void setID(String name) {
611        if (name == null) {
612            throw new NullPointerException();
613        }
614        ID = name;
615    }
616
617    /**
618     * Sets the offset for standard time from GMT for this {@code TimeZone}.
619     *
620     * @param offset
621     *            the offset from GMT in milliseconds.
622     */
623    abstract public void setRawOffset(int offset);
624
625    /**
626     * Returns whether this {@code TimeZone} has a daylight savings time period.
627     *
628     * @return {@code true} if this {@code TimeZone} has a daylight savings time period, {@code false}
629     *         otherwise.
630     */
631    abstract public boolean useDaylightTime();
632
633    /**
634     * Gets the name and the details of the user-selected TimeZone on the
635     * device.
636     *
637     * @param tzinfo
638     *            int array of 10 elements to be filled with the TimeZone
639     *            information. Once filled, the contents of the array are
640     *            formatted as follows: tzinfo[0] -> the timezone offset;
641     *            tzinfo[1] -> the dst adjustment; tzinfo[2] -> the dst start
642     *            hour; tzinfo[3] -> the dst start day of week; tzinfo[4] -> the
643     *            dst start week of month; tzinfo[5] -> the dst start month;
644     *            tzinfo[6] -> the dst end hour; tzinfo[7] -> the dst end day of
645     *            week; tzinfo[8] -> the dst end week of month; tzinfo[9] -> the
646     *            dst end month;
647     * @param isCustomTimeZone
648     *            boolean array of size 1 that indicates if a timezone match is
649     *            found
650     * @return the name of the TimeZone or null if error occurs in native
651     *         method.
652     */
653    private static native String getCustomTimeZone(int[] tzinfo,
654            boolean[] isCustomTimeZone);
655}
656