1/* 2 * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24/* 25 * @test 26 * @library /java/text/testlib 27 * @summary test Time Zone Boundary 28 */ 29 30import java.text.*; 31import java.util.*; 32 33/** 34 * A test which discovers the boundaries of DST programmatically and verifies 35 * that they are correct. 36 */ 37public class TimeZoneBoundaryTest extends IntlTest 38{ 39 static final int ONE_SECOND = 1000; 40 static final int ONE_MINUTE = 60*ONE_SECOND; 41 static final int ONE_HOUR = 60*ONE_MINUTE; 42 static final long ONE_DAY = 24*ONE_HOUR; 43 static final long ONE_YEAR = (long)(365.25 * ONE_DAY); 44 static final long SIX_MONTHS = ONE_YEAR / 2; 45 46 static final int MONTH_LENGTH[] = {31,29,31,30,31,30,31,31,30,31,30,31}; 47 48 // These values are empirically determined to be correct 49 static final long PST_1997_BEG = 860320800000L; 50 static final long PST_1997_END = 877856400000L; 51 52 // Minimum interval for binary searches in ms; should be no larger 53 // than 1000. 54 static final long INTERVAL = 10; // Milliseconds 55 56 static final String AUSTRALIA = "Australia/Adelaide"; 57 static final long AUSTRALIA_1997_BEG = 877797000000L; 58 static final long AUSTRALIA_1997_END = 859653000000L; 59 60 public static void main(String[] args) throws Exception { 61 new TimeZoneBoundaryTest().run(args); 62 } 63 64 /** 65 * Date.toString().substring() Boundary Test 66 * Look for a DST changeover to occur within 6 months of the given Date. 67 * The initial Date.toString() should yield a string containing the 68 * startMode as a SUBSTRING. The boundary will be tested to be 69 * at the expectedBoundary value. 70 */ 71 void findDaylightBoundaryUsingDate(Date d, String startMode, long expectedBoundary) 72 { 73 // Given a date with a year start, find the Daylight onset 74 // and end. The given date should be 1/1/xx in some year. 75 76 if (d.toString().indexOf(startMode) == -1) 77 { 78 logln("Error: " + startMode + " not present in " + d); 79 } 80 81 // Use a binary search, assuming that we have a Standard 82 // time at the midpoint. 83 long min = d.getTime(); 84 long max = min + SIX_MONTHS; 85 86 while ((max - min) > INTERVAL) 87 { 88 long mid = (min + max) >> 1; 89 String s = new Date(mid).toString(); 90 // logln(s); 91 if (s.indexOf(startMode) != -1) 92 { 93 min = mid; 94 } 95 else 96 { 97 max = mid; 98 } 99 } 100 101 logln("Date Before: " + showDate(min)); 102 logln("Date After: " + showDate(max)); 103 long mindelta = expectedBoundary - min; 104 long maxdelta = max - expectedBoundary; 105 if (mindelta >= 0 && mindelta <= INTERVAL && 106 mindelta >= 0 && mindelta <= INTERVAL) 107 logln("PASS: Expected boundary at " + expectedBoundary); 108 else 109 errln("FAIL: Expected boundary at " + expectedBoundary); 110 } 111 112 void findDaylightBoundaryUsingTimeZone(Date d, boolean startsInDST, long expectedBoundary) 113 { 114 findDaylightBoundaryUsingTimeZone(d, startsInDST, expectedBoundary, 115 TimeZone.getDefault()); 116 } 117 118 void findDaylightBoundaryUsingTimeZone(Date d, boolean startsInDST, 119 long expectedBoundary, TimeZone tz) 120 { 121 // Given a date with a year start, find the Daylight onset 122 // and end. The given date should be 1/1/xx in some year. 123 124 // Use a binary search, assuming that we have a Standard 125 // time at the midpoint. 126 long min = d.getTime(); 127 long max = min + SIX_MONTHS; 128 129 if (tz.inDaylightTime(d) != startsInDST) 130 { 131 errln("FAIL: " + tz.getID() + " inDaylightTime(" + 132 d + ") != " + startsInDST); 133 startsInDST = !startsInDST; // Flip over; find the apparent value 134 } 135 136 if (tz.inDaylightTime(new Date(max)) == startsInDST) 137 { 138 errln("FAIL: " + tz.getID() + " inDaylightTime(" + 139 (new Date(max)) + ") != " + (!startsInDST)); 140 return; 141 } 142 143 while ((max - min) > INTERVAL) 144 { 145 long mid = (min + max) >> 1; 146 boolean isIn = tz.inDaylightTime(new Date(mid)); 147 if (isIn == startsInDST) 148 { 149 min = mid; 150 } 151 else 152 { 153 max = mid; 154 } 155 } 156 157 logln(tz.getID() + " Before: " + showDate(min, tz)); 158 logln(tz.getID() + " After: " + showDate(max, tz)); 159 160 long mindelta = expectedBoundary - min; 161 long maxdelta = max - expectedBoundary; 162 if (mindelta >= 0 && mindelta <= INTERVAL && 163 mindelta >= 0 && mindelta <= INTERVAL) 164 logln("PASS: Expected boundary at " + expectedBoundary); 165 else 166 errln("FAIL: Expected boundary at " + expectedBoundary); 167 } 168 169 private static String showDate(long l) 170 { 171 return showDate(new Date(l)); 172 } 173 174 @SuppressWarnings("deprecation") 175 private static String showDate(Date d) 176 { 177 return "" + d.getYear() + "/" + showNN(d.getMonth()+1) + "/" + showNN(d.getDate()) + 178 " " + showNN(d.getHours()) + ":" + showNN(d.getMinutes()) + 179 " \"" + d + "\" = " + 180 d.getTime(); 181 } 182 183 private static String showDate(long l, TimeZone z) 184 { 185 return showDate(new Date(l), z); 186 } 187 188 @SuppressWarnings("deprecation") 189 private static String showDate(Date d, TimeZone zone) 190 { 191 DateFormat fmt = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG); 192 fmt.setTimeZone(zone); 193 return "" + d.getYear() + "/" + showNN(d.getMonth()+1) + "/" + showNN(d.getDate()) + 194 " " + showNN(d.getHours()) + ":" + showNN(d.getMinutes()) + 195 " \"" + d + "\" = " + 196 fmt.format(d); 197 } 198 199 private static String showNN(int n) 200 { 201 return ((n < 10) ? "0" : "") + n; 202 } 203 204 /** 205 * Given a date, a TimeZone, and expected values for inDaylightTime, 206 * useDaylightTime, zone and DST offset, verify that this is the case. 207 */ 208 void verifyDST(Date d, TimeZone time_zone, 209 boolean expUseDaylightTime, boolean expInDaylightTime, 210 int expZoneOffset, int expDSTOffset) 211 { 212 logln("-- Verifying time " + d + 213 " in zone " + time_zone.getID()); 214 215 if (time_zone.inDaylightTime(d) == expInDaylightTime) 216 logln("PASS: inDaylightTime = " + time_zone.inDaylightTime(d)); 217 else 218 errln("FAIL: inDaylightTime = " + time_zone.inDaylightTime(d)); 219 220 if (time_zone.useDaylightTime() == expUseDaylightTime) 221 logln("PASS: useDaylightTime = " + time_zone.useDaylightTime()); 222 else 223 errln("FAIL: useDaylightTime = " + time_zone.useDaylightTime()); 224 225 if (time_zone.getRawOffset() == expZoneOffset) 226 logln("PASS: getRawOffset() = " + expZoneOffset/(double)ONE_HOUR); 227 else 228 errln("FAIL: getRawOffset() = " + time_zone.getRawOffset()/(double)ONE_HOUR + 229 "; expected " + expZoneOffset/(double)ONE_HOUR); 230 231 GregorianCalendar gc = new GregorianCalendar(time_zone); 232 gc.setTime(d); 233 int offset = time_zone.getOffset(gc.get(gc.ERA), gc.get(gc.YEAR), gc.get(gc.MONTH), 234 gc.get(gc.DAY_OF_MONTH), gc.get(gc.DAY_OF_WEEK), 235 ((gc.get(gc.HOUR_OF_DAY) * 60 + 236 gc.get(gc.MINUTE)) * 60 + 237 gc.get(gc.SECOND)) * 1000 + 238 gc.get(gc.MILLISECOND)); 239 if (offset == expDSTOffset) 240 logln("PASS: getOffset() = " + offset/(double)ONE_HOUR); 241 else 242 errln("FAIL: getOffset() = " + offset/(double)ONE_HOUR + 243 "; expected " + expDSTOffset/(double)ONE_HOUR); 244 } 245 246 @SuppressWarnings("deprecation") 247 public void TestBoundaries() 248 { 249 TimeZone pst = TimeZone.getTimeZone("PST"); 250 TimeZone save = TimeZone.getDefault(); 251 try { 252 TimeZone.setDefault(pst); 253 254 // DST changeover for PST is 4/6/1997 at 2 hours past midnight 255 Date d = new Date(97,Calendar.APRIL,6); 256 257 // i is minutes past midnight standard time 258 for (int i=60; i<=180; i+=15) 259 { 260 boolean inDST = (i >= 120); 261 Date e = new Date(d.getTime() + i*60*1000); 262 verifyDST(e, pst, true, inDST, -8*ONE_HOUR, 263 inDST ? -7*ONE_HOUR : -8*ONE_HOUR); 264 } 265 266 logln("========================================"); 267 findDaylightBoundaryUsingDate(new Date(97,0,1), "PST", PST_1997_BEG); 268 logln("========================================"); 269 findDaylightBoundaryUsingDate(new Date(97,6,1), "PDT", PST_1997_END); 270 271 // Southern hemisphere test 272 logln("========================================"); 273 TimeZone z = TimeZone.getTimeZone(AUSTRALIA); 274 findDaylightBoundaryUsingTimeZone(new Date(97,0,1), true, AUSTRALIA_1997_END, z); 275 276 logln("========================================"); 277 findDaylightBoundaryUsingTimeZone(new Date(97,0,1), false, PST_1997_BEG); 278 logln("========================================"); 279 findDaylightBoundaryUsingTimeZone(new Date(97,6,1), true, PST_1997_END); 280 } finally { 281 TimeZone.setDefault(save); 282 } 283 } 284 285 void testUsingBinarySearch(SimpleTimeZone tz, Date d, long expectedBoundary) 286 { 287 // Given a date with a year start, find the Daylight onset 288 // and end. The given date should be 1/1/xx in some year. 289 290 // Use a binary search, assuming that we have a Standard 291 // time at the midpoint. 292 long min = d.getTime(); 293 long max = min + (long)(365.25 / 2 * ONE_DAY); 294 295 // First check the boundaries 296 boolean startsInDST = tz.inDaylightTime(d); 297 298 if (tz.inDaylightTime(new Date(max)) == startsInDST) 299 { 300 logln("Error: inDaylightTime(" + (new Date(max)) + ") != " + (!startsInDST)); 301 } 302 303 while ((max - min) > INTERVAL) 304 { 305 long mid = (min + max) >> 1; 306 if (tz.inDaylightTime(new Date(mid)) == startsInDST) 307 { 308 min = mid; 309 } 310 else 311 { 312 max = mid; 313 } 314 } 315 316 logln("Binary Search Before: " + showDate(min)); 317 logln("Binary Search After: " + showDate(max)); 318 319 long mindelta = expectedBoundary - min; 320 long maxdelta = max - expectedBoundary; 321 if (mindelta >= 0 && mindelta <= INTERVAL && 322 mindelta >= 0 && mindelta <= INTERVAL) 323 logln("PASS: Expected boundary at " + expectedBoundary); 324 else 325 errln("FAIL: Expected boundary at " + expectedBoundary); 326 } 327 328 /* 329 static void testUsingMillis(Date d, boolean startsInDST) 330 { 331 long millis = d.getTime(); 332 long max = millis + (long)(370 * ONE_DAY); // A year plus extra 333 334 boolean lastDST = startsInDST; 335 while (millis < max) 336 { 337 cal.setTime(new Date(millis)); 338 boolean inDaylight = cal.inDaylightTime(); 339 340 if (inDaylight != lastDST) 341 { 342 logln("Switch " + (inDaylight ? "into" : "out of") 343 + " DST at " + (new Date(millis))); 344 lastDST = inDaylight; 345 } 346 347 millis += 15*ONE_MINUTE; 348 } 349 } 350 */ 351 352 /** 353 * Test new rule formats. 354 */ 355 @SuppressWarnings("deprecation") 356 public void TestNewRules() 357 { 358 //logln(Locale.getDefault().getDisplayName()); 359 //logln(TimeZone.getDefault().getID()); 360 //logln(new Date(0)); 361 362 if (true) 363 { 364 // Doesn't matter what the default TimeZone is here, since we 365 // are creating our own TimeZone objects. 366 367 SimpleTimeZone tz; 368 369 logln("-----------------------------------------------------------------"); 370 logln("Aug 2ndTues .. Mar 15"); 371 tz = new SimpleTimeZone(-8*ONE_HOUR, "Test_1", 372 Calendar.AUGUST, 2, Calendar.TUESDAY, 2*ONE_HOUR, 373 Calendar.MARCH, 15, 0, 2*ONE_HOUR); 374 //logln(tz.toString()); 375 logln("========================================"); 376 testUsingBinarySearch(tz, new Date(97,0,1), 858416400000L); 377 logln("========================================"); 378 testUsingBinarySearch(tz, new Date(97,6,1), 871380000000L); 379 380 logln("-----------------------------------------------------------------"); 381 logln("Apr Wed>=14 .. Sep Sun<=20"); 382 tz = new SimpleTimeZone(-8*ONE_HOUR, "Test_2", 383 Calendar.APRIL, 14, -Calendar.WEDNESDAY, 2*ONE_HOUR, 384 Calendar.SEPTEMBER, -20, -Calendar.SUNDAY, 2*ONE_HOUR); 385 //logln(tz.toString()); 386 logln("========================================"); 387 testUsingBinarySearch(tz, new Date(97,0,1), 861184800000L); 388 logln("========================================"); 389 testUsingBinarySearch(tz, new Date(97,6,1), 874227600000L); 390 } 391 392 /* 393 if (true) 394 { 395 logln("========================================"); 396 logln("Stepping using millis"); 397 testUsingMillis(new Date(97,0,1), false); 398 } 399 400 if (true) 401 { 402 logln("========================================"); 403 logln("Stepping using fields"); 404 testUsingFields(1997, false); 405 } 406 407 if (false) 408 { 409 cal.clear(); 410 cal.set(1997, 3, 5, 10, 0); 411 // cal.inDaylightTime(); 412 logln("Date = " + cal.getTime()); 413 logln("Millis = " + cal.getTime().getTime()/3600000); 414 } 415 */ 416 } 417 418 //---------------------------------------------------------------------- 419 //---------------------------------------------------------------------- 420 //---------------------------------------------------------------------- 421 // Long Bug 422 //---------------------------------------------------------------------- 423 //---------------------------------------------------------------------- 424 //---------------------------------------------------------------------- 425 426 //public void Test3() 427 //{ 428 // findDaylightBoundaryUsingTimeZone(new Date(97,6,1), true); 429 //} 430 431 /** 432 * Find boundaries by stepping. 433 */ 434 @SuppressWarnings("deprecation") 435 void findBoundariesStepwise(int year, long interval, TimeZone z, int expectedChanges) 436 { 437 Date d = new Date(year - 1900, Calendar.JANUARY, 1); 438 long time = d.getTime(); // ms 439 long limit = time + ONE_YEAR + ONE_DAY; 440 boolean lastState = z.inDaylightTime(d); 441 int changes = 0; 442 logln("-- Zone " + z.getID() + " starts in " + year + " with DST = " + lastState); 443 logln("useDaylightTime = " + z.useDaylightTime()); 444 while (time < limit) 445 { 446 d.setTime(time); 447 boolean state = z.inDaylightTime(d); 448 if (state != lastState) 449 { 450 logln((state ? "Entry " : "Exit ") + 451 "at " + d); 452 lastState = state; 453 ++changes; 454 } 455 time += interval; 456 } 457 if (changes == 0) 458 { 459 if (!lastState && !z.useDaylightTime()) logln("No DST"); 460 else errln("FAIL: Timezone<" + z.getID() + "> DST all year, or no DST with true useDaylightTime"); 461 } 462 else if (changes != 2) 463 { 464 errln("FAIL: Timezone<" + z.getID() + "> " + changes + " changes seen; should see 0 or 2"); 465 } 466 else if (!z.useDaylightTime()) 467 { 468 errln("FAIL: Timezone<" + z.getID() + "> useDaylightTime false but 2 changes seen"); 469 } 470 if (changes != expectedChanges) 471 { 472 errln("FAIL: Timezone<" + z.getID() + "> " + changes + " changes seen; expected " + expectedChanges); 473 } 474 } 475 476 public void TestStepwise() 477 { 478 findBoundariesStepwise(1997, ONE_DAY, TimeZone.getTimeZone("ACT"), 0); 479 // "EST" is disabled because its behavior depends on the mapping property. (6466476). 480 //findBoundariesStepwise(1997, ONE_DAY, TimeZone.getTimeZone("EST"), 2); 481 findBoundariesStepwise(1997, ONE_DAY, TimeZone.getTimeZone("HST"), 0); 482 findBoundariesStepwise(1997, ONE_DAY, TimeZone.getTimeZone("PST"), 2); 483 findBoundariesStepwise(1997, ONE_DAY, TimeZone.getTimeZone("PST8PDT"), 2); 484 findBoundariesStepwise(1997, ONE_DAY, TimeZone.getTimeZone("SystemV/PST"), 0); 485 findBoundariesStepwise(1997, ONE_DAY, TimeZone.getTimeZone("SystemV/PST8PDT"), 2); 486 findBoundariesStepwise(1997, ONE_DAY, TimeZone.getTimeZone("Japan"), 0); 487 findBoundariesStepwise(1997, ONE_DAY, TimeZone.getTimeZone("Europe/Paris"), 2); 488 findBoundariesStepwise(1997, ONE_DAY, TimeZone.getTimeZone("America/Los_Angeles"), 2); 489 findBoundariesStepwise(1997, ONE_DAY, TimeZone.getTimeZone(AUSTRALIA), 2); 490 } 491} 492