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-2013, International Business Machines Corporation and
6* others. All Rights Reserved.
7*******************************************************************************
8*/
9
10#include "unicode/utypes.h"
11
12#if !UCONFIG_NO_FORMATTING
13
14#include "unicode/basictz.h"
15#include "gregoimp.h"
16#include "uvector.h"
17#include "cmemory.h"
18
19U_NAMESPACE_BEGIN
20
21#define MILLIS_PER_YEAR (365*24*60*60*1000.0)
22
23BasicTimeZone::BasicTimeZone()
24: TimeZone() {
25}
26
27BasicTimeZone::BasicTimeZone(const UnicodeString &id)
28: TimeZone(id) {
29}
30
31BasicTimeZone::BasicTimeZone(const BasicTimeZone& source)
32: TimeZone(source) {
33}
34
35BasicTimeZone::~BasicTimeZone() {
36}
37
38UBool
39BasicTimeZone::hasEquivalentTransitions(const BasicTimeZone& tz, UDate start, UDate end,
40                                        UBool ignoreDstAmount, UErrorCode& status) const {
41    if (U_FAILURE(status)) {
42        return FALSE;
43    }
44    if (hasSameRules(tz)) {
45        return TRUE;
46    }
47    // Check the offsets at the start time
48    int32_t raw1, raw2, dst1, dst2;
49    getOffset(start, FALSE, raw1, dst1, status);
50    if (U_FAILURE(status)) {
51        return FALSE;
52    }
53    tz.getOffset(start, FALSE, raw2, dst2, status);
54    if (U_FAILURE(status)) {
55        return FALSE;
56    }
57    if (ignoreDstAmount) {
58        if ((raw1 + dst1 != raw2 + dst2)
59            || (dst1 != 0 && dst2 == 0)
60            || (dst1 == 0 && dst2 != 0)) {
61            return FALSE;
62        }
63    } else {
64        if (raw1 != raw2 || dst1 != dst2) {
65            return FALSE;
66        }
67    }
68    // Check transitions in the range
69    UDate time = start;
70    TimeZoneTransition tr1, tr2;
71    while (TRUE) {
72        UBool avail1 = getNextTransition(time, FALSE, tr1);
73        UBool avail2 = tz.getNextTransition(time, FALSE, tr2);
74
75        if (ignoreDstAmount) {
76            // Skip a transition which only differ the amount of DST savings
77            while (TRUE) {
78                if (avail1
79                        && tr1.getTime() <= end
80                        && (tr1.getFrom()->getRawOffset() + tr1.getFrom()->getDSTSavings()
81                                == tr1.getTo()->getRawOffset() + tr1.getTo()->getDSTSavings())
82                        && (tr1.getFrom()->getDSTSavings() != 0 && tr1.getTo()->getDSTSavings() != 0)) {
83                    getNextTransition(tr1.getTime(), FALSE, tr1);
84                } else {
85                    break;
86                }
87            }
88            while (TRUE) {
89                if (avail2
90                        && tr2.getTime() <= end
91                        && (tr2.getFrom()->getRawOffset() + tr2.getFrom()->getDSTSavings()
92                                == tr2.getTo()->getRawOffset() + tr2.getTo()->getDSTSavings())
93                        && (tr2.getFrom()->getDSTSavings() != 0 && tr2.getTo()->getDSTSavings() != 0)) {
94                    tz.getNextTransition(tr2.getTime(), FALSE, tr2);
95                } else {
96                    break;
97                }
98            }
99        }
100
101        UBool inRange1 = (avail1 && tr1.getTime() <= end);
102        UBool inRange2 = (avail2 && tr2.getTime() <= end);
103        if (!inRange1 && !inRange2) {
104            // No more transition in the range
105            break;
106        }
107        if (!inRange1 || !inRange2) {
108            return FALSE;
109        }
110        if (tr1.getTime() != tr2.getTime()) {
111            return FALSE;
112        }
113        if (ignoreDstAmount) {
114            if (tr1.getTo()->getRawOffset() + tr1.getTo()->getDSTSavings()
115                        != tr2.getTo()->getRawOffset() + tr2.getTo()->getDSTSavings()
116                    || (tr1.getTo()->getDSTSavings() != 0 &&  tr2.getTo()->getDSTSavings() == 0)
117                    || (tr1.getTo()->getDSTSavings() == 0 &&  tr2.getTo()->getDSTSavings() != 0)) {
118                return FALSE;
119            }
120        } else {
121            if (tr1.getTo()->getRawOffset() != tr2.getTo()->getRawOffset() ||
122                tr1.getTo()->getDSTSavings() != tr2.getTo()->getDSTSavings()) {
123                return FALSE;
124            }
125        }
126        time = tr1.getTime();
127    }
128    return TRUE;
129}
130
131void
132BasicTimeZone::getSimpleRulesNear(UDate date, InitialTimeZoneRule*& initial,
133        AnnualTimeZoneRule*& std, AnnualTimeZoneRule*& dst, UErrorCode& status) const {
134    initial = NULL;
135    std = NULL;
136    dst = NULL;
137    if (U_FAILURE(status)) {
138        return;
139    }
140    int32_t initialRaw, initialDst;
141    UnicodeString initialName;
142
143    AnnualTimeZoneRule *ar1 = NULL;
144    AnnualTimeZoneRule *ar2 = NULL;
145    UnicodeString name;
146
147    UBool avail;
148    TimeZoneTransition tr;
149    // Get the next transition
150    avail = getNextTransition(date, FALSE, tr);
151    if (avail) {
152        tr.getFrom()->getName(initialName);
153        initialRaw = tr.getFrom()->getRawOffset();
154        initialDst = tr.getFrom()->getDSTSavings();
155
156        // Check if the next transition is either DST->STD or STD->DST and
157        // within roughly 1 year from the specified date
158        UDate nextTransitionTime = tr.getTime();
159        if (((tr.getFrom()->getDSTSavings() == 0 && tr.getTo()->getDSTSavings() != 0)
160              || (tr.getFrom()->getDSTSavings() != 0 && tr.getTo()->getDSTSavings() == 0))
161            && (date + MILLIS_PER_YEAR > nextTransitionTime)) {
162
163            int32_t year, month, dom, dow, doy, mid;
164            UDate d;
165
166            // Get local wall time for the next transition time
167            Grego::timeToFields(nextTransitionTime + initialRaw + initialDst,
168                year, month, dom, dow, doy, mid);
169            int32_t weekInMonth = Grego::dayOfWeekInMonth(year, month, dom);
170            // Create DOW rule
171            DateTimeRule *dtr = new DateTimeRule(month, weekInMonth, dow, mid, DateTimeRule::WALL_TIME);
172            tr.getTo()->getName(name);
173
174            // Note:  SimpleTimeZone does not support raw offset change.
175            // So we always use raw offset of the given time for the rule,
176            // even raw offset is changed.  This will result that the result
177            // zone to return wrong offset after the transition.
178            // When we encounter such case, we do not inspect next next
179            // transition for another rule.
180            ar1 = new AnnualTimeZoneRule(name, initialRaw, tr.getTo()->getDSTSavings(),
181                dtr, year, AnnualTimeZoneRule::MAX_YEAR);
182
183            if (tr.getTo()->getRawOffset() == initialRaw) {
184                // Get the next next transition
185                avail = getNextTransition(nextTransitionTime, FALSE, tr);
186                if (avail) {
187                    // Check if the next next transition is either DST->STD or STD->DST
188                    // and within roughly 1 year from the next transition
189                    if (((tr.getFrom()->getDSTSavings() == 0 && tr.getTo()->getDSTSavings() != 0)
190                          || (tr.getFrom()->getDSTSavings() != 0 && tr.getTo()->getDSTSavings() == 0))
191                         && nextTransitionTime + MILLIS_PER_YEAR > tr.getTime()) {
192
193                        // Get local wall time for the next transition time
194                        Grego::timeToFields(tr.getTime() + tr.getFrom()->getRawOffset() + tr.getFrom()->getDSTSavings(),
195                            year, month, dom, dow, doy, mid);
196                        weekInMonth = Grego::dayOfWeekInMonth(year, month, dom);
197                        // Generate another DOW rule
198                        dtr = new DateTimeRule(month, weekInMonth, dow, mid, DateTimeRule::WALL_TIME);
199                        tr.getTo()->getName(name);
200                        ar2 = new AnnualTimeZoneRule(name, tr.getTo()->getRawOffset(), tr.getTo()->getDSTSavings(),
201                            dtr, year - 1, AnnualTimeZoneRule::MAX_YEAR);
202
203                        // Make sure this rule can be applied to the specified date
204                        avail = ar2->getPreviousStart(date, tr.getFrom()->getRawOffset(), tr.getFrom()->getDSTSavings(), TRUE, d);
205                        if (!avail || d > date
206                                || initialRaw != tr.getTo()->getRawOffset()
207                                || initialDst != tr.getTo()->getDSTSavings()) {
208                            // We cannot use this rule as the second transition rule
209                            delete ar2;
210                            ar2 = NULL;
211                        }
212                    }
213                }
214            }
215            if (ar2 == NULL) {
216                // Try previous transition
217                avail = getPreviousTransition(date, TRUE, tr);
218                if (avail) {
219                    // Check if the previous transition is either DST->STD or STD->DST.
220                    // The actual transition time does not matter here.
221                    if ((tr.getFrom()->getDSTSavings() == 0 && tr.getTo()->getDSTSavings() != 0)
222                        || (tr.getFrom()->getDSTSavings() != 0 && tr.getTo()->getDSTSavings() == 0)) {
223
224                        // Generate another DOW rule
225                        Grego::timeToFields(tr.getTime() + tr.getFrom()->getRawOffset() + tr.getFrom()->getDSTSavings(),
226                            year, month, dom, dow, doy, mid);
227                        weekInMonth = Grego::dayOfWeekInMonth(year, month, dom);
228                        dtr = new DateTimeRule(month, weekInMonth, dow, mid, DateTimeRule::WALL_TIME);
229                        tr.getTo()->getName(name);
230
231                        // second rule raw/dst offsets should match raw/dst offsets
232                        // at the given time
233                        ar2 = new AnnualTimeZoneRule(name, initialRaw, initialDst,
234                            dtr, ar1->getStartYear() - 1, AnnualTimeZoneRule::MAX_YEAR);
235
236                        // Check if this rule start after the first rule after the specified date
237                        avail = ar2->getNextStart(date, tr.getFrom()->getRawOffset(), tr.getFrom()->getDSTSavings(), FALSE, d);
238                        if (!avail || d <= nextTransitionTime) {
239                            // We cannot use this rule as the second transition rule
240                            delete ar2;
241                            ar2 = NULL;
242                        }
243                    }
244                }
245            }
246            if (ar2 == NULL) {
247                // Cannot find a good pair of AnnualTimeZoneRule
248                delete ar1;
249                ar1 = NULL;
250            } else {
251                // The initial rule should represent the rule before the previous transition
252                ar1->getName(initialName);
253                initialRaw = ar1->getRawOffset();
254                initialDst = ar1->getDSTSavings();
255            }
256        }
257    }
258    else {
259        // Try the previous one
260        avail = getPreviousTransition(date, TRUE, tr);
261        if (avail) {
262            tr.getTo()->getName(initialName);
263            initialRaw = tr.getTo()->getRawOffset();
264            initialDst = tr.getTo()->getDSTSavings();
265        } else {
266            // No transitions in the past.  Just use the current offsets
267            getOffset(date, FALSE, initialRaw, initialDst, status);
268            if (U_FAILURE(status)) {
269                return;
270            }
271        }
272    }
273    // Set the initial rule
274    initial = new InitialTimeZoneRule(initialName, initialRaw, initialDst);
275
276    // Set the standard and daylight saving rules
277    if (ar1 != NULL && ar2 != NULL) {
278        if (ar1->getDSTSavings() != 0) {
279            dst = ar1;
280            std = ar2;
281        } else {
282            std = ar1;
283            dst = ar2;
284        }
285    }
286}
287
288void
289BasicTimeZone::getTimeZoneRulesAfter(UDate start, InitialTimeZoneRule*& initial,
290                                     UVector*& transitionRules, UErrorCode& status) const {
291    if (U_FAILURE(status)) {
292        return;
293    }
294
295    const InitialTimeZoneRule *orgini;
296    const TimeZoneRule **orgtrs = NULL;
297    TimeZoneTransition tzt;
298    UBool avail;
299    UVector *orgRules = NULL;
300    int32_t ruleCount;
301    TimeZoneRule *r = NULL;
302    UBool *done = NULL;
303    InitialTimeZoneRule *res_initial = NULL;
304    UVector *filteredRules = NULL;
305    UnicodeString name;
306    int32_t i;
307    UDate time, t;
308    UDate *newTimes = NULL;
309    UDate firstStart;
310    UBool bFinalStd = FALSE, bFinalDst = FALSE;
311
312    // Original transition rules
313    ruleCount = countTransitionRules(status);
314    if (U_FAILURE(status)) {
315        return;
316    }
317    orgRules = new UVector(ruleCount, status);
318    if (U_FAILURE(status)) {
319        return;
320    }
321    orgtrs = (const TimeZoneRule**)uprv_malloc(sizeof(TimeZoneRule*)*ruleCount);
322    if (orgtrs == NULL) {
323        status = U_MEMORY_ALLOCATION_ERROR;
324        goto error;
325    }
326    getTimeZoneRules(orgini, orgtrs, ruleCount, status);
327    if (U_FAILURE(status)) {
328        goto error;
329    }
330    for (i = 0; i < ruleCount; i++) {
331        orgRules->addElement(orgtrs[i]->clone(), status);
332        if (U_FAILURE(status)) {
333            goto error;
334        }
335    }
336    uprv_free(orgtrs);
337    orgtrs = NULL;
338
339    avail = getPreviousTransition(start, TRUE, tzt);
340    if (!avail) {
341        // No need to filter out rules only applicable to time before the start
342        initial = orgini->clone();
343        transitionRules = orgRules;
344        return;
345    }
346
347    done = (UBool*)uprv_malloc(sizeof(UBool)*ruleCount);
348    if (done == NULL) {
349        status = U_MEMORY_ALLOCATION_ERROR;
350        goto error;
351    }
352    filteredRules = new UVector(status);
353    if (U_FAILURE(status)) {
354        goto error;
355    }
356
357    // Create initial rule
358    tzt.getTo()->getName(name);
359    res_initial = new InitialTimeZoneRule(name, tzt.getTo()->getRawOffset(),
360        tzt.getTo()->getDSTSavings());
361
362    // Mark rules which does not need to be processed
363    for (i = 0; i < ruleCount; i++) {
364        r = (TimeZoneRule*)orgRules->elementAt(i);
365        avail = r->getNextStart(start, res_initial->getRawOffset(), res_initial->getDSTSavings(), FALSE, time);
366        done[i] = !avail;
367    }
368
369    time = start;
370    while (!bFinalStd || !bFinalDst) {
371        avail = getNextTransition(time, FALSE, tzt);
372        if (!avail) {
373            break;
374        }
375        UDate updatedTime = tzt.getTime();
376        if (updatedTime == time) {
377            // Can get here if rules for start & end of daylight time have exactly
378            // the same time.
379            // TODO:  fix getNextTransition() to prevent it?
380            status = U_INVALID_STATE_ERROR;
381            goto error;
382        }
383        time = updatedTime;
384
385        const TimeZoneRule *toRule = tzt.getTo();
386        for (i = 0; i < ruleCount; i++) {
387            r = (TimeZoneRule*)orgRules->elementAt(i);
388            if (*r == *toRule) {
389                break;
390            }
391        }
392        if (i >= ruleCount) {
393            // This case should never happen
394            status = U_INVALID_STATE_ERROR;
395            goto error;
396        }
397        if (done[i]) {
398            continue;
399        }
400        const TimeArrayTimeZoneRule *tar = dynamic_cast<const TimeArrayTimeZoneRule *>(toRule);
401        const AnnualTimeZoneRule *ar;
402        if (tar != NULL) {
403            // Get the previous raw offset and DST savings before the very first start time
404            TimeZoneTransition tzt0;
405            t = start;
406            while (TRUE) {
407                avail = getNextTransition(t, FALSE, tzt0);
408                if (!avail) {
409                    break;
410                }
411                if (*(tzt0.getTo()) == *tar) {
412                    break;
413                }
414                t = tzt0.getTime();
415            }
416            if (avail) {
417                // Check if the entire start times to be added
418                tar->getFirstStart(tzt.getFrom()->getRawOffset(), tzt.getFrom()->getDSTSavings(), firstStart);
419                if (firstStart > start) {
420                    // Just add the rule as is
421                    filteredRules->addElement(tar->clone(), status);
422                    if (U_FAILURE(status)) {
423                        goto error;
424                    }
425                } else {
426                    // Colllect transitions after the start time
427                    int32_t startTimes;
428                    DateTimeRule::TimeRuleType timeType;
429                    int32_t idx;
430
431                    startTimes = tar->countStartTimes();
432                    timeType = tar->getTimeType();
433                    for (idx = 0; idx < startTimes; idx++) {
434                        tar->getStartTimeAt(idx, t);
435                        if (timeType == DateTimeRule::STANDARD_TIME) {
436                            t -= tzt.getFrom()->getRawOffset();
437                        }
438                        if (timeType == DateTimeRule::WALL_TIME) {
439                            t -= tzt.getFrom()->getDSTSavings();
440                        }
441                        if (t > start) {
442                            break;
443                        }
444                    }
445                    int32_t asize = startTimes - idx;
446                    if (asize > 0) {
447                        newTimes = (UDate*)uprv_malloc(sizeof(UDate) * asize);
448                        if (newTimes == NULL) {
449                            status = U_MEMORY_ALLOCATION_ERROR;
450                            goto error;
451                        }
452                        for (int32_t newidx = 0; newidx < asize; newidx++) {
453                            tar->getStartTimeAt(idx + newidx, newTimes[newidx]);
454                            if (U_FAILURE(status)) {
455                                uprv_free(newTimes);
456                                newTimes = NULL;
457                                goto error;
458                            }
459                        }
460                        tar->getName(name);
461                        TimeArrayTimeZoneRule *newTar = new TimeArrayTimeZoneRule(name,
462                            tar->getRawOffset(), tar->getDSTSavings(), newTimes, asize, timeType);
463                        uprv_free(newTimes);
464                        filteredRules->addElement(newTar, status);
465                        if (U_FAILURE(status)) {
466                            goto error;
467                        }
468                    }
469                }
470            }
471        } else if ((ar = dynamic_cast<const AnnualTimeZoneRule *>(toRule)) != NULL) {
472            ar->getFirstStart(tzt.getFrom()->getRawOffset(), tzt.getFrom()->getDSTSavings(), firstStart);
473            if (firstStart == tzt.getTime()) {
474                // Just add the rule as is
475                filteredRules->addElement(ar->clone(), status);
476                if (U_FAILURE(status)) {
477                    goto error;
478                }
479            } else {
480                // Calculate the transition year
481                int32_t year, month, dom, dow, doy, mid;
482                Grego::timeToFields(tzt.getTime(), year, month, dom, dow, doy, mid);
483                // Re-create the rule
484                ar->getName(name);
485                AnnualTimeZoneRule *newAr = new AnnualTimeZoneRule(name, ar->getRawOffset(), ar->getDSTSavings(),
486                    *(ar->getRule()), year, ar->getEndYear());
487                filteredRules->addElement(newAr, status);
488                if (U_FAILURE(status)) {
489                    goto error;
490                }
491            }
492            // check if this is a final rule
493            if (ar->getEndYear() == AnnualTimeZoneRule::MAX_YEAR) {
494                // After bot final standard and dst rules are processed,
495                // exit this while loop.
496                if (ar->getDSTSavings() == 0) {
497                    bFinalStd = TRUE;
498                } else {
499                    bFinalDst = TRUE;
500                }
501            }
502        }
503        done[i] = TRUE;
504    }
505
506    // Set the results
507    if (orgRules != NULL) {
508        while (!orgRules->isEmpty()) {
509            r = (TimeZoneRule*)orgRules->orphanElementAt(0);
510            delete r;
511        }
512        delete orgRules;
513    }
514    if (done != NULL) {
515        uprv_free(done);
516    }
517
518    initial = res_initial;
519    transitionRules = filteredRules;
520    return;
521
522error:
523    if (orgtrs != NULL) {
524        uprv_free(orgtrs);
525    }
526    if (orgRules != NULL) {
527        while (!orgRules->isEmpty()) {
528            r = (TimeZoneRule*)orgRules->orphanElementAt(0);
529            delete r;
530        }
531        delete orgRules;
532    }
533    if (done != NULL) {
534        if (filteredRules != NULL) {
535            while (!filteredRules->isEmpty()) {
536                r = (TimeZoneRule*)filteredRules->orphanElementAt(0);
537                delete r;
538            }
539            delete filteredRules;
540        }
541        delete res_initial;
542        uprv_free(done);
543    }
544
545    initial = NULL;
546    transitionRules = NULL;
547}
548
549void
550BasicTimeZone::getOffsetFromLocal(UDate /*date*/, int32_t /*nonExistingTimeOpt*/, int32_t /*duplicatedTimeOpt*/,
551                            int32_t& /*rawOffset*/, int32_t& /*dstOffset*/, UErrorCode& status) const {
552    if (U_FAILURE(status)) {
553        return;
554    }
555    status = U_UNSUPPORTED_ERROR;
556}
557
558U_NAMESPACE_END
559
560#endif /* #if !UCONFIG_NO_FORMATTING */
561
562//eof
563