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