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 
19 U_NAMESPACE_BEGIN
20 
21 #define MILLIS_PER_YEAR (365*24*60*60*1000.0)
22 
BasicTimeZone()23 BasicTimeZone::BasicTimeZone()
24 : TimeZone() {
25 }
26 
BasicTimeZone(const UnicodeString & id)27 BasicTimeZone::BasicTimeZone(const UnicodeString &id)
28 : TimeZone(id) {
29 }
30 
BasicTimeZone(const BasicTimeZone & source)31 BasicTimeZone::BasicTimeZone(const BasicTimeZone& source)
32 : TimeZone(source) {
33 }
34 
~BasicTimeZone()35 BasicTimeZone::~BasicTimeZone() {
36 }
37 
38 UBool
hasEquivalentTransitions(const BasicTimeZone & tz,UDate start,UDate end,UBool ignoreDstAmount,UErrorCode & status) const39 BasicTimeZone::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 
131 void
getSimpleRulesNear(UDate date,InitialTimeZoneRule * & initial,AnnualTimeZoneRule * & std,AnnualTimeZoneRule * & dst,UErrorCode & status) const132 BasicTimeZone::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 
288 void
getTimeZoneRulesAfter(UDate start,InitialTimeZoneRule * & initial,UVector * & transitionRules,UErrorCode & status) const289 BasicTimeZone::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                     // Collect 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 
522 error:
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 
549 void
getOffsetFromLocal(UDate,UTimeZoneLocalOption,UTimeZoneLocalOption,int32_t &,int32_t &,UErrorCode & status) const550 BasicTimeZone::getOffsetFromLocal(UDate /*date*/, UTimeZoneLocalOption /*nonExistingTimeOpt*/,
551                                   UTimeZoneLocalOption /*duplicatedTimeOpt*/,
552                                   int32_t& /*rawOffset*/, int32_t& /*dstOffset*/,
553                                   UErrorCode& status) const {
554     if (U_FAILURE(status)) {
555         return;
556     }
557     status = U_UNSUPPORTED_ERROR;
558 }
559 
getOffsetFromLocal(UDate date,int32_t nonExistingTimeOpt,int32_t duplicatedTimeOpt,int32_t & rawOffset,int32_t & dstOffset,UErrorCode & status) const560 void BasicTimeZone::getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
561                                        int32_t& rawOffset, int32_t& dstOffset,
562                                        UErrorCode& status) const {
563     getOffsetFromLocal(date, (UTimeZoneLocalOption)nonExistingTimeOpt,
564                        (UTimeZoneLocalOption)duplicatedTimeOpt, rawOffset, dstOffset, status);
565 }
566 
567 U_NAMESPACE_END
568 
569 #endif /* #if !UCONFIG_NO_FORMATTING */
570 
571 //eof
572