1 /*
  2  * Copyright (c) 1997, 2023, 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  * @bug 4028006 4044013 4096694 4107276 4107570 4112869 4130885 7039469 7126465 7158483
 27  *      8008577 8077685 8098547 8133321 8138716 8148446 8151876 8159684 8166875 8181157
 28  *      8228469 8274407 8285844 8305113
 29  * @modules java.base/sun.util.resources
 30  * @summary test TimeZone
 31  * @run junit TimeZoneTest
 32  */
 33 
 34 import java.io.*;
 35 import java.text.*;
 36 import java.util.*;
 37 import sun.util.resources.LocaleData;
 38 
 39 import org.junit.jupiter.api.Test;
 40 
 41 import static org.junit.jupiter.api.Assertions.fail;
 42 
 43 public class TimeZoneTest
 44 {
 45     static final int millisPerHour = 3600000;
 46 
 47     /**
 48      * Bug 4130885
 49      * Certain short zone IDs, used since 1.1.x, are incorrect.
 50      *
 51      * The worst of these is:
 52      *
 53      * "CAT" (Central African Time) should be GMT+2:00, but instead returns a
 54      * zone at GMT-1:00. The zone at GMT-1:00 should be called EGT, CVT, EGST,
 55      * or AZOST, depending on which zone is meant, but in no case is it CAT.
 56      *
 57      * Other wrong zone IDs:
 58      *
 59      * ECT (European Central Time) GMT+1:00: ECT is Ecuador Time,
 60      * GMT-5:00. European Central time is abbreviated CEST.
 61      *
 62      * SST (Solomon Island Time) GMT+11:00. SST is actually Samoa Standard Time,
 63      * GMT-11:00. Solomon Island time is SBT.
 64      *
 65      * NST (New Zealand Time) GMT+12:00. NST is the abbreviation for
 66      * Newfoundland Standard Time, GMT-3:30. New Zealanders use NZST.
 67      *
 68      * AST (Alaska Standard Time) GMT-9:00. [This has already been noted in
 69      * another bug.] It should be "AKST". AST is Atlantic Standard Time,
 70      * GMT-4:00.
 71      *
 72      * PNT (Phoenix Time) GMT-7:00. PNT usually means Pitcairn Time,
 73      * GMT-8:30. There is no standard abbreviation for Phoenix time, as distinct
 74      * from MST with daylight savings.
 75      *
 76      * In addition to these problems, a number of zones are FAKE. That is, they
 77      * don't match what people use in the real world.
 78      *
 79      * FAKE zones:
 80      *
 81      * EET (should be EEST)
 82      * ART (should be EET)
 83      * MET (should be IRST)
 84      * NET (should be AMST)
 85      * PLT (should be PKT)
 86      * BST (should be BDT)
 87      * VST (should be ICT)
 88      * CTT (should be CST) +
 89      * ACT (should be CST) +
 90      * AET (should be EST) +
 91      * MIT (should be WST) +
 92      * IET (should be EST) +
 93      * PRT (should be AST) +
 94      * CNT (should be NST)
 95      * AGT (should be ARST)
 96      * BET (should be EST) +
 97      *
 98      * + A zone with the correct name already exists and means something
 99      * else. E.g., EST usually indicates the US Eastern zone, so it cannot be
100      * used for Brazil (BET).
101      */
102     @Test
103     public void TestShortZoneIDs() throws Exception {
104 
105         ZoneDescriptor[] JDK_116_REFERENCE_LIST = {
106             new ZoneDescriptor("MIT", 780, false), // Samoa no longer observes DST starting 2021b
107             new ZoneDescriptor("HST", -600, false),
108             new ZoneDescriptor("AST", -540, true),
109             new ZoneDescriptor("PST", -480, true),
110             new ZoneDescriptor("PNT", -420, false),
111             new ZoneDescriptor("MST", -420, false),
112             new ZoneDescriptor("CST", -360, true),
113             new ZoneDescriptor("IET", -300, true),
114             new ZoneDescriptor("EST", -300, false),
115             new ZoneDescriptor("PRT", -240, false),
116             new ZoneDescriptor("CNT", -210, true),
117             new ZoneDescriptor("AGT", -180, false),
118             new ZoneDescriptor("BET", -180, false),
119             // new ZoneDescriptor("CAT", -60, false), // Wrong:
120             // As of bug 4130885, fix CAT (Central Africa)
121             new ZoneDescriptor("CAT", 120, false), // Africa/Harare
122             new ZoneDescriptor("GMT", 0, false),
123             new ZoneDescriptor("UTC", 0, false),
124             new ZoneDescriptor("ECT", 60, true),
125             new ZoneDescriptor("ART", 120, true),
126             new ZoneDescriptor("EET", 120, true),
127             new ZoneDescriptor("EAT", 180, false),
128             new ZoneDescriptor("MET", 60, true),
129             new ZoneDescriptor("NET", 240, false),
130             new ZoneDescriptor("PLT", 300, false),
131             new ZoneDescriptor("IST", 330, false),
132             new ZoneDescriptor("BST", 360, false),
133             new ZoneDescriptor("VST", 420, false),
134             new ZoneDescriptor("CTT", 480, false),
135             new ZoneDescriptor("JST", 540, false),
136             new ZoneDescriptor("ACT", 570, false),
137             new ZoneDescriptor("AET", 600, true),
138             new ZoneDescriptor("SST", 660, false),
139             // new ZoneDescriptor("NST", 720, false),
140             // As of bug 4130885, fix NST (New Zealand)
141             new ZoneDescriptor("NST", 720, true), // Pacific/Auckland
142         };
143 
144         Map<String, ZoneDescriptor> hash = new HashMap<>();
145 
146         String[] ids = TimeZone.getAvailableIDs();
147         for (String id : ids) {
148             if (id.length() == 3) {
149                 hash.put(id, new ZoneDescriptor(TimeZone.getTimeZone(id)));
150             }
151         }
152 
153         for (int i = 0; i < JDK_116_REFERENCE_LIST.length; ++i) {
154             ZoneDescriptor referenceZone = JDK_116_REFERENCE_LIST[i];
155             ZoneDescriptor currentZone = hash.get(referenceZone.getID());
156             if (referenceZone.equals(currentZone)) {
157                 System.out.println("ok " + referenceZone);
158             }
159             else {
160                 fail("Fail: Expected " + referenceZone +
161                       "; got " + currentZone);
162             }
163         }
164     }
165 
166     /**
167      * A descriptor for a zone; used to regress the short zone IDs.
168      */
169     static class ZoneDescriptor {
170         String id;
171         int offset; // In minutes
172         boolean daylight;
173 
174         ZoneDescriptor(TimeZone zone) {
175             this.id = zone.getID();
176             this.offset = zone.getRawOffset() / 60000;
177             this.daylight = zone.useDaylightTime();
178         }
179 
180         ZoneDescriptor(String id, int offset, boolean daylight) {
181             this.id = id;
182             this.offset = offset;
183             this.daylight = daylight;
184         }
185 
186         public String getID() { return id; }
187 
188         @Override
189         public boolean equals(Object o) {
190             ZoneDescriptor that = (ZoneDescriptor)o;
191             return that != null &&
192                 id.equals(that.id) &&
193                 offset == that.offset &&
194                 daylight == that.daylight;
195         }
196 
197         @Override
198         public int hashCode() {
199             return id.hashCode() ^ offset | (daylight ? 1 : 0);
200         }
201 
202         @Override
203         public String toString() {
204             int min = offset;
205             char sign = '+';
206             if (min < 0) { sign = '-'; min = -min; }
207 
208             return "Zone[\"" + id + "\", GMT" + sign + (min/60) + ':' +
209                 (min%60<10?"0":"") + (min%60) + ", " +
210                 (daylight ? "Daylight" : "Standard") + "]";
211         }
212 
213         public static int compare(Object o1, Object o2) {
214             ZoneDescriptor i1 = (ZoneDescriptor)o1;
215             ZoneDescriptor i2 = (ZoneDescriptor)o2;
216             if (i1.offset > i2.offset) return 1;
217             if (i1.offset < i2.offset) return -1;
218             if (i1.daylight && !i2.daylight) return 1;
219             if (!i1.daylight && i2.daylight) return -1;
220             return i1.id.compareTo(i2.id);
221         }
222     }
223 
224     static final String formatSeconds(int sec) {
225         char sign = '+';
226         if (sec < 0) { sign = '-'; sec = -sec; }
227         int h = sec / 3_600;
228         int m = sec % 3_600 / 60;
229         sec = sec % 60;
230         return "" + sign + h + ":" + ((m<10) ? "0" : "") + m +
231                 (sec > 0 ? ":" + ((sec < 10) ? "0" : "") + sec : "");
232     }
233     /**
234      * As part of the VM fix (see CCC approved RFE 4028006, bug
235      * 4044013), TimeZone.getTimeZone() has been modified to recognize
236      * generic IDs of the form GMT[+-]hh:mm, GMT[+-]hhmm, and
237      * GMT[+-]hh.  Test this behavior here.
238      *
239      * Bug 4044013
240      *
241      * ID "Custom" is no longer used for TimeZone objects created with
242      * a custom time zone ID, such as "GMT-8". See 4322313.
243      */
244     @Test
245     public void TestCustomParse() throws Exception {
246         Object[] DATA = {
247             // ID               Expected offset in seconds
248             "GMT",              null,
249             "GMT+0",            0,
250             "GMT+1",            60 * 60,
251             "GMT-0030",         -30 * 60,
252             "GMT+15:99",        null,
253             "GMT+",             null,
254             "GMT-",             null,
255             "GMT+0:",           null,
256             "GMT-:",            null,
257             "GMT+0010",         10 * 60, // Interpret this as 00:10
258             "GMT-10",           -10 * 60 * 60,
259             "GMT+30",           null,
260             "GMT-3:30",         -(3 * 60 + 30) * 60,
261             "GMT-230",          -(2 * 60 + 30) * 60,
262             "GMT+00:00:01",     1,
263             "GMT-00:00:01",     -1,
264             "GMT+00000",        null,
265             "GMT+00:00:01:",    null,
266             "GMT+00:00:012",    null,
267             "GMT+00:00:0",      null,
268             "GMT+00:00:",       null,
269         };
270         for (int i=0; i<DATA.length; i+=2) {
271             String id = (String)DATA[i];
272             Integer exp = (Integer)DATA[i+1];
273             TimeZone zone = TimeZone.getTimeZone(id);
274             if (zone.getID().equals("GMT")) {
275                 System.out.println(id + " -> generic GMT");
276                 // When TimeZone.getTimeZone() can't parse the id, it
277                 // returns GMT -- a dubious practice, but required for
278                 // backward compatibility.
279                 if (exp != null) {
280                     throw new Exception("Expected offset of " + formatSeconds(exp.intValue()) +
281                                         " for " + id + ", got parse failure");
282                 }
283             }
284             else {
285                 int ioffset = zone.getRawOffset() / 1_000;
286                 String offset = formatSeconds(ioffset);
287                 System.out.println(id + " -> " + zone.getID() + " GMT" + offset);
288                 if (exp == null) {
289                     throw new Exception("Expected parse failure for " + id +
290                                         ", got offset of " + offset +
291                                         ", id " + zone.getID());
292                 }
293                 else if (ioffset != exp.intValue()) {
294                     throw new Exception("Expected offset of " + formatSeconds(exp.intValue()) +
295                                         ", id Custom, for " + id +
296                                         ", got offset of " + offset +
297                                         ", id " + zone.getID());
298                 }
299             }
300         }
301     }
302 
303     /**
304      * Test the basic functionality of the getDisplayName() API.
305      *
306      * Bug 4112869
307      * Bug 4028006
308      *
309      * See also API change request A41.
310      *
311      * 4/21/98 - make smarter, so the test works if the ext resources
312      * are present or not.
313      */
314     @Test
315     public void TestDisplayName() {
316         TimeZone zone = TimeZone.getTimeZone("PST");
317         String name = zone.getDisplayName(Locale.ENGLISH);
318         System.out.println("PST->" + name);
319         if (!name.equals("Pacific Standard Time"))
320             fail("Fail: Expected \"Pacific Standard Time\"");
321 
322         //*****************************************************************
323         // THE FOLLOWING LINES MUST BE UPDATED IF THE LOCALE DATA CHANGES
324         // THE FOLLOWING LINES MUST BE UPDATED IF THE LOCALE DATA CHANGES
325         // THE FOLLOWING LINES MUST BE UPDATED IF THE LOCALE DATA CHANGES
326         //*****************************************************************
327         Object[] DATA = {
328             new Boolean(false), new Integer(TimeZone.SHORT), "PST",
329             new Boolean(true),  new Integer(TimeZone.SHORT), "PDT",
330             new Boolean(false), new Integer(TimeZone.LONG),  "Pacific Standard Time",
331             new Boolean(true),  new Integer(TimeZone.LONG),  "Pacific Daylight Time",
332         };
333 
334         for (int i=0; i<DATA.length; i+=3) {
335             name = zone.getDisplayName(((Boolean)DATA[i]).booleanValue(),
336                                        ((Integer)DATA[i+1]).intValue(),
337                                        Locale.ENGLISH);
338             if (!name.equals(DATA[i+2]))
339                 fail("Fail: Expected " + DATA[i+2] + "; got " + name);
340         }
341 
342         // Make sure that we don't display the DST name by constructing a fake
343         // PST zone that has DST all year long.
344         SimpleTimeZone zone2 = new SimpleTimeZone(0, "PST");
345         zone2.setStartRule(Calendar.JANUARY, 1, 0);
346         zone2.setEndRule(Calendar.DECEMBER, 31, 0);
347         System.out.println("Modified PST inDaylightTime->" + zone2.inDaylightTime(new Date()));
348         name = zone2.getDisplayName(Locale.ENGLISH);
349         System.out.println("Modified PST->" + name);
350         if (!name.equals("Pacific Standard Time"))
351             fail("Fail: Expected \"Pacific Standard Time\"");
352 
353         // Make sure we get the default display format for Locales
354         // with no display name data.
355         Locale zh_CN = Locale.SIMPLIFIED_CHINESE;
356         name = zone.getDisplayName(zh_CN);
357         //*****************************************************************
358         // THE FOLLOWING LINE MUST BE UPDATED IF THE LOCALE DATA CHANGES
359         // THE FOLLOWING LINE MUST BE UPDATED IF THE LOCALE DATA CHANGES
360         // THE FOLLOWING LINE MUST BE UPDATED IF THE LOCALE DATA CHANGES
361         //*****************************************************************
362         System.out.println("PST(zh_CN)->" + name);
363 
364         // Now be smart -- check to see if zh resource is even present.
365         // If not, we expect the en fallback behavior.
366         ResourceBundle enRB = LocaleData.getBundle("sun.util.resources.TimeZoneNames",
367                                                    Locale.ENGLISH);
368         ResourceBundle zhRB = LocaleData.getBundle("sun.util.resources.TimeZoneNames",
369                                                    zh_CN);
370 
371         boolean noZH = enRB == zhRB;
372 
373         if (noZH) {
374             System.out.println("Warning: Not testing the zh_CN behavior because resource is absent");
375             if (!name.equals("Pacific Standard Time"))
376                 fail("Fail: Expected Pacific Standard Time");
377         }
378         else if (!name.equals("Pacific Standard Time") &&
379                  !name.equals("\u592a\u5e73\u6d0b\u6807\u51c6\u65f6\u95f4") &&
380                  !name.equals("\u5317\u7f8e\u592a\u5e73\u6d0b\u6807\u51c6\u65f6\u95f4") &&
381                  !name.equals("GMT-08:00") &&
382                  !name.equals("GMT-8:00") &&
383                  !name.equals("GMT-0800") &&
384                  !name.equals("GMT-800")) {
385             fail("Fail: Expected GMT-08:00 or something similar\n"
386             + "************************************************************\n"
387             + "THE ABOVE FAILURE MAY JUST MEAN THE LOCALE DATA HAS CHANGED\n"
388             + "************************************************************\n");
389         }
390 
391         // Now try a non-existent zone
392         zone2 = new SimpleTimeZone(90*60*1000, "xyzzy");
393         name = zone2.getDisplayName(Locale.ENGLISH);
394         System.out.println("GMT+90min->" + name);
395         if (!name.equals("GMT+01:30") &&
396             !name.equals("GMT+1:30") &&
397             !name.equals("GMT+0130") &&
398             !name.equals("GMT+130"))
399             fail("Fail: Expected GMT+01:30 or something similar");
400     }
401 
402     @Test
403     public void TestGenericAPI() {
404         String id = "NewGMT";
405         int offset = 12345;
406 
407         SimpleTimeZone zone = new SimpleTimeZone(offset, id);
408         if (zone.useDaylightTime()) {
409             fail("FAIL: useDaylightTime should return false");
410         }
411 
412         TimeZone zoneclone = (TimeZone)zone.clone();
413         if (!zoneclone.equals(zone)) {
414             fail("FAIL: clone or operator== failed");
415         }
416         zoneclone.setID("abc");
417         if (zoneclone.equals(zone)) {
418             fail("FAIL: clone or operator!= failed");
419         }
420 
421         zoneclone = (TimeZone)zone.clone();
422         if (!zoneclone.equals(zone)) {
423             fail("FAIL: clone or operator== failed");
424         }
425         zoneclone.setRawOffset(45678);
426         if (zoneclone.equals(zone)) {
427             fail("FAIL: clone or operator!= failed");
428         }
429 
430         TimeZone saveDefault = TimeZone.getDefault();
431         try {
432             TimeZone.setDefault(zone);
433             TimeZone defaultzone = TimeZone.getDefault();
434             if (defaultzone == zone) {
435                 fail("FAIL: Default object is identical, not clone");
436             }
437             if (!defaultzone.equals(zone)) {
438                 fail("FAIL: Default object is not equal");
439             }
440         }
441         finally {
442             TimeZone.setDefault(saveDefault);
443         }
444     }
445 
446     @SuppressWarnings("deprecation")
447     @Test
448     public void TestRuleAPI()
449     {
450         // ErrorCode status = ZERO_ERROR;
451 
452         int offset = (int)(60*60*1000*1.75); // Pick a weird offset
453         SimpleTimeZone zone = new SimpleTimeZone(offset, "TestZone");
454         if (zone.useDaylightTime()) fail("FAIL: useDaylightTime should return false");
455 
456         // Establish our expected transition times.  Do this with a non-DST
457         // calendar with the (above) declared local offset.
458         GregorianCalendar gc = new GregorianCalendar(zone);
459         gc.clear();
460         gc.set(1990, Calendar.MARCH, 1);
461         long marchOneStd = gc.getTime().getTime(); // Local Std time midnight
462         gc.clear();
463         gc.set(1990, Calendar.JULY, 1);
464         long julyOneStd = gc.getTime().getTime(); // Local Std time midnight
465 
466         // Starting and ending hours, WALL TIME
467         int startHour = (int)(2.25 * 3600000);
468         int endHour   = (int)(3.5  * 3600000);
469 
470         zone.setStartRule(Calendar.MARCH, 1, 0, startHour);
471         zone.setEndRule  (Calendar.JULY,  1, 0, endHour);
472 
473         gc = new GregorianCalendar(zone);
474         // if (failure(status, "new GregorianCalendar")) return;
475 
476         long marchOne = marchOneStd + startHour;
477         long julyOne = julyOneStd + endHour - 3600000; // Adjust from wall to Std time
478 
479         long expMarchOne = 636251400000L;
480         if (marchOne != expMarchOne)
481         {
482             fail("FAIL: Expected start computed as " + marchOne +
483                   " = " + new Date(marchOne));
484             System.out.println("      Should be                  " + expMarchOne +
485                   " = " + new Date(expMarchOne));
486         }
487 
488         long expJulyOne = 646793100000L;
489         if (julyOne != expJulyOne)
490         {
491             fail("FAIL: Expected start computed as " + julyOne +
492                   " = " + new Date(julyOne));
493             System.out.println("      Should be                  " + expJulyOne +
494                   " = " + new Date(expJulyOne));
495         }
496 
497         testUsingBinarySearch(zone, new Date(90, Calendar.JANUARY, 1).getTime(),
498                               new Date(90, Calendar.JUNE, 15).getTime(), marchOne);
499         testUsingBinarySearch(zone, new Date(90, Calendar.JUNE, 1).getTime(),
500                               new Date(90, Calendar.DECEMBER, 31).getTime(), julyOne);
501 
502         if (zone.inDaylightTime(new Date(marchOne - 1000)) ||
503             !zone.inDaylightTime(new Date(marchOne)))
504             fail("FAIL: Start rule broken");
505         if (!zone.inDaylightTime(new Date(julyOne - 1000)) ||
506             zone.inDaylightTime(new Date(julyOne)))
507             fail("FAIL: End rule broken");
508 
509         zone.setStartYear(1991);
510         if (zone.inDaylightTime(new Date(marchOne)) ||
511             zone.inDaylightTime(new Date(julyOne - 1000)))
512             fail("FAIL: Start year broken");
513 
514         // failure(status, "TestRuleAPI");
515         // delete gc;
516         // delete zone;
517     }
518 
519     void testUsingBinarySearch(SimpleTimeZone tz, long min, long max, long expectedBoundary)
520     {
521         // ErrorCode status = ZERO_ERROR;
522         boolean startsInDST = tz.inDaylightTime(new Date(min));
523         // if (failure(status, "SimpleTimeZone::inDaylightTime")) return;
524         if (tz.inDaylightTime(new Date(max)) == startsInDST) {
525             System.out.println("Error: inDaylightTime(" + new Date(max) + ") != " + (!startsInDST));
526             return;
527         }
528         // if (failure(status, "SimpleTimeZone::inDaylightTime")) return;
529         while ((max - min) > INTERVAL) {
530             long mid = (min + max) / 2;
531             if (tz.inDaylightTime(new Date(mid)) == startsInDST) {
532                 min = mid;
533             }
534             else {
535                 max = mid;
536             }
537             // if (failure(status, "SimpleTimeZone::inDaylightTime")) return;
538         }
539         System.out.println("Binary Search Before: " + min + " = " + new Date(min));
540         System.out.println("Binary Search After:  " + max + " = " + new Date(max));
541         long mindelta = expectedBoundary - min;
542         long maxdelta = max - expectedBoundary;
543         if (mindelta >= 0 &&
544             mindelta <= INTERVAL &&
545             mindelta >= 0 &&
546             mindelta <= INTERVAL)
547             System.out.println("PASS: Expected bdry:  " + expectedBoundary + " = " + new Date(expectedBoundary));
548         else
549             fail("FAIL: Expected bdry:  " + expectedBoundary + " = " + new Date(expectedBoundary));
550     }
551 
552     static final int INTERVAL = 100;
553 
554     // Bug 006; verify the offset for a specific zone.
555     @Test
556     public void TestPRTOffset()
557     {
558         TimeZone tz = TimeZone.getTimeZone( "PRT" );
559         if( tz == null ) {
560             fail( "FAIL: TimeZone(PRT) is null" );
561         }
562         else{
563             if (tz.getRawOffset() != (-4*millisPerHour))
564                 fail("FAIL: Offset for PRT should be -4");
565         }
566 
567     }
568 
569     // Test various calls
570     @SuppressWarnings("deprecation")
571     @Test
572     public void TestVariousAPI518()
573     {
574         TimeZone time_zone = TimeZone.getTimeZone("PST");
575         Date d = new Date(97, Calendar.APRIL, 30);
576 
577         System.out.println("The timezone is " + time_zone.getID());
578 
579         if (time_zone.inDaylightTime(d) != true)
580             fail("FAIL: inDaylightTime returned false");
581 
582         if (time_zone.useDaylightTime() != true)
583             fail("FAIL: useDaylightTime returned false");
584 
585         if (time_zone.getRawOffset() != -8*millisPerHour)
586             fail( "FAIL: getRawOffset returned wrong value");
587 
588         GregorianCalendar gc = new GregorianCalendar();
589         gc.setTime(d);
590         if (time_zone.getOffset(gc.AD, gc.get(gc.YEAR), gc.get(gc.MONTH),
591                                 gc.get(gc.DAY_OF_MONTH),
592                                 gc.get(gc.DAY_OF_WEEK), 0)
593             != -7*millisPerHour)
594             fail("FAIL: getOffset returned wrong value");
595     }
596 
597     // Test getAvailableID API
598     @Test
599     public void TestGetAvailableIDs913()
600     {
601         StringBuffer buf = new StringBuffer("TimeZone.getAvailableIDs() = { ");
602         String[] s = TimeZone.getAvailableIDs();
603         for (int i=0; i<s.length; ++i)
604         {
605             if (i > 0) buf.append(", ");
606             buf.append(s[i]);
607         }
608         buf.append(" };");
609         System.out.println(buf.toString());
610 
611         buf.setLength(0);
612         buf.append("TimeZone.getAvailableIDs(GMT+02:00) = { ");
613         s = TimeZone.getAvailableIDs(+2 * 60 * 60 * 1000);
614         for (int i=0; i<s.length; ++i)
615         {
616             if (i > 0) buf.append(", ");
617             buf.append(s[i]);
618         }
619         buf.append(" };");
620         System.out.println(buf.toString());
621 
622         TimeZone tz = TimeZone.getTimeZone("PST");
623         if (tz != null)
624             System.out.println("getTimeZone(PST) = " + tz.getID());
625         else
626             fail("FAIL: getTimeZone(PST) = null");
627 
628         tz = TimeZone.getTimeZone("America/Los_Angeles");
629         if (tz != null)
630             System.out.println("getTimeZone(America/Los_Angeles) = " + tz.getID());
631         else
632             fail("FAIL: getTimeZone(PST) = null");
633 
634         // Bug 4096694
635         tz = TimeZone.getTimeZone("NON_EXISTENT");
636         if (tz == null)
637             fail("FAIL: getTimeZone(NON_EXISTENT) = null");
638         else if (!tz.getID().equals("GMT"))
639             fail("FAIL: getTimeZone(NON_EXISTENT) = " + tz.getID());
640     }
641 
642     /**
643      * Bug 4107276
644      */
645     @Test
646     public void TestDSTSavings() {
647         // It might be better to find a way to integrate this test into the main TimeZone
648         // tests above, but I don't have time to figure out how to do this (or if it's
649         // even really a good idea).  Let's consider that a future.  --rtg 1/27/98
650         SimpleTimeZone tz = new SimpleTimeZone(-5 * millisPerHour, "dstSavingsTest",
651                                                Calendar.MARCH, 1, 0, 0, Calendar.SEPTEMBER, 1, 0, 0,
652                                                (int)(0.5 * millisPerHour));
653 
654         if (tz.getRawOffset() != -5 * millisPerHour)
655             fail("Got back a raw offset of " + (tz.getRawOffset() / millisPerHour) +
656                   " hours instead of -5 hours.");
657         if (!tz.useDaylightTime())
658             fail("Test time zone should use DST but claims it doesn't.");
659         if (tz.getDSTSavings() != 0.5 * millisPerHour)
660             fail("Set DST offset to 0.5 hour, but got back " + (tz.getDSTSavings() /
661                                                                  millisPerHour) + " hours instead.");
662 
663         int offset = tz.getOffset(GregorianCalendar.AD, 1998, Calendar.JANUARY, 1,
664                                   Calendar.THURSDAY, 10 * millisPerHour);
665         if (offset != -5 * millisPerHour)
666             fail("The offset for 10 AM, 1/1/98 should have been -5 hours, but we got "
667                   + (offset / millisPerHour) + " hours.");
668 
669         offset = tz.getOffset(GregorianCalendar.AD, 1998, Calendar.JUNE, 1, Calendar.MONDAY,
670                               10 * millisPerHour);
671         if (offset != -4.5 * millisPerHour)
672             fail("The offset for 10 AM, 6/1/98 should have been -4.5 hours, but we got "
673                   + (offset / millisPerHour) + " hours.");
674 
675         tz.setDSTSavings(millisPerHour);
676         offset = tz.getOffset(GregorianCalendar.AD, 1998, Calendar.JANUARY, 1,
677                               Calendar.THURSDAY, 10 * millisPerHour);
678         if (offset != -5 * millisPerHour)
679             fail("The offset for 10 AM, 1/1/98 should have been -5 hours, but we got "
680                   + (offset / millisPerHour) + " hours.");
681 
682         offset = tz.getOffset(GregorianCalendar.AD, 1998, Calendar.JUNE, 1, Calendar.MONDAY,
683                               10 * millisPerHour);
684         if (offset != -4 * millisPerHour)
685             fail("The offset for 10 AM, 6/1/98 (with a 1-hour DST offset) should have been -4 hours, but we got "
686                   + (offset / millisPerHour) + " hours.");
687     }
688 
689     /**
690      * Bug 4107570
691      */
692     @Test
693     public void TestAlternateRules() {
694         // Like TestDSTSavings, this test should probably be integrated somehow with the main
695         // test at the top of this class, but I didn't have time to figure out how to do that.
696         //                      --rtg 1/28/98
697 
698         SimpleTimeZone tz = new SimpleTimeZone(-5 * millisPerHour, "alternateRuleTest");
699 
700         // test the day-of-month API
701         tz.setStartRule(Calendar.MARCH, 10, 12 * millisPerHour);
702         tz.setEndRule(Calendar.OCTOBER, 20, 12 * millisPerHour);
703 
704         int offset = tz.getOffset(GregorianCalendar.AD, 1998, Calendar.MARCH, 5,
705                                   Calendar.THURSDAY, 10 * millisPerHour);
706         if (offset != -5 * millisPerHour)
707             fail("The offset for 10AM, 3/5/98 should have been -5 hours, but we got "
708                   + (offset / millisPerHour) + " hours.");
709 
710         offset = tz.getOffset(GregorianCalendar.AD, 1998, Calendar.MARCH, 15,
711                               Calendar.SUNDAY, 10 * millisPerHour);
712         if (offset != -4 * millisPerHour)
713             fail("The offset for 10AM, 3/15/98 should have been -4 hours, but we got "
714                   + (offset / millisPerHour) + " hours.");
715 
716         offset = tz.getOffset(GregorianCalendar.AD, 1998, Calendar.OCTOBER, 15,
717                               Calendar.THURSDAY, 10 * millisPerHour);
718         if (offset != -4 * millisPerHour)
719             fail("The offset for 10AM, 10/15/98 should have been -4 hours, but we got "
720                   + (offset / millisPerHour) + " hours.");
721 
722         offset = tz.getOffset(GregorianCalendar.AD, 1998, Calendar.OCTOBER, 25,
723                               Calendar.SUNDAY, 10 * millisPerHour);
724         if (offset != -5 * millisPerHour)
725             fail("The offset for 10AM, 10/25/98 should have been -5 hours, but we got "
726                   + (offset / millisPerHour) + " hours.");
727 
728         // test the day-of-week-after-day-in-month API
729         tz.setStartRule(Calendar.MARCH, 10, Calendar.FRIDAY, 12 * millisPerHour, true);
730         tz.setEndRule(Calendar.OCTOBER, 20, Calendar.FRIDAY, 12 * millisPerHour, false);
731 
732         offset = tz.getOffset(GregorianCalendar.AD, 1998, Calendar.MARCH, 11,
733                               Calendar.WEDNESDAY, 10 * millisPerHour);
734         if (offset != -5 * millisPerHour)
735             fail("The offset for 10AM, 3/11/98 should have been -5 hours, but we got "
736                   + (offset / millisPerHour) + " hours.");
737 
738         offset = tz.getOffset(GregorianCalendar.AD, 1998, Calendar.MARCH, 14,
739                               Calendar.SATURDAY, 10 * millisPerHour);
740         if (offset != -4 * millisPerHour)
741             fail("The offset for 10AM, 3/14/98 should have been -4 hours, but we got "
742                   + (offset / millisPerHour) + " hours.");
743 
744         offset = tz.getOffset(GregorianCalendar.AD, 1998, Calendar.OCTOBER, 15,
745                               Calendar.THURSDAY, 10 * millisPerHour);
746         if (offset != -4 * millisPerHour)
747             fail("The offset for 10AM, 10/15/98 should have been -4 hours, but we got "
748                   + (offset / millisPerHour) + " hours.");
749 
750         offset = tz.getOffset(GregorianCalendar.AD, 1998, Calendar.OCTOBER, 17,
751                               Calendar.SATURDAY, 10 * millisPerHour);
752         if (offset != -5 * millisPerHour)
753             fail("The offset for 10AM, 10/17/98 should have been -5 hours, but we got "
754                   + (offset / millisPerHour) + " hours.");
755     }
756 }
757 
758 //eof