1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 *******************************************************************************
5 * Copyright (C) 2007-2013, International Business Machines Corporation and
6 * others. All Rights Reserved.
7 *******************************************************************************
10 #include "unicode/utypes.h"
12 #if !UCONFIG_NO_FORMATTING
14 #include "unicode/basictz.h"
21 #define MILLIS_PER_YEAR (365*24*60*60*1000.0)
23 BasicTimeZone::BasicTimeZone()
27 BasicTimeZone::BasicTimeZone(const UnicodeString
&id
)
31 BasicTimeZone::BasicTimeZone(const BasicTimeZone
& source
)
35 BasicTimeZone::~BasicTimeZone() {
39 BasicTimeZone::hasEquivalentTransitions(const BasicTimeZone
& tz
, UDate start
, UDate end
,
40 UBool ignoreDstAmount
, UErrorCode
& status
) const {
41 if (U_FAILURE(status
)) {
44 if (hasSameRules(tz
)) {
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
)) {
53 tz
.getOffset(start
, FALSE
, raw2
, dst2
, status
);
54 if (U_FAILURE(status
)) {
57 if (ignoreDstAmount
) {
58 if ((raw1
+ dst1
!= raw2
+ dst2
)
59 || (dst1
!= 0 && dst2
== 0)
60 || (dst1
== 0 && dst2
!= 0)) {
64 if (raw1
!= raw2
|| dst1
!= dst2
) {
68 // Check transitions in the range
70 TimeZoneTransition tr1
, tr2
;
72 UBool avail1
= getNextTransition(time
, FALSE
, tr1
);
73 UBool avail2
= tz
.getNextTransition(time
, FALSE
, tr2
);
75 if (ignoreDstAmount
) {
76 // Skip a transition which only differ the amount of DST savings
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
);
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
);
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
107 if (!inRange1
|| !inRange2
) {
110 if (tr1
.getTime() != tr2
.getTime()) {
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)) {
121 if (tr1
.getTo()->getRawOffset() != tr2
.getTo()->getRawOffset() ||
122 tr1
.getTo()->getDSTSavings() != tr2
.getTo()->getDSTSavings()) {
126 time
= tr1
.getTime();
132 BasicTimeZone::getSimpleRulesNear(UDate date
, InitialTimeZoneRule
*& initial
,
133 AnnualTimeZoneRule
*& std
, AnnualTimeZoneRule
*& dst
, UErrorCode
& status
) const {
137 if (U_FAILURE(status
)) {
140 int32_t initialRaw
, initialDst
;
141 UnicodeString initialName
;
143 AnnualTimeZoneRule
*ar1
= NULL
;
144 AnnualTimeZoneRule
*ar2
= NULL
;
148 TimeZoneTransition tr
;
149 // Get the next transition
150 avail
= getNextTransition(date
, FALSE
, tr
);
152 tr
.getFrom()->getName(initialName
);
153 initialRaw
= tr
.getFrom()->getRawOffset();
154 initialDst
= tr
.getFrom()->getDSTSavings();
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
)) {
163 int32_t year
, month
, dom
, dow
, doy
, mid
;
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
);
171 DateTimeRule
*dtr
= new DateTimeRule(month
, weekInMonth
, dow
, mid
, DateTimeRule::WALL_TIME
);
172 tr
.getTo()->getName(name
);
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
);
183 if (tr
.getTo()->getRawOffset() == initialRaw
) {
184 // Get the next next transition
185 avail
= getNextTransition(nextTransitionTime
, FALSE
, tr
);
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()) {
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
);
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
216 // Try previous transition
217 avail
= getPreviousTransition(date
, TRUE
, tr
);
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)) {
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
);
231 // second rule raw/dst offsets should match raw/dst offsets
233 ar2
= new AnnualTimeZoneRule(name
, initialRaw
, initialDst
,
234 dtr
, ar1
->getStartYear() - 1, AnnualTimeZoneRule::MAX_YEAR
);
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
247 // Cannot find a good pair of AnnualTimeZoneRule
251 // The initial rule should represent the rule before the previous transition
252 ar1
->getName(initialName
);
253 initialRaw
= ar1
->getRawOffset();
254 initialDst
= ar1
->getDSTSavings();
259 // Try the previous one
260 avail
= getPreviousTransition(date
, TRUE
, tr
);
262 tr
.getTo()->getName(initialName
);
263 initialRaw
= tr
.getTo()->getRawOffset();
264 initialDst
= tr
.getTo()->getDSTSavings();
266 // No transitions in the past. Just use the current offsets
267 getOffset(date
, FALSE
, initialRaw
, initialDst
, status
);
268 if (U_FAILURE(status
)) {
273 // Set the initial rule
274 initial
= new InitialTimeZoneRule(initialName
, initialRaw
, initialDst
);
276 // Set the standard and daylight saving rules
277 if (ar1
!= NULL
&& ar2
!= NULL
) {
278 if (ar1
->getDSTSavings() != 0) {
289 BasicTimeZone::getTimeZoneRulesAfter(UDate start
, InitialTimeZoneRule
*& initial
,
290 UVector
*& transitionRules
, UErrorCode
& status
) const {
291 if (U_FAILURE(status
)) {
295 const InitialTimeZoneRule
*orgini
;
296 const TimeZoneRule
**orgtrs
= NULL
;
297 TimeZoneTransition tzt
;
299 UVector
*orgRules
= NULL
;
301 TimeZoneRule
*r
= NULL
;
303 InitialTimeZoneRule
*res_initial
= NULL
;
304 UVector
*filteredRules
= NULL
;
308 UDate
*newTimes
= NULL
;
310 UBool bFinalStd
= FALSE
, bFinalDst
= FALSE
;
312 // Original transition rules
313 ruleCount
= countTransitionRules(status
);
314 if (U_FAILURE(status
)) {
317 orgRules
= new UVector(ruleCount
, status
);
318 if (U_FAILURE(status
)) {
321 orgtrs
= (const TimeZoneRule
**)uprv_malloc(sizeof(TimeZoneRule
*)*ruleCount
);
322 if (orgtrs
== NULL
) {
323 status
= U_MEMORY_ALLOCATION_ERROR
;
326 getTimeZoneRules(orgini
, orgtrs
, ruleCount
, status
);
327 if (U_FAILURE(status
)) {
330 for (i
= 0; i
< ruleCount
; i
++) {
331 orgRules
->addElement(orgtrs
[i
]->clone(), status
);
332 if (U_FAILURE(status
)) {
339 avail
= getPreviousTransition(start
, TRUE
, tzt
);
341 // No need to filter out rules only applicable to time before the start
342 initial
= orgini
->clone();
343 transitionRules
= orgRules
;
347 done
= (UBool
*)uprv_malloc(sizeof(UBool
)*ruleCount
);
349 status
= U_MEMORY_ALLOCATION_ERROR
;
352 filteredRules
= new UVector(status
);
353 if (U_FAILURE(status
)) {
357 // Create initial rule
358 tzt
.getTo()->getName(name
);
359 res_initial
= new InitialTimeZoneRule(name
, tzt
.getTo()->getRawOffset(),
360 tzt
.getTo()->getDSTSavings());
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
);
370 while (!bFinalStd
|| !bFinalDst
) {
371 avail
= getNextTransition(time
, FALSE
, tzt
);
375 UDate updatedTime
= tzt
.getTime();
376 if (updatedTime
== time
) {
377 // Can get here if rules for start & end of daylight time have exactly
379 // TODO: fix getNextTransition() to prevent it?
380 status
= U_INVALID_STATE_ERROR
;
385 const TimeZoneRule
*toRule
= tzt
.getTo();
386 for (i
= 0; i
< ruleCount
; i
++) {
387 r
= (TimeZoneRule
*)orgRules
->elementAt(i
);
392 if (i
>= ruleCount
) {
393 // This case should never happen
394 status
= U_INVALID_STATE_ERROR
;
400 const TimeArrayTimeZoneRule
*tar
= dynamic_cast<const TimeArrayTimeZoneRule
*>(toRule
);
401 const AnnualTimeZoneRule
*ar
;
403 // Get the previous raw offset and DST savings before the very first start time
404 TimeZoneTransition tzt0
;
407 avail
= getNextTransition(t
, FALSE
, tzt0
);
411 if (*(tzt0
.getTo()) == *tar
) {
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
)) {
426 // Colllect transitions after the start time
428 DateTimeRule::TimeRuleType timeType
;
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();
438 if (timeType
== DateTimeRule::WALL_TIME
) {
439 t
-= tzt
.getFrom()->getDSTSavings();
445 int32_t asize
= startTimes
- idx
;
447 newTimes
= (UDate
*)uprv_malloc(sizeof(UDate
) * asize
);
448 if (newTimes
== NULL
) {
449 status
= U_MEMORY_ALLOCATION_ERROR
;
452 for (int32_t newidx
= 0; newidx
< asize
; newidx
++) {
453 tar
->getStartTimeAt(idx
+ newidx
, newTimes
[newidx
]);
454 if (U_FAILURE(status
)) {
461 TimeArrayTimeZoneRule
*newTar
= new TimeArrayTimeZoneRule(name
,
462 tar
->getRawOffset(), tar
->getDSTSavings(), newTimes
, asize
, timeType
);
464 filteredRules
->addElement(newTar
, status
);
465 if (U_FAILURE(status
)) {
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
)) {
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
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
)) {
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) {
507 if (orgRules
!= NULL
) {
508 while (!orgRules
->isEmpty()) {
509 r
= (TimeZoneRule
*)orgRules
->orphanElementAt(0);
518 initial
= res_initial
;
519 transitionRules
= filteredRules
;
523 if (orgtrs
!= NULL
) {
526 if (orgRules
!= NULL
) {
527 while (!orgRules
->isEmpty()) {
528 r
= (TimeZoneRule
*)orgRules
->orphanElementAt(0);
534 if (filteredRules
!= NULL
) {
535 while (!filteredRules
->isEmpty()) {
536 r
= (TimeZoneRule
*)filteredRules
->orphanElementAt(0);
539 delete filteredRules
;
546 transitionRules
= NULL
;
550 BasicTimeZone::getOffsetFromLocal(UDate
/*date*/, int32_t /*nonExistingTimeOpt*/, int32_t /*duplicatedTimeOpt*/,
551 int32_t& /*rawOffset*/, int32_t& /*dstOffset*/, UErrorCode
& status
) const {
552 if (U_FAILURE(status
)) {
555 status
= U_UNSUPPORTED_ERROR
;
560 #endif /* #if !UCONFIG_NO_FORMATTING */