Home | History | Annotate | Download | only in impl
      1 //  2016 and later: Unicode, Inc. and others.
      2 // License & terms of use: http://www.unicode.org/copyright.html#License
      3 /*
      4  *******************************************************************************
      5  * Copyright (C) 2008-2014, International Business Machines Corporation and    *
      6  * others. All Rights Reserved.                                                *
      7  *******************************************************************************
      8  */
      9 package com.ibm.icu.impl;
     10 
     11 import java.io.IOException;
     12 import java.io.ObjectInputStream;
     13 import java.lang.reflect.InvocationTargetException;
     14 import java.lang.reflect.Method;
     15 import java.util.Date;
     16 import java.util.TreeSet;
     17 
     18 import com.ibm.icu.util.TimeZone;
     19 
     20 /**
     21  * JavaTimeZone inherits com.ibm.icu.util.TimeZone and wraps java.util.TimeZone.
     22  * We used to have JDKTimeZone which wrapped Java TimeZone and used it as primary
     23  * TimeZone implementation until ICU4J 3.4.1.  This class works exactly like
     24  * JDKTimeZone and allows ICU users who use ICU4J and JDK date/time/calendar
     25  * services in mix to maintain only JDK timezone rules.
     26  *
     27  * This TimeZone subclass is returned by the TimeZone factory method getTimeZone(String)
     28  * when the default timezone type in TimeZone class is TimeZone.TIMEZONE_JDK.
     29  */
     30 public class JavaTimeZone extends TimeZone {
     31 
     32     private static final long serialVersionUID = 6977448185543929364L;
     33 
     34     private static final TreeSet<String> AVAILABLESET;
     35 
     36     private java.util.TimeZone javatz;
     37     private transient java.util.Calendar javacal;
     38     private static Method mObservesDaylightTime;
     39 
     40     static {
     41         AVAILABLESET = new TreeSet<String>();
     42         String[] availableIds = java.util.TimeZone.getAvailableIDs();
     43         for (int i = 0; i < availableIds.length; i++) {
     44             AVAILABLESET.add(availableIds[i]);
     45         }
     46 
     47         try {
     48             mObservesDaylightTime = java.util.TimeZone.class.getMethod("observesDaylightTime", (Class[]) null);
     49         } catch (NoSuchMethodException e) {
     50             // Java 6 or older
     51         } catch (SecurityException e) {
     52             // not visible
     53         }
     54     }
     55 
     56     /**
     57      * Constructs a JavaTimeZone with the default Java TimeZone
     58      */
     59     public JavaTimeZone() {
     60         this(java.util.TimeZone.getDefault(), null);
     61     }
     62 
     63     /**
     64      * Constructs a JavaTimeZone with the specified Java TimeZone and ID.
     65      * @param jtz the Java TimeZone
     66      * @param id the ID of the zone. if null, the zone ID is initialized
     67      * by the given Java TimeZone's ID.
     68      */
     69     public JavaTimeZone(java.util.TimeZone jtz, String id) {
     70         if (id == null) {
     71             id = jtz.getID();
     72         }
     73         javatz = jtz;
     74         setID(id);
     75         javacal = new java.util.GregorianCalendar(javatz);
     76     }
     77 
     78     /**
     79      * Creates an instance of JavaTimeZone with the given timezone ID.
     80      * @param id A timezone ID, either a system ID or a custom ID.
     81      * @return An instance of JavaTimeZone for the given ID, or null
     82      * when the ID cannot be understood.
     83      */
     84     public static JavaTimeZone createTimeZone(String id) {
     85         java.util.TimeZone jtz = null;
     86 
     87         if (AVAILABLESET.contains(id)) {
     88             jtz = java.util.TimeZone.getTimeZone(id);
     89         }
     90 
     91         if (jtz == null) {
     92             // Use ICU's canonical ID mapping
     93             boolean[] isSystemID = new boolean[1];
     94             String canonicalID = TimeZone.getCanonicalID(id, isSystemID);
     95             if (isSystemID[0] && AVAILABLESET.contains(canonicalID)) {
     96                 jtz = java.util.TimeZone.getTimeZone(canonicalID);
     97             }
     98         }
     99 
    100         if (jtz == null) {
    101             return null;
    102         }
    103 
    104         return new JavaTimeZone(jtz, id);
    105     }
    106 
    107     /* (non-Javadoc)
    108      * @see com.ibm.icu.util.TimeZone#getOffset(int, int, int, int, int, int)
    109      */
    110     @Override
    111     public int getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds) {
    112         return javatz.getOffset(era, year, month, day, dayOfWeek, milliseconds);
    113     }
    114 
    115     /* (non-Javadoc)
    116      * @see com.ibm.icu.util.TimeZone#getOffset(long, boolean, int[])
    117      */
    118     @Override
    119     public void getOffset(long date, boolean local, int[] offsets) {
    120         synchronized (javacal) {
    121             if (local) {
    122                 int fields[] = new int[6];
    123                 Grego.timeToFields(date, fields);
    124                 int hour, min, sec, mil;
    125                 int tmp = fields[5];
    126                 mil = tmp % 1000;
    127                 tmp /= 1000;
    128                 sec = tmp % 60;
    129                 tmp /= 60;
    130                 min = tmp % 60;
    131                 hour = tmp / 60;
    132                 javacal.clear();
    133                 javacal.set(fields[0], fields[1], fields[2], hour, min, sec);
    134                 javacal.set(java.util.Calendar.MILLISECOND, mil);
    135 
    136                 int doy1, hour1, min1, sec1, mil1;
    137                 doy1 = javacal.get(java.util.Calendar.DAY_OF_YEAR);
    138                 hour1 = javacal.get(java.util.Calendar.HOUR_OF_DAY);
    139                 min1 = javacal.get(java.util.Calendar.MINUTE);
    140                 sec1 = javacal.get(java.util.Calendar.SECOND);
    141                 mil1 = javacal.get(java.util.Calendar.MILLISECOND);
    142 
    143                 if (fields[4] != doy1 || hour != hour1 || min != min1 || sec != sec1 || mil != mil1) {
    144                     // Calendar field(s) were changed due to the adjustment for non-existing time
    145                     // Note: This code does not support non-existing local time at year boundary properly.
    146                     // But, it should work fine for real timezones.
    147                     int dayDelta = Math.abs(doy1 - fields[4]) > 1 ? 1 : doy1 - fields[4];
    148                     int delta = ((((dayDelta * 24) + hour1 - hour) * 60 + min1 - min) * 60 + sec1 - sec) * 1000 + mil1 - mil;
    149 
    150                     // In this case, we use the offsets before the transition
    151                    javacal.setTimeInMillis(javacal.getTimeInMillis() - delta - 1);
    152                 }
    153             } else {
    154                 javacal.setTimeInMillis(date);
    155             }
    156             offsets[0] = javacal.get(java.util.Calendar.ZONE_OFFSET);
    157             offsets[1] = javacal.get(java.util.Calendar.DST_OFFSET);
    158         }
    159     }
    160 
    161     /* (non-Javadoc)
    162      * @see com.ibm.icu.util.TimeZone#getRawOffset()
    163      */
    164     @Override
    165     public int getRawOffset() {
    166         return javatz.getRawOffset();
    167     }
    168 
    169     /* (non-Javadoc)
    170      * @see com.ibm.icu.util.TimeZone#inDaylightTime(java.util.Date)
    171      */
    172     @Override
    173     public boolean inDaylightTime(Date date) {
    174         return javatz.inDaylightTime(date);
    175     }
    176 
    177     /* (non-Javadoc)
    178      * @see com.ibm.icu.util.TimeZone#setRawOffset(int)
    179      */
    180     @Override
    181     public void setRawOffset(int offsetMillis) {
    182         if (isFrozen()) {
    183             throw new UnsupportedOperationException("Attempt to modify a frozen JavaTimeZone instance.");
    184         }
    185         javatz.setRawOffset(offsetMillis);
    186     }
    187 
    188     /* (non-Javadoc)
    189      * @see com.ibm.icu.util.TimeZone#useDaylightTime()
    190      */
    191     @Override
    192     public boolean useDaylightTime() {
    193         return javatz.useDaylightTime();
    194     }
    195 
    196     /* (non-Javadoc)
    197      * @see com.ibm.icu.util.TimeZone#observesDaylightTime()
    198      */
    199     @Override
    200     public boolean observesDaylightTime() {
    201         if (mObservesDaylightTime != null) {
    202             // Java 7+
    203             try {
    204                 return (Boolean)mObservesDaylightTime.invoke(javatz, (Object[]) null);
    205             } catch (IllegalAccessException e) {
    206             } catch (IllegalArgumentException e) {
    207             } catch (InvocationTargetException e) {
    208             }
    209         }
    210         return super.observesDaylightTime();
    211     }
    212 
    213     /* (non-Javadoc)
    214      * @see com.ibm.icu.util.TimeZone#getDSTSavings()
    215      */
    216     @Override
    217     public int getDSTSavings() {
    218         return javatz.getDSTSavings();
    219     }
    220 
    221     public java.util.TimeZone unwrap() {
    222         return javatz;
    223     }
    224 
    225     /* (non-Javadoc)
    226      * @see com.ibm.icu.util.TimeZone#clone()
    227      */
    228     @Override
    229     public Object clone() {
    230         if (isFrozen()) {
    231             return this;
    232         }
    233         return cloneAsThawed();
    234     }
    235 
    236     /* (non-Javadoc)
    237      * @see com.ibm.icu.util.TimeZone#hashCode()
    238      */
    239     @Override
    240     public int hashCode() {
    241         return super.hashCode() + javatz.hashCode();
    242     }
    243 
    244     private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
    245         s.defaultReadObject();
    246         javacal = new java.util.GregorianCalendar(javatz);
    247     }
    248 
    249     // Freezable stuffs
    250     private transient volatile boolean isFrozen = false;
    251 
    252     /* (non-Javadoc)
    253      * @see com.ibm.icu.util.TimeZone#isFrozen()
    254      */
    255     @Override
    256     public boolean isFrozen() {
    257         return isFrozen;
    258     }
    259 
    260     /* (non-Javadoc)
    261      * @see com.ibm.icu.util.TimeZone#freeze()
    262      */
    263     @Override
    264     public TimeZone freeze() {
    265         isFrozen = true;
    266         return this;
    267     }
    268 
    269     /* (non-Javadoc)
    270      * @see com.ibm.icu.util.TimeZone#cloneAsThawed()
    271      */
    272     @Override
    273     public TimeZone cloneAsThawed() {
    274         JavaTimeZone tz = (JavaTimeZone)super.cloneAsThawed();
    275         tz.javatz = (java.util.TimeZone)javatz.clone();
    276         tz.javacal = new java.util.GregorianCalendar(javatz);  // easier than synchronized javacal.clone()
    277         tz.isFrozen = false;
    278         return tz;
    279     }
    280 
    281 }
    282