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