1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*
4 *******************************************************************************
5 * Copyright (C) 2007-2011, International Business Machines Corporation and         *
6 * others. All Rights Reserved.                                                *
7 *******************************************************************************
8 */
9 #include "unicode/utypes.h"
10 
11 #if !UCONFIG_NO_FORMATTING
12 
13 #include "tzoffloc.h"
14 
15 #include "unicode/ucal.h"
16 #include "unicode/timezone.h"
17 #include "unicode/calendar.h"
18 #include "unicode/dtrule.h"
19 #include "unicode/tzrule.h"
20 #include "unicode/rbtz.h"
21 #include "unicode/simpletz.h"
22 #include "unicode/tzrule.h"
23 #include "unicode/smpdtfmt.h"
24 #include "unicode/gregocal.h"
25 
26 void
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)27 TimeZoneOffsetLocalTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
28 {
29     if (exec) {
30         logln("TestSuite TimeZoneOffsetLocalTest");
31     }
32     switch (index) {
33         TESTCASE(0, TestGetOffsetAroundTransition);
34         default: name = ""; break;
35     }
36 }
37 
38 /*
39  * Testing getOffset APIs around rule transition by local standard/wall time.
40  */
41 void
TestGetOffsetAroundTransition()42 TimeZoneOffsetLocalTest::TestGetOffsetAroundTransition() {
43     const int32_t NUM_DATES = 10;
44     const int32_t NUM_TIMEZONES = 3;
45 
46     const int32_t HOUR = 60*60*1000;
47     const int32_t MINUTE = 60*1000;
48 
49     const int32_t DATES[NUM_DATES][6] = {
50         {2006, UCAL_APRIL, 2, 1, 30, 1*HOUR+30*MINUTE},
51         {2006, UCAL_APRIL, 2, 2, 00, 2*HOUR},
52         {2006, UCAL_APRIL, 2, 2, 30, 2*HOUR+30*MINUTE},
53         {2006, UCAL_APRIL, 2, 3, 00, 3*HOUR},
54         {2006, UCAL_APRIL, 2, 3, 30, 3*HOUR+30*MINUTE},
55         {2006, UCAL_OCTOBER, 29, 0, 30, 0*HOUR+30*MINUTE},
56         {2006, UCAL_OCTOBER, 29, 1, 00, 1*HOUR},
57         {2006, UCAL_OCTOBER, 29, 1, 30, 1*HOUR+30*MINUTE},
58         {2006, UCAL_OCTOBER, 29, 2, 00, 2*HOUR},
59         {2006, UCAL_OCTOBER, 29, 2, 30, 2*HOUR+30*MINUTE},
60     };
61 
62     // Expected offsets by int32_t getOffset(uint8_t era, int32_t year, int32_t month, int32_t day,
63     // uint8_t dayOfWeek, int32_t millis, UErrorCode& status)
64     const int32_t OFFSETS1[NUM_DATES] = {
65         // April 2, 2006
66         -8*HOUR,
67         -7*HOUR,
68         -7*HOUR,
69         -7*HOUR,
70         -7*HOUR,
71 
72         // October 29, 2006
73         -7*HOUR,
74         -8*HOUR,
75         -8*HOUR,
76         -8*HOUR,
77         -8*HOUR,
78     };
79 
80     // Expected offsets by void getOffset(UDate date, UBool local, int32_t& rawOffset,
81     // int32_t& dstOffset, UErrorCode& ec) with local=TRUE
82     // or void getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
83     // int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) with
84     // nonExistingTimeOpt=kStandard/duplicatedTimeOpt=kStandard
85     const int32_t OFFSETS2[NUM_DATES][2] = {
86         // April 2, 2006
87         {-8*HOUR, 0},
88         {-8*HOUR, 0},
89         {-8*HOUR, 0},
90         {-8*HOUR, 1*HOUR},
91         {-8*HOUR, 1*HOUR},
92 
93         // Oct 29, 2006
94         {-8*HOUR, 1*HOUR},
95         {-8*HOUR, 0},
96         {-8*HOUR, 0},
97         {-8*HOUR, 0},
98         {-8*HOUR, 0},
99     };
100 
101     // Expected offsets by void getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt,
102     // int32_t duplicatedTimeOpt, int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) with
103     // nonExistingTimeOpt=kDaylight/duplicatedTimeOpt=kDaylight
104     const int32_t OFFSETS3[][2] = {
105         // April 2, 2006
106         {-8*HOUR, 0},
107         {-8*HOUR, 1*HOUR},
108         {-8*HOUR, 1*HOUR},
109         {-8*HOUR, 1*HOUR},
110         {-8*HOUR, 1*HOUR},
111 
112         // October 29, 2006
113         {-8*HOUR, 1*HOUR},
114         {-8*HOUR, 1*HOUR},
115         {-8*HOUR, 1*HOUR},
116         {-8*HOUR, 0},
117         {-8*HOUR, 0},
118     };
119 
120     UErrorCode status = U_ZERO_ERROR;
121 
122     int32_t rawOffset, dstOffset;
123     TimeZone* utc = TimeZone::createTimeZone("UTC");
124     Calendar* cal = Calendar::createInstance(*utc, status);
125     if (U_FAILURE(status)) {
126         dataerrln("Calendar::createInstance failed: %s", u_errorName(status));
127         return;
128     }
129     cal->clear();
130 
131     // Set up TimeZone objects - OlsonTimeZone, SimpleTimeZone and RuleBasedTimeZone
132     BasicTimeZone *TESTZONES[NUM_TIMEZONES];
133 
134     TESTZONES[0] = (BasicTimeZone*)TimeZone::createTimeZone("America/Los_Angeles");
135     TESTZONES[1] = new SimpleTimeZone(-8*HOUR, "Simple Pacific Time",
136                                         UCAL_APRIL, 1, UCAL_SUNDAY, 2*HOUR,
137                                         UCAL_OCTOBER, -1, UCAL_SUNDAY, 2*HOUR, status);
138     if (U_FAILURE(status)) {
139         errln("SimpleTimeZone constructor failed");
140         return;
141     }
142 
143     InitialTimeZoneRule *ir = new InitialTimeZoneRule(
144             "Pacific Standard Time", // Initial time Name
145             -8*HOUR,        // Raw offset
146             0*HOUR);        // DST saving amount
147 
148     RuleBasedTimeZone *rbPT = new RuleBasedTimeZone("Rule based Pacific Time", ir);
149 
150     DateTimeRule *dtr;
151     AnnualTimeZoneRule *atzr;
152     const int32_t STARTYEAR = 2000;
153 
154     dtr = new DateTimeRule(UCAL_APRIL, 1, UCAL_SUNDAY,
155                         2*HOUR, DateTimeRule::WALL_TIME); // 1st Sunday in April, at 2AM wall time
156     atzr = new AnnualTimeZoneRule("Pacific Daylight Time",
157             -8*HOUR /* rawOffset */, 1*HOUR /* dstSavings */, dtr,
158             STARTYEAR, AnnualTimeZoneRule::MAX_YEAR);
159     rbPT->addTransitionRule(atzr, status);
160     if (U_FAILURE(status)) {
161         errln("Could not add DST start rule to the RuleBasedTimeZone rbPT");
162         return;
163     }
164 
165     dtr = new DateTimeRule(UCAL_OCTOBER, -1, UCAL_SUNDAY,
166                         2*HOUR, DateTimeRule::WALL_TIME); // last Sunday in October, at 2AM wall time
167     atzr = new AnnualTimeZoneRule("Pacific Standard Time",
168             -8*HOUR /* rawOffset */, 0 /* dstSavings */, dtr,
169             STARTYEAR, AnnualTimeZoneRule::MAX_YEAR);
170     rbPT->addTransitionRule(atzr, status);
171     if (U_FAILURE(status)) {
172         errln("Could not add STD start rule to the RuleBasedTimeZone rbPT");
173         return;
174     }
175 
176     rbPT->complete(status);
177     if (U_FAILURE(status)) {
178         errln("complete() failed for RuleBasedTimeZone rbPT");
179         return;
180     }
181 
182     TESTZONES[2] = rbPT;
183 
184     // Calculate millis
185     UDate MILLIS[NUM_DATES];
186     for (int32_t i = 0; i < NUM_DATES; i++) {
187         cal->clear();
188         cal->set(DATES[i][0], DATES[i][1], DATES[i][2], DATES[i][3], DATES[i][4]);
189         MILLIS[i] = cal->getTime(status);
190         if (U_FAILURE(status)) {
191             errln("cal->getTime failed");
192             return;
193         }
194     }
195 
196     SimpleDateFormat df(UnicodeString("yyyy-MM-dd HH:mm:ss"), status);
197     if (U_FAILURE(status)) {
198         dataerrln("Failed to initialize a SimpleDateFormat - %s", u_errorName(status));
199     }
200     df.setTimeZone(*utc);
201     UnicodeString dateStr;
202 
203     // Test getOffset(uint8_t era, int32_t year, int32_t month, int32_t day,
204     // uint8_t dayOfWeek, int32_t millis, UErrorCode& status)
205     for (int32_t i = 0; i < NUM_TIMEZONES; i++) {
206         for (int32_t d = 0; d < NUM_DATES; d++) {
207             status = U_ZERO_ERROR;
208             int32_t offset = TESTZONES[i]->getOffset(GregorianCalendar::AD, DATES[d][0], DATES[d][1], DATES[d][2],
209                                                 UCAL_SUNDAY, DATES[d][5], status);
210             if (U_FAILURE(status)) {
211                 errln((UnicodeString)"getOffset(era,year,month,day,dayOfWeek,millis,status) failed for TESTZONES[" + i + "]");
212             } else if (offset != OFFSETS1[d]) {
213                 dateStr.remove();
214                 df.format(MILLIS[d], dateStr);
215                 dataerrln((UnicodeString)"Bad offset returned by TESTZONES[" + i + "] at "
216                         + dateStr + "(standard) - Got: " + offset + " Expected: " + OFFSETS1[d]);
217             }
218         }
219     }
220 
221     // Test getOffset(UDate date, UBool local, int32_t& rawOffset,
222     // int32_t& dstOffset, UErrorCode& ec) with local = TRUE
223     for (int32_t i = 0; i < NUM_TIMEZONES; i++) {
224         for (int32_t m = 0; m < NUM_DATES; m++) {
225             status = U_ZERO_ERROR;
226             TESTZONES[i]->getOffset(MILLIS[m], TRUE, rawOffset, dstOffset, status);
227             if (U_FAILURE(status)) {
228                 errln((UnicodeString)"getOffset(date,local,rawOfset,dstOffset,ec) failed for TESTZONES[" + i + "]");
229             } else if (rawOffset != OFFSETS2[m][0] || dstOffset != OFFSETS2[m][1]) {
230                 dateStr.remove();
231                 df.format(MILLIS[m], dateStr);
232                 dataerrln((UnicodeString)"Bad offset returned by TESTZONES[" + i + "] at "
233                         + dateStr + "(wall) - Got: "
234                         + rawOffset + "/" + dstOffset
235                         + " Expected: " + OFFSETS2[m][0] + "/" + OFFSETS2[m][1]);
236             }
237         }
238     }
239 
240     // Test getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
241     // int32_t& rawOffset, int32_t& dstOffset, UErroCode& status)
242     // with nonExistingTimeOpt=kStandard/duplicatedTimeOpt=kStandard
243     for (int32_t i = 0; i < NUM_TIMEZONES; i++) {
244         for (int m = 0; m < NUM_DATES; m++) {
245             status = U_ZERO_ERROR;
246             TESTZONES[i]->getOffsetFromLocal(MILLIS[m], BasicTimeZone::kStandard, BasicTimeZone::kStandard,
247                 rawOffset, dstOffset, status);
248             if (U_FAILURE(status)) {
249                 errln((UnicodeString)"getOffsetFromLocal with kStandard/kStandard failed for TESTZONES[" + i + "]");
250             } else if (rawOffset != OFFSETS2[m][0] || dstOffset != OFFSETS2[m][1]) {
251                 dateStr.remove();
252                 df.format(MILLIS[m], dateStr);
253                 dataerrln((UnicodeString)"Bad offset returned by TESTZONES[" + i + "] at "
254                         + dateStr + "(wall/kStandard/kStandard) - Got: "
255                         + rawOffset + "/" + dstOffset
256                         + " Expected: " + OFFSETS2[m][0] + "/" + OFFSETS2[m][1]);
257             }
258         }
259     }
260 
261     // Test getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
262     // int32_t& rawOffset, int32_t& dstOffset, UErroCode& status)
263     // with nonExistingTimeOpt=kDaylight/duplicatedTimeOpt=kDaylight
264     for (int32_t i = 0; i < NUM_TIMEZONES; i++) {
265         for (int m = 0; m < NUM_DATES; m++) {
266             status = U_ZERO_ERROR;
267             TESTZONES[i]->getOffsetFromLocal(MILLIS[m], BasicTimeZone::kDaylight, BasicTimeZone::kDaylight,
268                 rawOffset, dstOffset, status);
269             if (U_FAILURE(status)) {
270                 errln((UnicodeString)"getOffsetFromLocal with kDaylight/kDaylight failed for TESTZONES[" + i + "]");
271             } else if (rawOffset != OFFSETS3[m][0] || dstOffset != OFFSETS3[m][1]) {
272                 dateStr.remove();
273                 df.format(MILLIS[m], dateStr);
274                 dataerrln((UnicodeString)"Bad offset returned by TESTZONES[" + i + "] at "
275                         + dateStr + "(wall/kDaylight/kDaylight) - Got: "
276                         + rawOffset + "/" + dstOffset
277                         + " Expected: " + OFFSETS3[m][0] + "/" + OFFSETS3[m][1]);
278             }
279         }
280     }
281 
282     // Test getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
283     // int32_t& rawOffset, int32_t& dstOffset, UErroCode& status)
284     // with nonExistingTimeOpt=kFormer/duplicatedTimeOpt=kLatter
285     for (int32_t i = 0; i < NUM_TIMEZONES; i++) {
286         for (int m = 0; m < NUM_DATES; m++) {
287             status = U_ZERO_ERROR;
288             TESTZONES[i]->getOffsetFromLocal(MILLIS[m], BasicTimeZone::kFormer, BasicTimeZone::kLatter,
289                 rawOffset, dstOffset, status);
290             if (U_FAILURE(status)) {
291                 errln((UnicodeString)"getOffsetFromLocal with kFormer/kLatter failed for TESTZONES[" + i + "]");
292             } else if (rawOffset != OFFSETS2[m][0] || dstOffset != OFFSETS2[m][1]) {
293                 dateStr.remove();
294                 df.format(MILLIS[m], dateStr);
295                 dataerrln((UnicodeString)"Bad offset returned by TESTZONES[" + i + "] at "
296                         + dateStr + "(wall/kFormer/kLatter) - Got: "
297                         + rawOffset + "/" + dstOffset
298                         + " Expected: " + OFFSETS2[m][0] + "/" + OFFSETS2[m][1]);
299             }
300         }
301     }
302 
303     // Test getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
304     // int32_t& rawOffset, int32_t& dstOffset, UErroCode& status)
305     // with nonExistingTimeOpt=kLatter/duplicatedTimeOpt=kFormer
306     for (int32_t i = 0; i < NUM_TIMEZONES; i++) {
307         for (int m = 0; m < NUM_DATES; m++) {
308             status = U_ZERO_ERROR;
309             TESTZONES[i]->getOffsetFromLocal(MILLIS[m], BasicTimeZone::kLatter, BasicTimeZone::kFormer,
310                 rawOffset, dstOffset, status);
311             if (U_FAILURE(status)) {
312                 errln((UnicodeString)"getOffsetFromLocal with kLatter/kFormer failed for TESTZONES[" + i + "]");
313             } else if (rawOffset != OFFSETS3[m][0] || dstOffset != OFFSETS3[m][1]) {
314                 dateStr.remove();
315                 df.format(MILLIS[m], dateStr);
316                 dataerrln((UnicodeString)"Bad offset returned by TESTZONES[" + i + "] at "
317                         + dateStr + "(wall/kLatter/kFormer) - Got: "
318                         + rawOffset + "/" + dstOffset
319                         + " Expected: " + OFFSETS3[m][0] + "/" + OFFSETS3[m][1]);
320             }
321         }
322     }
323 
324     for (int32_t i = 0; i < NUM_TIMEZONES; i++) {
325         delete TESTZONES[i];
326     }
327     delete utc;
328     delete cal;
329 }
330 
331 #endif /* #if !UCONFIG_NO_FORMATTING */
332