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
31import java.text.SimpleDateFormat;
32import java.util.Calendar;
33import java.util.Date;
34import java.util.GregorianCalendar;
35import java.util.Locale;
36import java.util.SimpleTimeZone;
37import java.util.TimeZone;
38
39public 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}
288