1/*
2 *******************************************************************************
3 * Copyright (C) 2008-2014, International Business Machines Corporation and    *
4 * others. All Rights Reserved.                                                *
5 *******************************************************************************
6 */
7package com.ibm.icu.impl.icuadapter;
8
9import java.lang.reflect.InvocationTargetException;
10import java.lang.reflect.Method;
11import java.util.Calendar;
12import java.util.Date;
13import java.util.GregorianCalendar;
14import java.util.Locale;
15import java.util.TimeZone;
16
17import com.ibm.icu.impl.Grego;
18import com.ibm.icu.impl.jdkadapter.TimeZoneICU;
19import com.ibm.icu.util.ULocale;
20
21/**
22 * TimeZoneJDK is an adapter class which wraps java.util.TimeZone and
23 * implements ICU4J TimeZone APIs.
24 */
25public class TimeZoneJDK extends com.ibm.icu.util.TimeZone {
26
27    private static final long serialVersionUID = -1137052823551791933L;
28
29    private TimeZone fJdkTz;
30    private transient Calendar fJdkCal;
31    private static Method mObservesDaylightTime;
32
33    static {
34        try {
35            mObservesDaylightTime = TimeZone.class.getMethod("observesDaylightTime", (Class[]) null);
36        } catch (NoSuchMethodException e) {
37            // Java 6 or older
38        } catch (SecurityException e) {
39            // not visible
40        }
41    }
42
43    private TimeZoneJDK(TimeZone jdkTz) {
44        fJdkTz = (TimeZone)jdkTz.clone();
45    }
46
47    public static com.ibm.icu.util.TimeZone wrap(TimeZone jdkTz) {
48        if (jdkTz instanceof TimeZoneICU) {
49            return ((TimeZoneICU)jdkTz).unwrap();
50        }
51        return new TimeZoneJDK(jdkTz);
52    }
53
54    public TimeZone unwrap() {
55        return (TimeZone)fJdkTz.clone();
56    }
57
58    @Override
59    public Object clone() {
60        if (isFrozen()) {
61            return this;
62        }
63        return cloneAsThawed();
64    }
65
66    @Override
67    public boolean equals(Object obj) {
68        if (obj instanceof TimeZoneJDK) {
69            return (((TimeZoneJDK)obj).fJdkTz).equals(fJdkTz);
70        }
71        return false;
72    }
73
74    //public String getDisplayName()
75    //public String getDisplayName(boolean daylight, int style)
76    //public String getDisplayName(Locale locale)
77    //public String getDisplayName(ULocale locale)
78    @Override
79    public String getDisplayName(boolean daylight, int style, Locale locale) {
80        return fJdkTz.getDisplayName(daylight, style, locale);
81    }
82
83    @Override
84    public String getDisplayName(boolean daylight, int style, ULocale locale) {
85        return fJdkTz.getDisplayName(daylight, style, locale.toLocale());
86    }
87
88    @Override
89    public int getDSTSavings() {
90        return fJdkTz.getDSTSavings();
91    }
92
93    @Override
94    public String getID() {
95        return fJdkTz.getID();
96    }
97
98    @Override
99    public int getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds) {
100        return fJdkTz.getOffset(era, year, month, day, dayOfWeek, milliseconds);
101    }
102
103    @Override
104    public int getOffset(long date) {
105        return fJdkTz.getOffset(date);
106    }
107
108    @Override
109    public void getOffset(long date, boolean local, int[] offsets) {
110        synchronized(this) {
111            if (fJdkCal == null) {
112                fJdkCal = new GregorianCalendar(fJdkTz);
113            }
114            if (local) {
115                int fields[] = new int[6];
116                Grego.timeToFields(date, fields);
117                int hour, min, sec, mil;
118                int tmp = fields[5];
119                mil = tmp % 1000;
120                tmp /= 1000;
121                sec = tmp % 60;
122                tmp /= 60;
123                min = tmp % 60;
124                hour = tmp / 60;
125                fJdkCal.clear();
126                fJdkCal.set(fields[0], fields[1], fields[2], hour, min, sec);
127                fJdkCal.set(java.util.Calendar.MILLISECOND, mil);
128
129                int doy1, hour1, min1, sec1, mil1;
130                doy1 = fJdkCal.get(java.util.Calendar.DAY_OF_YEAR);
131                hour1 = fJdkCal.get(java.util.Calendar.HOUR_OF_DAY);
132                min1 = fJdkCal.get(java.util.Calendar.MINUTE);
133                sec1 = fJdkCal.get(java.util.Calendar.SECOND);
134                mil1 = fJdkCal.get(java.util.Calendar.MILLISECOND);
135
136                if (fields[4] != doy1 || hour != hour1 || min != min1 || sec != sec1 || mil != mil1) {
137                    // Calendar field(s) were changed due to the adjustment for non-existing time
138                    // Note: This code does not support non-existing local time at year boundary properly.
139                    // But, it should work fine for real timezones.
140                    int dayDelta = Math.abs(doy1 - fields[4]) > 1 ? 1 : doy1 - fields[4];
141                    int delta = ((((dayDelta * 24) + hour1 - hour) * 60 + min1 - min) * 60 + sec1 - sec) * 1000 + mil1 - mil;
142
143                    // In this case, we use the offsets before the transition
144                    fJdkCal.setTimeInMillis(fJdkCal.getTimeInMillis() - delta - 1);
145                }
146            } else {
147                fJdkCal.setTimeInMillis(date);
148            }
149            offsets[0] = fJdkCal.get(java.util.Calendar.ZONE_OFFSET);
150            offsets[1] = fJdkCal.get(java.util.Calendar.DST_OFFSET);
151        }
152    }
153
154    @Override
155    public int getRawOffset() {
156        return fJdkTz.getRawOffset();
157    }
158
159    @Override
160    public int hashCode() {
161        return fJdkTz.hashCode();
162    }
163
164    @Override
165    public boolean hasSameRules(com.ibm.icu.util.TimeZone other) {
166        return other.hasSameRules(TimeZoneJDK.wrap(fJdkTz));
167    }
168
169    @Override
170    public boolean inDaylightTime(Date date) {
171        return fJdkTz.inDaylightTime(date);
172    }
173
174    @Override
175    public void setID(String ID) {
176        if (isFrozen()) {
177            throw new UnsupportedOperationException("Attempt to modify a frozen TimeZoneJDK instance.");
178        }
179        fJdkTz.setID(ID);
180    }
181
182    @Override
183    public void setRawOffset(int offsetMillis) {
184        if (isFrozen()) {
185            throw new UnsupportedOperationException("Attempt to modify a frozen TimeZoneJDK instance.");
186        }
187        fJdkTz.setRawOffset(offsetMillis);
188    }
189
190    @Override
191    public boolean useDaylightTime() {
192        return fJdkTz.useDaylightTime();
193    }
194
195    @Override
196    public boolean observesDaylightTime() {
197        if (mObservesDaylightTime != null) {
198            // Java 7+
199            try {
200                return (Boolean)mObservesDaylightTime.invoke(fJdkTz, (Object[]) null);
201            } catch (IllegalAccessException e) {
202            } catch (IllegalArgumentException e) {
203            } catch (InvocationTargetException e) {
204            }
205        }
206        return super.observesDaylightTime();
207    }
208
209    // Freezable stuffs
210    private volatile transient boolean fIsFrozen = false;
211
212    @Override
213    public boolean isFrozen() {
214        return fIsFrozen;
215    }
216
217    @Override
218    public com.ibm.icu.util.TimeZone freeze() {
219        fIsFrozen = true;
220        return this;
221    }
222
223    @Override
224    public com.ibm.icu.util.TimeZone cloneAsThawed() {
225        TimeZoneJDK tz = (TimeZoneJDK)super.cloneAsThawed();
226        tz.fJdkTz = (TimeZone)fJdkTz.clone();
227        tz.fJdkCal = null;  // To be instantiated when necessary
228        tz.fIsFrozen = false;
229        return tz;
230    }
231
232}
233