1 /*
  2  * Copyright (c) 2002, 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  * @bug 4278609 4761696
 27  * @library /java/text/testlib
 28  * @summary Make sure to handle DST transition ending at 0:00 January 1.
 29  */
 30 
 31 import java.text.SimpleDateFormat;
 32 import java.util.Calendar;
 33 import java.util.Date;
 34 import java.util.GregorianCalendar;
 35 import java.util.Locale;
 36 import java.util.SimpleTimeZone;
 37 import java.util.TimeZone;
 38 
 39 public class TransitionTest extends IntlTest {
 40 
 41     public static void main(String[] args) throws Exception {
 42         new TransitionTest().run(args);
 43     }
 44 
 45     public void Test4278609() {
 46         SimpleTimeZone tz = new SimpleTimeZone(0, "MyTimeZone",
 47                                /* DST start day: August, 1, 0:00 */
 48                                Calendar.AUGUST, 1, 0, 0,
 49                                /* DST end day: January, 1, 0:00 (wall-clock)*/
 50                                Calendar.JANUARY, 1, 0, 0,
 51                                60 * 60 * 1000);
 52 
 53         Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
 54 
 55         // setting a date using GMT zone just after the end rule of tz zone
 56         cal.clear();
 57         cal.set(Calendar.ERA, GregorianCalendar.AD);
 58         cal.set(1998, Calendar.DECEMBER, 31, 23, 01, 00);
 59 
 60         Date date = cal.getTime();
 61 
 62         int millis = cal.get(Calendar.HOUR_OF_DAY) * 3600000
 63                      + cal.get(Calendar.MINUTE) * 60000
 64                      + cal.get(Calendar.SECOND) * 1000
 65                      + cal.get(Calendar.MILLISECOND);
 66         /* we must use standard local time */
 67         millis += tz.getRawOffset();
 68 
 69         int offset = tz.getOffset(cal.get(Calendar.ERA),
 70                                   cal.get(Calendar.YEAR),
 71                                   cal.get(Calendar.MONTH),
 72                                   cal.get(Calendar.DATE),
 73                                   cal.get(Calendar.DAY_OF_WEEK),
 74                                   millis);
 75 
 76         if (offset != 0) {
 77             SimpleDateFormat format = new SimpleDateFormat("dd MMM HH:mm:ss zzz",
 78                                                            Locale.US);
 79             format.setTimeZone(tz);
 80             errln("Wrong DST transition: " + tz
 81                   + "\na date just after DST = " + format.format(date)
 82                   + "\ngetOffset = " + offset);
 83         }
 84     }
 85 
 86     /*
 87      * 4761696: Rewrite SimpleTimeZone to support correct DST transitions
 88      *
 89      * Derived from JCK test cases some of which specify wrong day of week values.
 90      */
 91     public void Test4761696() {
 92         GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
 93 
 94         // test#1
 95         int rawOffset = -43200000;
 96         int saving = 1800000;
 97         int timeOfDay = 84600001;
 98         SimpleTimeZone tz = new SimpleTimeZone(rawOffset, "stz",
 99                                 Calendar.JULY, 1, 0, 0,
100                                 Calendar.JANUARY, 1, 0, 0,
101                                 saving);
102         int year = Integer.MIN_VALUE;
103         tz.setStartYear(year);
104         int offset = tz.getOffset(GregorianCalendar.AD,
105                               year,
106                               Calendar.DECEMBER,
107                               31,
108                               1, // should be SATURDAY
109                               timeOfDay);
110         int y = (int) mod((long)year, 28L); // 28-year cycle
111         cal.clear();
112         cal.set(cal.ERA, cal.AD);
113         cal.set(y, Calendar.DECEMBER, 31);
114         cal.set(cal.MILLISECOND, timeOfDay);
115         long localtime = cal.getTimeInMillis() + rawOffset; // local standard time
116 
117         cal.clear();
118         cal.set(cal.ERA, cal.AD);
119         cal.set(y + 1, Calendar.JANUARY, 1);
120         cal.set(cal.MILLISECOND, -saving);
121         long endTime = cal.getTimeInMillis() + rawOffset;
122         long expectedOffset = (localtime < endTime) ? rawOffset + saving : rawOffset;
123         if (offset != expectedOffset) {
124             errln("test#1: wrong offset: got "+offset+", expected="+expectedOffset);
125         }
126 
127         // test#2
128         saving = 1;
129         timeOfDay = 0;
130         tz = new SimpleTimeZone(rawOffset, "stz",
131                                 Calendar.JULY, 1, 0, 0,
132                                 Calendar.JANUARY, 1, 0, 0,
133                                 saving);
134         tz.setStartYear(year);
135         offset = tz.getOffset(GregorianCalendar.AD,
136                               year,
137                               Calendar.AUGUST,
138                               15,
139                               1, // should be MONDAY
140                               timeOfDay);
141         y = (int) mod((long)year, 28L); // 28-year cycle
142         cal.clear();
143         cal.set(y, Calendar.AUGUST, 15);
144         cal.set(cal.MILLISECOND, timeOfDay);
145         localtime = cal.getTimeInMillis() + rawOffset; // local standard time
146 
147         cal.clear();
148         cal.set(y + 1, Calendar.JANUARY, 1);
149         cal.set(cal.MILLISECOND, -saving);
150         endTime = cal.getTimeInMillis() + rawOffset;
151         expectedOffset = (localtime < endTime) ? rawOffset + saving : rawOffset;
152         if (offset != expectedOffset) {
153             errln("Wrong offset: got "+offset+", expected="+expectedOffset);
154         }
155 
156         rawOffset = 43200000;
157         saving = 1;
158         timeOfDay = 3599998;
159         tz = new SimpleTimeZone(rawOffset, "stz",
160                                 Calendar.JULY, 1, 0, 3600000,
161                                 Calendar.JANUARY, 1, 0, 3600000,
162                                 saving);
163         tz.setStartYear(year);
164         offset = tz.getOffset(GregorianCalendar.AD,
165                               year,
166                               Calendar.JANUARY,
167                               1,
168                               1,
169                               timeOfDay);
170         y = (int) mod((long)year, 28L); // 28-year cycle
171         cal.clear();
172         cal.set(y, Calendar.JANUARY, 1);
173         cal.set(cal.MILLISECOND, timeOfDay);
174         localtime = cal.getTimeInMillis() + rawOffset; // local standard time
175 
176         cal.clear();
177         cal.set(y + 1, Calendar.JANUARY, 1);
178         cal.set(cal.MILLISECOND, 3600000-saving);
179         endTime = cal.getTimeInMillis() + rawOffset;
180         expectedOffset = (localtime < endTime) ? rawOffset + saving : rawOffset;
181         if (offset != expectedOffset) {
182             errln("test#2: wrong offset: got "+offset+", expected="+expectedOffset);
183         }
184 
185         // test#3
186         rawOffset = -43200000;
187         saving = 1800000;
188         timeOfDay = 84600001;
189         tz = new SimpleTimeZone(rawOffset, "stz",
190                                 Calendar.SEPTEMBER, 1, 0, 0,
191                                 Calendar.MARCH, 1, 0, 0,
192                                 saving);
193         tz.setStartYear(year);
194         offset = tz.getOffset(GregorianCalendar.AD,
195                               year,
196                               Calendar.FEBRUARY,
197                               28,
198                               1,
199                               timeOfDay);
200         y = (int) mod((long)year, 28L); // 28-year cycle
201         cal.clear();
202         cal.set(y, Calendar.FEBRUARY, 28);
203         cal.set(cal.MILLISECOND, timeOfDay);
204         localtime = cal.getTimeInMillis() + rawOffset; // local standard time
205 
206         cal.clear();
207         cal.set(y, Calendar.MARCH, 1);
208         cal.set(cal.MILLISECOND, -saving);
209         endTime = cal.getTimeInMillis() + rawOffset;
210         expectedOffset = (localtime < endTime) ? rawOffset + saving : rawOffset;
211         if (offset != expectedOffset) {
212             errln("test#3: wrong offset: got "+offset+", expected="+expectedOffset);
213         }
214 
215         // test#4
216         rawOffset = -43200000;
217         saving = 1;
218         timeOfDay = 0;
219         tz = new SimpleTimeZone(rawOffset, "stz",
220                                 Calendar.JANUARY, -4, 1, 3600000,
221                                 Calendar.JULY, -4, 1, 3600000,
222                                 saving);
223         tz.setStartYear(year);
224         offset = tz.getOffset(GregorianCalendar.AD,
225                               year,
226                               Calendar.JANUARY,
227                               10,
228                               2, // should be 1 (SUNDAY)
229                               timeOfDay);
230         y = (int) mod((long)year, 28L); // 28-year cycle
231         cal.clear();
232         cal.set(y, Calendar.JANUARY, 10);
233         cal.set(cal.MILLISECOND, timeOfDay);
234         localtime = cal.getTimeInMillis() + rawOffset; // local standard time
235 
236         cal.clear();
237         cal.set(cal.YEAR, y);
238         cal.set(cal.MONTH, Calendar.JANUARY);
239         cal.set(cal.DAY_OF_MONTH, 8);
240         cal.set(cal.WEEK_OF_MONTH, cal.getActualMaximum(cal.WEEK_OF_MONTH)-4+1);
241         cal.set(cal.DAY_OF_WEEK, 1);
242         cal.set(cal.MILLISECOND, 3600000-saving);
243         long startTime = cal.getTimeInMillis() + rawOffset;
244         expectedOffset = (localtime >= startTime) ? rawOffset + saving : rawOffset;
245         if (offset != expectedOffset) {
246             errln("test#4: wrong offset: got "+offset+", expected="+expectedOffset);
247         }
248 
249         // test#5
250         rawOffset = 0;
251         saving = 3600000;
252         timeOfDay = 7200000;
253         year = 1982;
254         tz = new SimpleTimeZone(rawOffset, "stz",
255                                 Calendar.APRIL, 1, 0, 7200000,
256                                 Calendar.OCTOBER, 10, 0, 7200000,
257                                 saving);
258         offset = tz.getOffset(GregorianCalendar.AD,
259                               year,
260                               Calendar.OCTOBER,
261                               10,
262                               1,
263                               timeOfDay);
264         cal.clear();
265         cal.set(year, Calendar.OCTOBER, 10);
266         cal.set(cal.MILLISECOND, timeOfDay);
267         localtime = cal.getTimeInMillis() + rawOffset; // local standard time
268 
269         cal.clear();
270         cal.set(year, Calendar.OCTOBER, 10);
271         cal.set(cal.MILLISECOND, 7200000-saving);
272         endTime = cal.getTimeInMillis() + rawOffset;
273         expectedOffset = (localtime < endTime) ? rawOffset + saving : rawOffset;
274         if (offset != expectedOffset) {
275             errln("test#5: wrong offset: got "+offset+", expected="+expectedOffset);
276         }
277     }
278 
279     public static final long floorDivide(long n, long d) {
280         return ((n >= 0) ?
281                 (n / d) : (((n + 1L) / d) - 1L));
282     }
283 
284     public static final long mod(long x, long y) {
285         return (x - y * floorDivide(x, y));
286     }
287 }