1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 *******************************************************************************
5 * Copyright (C) 1997-2013, International Business Machines Corporation and
6 * others. All Rights Reserved.
7 *******************************************************************************
11 * Modification History:
13 * Date Name Description
14 * 12/05/96 clhuang Creation.
15 * 04/21/97 aliu Fixed miscellaneous bugs found by inspection and
17 * 07/29/97 aliu Ported source bodies back from Java version with
18 * numerous feature enhancements and bug fixes.
19 * 08/10/98 stephen JDK 1.2 sync.
20 * 09/17/98 stephen Fixed getOffset() for last hour of year and DST
21 * 12/02/99 aliu Added TimeMode and constructor and setStart/EndRule
22 * methods that take TimeMode. Whitespace cleanup.
23 ********************************************************************************
26 #include "utypeinfo.h" // for 'typeid' to work
28 #include "unicode/utypes.h"
30 #if !UCONFIG_NO_FORMATTING
32 #include "unicode/simpletz.h"
33 #include "unicode/gregocal.h"
34 #include "unicode/smpdtfmt.h"
41 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(SimpleTimeZone
)
43 // Use only for decodeStartRule() and decodeEndRule() where the year is not
44 // available. Set February to 29 days to accomodate rules with that date
45 // and day-of-week-on-or-before-that-date mode (DOW_LE_DOM_MODE).
46 // The compareToRule() method adjusts to February 28 in non-leap years.
48 // For actual getOffset() calculations, use Grego::monthLength() and
49 // Grego::previousMonthLength() which take leap years into account.
50 // We handle leap years assuming always
51 // Gregorian, since we know they didn't have daylight time when
52 // Gregorian calendar started.
53 const int8_t SimpleTimeZone::STATICMONTHLENGTH
[] = {31,29,31,30,31,30,31,31,30,31,30,31};
55 static const UChar DST_STR
[] = {0x0028,0x0044,0x0053,0x0054,0x0029,0}; // "(DST)"
56 static const UChar STD_STR
[] = {0x0028,0x0053,0x0054,0x0044,0x0029,0}; // "(STD)"
59 // *****************************************************************************
60 // class SimpleTimeZone
61 // *****************************************************************************
64 SimpleTimeZone::SimpleTimeZone(int32_t rawOffsetGMT
, const UnicodeString
& ID
)
70 startTimeMode(WALL_TIME
),
71 endTimeMode(WALL_TIME
),
77 rawOffset(rawOffsetGMT
),
81 dstSavings(U_MILLIS_PER_HOUR
)
83 clearTransitionRules();
86 // -------------------------------------
88 SimpleTimeZone::SimpleTimeZone(int32_t rawOffsetGMT
, const UnicodeString
& ID
,
89 int8_t savingsStartMonth
, int8_t savingsStartDay
,
90 int8_t savingsStartDayOfWeek
, int32_t savingsStartTime
,
91 int8_t savingsEndMonth
, int8_t savingsEndDay
,
92 int8_t savingsEndDayOfWeek
, int32_t savingsEndTime
,
96 clearTransitionRules();
97 construct(rawOffsetGMT
,
98 savingsStartMonth
, savingsStartDay
, savingsStartDayOfWeek
,
99 savingsStartTime
, WALL_TIME
,
100 savingsEndMonth
, savingsEndDay
, savingsEndDayOfWeek
,
101 savingsEndTime
, WALL_TIME
,
102 U_MILLIS_PER_HOUR
, status
);
105 // -------------------------------------
107 SimpleTimeZone::SimpleTimeZone(int32_t rawOffsetGMT
, const UnicodeString
& ID
,
108 int8_t savingsStartMonth
, int8_t savingsStartDay
,
109 int8_t savingsStartDayOfWeek
, int32_t savingsStartTime
,
110 int8_t savingsEndMonth
, int8_t savingsEndDay
,
111 int8_t savingsEndDayOfWeek
, int32_t savingsEndTime
,
112 int32_t savingsDST
, UErrorCode
& status
)
115 clearTransitionRules();
116 construct(rawOffsetGMT
,
117 savingsStartMonth
, savingsStartDay
, savingsStartDayOfWeek
,
118 savingsStartTime
, WALL_TIME
,
119 savingsEndMonth
, savingsEndDay
, savingsEndDayOfWeek
,
120 savingsEndTime
, WALL_TIME
,
124 // -------------------------------------
126 SimpleTimeZone::SimpleTimeZone(int32_t rawOffsetGMT
, const UnicodeString
& ID
,
127 int8_t savingsStartMonth
, int8_t savingsStartDay
,
128 int8_t savingsStartDayOfWeek
, int32_t savingsStartTime
,
129 TimeMode savingsStartTimeMode
,
130 int8_t savingsEndMonth
, int8_t savingsEndDay
,
131 int8_t savingsEndDayOfWeek
, int32_t savingsEndTime
,
132 TimeMode savingsEndTimeMode
,
133 int32_t savingsDST
, UErrorCode
& status
)
136 clearTransitionRules();
137 construct(rawOffsetGMT
,
138 savingsStartMonth
, savingsStartDay
, savingsStartDayOfWeek
,
139 savingsStartTime
, savingsStartTimeMode
,
140 savingsEndMonth
, savingsEndDay
, savingsEndDayOfWeek
,
141 savingsEndTime
, savingsEndTimeMode
,
146 * Internal construction method.
148 void SimpleTimeZone::construct(int32_t rawOffsetGMT
,
149 int8_t savingsStartMonth
,
150 int8_t savingsStartDay
,
151 int8_t savingsStartDayOfWeek
,
152 int32_t savingsStartTime
,
153 TimeMode savingsStartTimeMode
,
154 int8_t savingsEndMonth
,
155 int8_t savingsEndDay
,
156 int8_t savingsEndDayOfWeek
,
157 int32_t savingsEndTime
,
158 TimeMode savingsEndTimeMode
,
162 this->rawOffset
= rawOffsetGMT
;
163 this->startMonth
= savingsStartMonth
;
164 this->startDay
= savingsStartDay
;
165 this->startDayOfWeek
= savingsStartDayOfWeek
;
166 this->startTime
= savingsStartTime
;
167 this->startTimeMode
= savingsStartTimeMode
;
168 this->endMonth
= savingsEndMonth
;
169 this->endDay
= savingsEndDay
;
170 this->endDayOfWeek
= savingsEndDayOfWeek
;
171 this->endTime
= savingsEndTime
;
172 this->endTimeMode
= savingsEndTimeMode
;
173 this->dstSavings
= savingsDST
;
175 this->startMode
= DOM_MODE
;
176 this->endMode
= DOM_MODE
;
180 if (savingsDST
<= 0) {
181 status
= U_ILLEGAL_ARGUMENT_ERROR
;
185 // -------------------------------------
187 SimpleTimeZone::~SimpleTimeZone()
189 deleteTransitionRules();
192 // -------------------------------------
194 // Called by TimeZone::createDefault(), then clone() inside a Mutex - be careful.
195 SimpleTimeZone::SimpleTimeZone(const SimpleTimeZone
&source
)
196 : BasicTimeZone(source
)
201 // -------------------------------------
203 // Called by TimeZone::createDefault(), then clone() inside a Mutex - be careful.
205 SimpleTimeZone::operator=(const SimpleTimeZone
&right
)
209 TimeZone::operator=(right
);
210 rawOffset
= right
.rawOffset
;
211 startMonth
= right
.startMonth
;
212 startDay
= right
.startDay
;
213 startDayOfWeek
= right
.startDayOfWeek
;
214 startTime
= right
.startTime
;
215 startTimeMode
= right
.startTimeMode
;
216 startMode
= right
.startMode
;
217 endMonth
= right
.endMonth
;
218 endDay
= right
.endDay
;
219 endDayOfWeek
= right
.endDayOfWeek
;
220 endTime
= right
.endTime
;
221 endTimeMode
= right
.endTimeMode
;
222 endMode
= right
.endMode
;
223 startYear
= right
.startYear
;
224 dstSavings
= right
.dstSavings
;
225 useDaylight
= right
.useDaylight
;
226 clearTransitionRules();
231 // -------------------------------------
234 SimpleTimeZone::operator==(const TimeZone
& that
) const
236 return ((this == &that
) ||
237 (typeid(*this) == typeid(that
) &&
238 TimeZone::operator==(that
) &&
239 hasSameRules(that
)));
242 // -------------------------------------
244 // Called by TimeZone::createDefault() inside a Mutex - be careful.
246 SimpleTimeZone::clone() const
248 return new SimpleTimeZone(*this);
251 // -------------------------------------
254 * Sets the daylight savings starting year, that is, the year this time zone began
255 * observing its specified daylight savings time rules. The time zone is considered
256 * not to observe daylight savings time prior to that year; SimpleTimeZone doesn't
257 * support historical daylight-savings-time rules.
258 * @param year the daylight savings starting year.
261 SimpleTimeZone::setStartYear(int32_t year
)
264 transitionRulesInitialized
= FALSE
;
267 // -------------------------------------
270 * Sets the daylight savings starting rule. For example, in the U.S., Daylight Savings
271 * Time starts at the first Sunday in April, at 2 AM in standard time.
272 * Therefore, you can set the start rule by calling:
273 * setStartRule(TimeFields.APRIL, 1, TimeFields.SUNDAY, 2*60*60*1000);
274 * The dayOfWeekInMonth and dayOfWeek parameters together specify how to calculate
275 * the exact starting date. Their exact meaning depend on their respective signs,
276 * allowing various types of rules to be constructed, as follows:<ul>
277 * <li>If both dayOfWeekInMonth and dayOfWeek are positive, they specify the
278 * day of week in the month (e.g., (2, WEDNESDAY) is the second Wednesday
280 * <li>If dayOfWeek is positive and dayOfWeekInMonth is negative, they specify
281 * the day of week in the month counting backward from the end of the month.
282 * (e.g., (-1, MONDAY) is the last Monday in the month)
283 * <li>If dayOfWeek is zero and dayOfWeekInMonth is positive, dayOfWeekInMonth
284 * specifies the day of the month, regardless of what day of the week it is.
285 * (e.g., (10, 0) is the tenth day of the month)
286 * <li>If dayOfWeek is zero and dayOfWeekInMonth is negative, dayOfWeekInMonth
287 * specifies the day of the month counting backward from the end of the
288 * month, regardless of what day of the week it is (e.g., (-2, 0) is the
289 * next-to-last day of the month).
290 * <li>If dayOfWeek is negative and dayOfWeekInMonth is positive, they specify the
291 * first specified day of the week on or after the specfied day of the month.
292 * (e.g., (15, -SUNDAY) is the first Sunday after the 15th of the month
293 * [or the 15th itself if the 15th is a Sunday].)
294 * <li>If dayOfWeek and DayOfWeekInMonth are both negative, they specify the
295 * last specified day of the week on or before the specified day of the month.
296 * (e.g., (-20, -TUESDAY) is the last Tuesday before the 20th of the month
297 * [or the 20th itself if the 20th is a Tuesday].)</ul>
298 * @param month the daylight savings starting month. Month is 0-based.
300 * @param dayOfWeekInMonth the daylight savings starting
301 * day-of-week-in-month. Please see the member description for an example.
302 * @param dayOfWeek the daylight savings starting day-of-week. Please see
303 * the member description for an example.
304 * @param time the daylight savings starting time. Please see the member
305 * description for an example.
309 SimpleTimeZone::setStartRule(int32_t month
, int32_t dayOfWeekInMonth
, int32_t dayOfWeek
,
310 int32_t time
, TimeMode mode
, UErrorCode
& status
)
312 startMonth
= (int8_t)month
;
313 startDay
= (int8_t)dayOfWeekInMonth
;
314 startDayOfWeek
= (int8_t)dayOfWeek
;
316 startTimeMode
= mode
;
317 decodeStartRule(status
);
318 transitionRulesInitialized
= FALSE
;
321 // -------------------------------------
324 SimpleTimeZone::setStartRule(int32_t month
, int32_t dayOfMonth
,
325 int32_t time
, TimeMode mode
, UErrorCode
& status
)
327 setStartRule(month
, dayOfMonth
, 0, time
, mode
, status
);
330 // -------------------------------------
333 SimpleTimeZone::setStartRule(int32_t month
, int32_t dayOfMonth
, int32_t dayOfWeek
,
334 int32_t time
, TimeMode mode
, UBool after
, UErrorCode
& status
)
336 setStartRule(month
, after
? dayOfMonth
: -dayOfMonth
,
337 -dayOfWeek
, time
, mode
, status
);
340 // -------------------------------------
343 * Sets the daylight savings ending rule. For example, in the U.S., Daylight
344 * Savings Time ends at the last (-1) Sunday in October, at 2 AM in standard time.
345 * Therefore, you can set the end rule by calling:
346 * setEndRule(TimeFields.OCTOBER, -1, TimeFields.SUNDAY, 2*60*60*1000);
347 * Various other types of rules can be specified by manipulating the dayOfWeek
348 * and dayOfWeekInMonth parameters. For complete details, see the documentation
349 * for setStartRule().
350 * @param month the daylight savings ending month. Month is 0-based.
352 * @param dayOfWeekInMonth the daylight savings ending
353 * day-of-week-in-month. See setStartRule() for a complete explanation.
354 * @param dayOfWeek the daylight savings ending day-of-week. See setStartRule()
355 * for a complete explanation.
356 * @param time the daylight savings ending time. Please see the member
357 * description for an example.
361 SimpleTimeZone::setEndRule(int32_t month
, int32_t dayOfWeekInMonth
, int32_t dayOfWeek
,
362 int32_t time
, TimeMode mode
, UErrorCode
& status
)
364 endMonth
= (int8_t)month
;
365 endDay
= (int8_t)dayOfWeekInMonth
;
366 endDayOfWeek
= (int8_t)dayOfWeek
;
369 decodeEndRule(status
);
370 transitionRulesInitialized
= FALSE
;
373 // -------------------------------------
376 SimpleTimeZone::setEndRule(int32_t month
, int32_t dayOfMonth
,
377 int32_t time
, TimeMode mode
, UErrorCode
& status
)
379 setEndRule(month
, dayOfMonth
, 0, time
, mode
, status
);
382 // -------------------------------------
385 SimpleTimeZone::setEndRule(int32_t month
, int32_t dayOfMonth
, int32_t dayOfWeek
,
386 int32_t time
, TimeMode mode
, UBool after
, UErrorCode
& status
)
388 setEndRule(month
, after
? dayOfMonth
: -dayOfMonth
,
389 -dayOfWeek
, time
, mode
, status
);
392 // -------------------------------------
395 SimpleTimeZone::getOffset(uint8_t era
, int32_t year
, int32_t month
, int32_t day
,
396 uint8_t dayOfWeek
, int32_t millis
, UErrorCode
& status
) const
398 // Check the month before calling Grego::monthLength(). This
399 // duplicates the test that occurs in the 7-argument getOffset(),
400 // however, this is unavoidable. We don't mind because this method, in
401 // fact, should not be called; internal code should always call the
402 // 7-argument getOffset(), and outside code should use Calendar.get(int
403 // field) with fields ZONE_OFFSET and DST_OFFSET. We can't get rid of
404 // this method because it's public API. - liu 8/10/98
405 if(month
< UCAL_JANUARY
|| month
> UCAL_DECEMBER
) {
406 status
= U_ILLEGAL_ARGUMENT_ERROR
;
410 return getOffset(era
, year
, month
, day
, dayOfWeek
, millis
, Grego::monthLength(year
, month
), status
);
414 SimpleTimeZone::getOffset(uint8_t era
, int32_t year
, int32_t month
, int32_t day
,
415 uint8_t dayOfWeek
, int32_t millis
,
416 int32_t /*monthLength*/, UErrorCode
& status
) const
418 // Check the month before calling Grego::monthLength(). This
419 // duplicates a test that occurs in the 9-argument getOffset(),
420 // however, this is unavoidable. We don't mind because this method, in
421 // fact, should not be called; internal code should always call the
422 // 9-argument getOffset(), and outside code should use Calendar.get(int
423 // field) with fields ZONE_OFFSET and DST_OFFSET. We can't get rid of
424 // this method because it's public API. - liu 8/10/98
425 if (month
< UCAL_JANUARY
426 || month
> UCAL_DECEMBER
) {
427 status
= U_ILLEGAL_ARGUMENT_ERROR
;
431 // We ignore monthLength because it can be derived from year and month.
432 // This is so that February in leap years is calculated correctly.
433 // We keep this argument in this function for backwards compatibility.
434 return getOffset(era
, year
, month
, day
, dayOfWeek
, millis
,
435 Grego::monthLength(year
, month
),
436 Grego::previousMonthLength(year
, month
),
441 SimpleTimeZone::getOffset(uint8_t era
, int32_t year
, int32_t month
, int32_t day
,
442 uint8_t dayOfWeek
, int32_t millis
,
443 int32_t monthLength
, int32_t prevMonthLength
,
444 UErrorCode
& status
) const
446 if(U_FAILURE(status
)) return 0;
448 if ((era
!= GregorianCalendar::AD
&& era
!= GregorianCalendar::BC
)
449 || month
< UCAL_JANUARY
450 || month
> UCAL_DECEMBER
453 || dayOfWeek
< UCAL_SUNDAY
454 || dayOfWeek
> UCAL_SATURDAY
456 || millis
>= U_MILLIS_PER_DAY
459 || prevMonthLength
< 28
460 || prevMonthLength
> 31) {
461 status
= U_ILLEGAL_ARGUMENT_ERROR
;
465 int32_t result
= rawOffset
;
467 // Bail out if we are before the onset of daylight savings time
468 if(!useDaylight
|| year
< startYear
|| era
!= GregorianCalendar::AD
)
471 // Check for southern hemisphere. We assume that the start and end
472 // month are different.
473 UBool southern
= (startMonth
> endMonth
);
475 // Compare the date to the starting and ending rules.+1 = date>rule, -1
476 // = date<rule, 0 = date==rule.
477 int32_t startCompare
= compareToRule((int8_t)month
, (int8_t)monthLength
, (int8_t)prevMonthLength
,
478 (int8_t)day
, (int8_t)dayOfWeek
, millis
,
479 startTimeMode
== UTC_TIME
? -rawOffset
: 0,
480 startMode
, (int8_t)startMonth
, (int8_t)startDayOfWeek
,
481 (int8_t)startDay
, startTime
);
482 int32_t endCompare
= 0;
484 /* We don't always have to compute endCompare. For many instances,
485 * startCompare is enough to determine if we are in DST or not. In the
486 * northern hemisphere, if we are before the start rule, we can't have
487 * DST. In the southern hemisphere, if we are after the start rule, we
488 * must have DST. This is reflected in the way the next if statement
489 * (not the one immediately following) short circuits. */
490 if(southern
!= (startCompare
>= 0)) {
491 endCompare
= compareToRule((int8_t)month
, (int8_t)monthLength
, (int8_t)prevMonthLength
,
492 (int8_t)day
, (int8_t)dayOfWeek
, millis
,
493 endTimeMode
== WALL_TIME
? dstSavings
:
494 (endTimeMode
== UTC_TIME
? -rawOffset
: 0),
495 endMode
, (int8_t)endMonth
, (int8_t)endDayOfWeek
,
496 (int8_t)endDay
, endTime
);
499 // Check for both the northern and southern hemisphere cases. We
500 // assume that in the northern hemisphere, the start rule is before the
501 // end rule within the calendar year, and vice versa for the southern
503 if ((!southern
&& (startCompare
>= 0 && endCompare
< 0)) ||
504 (southern
&& (startCompare
>= 0 || endCompare
< 0)))
505 result
+= dstSavings
;
511 SimpleTimeZone::getOffsetFromLocal(UDate date
, int32_t nonExistingTimeOpt
, int32_t duplicatedTimeOpt
,
512 int32_t& rawOffsetGMT
, int32_t& savingsDST
, UErrorCode
& status
) const {
513 if (U_FAILURE(status
)) {
517 rawOffsetGMT
= getRawOffset();
518 int32_t year
, month
, dom
, dow
;
519 double day
= uprv_floor(date
/ U_MILLIS_PER_DAY
);
520 int32_t millis
= (int32_t) (date
- day
* U_MILLIS_PER_DAY
);
522 Grego::dayToFields(day
, year
, month
, dom
, dow
);
524 savingsDST
= getOffset(GregorianCalendar::AD
, year
, month
, dom
,
525 (uint8_t) dow
, millis
,
526 Grego::monthLength(year
, month
),
527 status
) - rawOffsetGMT
;
528 if (U_FAILURE(status
)) {
532 UBool recalc
= FALSE
;
534 // Now we need some adjustment
535 if (savingsDST
> 0) {
536 if ((nonExistingTimeOpt
& kStdDstMask
) == kStandard
537 || ((nonExistingTimeOpt
& kStdDstMask
) != kDaylight
&& (nonExistingTimeOpt
& kFormerLatterMask
) != kLatter
)) {
538 date
-= getDSTSavings();
542 if ((duplicatedTimeOpt
& kStdDstMask
) == kDaylight
543 || ((duplicatedTimeOpt
& kStdDstMask
) != kStandard
&& (duplicatedTimeOpt
& kFormerLatterMask
) == kFormer
)) {
544 date
-= getDSTSavings();
549 day
= uprv_floor(date
/ U_MILLIS_PER_DAY
);
550 millis
= (int32_t) (date
- day
* U_MILLIS_PER_DAY
);
551 Grego::dayToFields(day
, year
, month
, dom
, dow
);
552 savingsDST
= getOffset(GregorianCalendar::AD
, year
, month
, dom
,
553 (uint8_t) dow
, millis
,
554 Grego::monthLength(year
, month
),
555 status
) - rawOffsetGMT
;
559 // -------------------------------------
562 * Compare a given date in the year to a rule. Return 1, 0, or -1, depending
563 * on whether the date is after, equal to, or before the rule date. The
564 * millis are compared directly against the ruleMillis, so any
565 * standard-daylight adjustments must be handled by the caller.
567 * @return 1 if the date is after the rule date, -1 if the date is before
568 * the rule date, or 0 if the date is equal to the rule date.
571 SimpleTimeZone::compareToRule(int8_t month
, int8_t monthLen
, int8_t prevMonthLen
,
573 int8_t dayOfWeek
, int32_t millis
, int32_t millisDelta
,
574 EMode ruleMode
, int8_t ruleMonth
, int8_t ruleDayOfWeek
,
575 int8_t ruleDay
, int32_t ruleMillis
)
577 // Make adjustments for startTimeMode and endTimeMode
578 millis
+= millisDelta
;
579 while (millis
>= U_MILLIS_PER_DAY
) {
580 millis
-= U_MILLIS_PER_DAY
;
582 dayOfWeek
= (int8_t)(1 + (dayOfWeek
% 7)); // dayOfWeek is one-based
583 if (dayOfMonth
> monthLen
) {
585 /* When incrementing the month, it is desirible to overflow
586 * from DECEMBER to DECEMBER+1, since we use the result to
587 * compare against a real month. Wraparound of the value
588 * leads to bug 4173604. */
593 millis
+= U_MILLIS_PER_DAY
;
595 dayOfWeek
= (int8_t)(1 + ((dayOfWeek
+5) % 7)); // dayOfWeek is one-based
596 if (dayOfMonth
< 1) {
597 dayOfMonth
= prevMonthLen
;
602 // first compare months. If they're different, we don't have to worry about days
604 if (month
< ruleMonth
) return -1;
605 else if (month
> ruleMonth
) return 1;
607 // calculate the actual day of month for the rule
608 int32_t ruleDayOfMonth
= 0;
610 // Adjust the ruleDay to the monthLen, for non-leap year February 29 rule days.
611 if (ruleDay
> monthLen
) {
617 // if the mode is day-of-month, the day of month is given
619 ruleDayOfMonth
= ruleDay
;
622 // if the mode is day-of-week-in-month, calculate the day-of-month from it
623 case DOW_IN_MONTH_MODE
:
624 // In this case ruleDay is the day-of-week-in-month (this code is using
625 // the dayOfWeek and dayOfMonth parameters to figure out the day-of-week
626 // of the first day of the month, so it's trusting that they're really
627 // consistent with each other)
629 ruleDayOfMonth
= 1 + (ruleDay
- 1) * 7 +
630 (7 + ruleDayOfWeek
- (dayOfWeek
- dayOfMonth
+ 1)) % 7;
632 // if ruleDay is negative (we assume it's not zero here), we have to do
633 // the same calculation figuring backward from the last day of the month.
636 // (again, this code is trusting that dayOfWeek and dayOfMonth are
637 // consistent with each other here, since we're using them to figure
638 // the day of week of the first of the month)
639 ruleDayOfMonth
= monthLen
+ (ruleDay
+ 1) * 7 -
640 (7 + (dayOfWeek
+ monthLen
- dayOfMonth
) - ruleDayOfWeek
) % 7;
644 case DOW_GE_DOM_MODE
:
645 ruleDayOfMonth
= ruleDay
+
646 (49 + ruleDayOfWeek
- ruleDay
- dayOfWeek
+ dayOfMonth
) % 7;
649 case DOW_LE_DOM_MODE
:
650 ruleDayOfMonth
= ruleDay
-
651 (49 - ruleDayOfWeek
+ ruleDay
+ dayOfWeek
- dayOfMonth
) % 7;
652 // Note at this point ruleDayOfMonth may be <1, although it will
653 // be >=1 for well-formed rules.
657 // now that we have a real day-in-month for the rule, we can compare days...
658 if (dayOfMonth
< ruleDayOfMonth
) return -1;
659 else if (dayOfMonth
> ruleDayOfMonth
) return 1;
661 // ...and if they're equal, we compare times
662 if (millis
< ruleMillis
) return -1;
663 else if (millis
> ruleMillis
) return 1;
667 // -------------------------------------
670 SimpleTimeZone::getRawOffset() const
675 // -------------------------------------
678 SimpleTimeZone::setRawOffset(int32_t offsetMillis
)
680 rawOffset
= offsetMillis
;
681 transitionRulesInitialized
= FALSE
;
684 // -------------------------------------
687 SimpleTimeZone::setDSTSavings(int32_t millisSavedDuringDST
, UErrorCode
& status
)
689 if (millisSavedDuringDST
<= 0) {
690 status
= U_ILLEGAL_ARGUMENT_ERROR
;
693 dstSavings
= millisSavedDuringDST
;
695 transitionRulesInitialized
= FALSE
;
698 // -------------------------------------
701 SimpleTimeZone::getDSTSavings() const
706 // -------------------------------------
709 SimpleTimeZone::useDaylightTime() const
714 // -------------------------------------
718 * Queries if the given date is in Daylight Savings Time.
720 UBool
SimpleTimeZone::inDaylightTime(UDate date
, UErrorCode
& status
) const
722 // This method is wasteful since it creates a new GregorianCalendar and
723 // deletes it each time it is called. However, this is a deprecated method
724 // and provided only for Java compatibility as of 8/6/97 [LIU].
725 if (U_FAILURE(status
)) return FALSE
;
726 GregorianCalendar
*gc
= new GregorianCalendar(*this, status
);
729 status
= U_MEMORY_ALLOCATION_ERROR
;
732 gc
->setTime(date
, status
);
733 UBool result
= gc
->inDaylightTime(status
);
738 // -------------------------------------
741 * Return true if this zone has the same rules and offset as another zone.
742 * @param other the TimeZone object to be compared with
743 * @return true if the given zone has the same rules and offset as this one
746 SimpleTimeZone::hasSameRules(const TimeZone
& other
) const
748 if (this == &other
) return TRUE
;
749 if (typeid(*this) != typeid(other
)) return FALSE
;
750 SimpleTimeZone
*that
= (SimpleTimeZone
*)&other
;
751 return rawOffset
== that
->rawOffset
&&
752 useDaylight
== that
->useDaylight
&&
754 // Only check rules if using DST
755 || (dstSavings
== that
->dstSavings
&&
756 startMode
== that
->startMode
&&
757 startMonth
== that
->startMonth
&&
758 startDay
== that
->startDay
&&
759 startDayOfWeek
== that
->startDayOfWeek
&&
760 startTime
== that
->startTime
&&
761 startTimeMode
== that
->startTimeMode
&&
762 endMode
== that
->endMode
&&
763 endMonth
== that
->endMonth
&&
764 endDay
== that
->endDay
&&
765 endDayOfWeek
== that
->endDayOfWeek
&&
766 endTime
== that
->endTime
&&
767 endTimeMode
== that
->endTimeMode
&&
768 startYear
== that
->startYear
));
771 // -------------------------------------
773 //----------------------------------------------------------------------
774 // Rule representation
776 // We represent the following flavors of rules:
777 // 5 the fifth of the month
778 // lastSun the last Sunday in the month
779 // lastMon the last Monday in the month
780 // Sun>=8 first Sunday on or after the eighth
781 // Sun<=25 last Sunday on or before the 25th
782 // This is further complicated by the fact that we need to remain
783 // backward compatible with the 1.1 FCS. Finally, we need to minimize
784 // API changes. In order to satisfy these requirements, we support
785 // three representation systems, and we translate between them.
787 // INTERNAL REPRESENTATION
788 // This is the format SimpleTimeZone objects take after construction or
789 // streaming in is complete. Rules are represented directly, using an
790 // unencoded format. We will discuss the start rule only below; the end
791 // rule is analogous.
792 // startMode Takes on enumerated values DAY_OF_MONTH,
793 // DOW_IN_MONTH, DOW_AFTER_DOM, or DOW_BEFORE_DOM.
794 // startDay The day of the month, or for DOW_IN_MONTH mode, a
795 // value indicating which DOW, such as +1 for first,
796 // +2 for second, -1 for last, etc.
797 // startDayOfWeek The day of the week. Ignored for DAY_OF_MONTH.
799 // ENCODED REPRESENTATION
800 // This is the format accepted by the constructor and by setStartRule()
801 // and setEndRule(). It uses various combinations of positive, negative,
802 // and zero values to encode the different rules. This representation
803 // allows us to specify all the different rule flavors without altering
805 // MODE startMonth startDay startDayOfWeek
806 // DOW_IN_MONTH_MODE >=0 !=0 >0
807 // DOM_MODE >=0 >0 ==0
808 // DOW_GE_DOM_MODE >=0 >0 <0
809 // DOW_LE_DOM_MODE >=0 <0 <0
810 // (no DST) don't care ==0 don't care
812 // STREAMED REPRESENTATION
813 // We must retain binary compatibility with the 1.1 FCS. The 1.1 code only
814 // handles DOW_IN_MONTH_MODE and non-DST mode, the latter indicated by the
815 // flag useDaylight. When we stream an object out, we translate into an
816 // approximate DOW_IN_MONTH_MODE representation so the object can be parsed
817 // and used by 1.1 code. Following that, we write out the full
818 // representation separately so that contemporary code can recognize and
819 // parse it. The full representation is written in a "packed" format,
820 // consisting of a version number, a length, and an array of bytes. Future
821 // versions of this class may specify different versions. If they wish to
822 // include additional data, they should do so by storing them after the
823 // packed representation below.
824 //----------------------------------------------------------------------
827 * Given a set of encoded rules in startDay and startDayOfMonth, decode
828 * them and set the startMode appropriately. Do the same for endDay and
829 * endDayOfMonth. Upon entry, the day of week variables may be zero or
830 * negative, in order to indicate special modes. The day of month
831 * variables may also be negative. Upon exit, the mode variables will be
832 * set, and the day of week and day of month variables will be positive.
833 * This method also recognizes a startDay or endDay of zero as indicating
837 SimpleTimeZone::decodeRules(UErrorCode
& status
)
839 decodeStartRule(status
);
840 decodeEndRule(status
);
844 * Decode the start rule and validate the parameters. The parameters are
845 * expected to be in encoded form, which represents the various rule modes
846 * by negating or zeroing certain values. Representation formats are:
849 * DOW_IN_MONTH DOM DOW>=DOM DOW<=DOM no DST
850 * ------------ ----- -------- -------- ----------
851 * month 0..11 same same same don't care
852 * day -5..5 1..31 1..31 -1..-31 0
853 * dayOfWeek 1..7 0 -1..-7 -1..-7 don't care
854 * time 0..ONEDAY same same same don't care
856 * The range for month does not include UNDECIMBER since this class is
857 * really specific to GregorianCalendar, which does not use that month.
858 * The range for time includes ONEDAY (vs. ending at ONEDAY-1) because the
859 * end rule is an exclusive limit point. That is, the range of times that
860 * are in DST include those >= the start and < the end. For this reason,
861 * it should be possible to specify an end of ONEDAY in order to include the
862 * entire day. Although this is equivalent to time 0 of the following day,
863 * it's not always possible to specify that, for example, on December 31.
864 * While arguably the start range should still be 0..ONEDAY-1, we keep
865 * the start and end ranges the same for consistency.
868 SimpleTimeZone::decodeStartRule(UErrorCode
& status
)
870 if(U_FAILURE(status
)) return;
872 useDaylight
= (UBool
)((startDay
!= 0) && (endDay
!= 0) ? TRUE
: FALSE
);
873 if (useDaylight
&& dstSavings
== 0) {
874 dstSavings
= U_MILLIS_PER_HOUR
;
877 if (startMonth
< UCAL_JANUARY
|| startMonth
> UCAL_DECEMBER
) {
878 status
= U_ILLEGAL_ARGUMENT_ERROR
;
881 if (startTime
< 0 || startTime
> U_MILLIS_PER_DAY
||
882 startTimeMode
< WALL_TIME
|| startTimeMode
> UTC_TIME
) {
883 status
= U_ILLEGAL_ARGUMENT_ERROR
;
886 if (startDayOfWeek
== 0) {
887 startMode
= DOM_MODE
;
889 if (startDayOfWeek
> 0) {
890 startMode
= DOW_IN_MONTH_MODE
;
892 startDayOfWeek
= (int8_t)-startDayOfWeek
;
894 startMode
= DOW_GE_DOM_MODE
;
896 startDay
= (int8_t)-startDay
;
897 startMode
= DOW_LE_DOM_MODE
;
900 if (startDayOfWeek
> UCAL_SATURDAY
) {
901 status
= U_ILLEGAL_ARGUMENT_ERROR
;
905 if (startMode
== DOW_IN_MONTH_MODE
) {
906 if (startDay
< -5 || startDay
> 5) {
907 status
= U_ILLEGAL_ARGUMENT_ERROR
;
910 } else if (startDay
<1 || startDay
> STATICMONTHLENGTH
[startMonth
]) {
911 status
= U_ILLEGAL_ARGUMENT_ERROR
;
918 * Decode the end rule and validate the parameters. This method is exactly
919 * analogous to decodeStartRule().
920 * @see decodeStartRule
923 SimpleTimeZone::decodeEndRule(UErrorCode
& status
)
925 if(U_FAILURE(status
)) return;
927 useDaylight
= (UBool
)((startDay
!= 0) && (endDay
!= 0) ? TRUE
: FALSE
);
928 if (useDaylight
&& dstSavings
== 0) {
929 dstSavings
= U_MILLIS_PER_HOUR
;
932 if (endMonth
< UCAL_JANUARY
|| endMonth
> UCAL_DECEMBER
) {
933 status
= U_ILLEGAL_ARGUMENT_ERROR
;
936 if (endTime
< 0 || endTime
> U_MILLIS_PER_DAY
||
937 endTimeMode
< WALL_TIME
|| endTimeMode
> UTC_TIME
) {
938 status
= U_ILLEGAL_ARGUMENT_ERROR
;
941 if (endDayOfWeek
== 0) {
944 if (endDayOfWeek
> 0) {
945 endMode
= DOW_IN_MONTH_MODE
;
947 endDayOfWeek
= (int8_t)-endDayOfWeek
;
949 endMode
= DOW_GE_DOM_MODE
;
951 endDay
= (int8_t)-endDay
;
952 endMode
= DOW_LE_DOM_MODE
;
955 if (endDayOfWeek
> UCAL_SATURDAY
) {
956 status
= U_ILLEGAL_ARGUMENT_ERROR
;
960 if (endMode
== DOW_IN_MONTH_MODE
) {
961 if (endDay
< -5 || endDay
> 5) {
962 status
= U_ILLEGAL_ARGUMENT_ERROR
;
965 } else if (endDay
<1 || endDay
> STATICMONTHLENGTH
[endMonth
]) {
966 status
= U_ILLEGAL_ARGUMENT_ERROR
;
973 SimpleTimeZone::getNextTransition(UDate base
, UBool inclusive
, TimeZoneTransition
& result
) const {
978 UErrorCode status
= U_ZERO_ERROR
;
979 checkTransitionRules(status
);
980 if (U_FAILURE(status
)) {
984 UDate firstTransitionTime
= firstTransition
->getTime();
985 if (base
< firstTransitionTime
|| (inclusive
&& base
== firstTransitionTime
)) {
986 result
= *firstTransition
;
988 UDate stdDate
, dstDate
;
989 UBool stdAvail
= stdRule
->getNextStart(base
, dstRule
->getRawOffset(), dstRule
->getDSTSavings(), inclusive
, stdDate
);
990 UBool dstAvail
= dstRule
->getNextStart(base
, stdRule
->getRawOffset(), stdRule
->getDSTSavings(), inclusive
, dstDate
);
991 if (stdAvail
&& (!dstAvail
|| stdDate
< dstDate
)) {
992 result
.setTime(stdDate
);
993 result
.setFrom((const TimeZoneRule
&)*dstRule
);
994 result
.setTo((const TimeZoneRule
&)*stdRule
);
997 if (dstAvail
&& (!stdAvail
|| dstDate
< stdDate
)) {
998 result
.setTime(dstDate
);
999 result
.setFrom((const TimeZoneRule
&)*stdRule
);
1000 result
.setTo((const TimeZoneRule
&)*dstRule
);
1007 SimpleTimeZone::getPreviousTransition(UDate base
, UBool inclusive
, TimeZoneTransition
& result
) const {
1012 UErrorCode status
= U_ZERO_ERROR
;
1013 checkTransitionRules(status
);
1014 if (U_FAILURE(status
)) {
1018 UDate firstTransitionTime
= firstTransition
->getTime();
1019 if (base
< firstTransitionTime
|| (!inclusive
&& base
== firstTransitionTime
)) {
1022 UDate stdDate
, dstDate
;
1023 UBool stdAvail
= stdRule
->getPreviousStart(base
, dstRule
->getRawOffset(), dstRule
->getDSTSavings(), inclusive
, stdDate
);
1024 UBool dstAvail
= dstRule
->getPreviousStart(base
, stdRule
->getRawOffset(), stdRule
->getDSTSavings(), inclusive
, dstDate
);
1025 if (stdAvail
&& (!dstAvail
|| stdDate
> dstDate
)) {
1026 result
.setTime(stdDate
);
1027 result
.setFrom((const TimeZoneRule
&)*dstRule
);
1028 result
.setTo((const TimeZoneRule
&)*stdRule
);
1031 if (dstAvail
&& (!stdAvail
|| dstDate
> stdDate
)) {
1032 result
.setTime(dstDate
);
1033 result
.setFrom((const TimeZoneRule
&)*stdRule
);
1034 result
.setTo((const TimeZoneRule
&)*dstRule
);
1041 SimpleTimeZone::clearTransitionRules(void) {
1043 firstTransition
= NULL
;
1046 transitionRulesInitialized
= FALSE
;
1050 SimpleTimeZone::deleteTransitionRules(void) {
1051 if (initialRule
!= NULL
) {
1054 if (firstTransition
!= NULL
) {
1055 delete firstTransition
;
1057 if (stdRule
!= NULL
) {
1060 if (dstRule
!= NULL
) {
1063 clearTransitionRules();
1067 * Lazy transition rules initializer
1069 * Note On the removal of UMTX_CHECK from checkTransitionRules():
1071 * It would be faster to have a UInitOnce as part of a SimpleTimeZone object,
1072 * which would avoid needing to lock a mutex to check the initialization state.
1073 * But we can't easily because simpletz.h is a public header, and including
1074 * a UInitOnce as a member of SimpleTimeZone would publicly expose internal ICU headers.
1076 * Alternatively we could have a pointer to a UInitOnce in the SimpleTimeZone object,
1077 * allocate it in the constructors. This would be a more intrusive change, but doable
1078 * if performance turns out to be an issue.
1080 static UMutex gLock
= U_MUTEX_INITIALIZER
;
1083 SimpleTimeZone::checkTransitionRules(UErrorCode
& status
) const {
1084 if (U_FAILURE(status
)) {
1088 if (!transitionRulesInitialized
) {
1089 SimpleTimeZone
*ncThis
= const_cast<SimpleTimeZone
*>(this);
1090 ncThis
->initTransitionRules(status
);
1092 umtx_unlock(&gLock
);
1096 SimpleTimeZone::initTransitionRules(UErrorCode
& status
) {
1097 if (U_FAILURE(status
)) {
1100 if (transitionRulesInitialized
) {
1103 deleteTransitionRules();
1108 DateTimeRule
* dtRule
;
1109 DateTimeRule::TimeRuleType timeRuleType
;
1110 UDate firstStdStart
, firstDstStart
;
1112 // Create a TimeZoneRule for daylight saving time
1113 timeRuleType
= (startTimeMode
== STANDARD_TIME
) ? DateTimeRule::STANDARD_TIME
:
1114 ((startTimeMode
== UTC_TIME
) ? DateTimeRule::UTC_TIME
: DateTimeRule::WALL_TIME
);
1115 switch (startMode
) {
1117 dtRule
= new DateTimeRule(startMonth
, startDay
, startTime
, timeRuleType
);
1119 case DOW_IN_MONTH_MODE
:
1120 dtRule
= new DateTimeRule(startMonth
, startDay
, startDayOfWeek
, startTime
, timeRuleType
);
1122 case DOW_GE_DOM_MODE
:
1123 dtRule
= new DateTimeRule(startMonth
, startDay
, startDayOfWeek
, true, startTime
, timeRuleType
);
1125 case DOW_LE_DOM_MODE
:
1126 dtRule
= new DateTimeRule(startMonth
, startDay
, startDayOfWeek
, false, startTime
, timeRuleType
);
1129 status
= U_INVALID_STATE_ERROR
;
1132 // Check for Null pointer
1133 if (dtRule
== NULL
) {
1134 status
= U_MEMORY_ALLOCATION_ERROR
;
1137 // For now, use ID + "(DST)" as the name
1138 dstRule
= new AnnualTimeZoneRule(tzid
+UnicodeString(DST_STR
), getRawOffset(), getDSTSavings(),
1139 dtRule
, startYear
, AnnualTimeZoneRule::MAX_YEAR
);
1141 // Check for Null pointer
1142 if (dstRule
== NULL
) {
1143 status
= U_MEMORY_ALLOCATION_ERROR
;
1144 deleteTransitionRules();
1148 // Calculate the first DST start time
1149 dstRule
->getFirstStart(getRawOffset(), 0, firstDstStart
);
1151 // Create a TimeZoneRule for standard time
1152 timeRuleType
= (endTimeMode
== STANDARD_TIME
) ? DateTimeRule::STANDARD_TIME
:
1153 ((endTimeMode
== UTC_TIME
) ? DateTimeRule::UTC_TIME
: DateTimeRule::WALL_TIME
);
1156 dtRule
= new DateTimeRule(endMonth
, endDay
, endTime
, timeRuleType
);
1158 case DOW_IN_MONTH_MODE
:
1159 dtRule
= new DateTimeRule(endMonth
, endDay
, endDayOfWeek
, endTime
, timeRuleType
);
1161 case DOW_GE_DOM_MODE
:
1162 dtRule
= new DateTimeRule(endMonth
, endDay
, endDayOfWeek
, true, endTime
, timeRuleType
);
1164 case DOW_LE_DOM_MODE
:
1165 dtRule
= new DateTimeRule(endMonth
, endDay
, endDayOfWeek
, false, endTime
, timeRuleType
);
1169 // Check for Null pointer
1170 if (dtRule
== NULL
) {
1171 status
= U_MEMORY_ALLOCATION_ERROR
;
1172 deleteTransitionRules();
1175 // For now, use ID + "(STD)" as the name
1176 stdRule
= new AnnualTimeZoneRule(tzid
+UnicodeString(STD_STR
), getRawOffset(), 0,
1177 dtRule
, startYear
, AnnualTimeZoneRule::MAX_YEAR
);
1179 //Check for Null pointer
1180 if (stdRule
== NULL
) {
1181 status
= U_MEMORY_ALLOCATION_ERROR
;
1182 deleteTransitionRules();
1186 // Calculate the first STD start time
1187 stdRule
->getFirstStart(getRawOffset(), dstRule
->getDSTSavings(), firstStdStart
);
1189 // Create a TimeZoneRule for initial time
1190 if (firstStdStart
< firstDstStart
) {
1191 initialRule
= new InitialTimeZoneRule(tzid
+UnicodeString(DST_STR
), getRawOffset(), dstRule
->getDSTSavings());
1192 if (initialRule
== NULL
) {
1193 status
= U_MEMORY_ALLOCATION_ERROR
;
1194 deleteTransitionRules();
1197 firstTransition
= new TimeZoneTransition(firstStdStart
, *initialRule
, *stdRule
);
1199 initialRule
= new InitialTimeZoneRule(tzid
+UnicodeString(STD_STR
), getRawOffset(), 0);
1200 if (initialRule
== NULL
) {
1201 status
= U_MEMORY_ALLOCATION_ERROR
;
1202 deleteTransitionRules();
1205 firstTransition
= new TimeZoneTransition(firstDstStart
, *initialRule
, *dstRule
);
1207 if (firstTransition
== NULL
) {
1208 status
= U_MEMORY_ALLOCATION_ERROR
;
1209 deleteTransitionRules();
1214 // Create a TimeZoneRule for initial time
1215 initialRule
= new InitialTimeZoneRule(tzid
, getRawOffset(), 0);
1216 // Check for null pointer.
1217 if (initialRule
== NULL
) {
1218 status
= U_MEMORY_ALLOCATION_ERROR
;
1219 deleteTransitionRules();
1224 transitionRulesInitialized
= TRUE
;
1228 SimpleTimeZone::countTransitionRules(UErrorCode
& /*status*/) const {
1229 return (useDaylight
) ? 2 : 0;
1233 SimpleTimeZone::getTimeZoneRules(const InitialTimeZoneRule
*& initial
,
1234 const TimeZoneRule
* trsrules
[],
1236 UErrorCode
& status
) const {
1237 if (U_FAILURE(status
)) {
1240 checkTransitionRules(status
);
1241 if (U_FAILURE(status
)) {
1244 initial
= initialRule
;
1246 if (stdRule
!= NULL
) {
1247 if (cnt
< trscount
) {
1248 trsrules
[cnt
++] = stdRule
;
1250 if (cnt
< trscount
) {
1251 trsrules
[cnt
++] = dstRule
;
1260 #endif /* #if !UCONFIG_NO_FORMATTING */