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
26void
27TimeZoneOffsetLocalTest::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 */
41void
42TimeZoneOffsetLocalTest::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