1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package libcore.java.util;
18
19import java.text.SimpleDateFormat;
20import java.util.Calendar;
21import java.util.Date;
22import java.util.Locale;
23import java.util.SimpleTimeZone;
24import java.util.TimeZone;
25import junit.framework.TestCase;
26
27public class TimeZoneTest extends TestCase {
28    // http://code.google.com/p/android/issues/detail?id=877
29    public void test_useDaylightTime_Taiwan() {
30        TimeZone asiaTaipei = TimeZone.getTimeZone("Asia/Taipei");
31        assertFalse("Taiwan doesn't use DST", asiaTaipei.useDaylightTime());
32    }
33
34    // http://code.google.com/p/android/issues/detail?id=8016
35    public void test_useDaylightTime_Iceland() {
36        TimeZone atlanticReykjavik = TimeZone.getTimeZone("Atlantic/Reykjavik");
37        assertFalse("Reykjavik doesn't use DST", atlanticReykjavik.useDaylightTime());
38    }
39
40    // http://code.google.com/p/android/issues/detail?id=11542
41    public void test_clone_SimpleTimeZone() {
42        SimpleTimeZone stz = new SimpleTimeZone(21600000, "Central Standard Time");
43        stz.setStartYear(1000);
44        stz.inDaylightTime(new Date());
45        stz.clone();
46    }
47
48    // http://b/3049014
49    public void testCustomTimeZoneDisplayNames() {
50        TimeZone tz0001 = new SimpleTimeZone(60000, "ONE MINUTE");
51        TimeZone tz0130 = new SimpleTimeZone(5400000, "ONE HOUR, THIRTY");
52        TimeZone tzMinus0130 = new SimpleTimeZone(-5400000, "NEG ONE HOUR, THIRTY");
53        assertEquals("GMT+00:01", tz0001.getDisplayName(false, TimeZone.SHORT, Locale.US));
54        assertEquals("GMT+01:30", tz0130.getDisplayName(false, TimeZone.SHORT, Locale.US));
55        assertEquals("GMT-01:30", tzMinus0130.getDisplayName(false, TimeZone.SHORT, Locale.US));
56    }
57
58    // http://code.google.com/p/android/issues/detail?id=14395
59    public void testPreHistoricInDaylightTime() throws Exception {
60        TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
61        TimeZone.setDefault(tz);
62        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
63        Date date = sdf.parse("1902-11-01T00:00:00.000+0800");
64        assertEquals(-2119680000000L, date.getTime());
65        assertEquals(-28800000, tz.getOffset(date.getTime()));
66        assertFalse(tz.inDaylightTime(date));
67        assertEquals("Fri Oct 31 08:00:00 PST 1902", date.toString());
68        assertEquals("31 Oct 1902 16:00:00 GMT", date.toGMTString());
69        // Any time before we have transition data is considered non-daylight, even in summer.
70        date = sdf.parse("1902-06-01T00:00:00.000+0800");
71        assertEquals(-28800000, tz.getOffset(date.getTime()));
72        assertFalse(tz.inDaylightTime(date));
73    }
74
75    public void testPreHistoricOffsets() throws Exception {
76        // The "Asia/Saigon" time zone has just a few transitions, and hasn't changed in a
77        // long time, which is convenient for testing:
78        //
79        // libcore.util.ZoneInfo[Asia/Saigon,mRawOffset=25200000,mUseDst=false]
80        // 0 : time=-2005974400 Fri Jun 08 16:53:20 1906 GMT+00:00 = Fri Jun 08 23:59:40 1906 ICT isDst=0 offset=  380 gmtOffset=25580
81        // 1 : time=-1855983920 Fri Mar 10 16:54:40 1911 GMT+00:00 = Fri Mar 10 23:54:40 1911 ICT isDst=0 offset=    0 gmtOffset=25200
82        // 2 : time=-1819954800 Tue Apr 30 17:00:00 1912 GMT+00:00 = Wed May 01 01:00:00 1912 ICT isDst=0 offset= 3600 gmtOffset=28800
83        // 3 : time=-1220428800 Thu Apr 30 16:00:00 1931 GMT+00:00 = Thu Apr 30 23:00:00 1931 ICT isDst=0 offset=    0 gmtOffset=25200
84        TimeZone tz = TimeZone.getTimeZone("Asia/Saigon");
85
86        // Times before our first transition should assume we're still following that transition.
87        // Note: the RI reports 25600 here because it has more transitions than we do.
88        assertNonDaylightOffset(25580, -2005975000L, tz);
89
90        assertNonDaylightOffset(25580, -2005974400L, tz); // 0
91        assertNonDaylightOffset(25580, -2005974000L, tz);
92
93        assertNonDaylightOffset(25200, -1855983920L, tz); // 1
94        assertNonDaylightOffset(25200, -1855983900L, tz);
95
96        assertNonDaylightOffset(28800, -1819954800L, tz); // 2
97        assertNonDaylightOffset(28800, -1819954000L, tz);
98
99        assertNonDaylightOffset(25200, -1220428800L, tz); // 3
100
101        // Times after out last transition should assume we're still following that transition.
102        assertNonDaylightOffset(25200, -1220428000L, tz);
103
104        // There are plenty more examples. "Africa/Bissau" is one:
105        //
106        // libcore.util.ZoneInfo[Africa/Bissau,mRawOffset=0,mUseDst=false]
107        // 0 : time=-1849388260 Fri May 26 01:02:20 1911 GMT+00:00 = Fri May 26 00:02:20 1911 GMT isDst=0 offset=-3600 gmtOffset=-3600
108        // 1 : time=  157770000 Wed Jan 01 01:00:00 1975 GMT+00:00 = Wed Jan 01 01:00:00 1975 GMT isDst=0 offset=    0 gmtOffset=0
109        tz = TimeZone.getTimeZone("Africa/Bissau");
110        assertNonDaylightOffset(-3600, -1849388300L, tz);
111        assertNonDaylightOffset(-3600, -1849388260L, tz); // 0
112        assertNonDaylightOffset(-3600, -1849388200L, tz);
113        assertNonDaylightOffset(0, 157770000L, tz); // 1
114        assertNonDaylightOffset(0, 157780000L, tz);
115    }
116
117    private static void assertNonDaylightOffset(int expectedOffsetSeconds, long epochSeconds, TimeZone tz) {
118        assertEquals(expectedOffsetSeconds * 1000, tz.getOffset(epochSeconds * 1000));
119        assertFalse(tz.inDaylightTime(new Date(epochSeconds * 1000)));
120    }
121
122    public void testZeroTransitionZones() throws Exception {
123        // Zones with no transitions historical or future seem ideal for testing.
124        String[] ids = new String[] { "Africa/Bujumbura", "Indian/Cocos", "Pacific/Wake", "UTC" };
125        for (String id : ids) {
126            TimeZone tz = TimeZone.getTimeZone(id);
127            assertFalse(tz.useDaylightTime());
128            assertFalse(tz.inDaylightTime(new Date(Integer.MIN_VALUE)));
129            assertFalse(tz.inDaylightTime(new Date(0)));
130            assertFalse(tz.inDaylightTime(new Date(Integer.MAX_VALUE)));
131            int currentOffset = tz.getOffset(new Date(0).getTime());
132            assertEquals(currentOffset, tz.getOffset(new Date(Integer.MIN_VALUE).getTime()));
133            assertEquals(currentOffset, tz.getOffset(new Date(Integer.MAX_VALUE).getTime()));
134        }
135    }
136
137    // http://code.google.com/p/android/issues/detail?id=16608
138    public void testHelsinkiOverflow() throws Exception {
139        long time = 2147483648000L;// Tue, 19 Jan 2038 03:14:08 GMT
140        TimeZone timeZone = TimeZone.getTimeZone("Europe/Helsinki");
141        long offset = timeZone.getOffset(time);
142        // This might cause us trouble if Europe/Helsinki changes its rules. See the bug for
143        // details of the intent of this test.
144        assertEquals(7200000, offset);
145    }
146
147    // http://code.google.com/p/android/issues/detail?id=11918
148    public void testHasSameRules() throws Exception {
149        TimeZone denver = TimeZone.getTimeZone("America/Denver");
150        TimeZone phoenix = TimeZone.getTimeZone("America/Phoenix");
151        assertFalse(denver.hasSameRules(phoenix));
152    }
153
154    // http://code.google.com/p/android/issues/detail?id=24036
155    public void testNullId() throws Exception {
156        try {
157            TimeZone.getTimeZone(null);
158            fail();
159        } catch (NullPointerException expected) {
160        }
161    }
162
163    // http://b.corp.google.com/issue?id=6556561
164    public void testCustomZoneIds() throws Exception {
165        // These are all okay (and equivalent).
166        assertEquals("GMT+05:00", TimeZone.getTimeZone("GMT+05:00").getID());
167        assertEquals("GMT+05:00", TimeZone.getTimeZone("GMT+5:00").getID());
168        assertEquals("GMT+05:00", TimeZone.getTimeZone("GMT+0500").getID());
169        assertEquals("GMT+05:00", TimeZone.getTimeZone("GMT+500").getID());
170        assertEquals("GMT+05:00", TimeZone.getTimeZone("GMT+5").getID());
171        // These aren't.
172        assertEquals("GMT", TimeZone.getTimeZone("GMT+5.5").getID());
173        assertEquals("GMT", TimeZone.getTimeZone("GMT+5:5").getID());
174        assertEquals("GMT", TimeZone.getTimeZone("GMT+5:0").getID());
175        assertEquals("GMT", TimeZone.getTimeZone("GMT+5:005").getID());
176        assertEquals("GMT", TimeZone.getTimeZone("GMT+5:000").getID());
177        assertEquals("GMT", TimeZone.getTimeZone("GMT+005:00").getID());
178        assertEquals("GMT", TimeZone.getTimeZone("GMT+05:99").getID());
179        assertEquals("GMT", TimeZone.getTimeZone("GMT+28:00").getID());
180        assertEquals("GMT", TimeZone.getTimeZone("GMT+05:00.00").getID());
181        assertEquals("GMT", TimeZone.getTimeZone("GMT+05:00:00").getID());
182        assertEquals("GMT", TimeZone.getTimeZone("GMT+5:").getID());
183        assertEquals("GMT", TimeZone.getTimeZone("GMT+junk").getID());
184        assertEquals("GMT", TimeZone.getTimeZone("GMT+5junk").getID());
185        assertEquals("GMT", TimeZone.getTimeZone("GMT+5:junk").getID());
186        assertEquals("GMT", TimeZone.getTimeZone("GMT+5:00junk").getID());
187        assertEquals("GMT", TimeZone.getTimeZone("junkGMT+5:00").getID());
188        assertEquals("GMT", TimeZone.getTimeZone("junk").getID());
189        assertEquals("GMT", TimeZone.getTimeZone("gmt+5:00").getID());
190    }
191
192    public void test_getDSTSavings() throws Exception {
193      assertEquals(0, TimeZone.getTimeZone("UTC").getDSTSavings());
194      assertEquals(3600000, TimeZone.getTimeZone("America/Los_Angeles").getDSTSavings());
195      assertEquals(1800000, TimeZone.getTimeZone("Australia/Lord_Howe").getDSTSavings());
196    }
197
198    public void testSimpleTimeZoneDoesNotCallOverrideableMethodsFromConstructor() {
199        new SimpleTimeZone(0, "X", Calendar.MARCH, 1, 1, 1, Calendar.SEPTEMBER, 1, 1, 1) {
200            @Override public void setStartRule(int m, int d, int dow, int time) {
201                fail();
202            }
203            @Override public void setStartRule(int m, int d, int dow, int time, boolean after) {
204                fail();
205            }
206            @Override public void setEndRule(int m, int d, int dow, int time) {
207                fail();
208            }
209            @Override public void setEndRule(int m, int d, int dow, int time, boolean after) {
210                fail();
211            }
212        };
213    }
214
215    // http://b/7955614 and http://b/8026776.
216    public void testDisplayNames() throws Exception {
217        // Check that there are no time zones that use DST but have the same display name for
218        // both standard and daylight time.
219        StringBuilder failures = new StringBuilder();
220        for (String id : TimeZone.getAvailableIDs()) {
221            TimeZone tz = TimeZone.getTimeZone(id);
222            String longDst = tz.getDisplayName(true, TimeZone.LONG, Locale.US);
223            String longStd = tz.getDisplayName(false, TimeZone.LONG, Locale.US);
224            String shortDst = tz.getDisplayName(true, TimeZone.SHORT, Locale.US);
225            String shortStd = tz.getDisplayName(false, TimeZone.SHORT, Locale.US);
226
227            if (tz.useDaylightTime()) {
228                // The long std and dst strings must differ!
229                if (longDst.equals(longStd)) {
230                    failures.append(String.format("\n%20s: LD='%s' LS='%s'!",
231                                                  id, longDst, longStd));
232                }
233                // The short std and dst strings must differ!
234                if (shortDst.equals(shortStd)) {
235                    failures.append(String.format("\n%20s: SD='%s' SS='%s'!",
236                                                  id, shortDst, shortStd));
237                }
238
239                // If the short std matches the long dst, or the long std matches the short dst,
240                // it probably means we have a time zone that icu4c doesn't believe has ever
241                // observed dst.
242                if (shortStd.equals(longDst)) {
243                    failures.append(String.format("\n%20s: SS='%s' LD='%s'!",
244                                                  id, shortStd, longDst));
245                }
246                if (longStd.equals(shortDst)) {
247                    failures.append(String.format("\n%20s: LS='%s' SD='%s'!",
248                                                  id, longStd, shortDst));
249                }
250
251                // The long and short dst strings must differ!
252                if (longDst.equals(shortDst) && !longDst.startsWith("GMT")) {
253                  failures.append(String.format("\n%20s: LD='%s' SD='%s'!",
254                                                id, longDst, shortDst));
255                }
256            }
257
258            // Sanity check that whenever a display name is just a GMT string that it's the
259            // right GMT string.
260            String gmtDst = formatGmtString(tz, true);
261            String gmtStd = formatGmtString(tz, false);
262            if (isGmtString(longDst) && !longDst.equals(gmtDst)) {
263                failures.append(String.format("\n%s: LD %s", id, longDst));
264            }
265            if (isGmtString(longStd) && !longStd.equals(gmtStd)) {
266                failures.append(String.format("\n%s: LS %s", id, longStd));
267            }
268            if (isGmtString(shortDst) && !shortDst.equals(gmtDst)) {
269                failures.append(String.format("\n%s: SD %s", id, shortDst));
270            }
271            if (isGmtString(shortStd) && !shortStd.equals(gmtStd)) {
272                failures.append(String.format("\n%s: SS %s", id, shortStd));
273            }
274        }
275        assertEquals("", failures.toString());
276    }
277
278    public void testSantiago() throws Exception {
279        TimeZone tz = TimeZone.getTimeZone("America/Santiago");
280        assertEquals("Chile Summer Time", tz.getDisplayName(true, TimeZone.LONG, Locale.US));
281        assertEquals("Chile Standard Time", tz.getDisplayName(false, TimeZone.LONG, Locale.US));
282        assertEquals("GMT-03:00", tz.getDisplayName(true, TimeZone.SHORT, Locale.US));
283        assertEquals("GMT-04:00", tz.getDisplayName(false, TimeZone.SHORT, Locale.US));
284    }
285
286    // http://b/7955614
287    public void testApia() throws Exception {
288        TimeZone tz = TimeZone.getTimeZone("Pacific/Apia");
289        assertEquals("Samoa Daylight Time", tz.getDisplayName(true, TimeZone.LONG, Locale.US));
290        assertEquals("Samoa Standard Time", tz.getDisplayName(false, TimeZone.LONG, Locale.US));
291        assertEquals("GMT+14:00", tz.getDisplayName(true, TimeZone.SHORT, Locale.US));
292        assertEquals("GMT+13:00", tz.getDisplayName(false, TimeZone.SHORT, Locale.US));
293    }
294
295    private static boolean isGmtString(String s) {
296        return s.startsWith("GMT+") || s.startsWith("GMT-");
297    }
298
299    private static String formatGmtString(TimeZone tz, boolean daylight) {
300        int offset = tz.getRawOffset();
301        if (daylight) {
302            offset += tz.getDSTSavings();
303        }
304        offset /= 60000;
305        char sign = '+';
306        if (offset < 0) {
307            sign = '-';
308            offset = -offset;
309        }
310        return String.format("GMT%c%02d:%02d", sign, offset / 60, offset % 60);
311    }
312
313    public void testAllDisplayNames() throws Exception {
314      for (Locale locale : Locale.getAvailableLocales()) {
315        for (String id : TimeZone.getAvailableIDs()) {
316          TimeZone tz = TimeZone.getTimeZone(id);
317          assertNotNull(tz.getDisplayName(false, TimeZone.LONG, locale));
318        }
319      }
320    }
321}
322