• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 = nullptr;
135     std = nullptr;
136     dst = nullptr;
137     if (U_FAILURE(status)) {
138         return;
139     }
140     int32_t initialRaw, initialDst;
141     UnicodeString initialName;
142 
143     AnnualTimeZoneRule *ar1 = nullptr;
144     AnnualTimeZoneRule *ar2 = nullptr;
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, status);
169             if (U_FAILURE(status)) return;
170             int32_t weekInMonth = Grego::dayOfWeekInMonth(year, month, dom);
171             // Create DOW rule
172             DateTimeRule *dtr = new DateTimeRule(month, weekInMonth, dow, mid, DateTimeRule::WALL_TIME);
173             tr.getTo()->getName(name);
174 
175             // Note:  SimpleTimeZone does not support raw offset change.
176             // So we always use raw offset of the given time for the rule,
177             // even raw offset is changed.  This will result that the result
178             // zone to return wrong offset after the transition.
179             // When we encounter such case, we do not inspect next next
180             // transition for another rule.
181             ar1 = new AnnualTimeZoneRule(name, initialRaw, tr.getTo()->getDSTSavings(),
182                 dtr, year, AnnualTimeZoneRule::MAX_YEAR);
183 
184             if (tr.getTo()->getRawOffset() == initialRaw) {
185                 // Get the next next transition
186                 avail = getNextTransition(nextTransitionTime, false, tr);
187                 if (avail) {
188                     // Check if the next next transition is either DST->STD or STD->DST
189                     // and within roughly 1 year from the next transition
190                     if (((tr.getFrom()->getDSTSavings() == 0 && tr.getTo()->getDSTSavings() != 0)
191                           || (tr.getFrom()->getDSTSavings() != 0 && tr.getTo()->getDSTSavings() == 0))
192                          && nextTransitionTime + MILLIS_PER_YEAR > tr.getTime()) {
193 
194                         // Get local wall time for the next transition time
195                         Grego::timeToFields(tr.getTime() + tr.getFrom()->getRawOffset() + tr.getFrom()->getDSTSavings(),
196                             year, month, dom, dow, doy, mid, status);
197                         if (U_FAILURE(status)) return;
198                         weekInMonth = Grego::dayOfWeekInMonth(year, month, dom);
199                         // Generate another DOW rule
200                         dtr = new DateTimeRule(month, weekInMonth, dow, mid, DateTimeRule::WALL_TIME);
201                         tr.getTo()->getName(name);
202                         ar2 = new AnnualTimeZoneRule(name, tr.getTo()->getRawOffset(), tr.getTo()->getDSTSavings(),
203                             dtr, year - 1, AnnualTimeZoneRule::MAX_YEAR);
204 
205                         // Make sure this rule can be applied to the specified date
206                         avail = ar2->getPreviousStart(date, tr.getFrom()->getRawOffset(), tr.getFrom()->getDSTSavings(), true, d);
207                         if (!avail || d > date
208                                 || initialRaw != tr.getTo()->getRawOffset()
209                                 || initialDst != tr.getTo()->getDSTSavings()) {
210                             // We cannot use this rule as the second transition rule
211                             delete ar2;
212                             ar2 = nullptr;
213                         }
214                     }
215                 }
216             }
217             if (ar2 == nullptr) {
218                 // Try previous transition
219                 avail = getPreviousTransition(date, true, tr);
220                 if (avail) {
221                     // Check if the previous transition is either DST->STD or STD->DST.
222                     // The actual transition time does not matter here.
223                     if ((tr.getFrom()->getDSTSavings() == 0 && tr.getTo()->getDSTSavings() != 0)
224                         || (tr.getFrom()->getDSTSavings() != 0 && tr.getTo()->getDSTSavings() == 0)) {
225 
226                         // Generate another DOW rule
227                         Grego::timeToFields(tr.getTime() + tr.getFrom()->getRawOffset() + tr.getFrom()->getDSTSavings(),
228                             year, month, dom, dow, doy, mid, status);
229                         if (U_FAILURE(status)) return;
230                         weekInMonth = Grego::dayOfWeekInMonth(year, month, dom);
231                         dtr = new DateTimeRule(month, weekInMonth, dow, mid, DateTimeRule::WALL_TIME);
232                         tr.getTo()->getName(name);
233 
234                         // second rule raw/dst offsets should match raw/dst offsets
235                         // at the given time
236                         ar2 = new AnnualTimeZoneRule(name, initialRaw, initialDst,
237                             dtr, ar1->getStartYear() - 1, AnnualTimeZoneRule::MAX_YEAR);
238 
239                         // Check if this rule start after the first rule after the specified date
240                         avail = ar2->getNextStart(date, tr.getFrom()->getRawOffset(), tr.getFrom()->getDSTSavings(), false, d);
241                         if (!avail || d <= nextTransitionTime) {
242                             // We cannot use this rule as the second transition rule
243                             delete ar2;
244                             ar2 = nullptr;
245                         }
246                     }
247                 }
248             }
249             if (ar2 == nullptr) {
250                 // Cannot find a good pair of AnnualTimeZoneRule
251                 delete ar1;
252                 ar1 = nullptr;
253             } else {
254                 // The initial rule should represent the rule before the previous transition
255                 ar1->getName(initialName);
256                 initialRaw = ar1->getRawOffset();
257                 initialDst = ar1->getDSTSavings();
258             }
259         }
260     }
261     else {
262         // Try the previous one
263         avail = getPreviousTransition(date, true, tr);
264         if (avail) {
265             tr.getTo()->getName(initialName);
266             initialRaw = tr.getTo()->getRawOffset();
267             initialDst = tr.getTo()->getDSTSavings();
268         } else {
269             // No transitions in the past.  Just use the current offsets
270             getOffset(date, false, initialRaw, initialDst, status);
271             if (U_FAILURE(status)) {
272                 return;
273             }
274         }
275     }
276     // Set the initial rule
277     initial = new InitialTimeZoneRule(initialName, initialRaw, initialDst);
278 
279     // Set the standard and daylight saving rules
280     if (ar1 != nullptr && ar2 != nullptr) {
281         if (ar1->getDSTSavings() != 0) {
282             dst = ar1;
283             std = ar2;
284         } else {
285             std = ar1;
286             dst = ar2;
287         }
288     }
289 }
290 
291 void
getTimeZoneRulesAfter(UDate start,InitialTimeZoneRule * & initial,UVector * & transitionRules,UErrorCode & status) const292 BasicTimeZone::getTimeZoneRulesAfter(UDate start, InitialTimeZoneRule*& initial,
293                                      UVector*& transitionRules, UErrorCode& status) const {
294     if (U_FAILURE(status)) {
295         return;
296     }
297 
298     const InitialTimeZoneRule *orgini;
299     TimeZoneTransition tzt;
300     bool avail;
301     int32_t ruleCount;
302     TimeZoneRule *r = nullptr;
303     UnicodeString name;
304     int32_t i;
305     UDate time, t;
306     UDate firstStart;
307     UBool bFinalStd = false, bFinalDst = false;
308 
309     initial = nullptr;
310     transitionRules = nullptr;
311 
312     // Original transition rules
313     ruleCount = countTransitionRules(status);
314     if (U_FAILURE(status)) {
315         return;
316     }
317     LocalPointer<UVector> orgRules(
318         new UVector(uprv_deleteUObject, nullptr, ruleCount, status), status);
319     if (U_FAILURE(status)) {
320         return;
321     }
322     LocalMemory<const TimeZoneRule *> orgtrs(
323         static_cast<const TimeZoneRule **>(uprv_malloc(sizeof(TimeZoneRule*)*ruleCount)));
324     if (orgtrs.isNull()) {
325         status = U_MEMORY_ALLOCATION_ERROR;
326         return;
327     }
328     getTimeZoneRules(orgini, &orgtrs[0], ruleCount, status);
329     if (U_FAILURE(status)) {
330         return;
331     }
332     for (i = 0; i < ruleCount; i++) {
333         LocalPointer<TimeZoneRule> lpRule(orgtrs[i]->clone(), status);
334         orgRules->adoptElement(lpRule.orphan(), status);
335         if (U_FAILURE(status)) {
336             return;
337         }
338     }
339 
340     avail = getPreviousTransition(start, true, tzt);
341     if (!avail) {
342         // No need to filter out rules only applicable to time before the start
343         initial = orgini->clone();
344         if (initial == nullptr) {
345             status = U_MEMORY_ALLOCATION_ERROR;
346             return;
347         }
348         transitionRules = orgRules.orphan();
349         return;
350     }
351 
352     LocalMemory<bool> done(static_cast<bool *>(uprv_malloc(sizeof(bool)*ruleCount)));
353     if (done.isNull()) {
354         status = U_MEMORY_ALLOCATION_ERROR;
355         return;
356     }
357     LocalPointer<UVector> filteredRules(
358         new UVector(uprv_deleteUObject, nullptr, status), status);
359     if (U_FAILURE(status)) {
360         return;
361     }
362 
363     // Create initial rule
364     tzt.getTo()->getName(name);
365     LocalPointer<InitialTimeZoneRule> res_initial(
366         new InitialTimeZoneRule(name, tzt.getTo()->getRawOffset(), tzt.getTo()->getDSTSavings()), status);
367     if (U_FAILURE(status)) {
368         return;
369     }
370 
371     // Mark rules which does not need to be processed
372     for (i = 0; i < ruleCount; i++) {
373         r = static_cast<TimeZoneRule*>(orgRules->elementAt(i));
374         avail = r->getNextStart(start, res_initial->getRawOffset(), res_initial->getDSTSavings(), false, time);
375         done[i] = !avail;
376     }
377 
378     time = start;
379     while (!bFinalStd || !bFinalDst) {
380         avail = getNextTransition(time, false, tzt);
381         if (!avail) {
382             break;
383         }
384         UDate updatedTime = tzt.getTime();
385         if (updatedTime == time) {
386             // Can get here if rules for start & end of daylight time have exactly
387             // the same time.
388             // TODO:  fix getNextTransition() to prevent it?
389             status = U_INVALID_STATE_ERROR;
390             return;
391         }
392         time = updatedTime;
393 
394         const TimeZoneRule *toRule = tzt.getTo();
395         for (i = 0; i < ruleCount; i++) {
396             r = static_cast<TimeZoneRule*>(orgRules->elementAt(i));
397             if (*r == *toRule) {
398                 break;
399             }
400         }
401         if (i >= ruleCount) {
402             // This case should never happen
403             status = U_INVALID_STATE_ERROR;
404             return;
405         }
406         if (done[i]) {
407             continue;
408         }
409         const TimeArrayTimeZoneRule *tar = dynamic_cast<const TimeArrayTimeZoneRule *>(toRule);
410         const AnnualTimeZoneRule *ar;
411         if (tar != nullptr) {
412             // Get the previous raw offset and DST savings before the very first start time
413             TimeZoneTransition tzt0;
414             t = start;
415             while (true) {
416                 avail = getNextTransition(t, false, tzt0);
417                 if (!avail) {
418                     break;
419                 }
420                 if (*(tzt0.getTo()) == *tar) {
421                     break;
422                 }
423                 t = tzt0.getTime();
424             }
425             if (avail) {
426                 // Check if the entire start times to be added
427                 tar->getFirstStart(tzt.getFrom()->getRawOffset(), tzt.getFrom()->getDSTSavings(), firstStart);
428                 if (firstStart > start) {
429                     // Just add the rule as is
430                     LocalPointer<TimeArrayTimeZoneRule> lpTar(tar->clone(), status);
431                     filteredRules->adoptElement(lpTar.orphan(), status);
432                     if (U_FAILURE(status)) {
433                         return;
434                     }
435                 } else {
436                     // Collect transitions after the start time
437                     int32_t startTimes;
438                     DateTimeRule::TimeRuleType timeType;
439                     int32_t idx;
440 
441                     startTimes = tar->countStartTimes();
442                     timeType = tar->getTimeType();
443                     for (idx = 0; idx < startTimes; idx++) {
444                         tar->getStartTimeAt(idx, t);
445                         if (timeType == DateTimeRule::STANDARD_TIME) {
446                             t -= tzt.getFrom()->getRawOffset();
447                         }
448                         if (timeType == DateTimeRule::WALL_TIME) {
449                             t -= tzt.getFrom()->getDSTSavings();
450                         }
451                         if (t > start) {
452                             break;
453                         }
454                     }
455                     if (U_FAILURE(status)) {
456                         return;
457                     }
458                     int32_t asize = startTimes - idx;
459                     if (asize > 0) {
460                         LocalMemory<UDate> newTimes(static_cast<UDate *>(uprv_malloc(sizeof(UDate) * asize)));
461                         if (newTimes.isNull()) {
462                             status = U_MEMORY_ALLOCATION_ERROR;
463                             return;
464                         }
465                         for (int32_t newidx = 0; newidx < asize; newidx++) {
466                             tar->getStartTimeAt(idx + newidx, newTimes[newidx]);
467                         }
468                         tar->getName(name);
469                         LocalPointer<TimeArrayTimeZoneRule> newTar(new TimeArrayTimeZoneRule(
470                                 name, tar->getRawOffset(), tar->getDSTSavings(), &newTimes[0], asize, timeType), status);
471                         filteredRules->adoptElement(newTar.orphan(), status);
472                         if (U_FAILURE(status)) {
473                             return;
474                         }
475                     }
476                 }
477             }
478         } else if ((ar = dynamic_cast<const AnnualTimeZoneRule *>(toRule)) != nullptr) {
479             ar->getFirstStart(tzt.getFrom()->getRawOffset(), tzt.getFrom()->getDSTSavings(), firstStart);
480             if (firstStart == tzt.getTime()) {
481                 // Just add the rule as is
482                 LocalPointer<AnnualTimeZoneRule> arClone(ar->clone(), status);
483                 filteredRules->adoptElement(arClone.orphan(), status);
484                 if (U_FAILURE(status)) {
485                     return;
486                 }
487             } else {
488                 // Calculate the transition year
489                 int32_t year, month, dom, dow, doy, mid;
490                 Grego::timeToFields(tzt.getTime(), year, month, dom, dow, doy, mid, status);
491                 if (U_FAILURE(status)) {
492                     return;
493                 }
494                 // Re-create the rule
495                 ar->getName(name);
496                 LocalPointer<AnnualTimeZoneRule> newAr(new AnnualTimeZoneRule(name, ar->getRawOffset(), ar->getDSTSavings(),
497                     *(ar->getRule()), year, ar->getEndYear()), status);
498                 filteredRules->adoptElement(newAr.orphan(), status);
499                 if (U_FAILURE(status)) {
500                     return;
501                 }
502             }
503             // check if this is a final rule
504             if (ar->getEndYear() == AnnualTimeZoneRule::MAX_YEAR) {
505                 // After bot final standard and dst rules are processed,
506                 // exit this while loop.
507                 if (ar->getDSTSavings() == 0) {
508                     bFinalStd = true;
509                 } else {
510                     bFinalDst = true;
511                 }
512             }
513         }
514         done[i] = true;
515     }
516 
517     // Set the results
518     initial = res_initial.orphan();
519     transitionRules = filteredRules.orphan();
520 }
521 
522 void
getOffsetFromLocal(UDate,UTimeZoneLocalOption,UTimeZoneLocalOption,int32_t &,int32_t &,UErrorCode & status) const523 BasicTimeZone::getOffsetFromLocal(UDate /*date*/, UTimeZoneLocalOption /*nonExistingTimeOpt*/,
524                                   UTimeZoneLocalOption /*duplicatedTimeOpt*/,
525                                   int32_t& /*rawOffset*/, int32_t& /*dstOffset*/,
526                                   UErrorCode& status) const {
527     if (U_FAILURE(status)) {
528         return;
529     }
530     status = U_UNSUPPORTED_ERROR;
531 }
532 
getOffsetFromLocal(UDate date,int32_t nonExistingTimeOpt,int32_t duplicatedTimeOpt,int32_t & rawOffset,int32_t & dstOffset,UErrorCode & status) const533 void BasicTimeZone::getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
534                                        int32_t& rawOffset, int32_t& dstOffset,
535                                        UErrorCode& status) const {
536     getOffsetFromLocal(date, static_cast<UTimeZoneLocalOption>(nonExistingTimeOpt),
537                        static_cast<UTimeZoneLocalOption>(duplicatedTimeOpt), rawOffset, dstOffset, status);
538 }
539 
540 U_NAMESPACE_END
541 
542 #endif /* #if !UCONFIG_NO_FORMATTING */
543 
544 //eof
545