1 /*
   2  * Copyright (c) 2005, 2019, 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 6231602
  27  * @summary Make sure that ZONE_OFFSET and/or DST_OFFSET setting is
  28  * taken into account for time calculations.
  29  */
  30 
  31 import java.util.Calendar;
  32 import java.util.Date;
  33 import java.util.GregorianCalendar;
  34 import java.util.Locale;
  35 import java.util.TimeZone;
  36 
  37 import static java.util.GregorianCalendar.*;
  38 
  39 public class ZoneOffsets {
  40 
  41     // This TimeZone always returns the dstOffset value.
  42     @SuppressWarnings("serial")
  43     private static class TestTimeZone extends TimeZone {
  44 
  45         private int gmtOffset;
  46         private int dstOffset;
  47 
  48         TestTimeZone(int gmtOffset, String id, int dstOffset) {
  49             this.gmtOffset = gmtOffset;
  50             setID(id);
  51             this.dstOffset = dstOffset;
  52         }
  53 
  54         public int getOffset(int era, int year, int month, int day,
  55                 int dayOfWeek, int milliseconds) {
  56             return gmtOffset + dstOffset;
  57         }
  58 
  59         public int getOffset(long date) {
  60             return gmtOffset + dstOffset;
  61         }
  62 
  63         public void setRawOffset(int offsetMillis) {
  64             gmtOffset = offsetMillis;
  65         }
  66 
  67         public int getRawOffset() {
  68             return gmtOffset;
  69         }
  70 
  71         public int getDSTSavings() {
  72             return dstOffset;
  73         }
  74 
  75         public boolean useDaylightTime() {
  76             return dstOffset != 0;
  77         }
  78 
  79         public boolean inDaylightTime(Date date) {
  80             return dstOffset != 0;
  81         }
  82 
  83         public String toString() {
  84             return "TestTimeZone[" + getID() + ", " + gmtOffset + ", " + dstOffset + "]";
  85         }
  86     }
  87 
  88     private static Locale[] locales = {
  89         Locale.getDefault(),
  90         new Locale("th", "TH"),
  91         new Locale("ja", "JP", "JP")};
  92 
  93     private static final int HOUR = 60 * 60 * 1000;
  94 
  95     private static int[][] offsets = {
  96         {0, 0},
  97         {0, HOUR},
  98         {0, 2 * HOUR},
  99         {-8 * HOUR, 0},
 100         {-8 * HOUR, HOUR},
 101         {-8 * HOUR, 2 * HOUR},
 102         {9 * HOUR, 0},
 103         {9 * HOUR, HOUR},
 104         {9 * HOUR, 2 * HOUR}};
 105 
 106     public static void main(String[] args) {
 107         for (int l = 0; l < locales.length; l++) {
 108             Locale loc = locales[l];
 109             for (int i = 0; i < offsets.length; i++) {
 110                 test(loc, offsets[i][0], offsets[i][1]);
 111             }
 112         }
 113 
 114         // The test case in the bug report.
 115         GregorianCalendar cal = new GregorianCalendar();
 116         cal.setLenient(false);
 117         cal.setGregorianChange(new Date(Long.MIN_VALUE));
 118         cal.clear();
 119         cal.set(ZONE_OFFSET, 0);
 120         cal.set(DST_OFFSET, 0);
 121         cal.set(ERA, AD);
 122         cal.set(2004, FEBRUARY, 3, 0, 0, 0);
 123         cal.set(MILLISECOND, 0);
 124         // The following line should not throw an IllegalArgumentException.
 125         cal.getTime();
 126     }
 127 
 128     private static void test(Locale loc, int gmtOffset, int dstOffset) {
 129         TimeZone tz1 = new TestTimeZone(gmtOffset,
 130                 "GMT" + (gmtOffset / HOUR) + "." + (dstOffset / HOUR),
 131                 dstOffset);
 132         int someDifferentOffset = gmtOffset + 2 * HOUR;
 133         TimeZone tz2 = new TestTimeZone(someDifferentOffset,
 134                 "GMT" + (someDifferentOffset / HOUR) + "." + (dstOffset / HOUR),
 135                 dstOffset);
 136 
 137         int someDifferentDSTOffset = dstOffset == 2 * HOUR ? HOUR : dstOffset + HOUR;
 138         TimeZone tz3 = new TestTimeZone(gmtOffset,
 139                 "GMT" + (gmtOffset / HOUR) + "." + (someDifferentDSTOffset / HOUR),
 140                 someDifferentDSTOffset);
 141 
 142         // cal1 is the base line.
 143         Calendar cal1 = Calendar.getInstance(tz1, loc);
 144         cal1.clear();
 145         cal1.set(2005, MARCH, 11);
 146         long t1 = cal1.getTime().getTime();
 147         int gmt = cal1.get(ZONE_OFFSET);
 148         int dst = cal1.get(DST_OFFSET);
 149 
 150         // Test 8 cases with cal2.
 151         Calendar cal2 = Calendar.getInstance(tz2, loc);
 152         cal2.clear();
 153         cal2.set(2005, MARCH, 11);
 154         // test1: set only ZONE_OFFSET
 155         cal2.set(ZONE_OFFSET, gmtOffset);
 156         if (t1 != cal2.getTime().getTime() || dst != cal2.get(DST_OFFSET)) {
 157             error("Test1", loc, cal2, gmtOffset, dstOffset, t1);
 158         }
 159 
 160         cal2.setTimeZone(tz3);
 161         cal2.clear();
 162         cal2.set(2005, MARCH, 11);
 163         // test2: set only DST_OFFSET
 164         cal2.set(DST_OFFSET, dstOffset);
 165         if (t1 != cal2.getTime().getTime() || gmt != cal2.get(ZONE_OFFSET)) {
 166             error("Test2", loc, cal2, gmtOffset, dstOffset, t1);
 167         }
 168 
 169         cal2.setTimeZone(tz2);
 170         cal2.clear();
 171         cal2.set(2005, MARCH, 11);
 172         // test3: set both ZONE_OFFSET and DST_OFFSET
 173         cal2.set(ZONE_OFFSET, gmtOffset);
 174         cal2.set(DST_OFFSET, dstOffset);
 175         if (t1 != cal2.getTime().getTime()) {
 176             error("Test3", loc, cal2, gmtOffset, dstOffset, t1);
 177         }
 178 
 179         cal2.setTimeZone(tz3);
 180         cal2.clear();
 181         cal2.set(2005, MARCH, 11);
 182         // test4: set both ZONE_OFFSET and DST_OFFSET
 183         cal2.set(ZONE_OFFSET, gmtOffset);
 184         cal2.set(DST_OFFSET, dstOffset);
 185         if (t1 != cal2.getTime().getTime()) {
 186             error("Test4", loc, cal2, gmtOffset, dstOffset, t1);
 187         }
 188 
 189         // Test the same thing in non-lenient
 190         cal2.setLenient(false);
 191 
 192         cal2.setTimeZone(tz2);
 193         cal2.clear();
 194         cal2.set(2005, MARCH, 11);
 195         adjustJapaneseEra(cal2);
 196         // test5: set only ZONE_OFFSET in non-lenient
 197         cal2.set(ZONE_OFFSET, gmtOffset);
 198         if (t1 != cal2.getTime().getTime() || dst != cal2.get(DST_OFFSET)) {
 199             error("Test5", loc, cal2, gmtOffset, dstOffset, t1);
 200         }
 201 
 202         cal2.setTimeZone(tz3);
 203         cal2.clear();
 204         cal2.set(2005, MARCH, 11);
 205         adjustJapaneseEra(cal2);
 206         // test6: set only DST_OFFSET in non-lenient
 207         cal2.set(DST_OFFSET, dstOffset);
 208         if (t1 != cal2.getTime().getTime() || gmt != cal2.get(ZONE_OFFSET)) {
 209             error("Test6", loc, cal2, gmtOffset, dstOffset, t1);
 210         }
 211 
 212         cal2.setTimeZone(tz2);
 213         cal2.clear();
 214         cal2.set(2005, MARCH, 11);
 215         adjustJapaneseEra(cal2);
 216         // test7: set both ZONE_OFFSET and DST_OFFSET in non-lenient
 217         cal2.set(ZONE_OFFSET, gmtOffset);
 218         cal2.set(DST_OFFSET, dstOffset);
 219         if (t1 != cal2.getTime().getTime()) {
 220             error("Test7", loc, cal2, gmtOffset, dstOffset, t1);
 221         }
 222 
 223         cal2.setTimeZone(tz3);
 224         cal2.clear();
 225         cal2.set(2005, MARCH, 11);
 226         adjustJapaneseEra(cal2);
 227         // test8: set both ZONE_OFFSET and DST_OFFSET in non-lenient
 228         cal2.set(ZONE_OFFSET, gmtOffset);
 229         cal2.set(DST_OFFSET, dstOffset);
 230         if (t1 != cal2.getTime().getTime()) {
 231             error("Test8", loc, cal2, gmtOffset, dstOffset, t1);
 232         }
 233     }
 234 
 235     private static void error(String msg, Locale loc, Calendar cal2, int gmtOffset, int dstOffset, long t1) {
 236         System.err.println(cal2);
 237         throw new RuntimeException(msg + ": Locale=" + loc
 238                 + ", gmtOffset=" + gmtOffset + ", dstOffset=" + dstOffset
 239                 + ", cal1 time=" + t1 + ", cal2 time=" + cal2.getTime().getTime());
 240     }
 241 
 242     private static void adjustJapaneseEra(Calendar cal) {
 243         // In case of Japanese calendar, explicitly set the last era; REIWA so that
 244         // year 2005 won't throw exception
 245         if (!cal.isLenient() &&
 246                 cal.getCalendarType().equals("japanese") &&
 247                 System.currentTimeMillis() < 1556668800000L) { // Current time not in REIWA
 248             cal.set(Calendar.ERA, 5);
 249             cal.add(Calendar.YEAR, -30); // -30: Subtract year-length of HEISEI era
 250         }
 251         return;
 252     }
 253 }