1/*
2 *******************************************************************************
3 * Copyright (C) 1997-2009, International Business Machines Corporation and    *
4 * others. All Rights Reserved.                                                *
5 *******************************************************************************
6 *
7 * File SIMPLETZ.H
8 *
9 * Modification History:
10 *
11 *   Date        Name        Description
12 *   12/05/96    clhuang     Creation.
13 *   04/21/97    aliu        Fixed miscellaneous bugs found by inspection and
14 *                           testing.
15 *   07/29/97    aliu        Ported source bodies back from Java version with
16 *                           numerous feature enhancements and bug fixes.
17 *   08/10/98    stephen     JDK 1.2 sync.
18 *   09/17/98    stephen     Fixed getOffset() for last hour of year and DST
19 *   12/02/99    aliu        Added TimeMode and constructor and setStart/EndRule
20 *                           methods that take TimeMode. Whitespace cleanup.
21 ********************************************************************************
22 */
23
24#include "unicode/utypes.h"
25
26#if !UCONFIG_NO_FORMATTING
27
28#include "unicode/simpletz.h"
29#include "unicode/gregocal.h"
30#include "unicode/smpdtfmt.h"
31
32#include "gregoimp.h"
33
34U_NAMESPACE_BEGIN
35
36UOBJECT_DEFINE_RTTI_IMPLEMENTATION(SimpleTimeZone)
37
38// Use only for decodeStartRule() and decodeEndRule() where the year is not
39// available. Set February to 29 days to accomodate rules with that date
40// and day-of-week-on-or-before-that-date mode (DOW_LE_DOM_MODE).
41// The compareToRule() method adjusts to February 28 in non-leap years.
42//
43// For actual getOffset() calculations, use Grego::monthLength() and
44// Grego::previousMonthLength() which take leap years into account.
45// We handle leap years assuming always
46// Gregorian, since we know they didn't have daylight time when
47// Gregorian calendar started.
48const int8_t SimpleTimeZone::STATICMONTHLENGTH[] = {31,29,31,30,31,30,31,31,30,31,30,31};
49
50static const UChar DST_STR[] = {0x0028,0x0044,0x0053,0x0054,0x0029,0}; // "(DST)"
51static const UChar STD_STR[] = {0x0028,0x0053,0x0054,0x0044,0x0029,0}; // "(STD)"
52
53
54// *****************************************************************************
55// class SimpleTimeZone
56// *****************************************************************************
57
58
59SimpleTimeZone::SimpleTimeZone(int32_t rawOffsetGMT, const UnicodeString& ID)
60:   BasicTimeZone(ID),
61    startMonth(0),
62    startDay(0),
63    startDayOfWeek(0),
64    startTime(0),
65    startTimeMode(WALL_TIME),
66    endTimeMode(WALL_TIME),
67    endMonth(0),
68    endDay(0),
69    endDayOfWeek(0),
70    endTime(0),
71    startYear(0),
72    rawOffset(rawOffsetGMT),
73    useDaylight(FALSE),
74    startMode(DOM_MODE),
75    endMode(DOM_MODE),
76    dstSavings(U_MILLIS_PER_HOUR)
77{
78    clearTransitionRules();
79}
80
81// -------------------------------------
82
83SimpleTimeZone::SimpleTimeZone(int32_t rawOffsetGMT, const UnicodeString& ID,
84    int8_t savingsStartMonth, int8_t savingsStartDay,
85    int8_t savingsStartDayOfWeek, int32_t savingsStartTime,
86    int8_t savingsEndMonth, int8_t savingsEndDay,
87    int8_t savingsEndDayOfWeek, int32_t savingsEndTime,
88    UErrorCode& status)
89:   BasicTimeZone(ID)
90{
91    clearTransitionRules();
92    construct(rawOffsetGMT,
93              savingsStartMonth, savingsStartDay, savingsStartDayOfWeek,
94              savingsStartTime, WALL_TIME,
95              savingsEndMonth, savingsEndDay, savingsEndDayOfWeek,
96              savingsEndTime, WALL_TIME,
97              U_MILLIS_PER_HOUR, status);
98}
99
100// -------------------------------------
101
102SimpleTimeZone::SimpleTimeZone(int32_t rawOffsetGMT, const UnicodeString& ID,
103    int8_t savingsStartMonth, int8_t savingsStartDay,
104    int8_t savingsStartDayOfWeek, int32_t savingsStartTime,
105    int8_t savingsEndMonth, int8_t savingsEndDay,
106    int8_t savingsEndDayOfWeek, int32_t savingsEndTime,
107    int32_t savingsDST, UErrorCode& status)
108:   BasicTimeZone(ID)
109{
110    clearTransitionRules();
111    construct(rawOffsetGMT,
112              savingsStartMonth, savingsStartDay, savingsStartDayOfWeek,
113              savingsStartTime, WALL_TIME,
114              savingsEndMonth, savingsEndDay, savingsEndDayOfWeek,
115              savingsEndTime, WALL_TIME,
116              savingsDST, status);
117}
118
119// -------------------------------------
120
121SimpleTimeZone::SimpleTimeZone(int32_t rawOffsetGMT, const UnicodeString& ID,
122    int8_t savingsStartMonth, int8_t savingsStartDay,
123    int8_t savingsStartDayOfWeek, int32_t savingsStartTime,
124    TimeMode savingsStartTimeMode,
125    int8_t savingsEndMonth, int8_t savingsEndDay,
126    int8_t savingsEndDayOfWeek, int32_t savingsEndTime,
127    TimeMode savingsEndTimeMode,
128    int32_t savingsDST, UErrorCode& status)
129:   BasicTimeZone(ID)
130{
131    clearTransitionRules();
132    construct(rawOffsetGMT,
133              savingsStartMonth, savingsStartDay, savingsStartDayOfWeek,
134              savingsStartTime, savingsStartTimeMode,
135              savingsEndMonth, savingsEndDay, savingsEndDayOfWeek,
136              savingsEndTime, savingsEndTimeMode,
137              savingsDST, status);
138}
139
140/**
141 * Internal construction method.
142 */
143void SimpleTimeZone::construct(int32_t rawOffsetGMT,
144                               int8_t savingsStartMonth,
145                               int8_t savingsStartDay,
146                               int8_t savingsStartDayOfWeek,
147                               int32_t savingsStartTime,
148                               TimeMode savingsStartTimeMode,
149                               int8_t savingsEndMonth,
150                               int8_t savingsEndDay,
151                               int8_t savingsEndDayOfWeek,
152                               int32_t savingsEndTime,
153                               TimeMode savingsEndTimeMode,
154                               int32_t savingsDST,
155                               UErrorCode& status)
156{
157    this->rawOffset      = rawOffsetGMT;
158    this->startMonth     = savingsStartMonth;
159    this->startDay       = savingsStartDay;
160    this->startDayOfWeek = savingsStartDayOfWeek;
161    this->startTime      = savingsStartTime;
162    this->startTimeMode  = savingsStartTimeMode;
163    this->endMonth       = savingsEndMonth;
164    this->endDay         = savingsEndDay;
165    this->endDayOfWeek   = savingsEndDayOfWeek;
166    this->endTime        = savingsEndTime;
167    this->endTimeMode    = savingsEndTimeMode;
168    this->dstSavings     = savingsDST;
169    this->startYear      = 0;
170    this->startMode      = DOM_MODE;
171    this->endMode        = DOM_MODE;
172
173    decodeRules(status);
174
175    if (savingsDST <= 0) {
176        status = U_ILLEGAL_ARGUMENT_ERROR;
177    }
178}
179
180// -------------------------------------
181
182SimpleTimeZone::~SimpleTimeZone()
183{
184    deleteTransitionRules();
185}
186
187// -------------------------------------
188
189// Called by TimeZone::createDefault(), then clone() inside a Mutex - be careful.
190SimpleTimeZone::SimpleTimeZone(const SimpleTimeZone &source)
191:   BasicTimeZone(source)
192{
193    *this = source;
194}
195
196// -------------------------------------
197
198// Called by TimeZone::createDefault(), then clone() inside a Mutex - be careful.
199SimpleTimeZone &
200SimpleTimeZone::operator=(const SimpleTimeZone &right)
201{
202    if (this != &right)
203    {
204        TimeZone::operator=(right);
205        rawOffset      = right.rawOffset;
206        startMonth     = right.startMonth;
207        startDay       = right.startDay;
208        startDayOfWeek = right.startDayOfWeek;
209        startTime      = right.startTime;
210        startTimeMode  = right.startTimeMode;
211        startMode      = right.startMode;
212        endMonth       = right.endMonth;
213        endDay         = right.endDay;
214        endDayOfWeek   = right.endDayOfWeek;
215        endTime        = right.endTime;
216        endTimeMode    = right.endTimeMode;
217        endMode        = right.endMode;
218        startYear      = right.startYear;
219        dstSavings     = right.dstSavings;
220        useDaylight    = right.useDaylight;
221        clearTransitionRules();
222    }
223    return *this;
224}
225
226// -------------------------------------
227
228UBool
229SimpleTimeZone::operator==(const TimeZone& that) const
230{
231    return ((this == &that) ||
232            (getDynamicClassID() == that.getDynamicClassID() &&
233            TimeZone::operator==(that) &&
234            hasSameRules(that)));
235}
236
237// -------------------------------------
238
239// Called by TimeZone::createDefault() inside a Mutex - be careful.
240TimeZone*
241SimpleTimeZone::clone() const
242{
243    return new SimpleTimeZone(*this);
244}
245
246// -------------------------------------
247
248/**
249 * Sets the daylight savings starting year, that is, the year this time zone began
250 * observing its specified daylight savings time rules.  The time zone is considered
251 * not to observe daylight savings time prior to that year; SimpleTimeZone doesn't
252 * support historical daylight-savings-time rules.
253 * @param year the daylight savings starting year.
254 */
255void
256SimpleTimeZone::setStartYear(int32_t year)
257{
258    startYear = year;
259    transitionRulesInitialized = FALSE;
260}
261
262// -------------------------------------
263
264/**
265 * Sets the daylight savings starting rule. For example, in the U.S., Daylight Savings
266 * Time starts at the first Sunday in April, at 2 AM in standard time.
267 * Therefore, you can set the start rule by calling:
268 * setStartRule(TimeFields.APRIL, 1, TimeFields.SUNDAY, 2*60*60*1000);
269 * The dayOfWeekInMonth and dayOfWeek parameters together specify how to calculate
270 * the exact starting date.  Their exact meaning depend on their respective signs,
271 * allowing various types of rules to be constructed, as follows:<ul>
272 *   <li>If both dayOfWeekInMonth and dayOfWeek are positive, they specify the
273 *       day of week in the month (e.g., (2, WEDNESDAY) is the second Wednesday
274 *       of the month).
275 *   <li>If dayOfWeek is positive and dayOfWeekInMonth is negative, they specify
276 *       the day of week in the month counting backward from the end of the month.
277 *       (e.g., (-1, MONDAY) is the last Monday in the month)
278 *   <li>If dayOfWeek is zero and dayOfWeekInMonth is positive, dayOfWeekInMonth
279 *       specifies the day of the month, regardless of what day of the week it is.
280 *       (e.g., (10, 0) is the tenth day of the month)
281 *   <li>If dayOfWeek is zero and dayOfWeekInMonth is negative, dayOfWeekInMonth
282 *       specifies the day of the month counting backward from the end of the
283 *       month, regardless of what day of the week it is (e.g., (-2, 0) is the
284 *       next-to-last day of the month).
285 *   <li>If dayOfWeek is negative and dayOfWeekInMonth is positive, they specify the
286 *       first specified day of the week on or after the specfied day of the month.
287 *       (e.g., (15, -SUNDAY) is the first Sunday after the 15th of the month
288 *       [or the 15th itself if the 15th is a Sunday].)
289 *   <li>If dayOfWeek and DayOfWeekInMonth are both negative, they specify the
290 *       last specified day of the week on or before the specified day of the month.
291 *       (e.g., (-20, -TUESDAY) is the last Tuesday before the 20th of the month
292 *       [or the 20th itself if the 20th is a Tuesday].)</ul>
293 * @param month the daylight savings starting month. Month is 0-based.
294 * eg, 0 for January.
295 * @param dayOfWeekInMonth the daylight savings starting
296 * day-of-week-in-month. Please see the member description for an example.
297 * @param dayOfWeek the daylight savings starting day-of-week. Please see
298 * the member description for an example.
299 * @param time the daylight savings starting time. Please see the member
300 * description for an example.
301 */
302
303void
304SimpleTimeZone::setStartRule(int32_t month, int32_t dayOfWeekInMonth, int32_t dayOfWeek,
305                             int32_t time, TimeMode mode, UErrorCode& status)
306{
307    startMonth     = (int8_t)month;
308    startDay       = (int8_t)dayOfWeekInMonth;
309    startDayOfWeek = (int8_t)dayOfWeek;
310    startTime      = time;
311    startTimeMode  = mode;
312    decodeStartRule(status);
313    transitionRulesInitialized = FALSE;
314}
315
316// -------------------------------------
317
318void
319SimpleTimeZone::setStartRule(int32_t month, int32_t dayOfMonth,
320                             int32_t time, TimeMode mode, UErrorCode& status)
321{
322    setStartRule(month, dayOfMonth, 0, time, mode, status);
323}
324
325// -------------------------------------
326
327void
328SimpleTimeZone::setStartRule(int32_t month, int32_t dayOfMonth, int32_t dayOfWeek,
329                             int32_t time, TimeMode mode, UBool after, UErrorCode& status)
330{
331    setStartRule(month, after ? dayOfMonth : -dayOfMonth,
332                 -dayOfWeek, time, mode, status);
333}
334
335// -------------------------------------
336
337/**
338 * Sets the daylight savings ending rule. For example, in the U.S., Daylight
339 * Savings Time ends at the last (-1) Sunday in October, at 2 AM in standard time.
340 * Therefore, you can set the end rule by calling:
341 * setEndRule(TimeFields.OCTOBER, -1, TimeFields.SUNDAY, 2*60*60*1000);
342 * Various other types of rules can be specified by manipulating the dayOfWeek
343 * and dayOfWeekInMonth parameters.  For complete details, see the documentation
344 * for setStartRule().
345 * @param month the daylight savings ending month. Month is 0-based.
346 * eg, 0 for January.
347 * @param dayOfWeekInMonth the daylight savings ending
348 * day-of-week-in-month. See setStartRule() for a complete explanation.
349 * @param dayOfWeek the daylight savings ending day-of-week. See setStartRule()
350 * for a complete explanation.
351 * @param time the daylight savings ending time. Please see the member
352 * description for an example.
353 */
354
355void
356SimpleTimeZone::setEndRule(int32_t month, int32_t dayOfWeekInMonth, int32_t dayOfWeek,
357                           int32_t time, TimeMode mode, UErrorCode& status)
358{
359    endMonth     = (int8_t)month;
360    endDay       = (int8_t)dayOfWeekInMonth;
361    endDayOfWeek = (int8_t)dayOfWeek;
362    endTime      = time;
363    endTimeMode  = mode;
364    decodeEndRule(status);
365    transitionRulesInitialized = FALSE;
366}
367
368// -------------------------------------
369
370void
371SimpleTimeZone::setEndRule(int32_t month, int32_t dayOfMonth,
372                           int32_t time, TimeMode mode, UErrorCode& status)
373{
374    setEndRule(month, dayOfMonth, 0, time, mode, status);
375}
376
377// -------------------------------------
378
379void
380SimpleTimeZone::setEndRule(int32_t month, int32_t dayOfMonth, int32_t dayOfWeek,
381                           int32_t time, TimeMode mode, UBool after, UErrorCode& status)
382{
383    setEndRule(month, after ? dayOfMonth : -dayOfMonth,
384               -dayOfWeek, time, mode, status);
385}
386
387// -------------------------------------
388
389int32_t
390SimpleTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day,
391                          uint8_t dayOfWeek, int32_t millis, UErrorCode& status) const
392{
393    // Check the month before calling Grego::monthLength(). This
394    // duplicates the test that occurs in the 7-argument getOffset(),
395    // however, this is unavoidable. We don't mind because this method, in
396    // fact, should not be called; internal code should always call the
397    // 7-argument getOffset(), and outside code should use Calendar.get(int
398    // field) with fields ZONE_OFFSET and DST_OFFSET. We can't get rid of
399    // this method because it's public API. - liu 8/10/98
400    if(month < UCAL_JANUARY || month > UCAL_DECEMBER) {
401        status = U_ILLEGAL_ARGUMENT_ERROR;
402        return 0;
403    }
404
405    return getOffset(era, year, month, day, dayOfWeek, millis, Grego::monthLength(year, month), status);
406}
407
408int32_t
409SimpleTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day,
410                          uint8_t dayOfWeek, int32_t millis,
411                          int32_t /*monthLength*/, UErrorCode& status) const
412{
413    // Check the month before calling Grego::monthLength(). This
414    // duplicates a test that occurs in the 9-argument getOffset(),
415    // however, this is unavoidable. We don't mind because this method, in
416    // fact, should not be called; internal code should always call the
417    // 9-argument getOffset(), and outside code should use Calendar.get(int
418    // field) with fields ZONE_OFFSET and DST_OFFSET. We can't get rid of
419    // this method because it's public API. - liu 8/10/98
420    if (month < UCAL_JANUARY
421        || month > UCAL_DECEMBER) {
422        status = U_ILLEGAL_ARGUMENT_ERROR;
423        return -1;
424    }
425
426    // We ignore monthLength because it can be derived from year and month.
427    // This is so that February in leap years is calculated correctly.
428    // We keep this argument in this function for backwards compatibility.
429    return getOffset(era, year, month, day, dayOfWeek, millis,
430                     Grego::monthLength(year, month),
431                     Grego::previousMonthLength(year, month),
432                     status);
433}
434
435int32_t
436SimpleTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day,
437                          uint8_t dayOfWeek, int32_t millis,
438                          int32_t monthLength, int32_t prevMonthLength,
439                          UErrorCode& status) const
440{
441    if(U_FAILURE(status)) return 0;
442
443    if ((era != GregorianCalendar::AD && era != GregorianCalendar::BC)
444        || month < UCAL_JANUARY
445        || month > UCAL_DECEMBER
446        || day < 1
447        || day > monthLength
448        || dayOfWeek < UCAL_SUNDAY
449        || dayOfWeek > UCAL_SATURDAY
450        || millis < 0
451        || millis >= U_MILLIS_PER_DAY
452        || monthLength < 28
453        || monthLength > 31
454        || prevMonthLength < 28
455        || prevMonthLength > 31) {
456        status = U_ILLEGAL_ARGUMENT_ERROR;
457        return -1;
458    }
459
460    int32_t result = rawOffset;
461
462    // Bail out if we are before the onset of daylight savings time
463    if(!useDaylight || year < startYear || era != GregorianCalendar::AD)
464        return result;
465
466    // Check for southern hemisphere.  We assume that the start and end
467    // month are different.
468    UBool southern = (startMonth > endMonth);
469
470    // Compare the date to the starting and ending rules.+1 = date>rule, -1
471    // = date<rule, 0 = date==rule.
472    int32_t startCompare = compareToRule((int8_t)month, (int8_t)monthLength, (int8_t)prevMonthLength,
473                                         (int8_t)day, (int8_t)dayOfWeek, millis,
474                                         startTimeMode == UTC_TIME ? -rawOffset : 0,
475                                         startMode, (int8_t)startMonth, (int8_t)startDayOfWeek,
476                                         (int8_t)startDay, startTime);
477    int32_t endCompare = 0;
478
479    /* We don't always have to compute endCompare.  For many instances,
480     * startCompare is enough to determine if we are in DST or not.  In the
481     * northern hemisphere, if we are before the start rule, we can't have
482     * DST.  In the southern hemisphere, if we are after the start rule, we
483     * must have DST.  This is reflected in the way the next if statement
484     * (not the one immediately following) short circuits. */
485    if(southern != (startCompare >= 0)) {
486        endCompare = compareToRule((int8_t)month, (int8_t)monthLength, (int8_t)prevMonthLength,
487                                   (int8_t)day, (int8_t)dayOfWeek, millis,
488                                   endTimeMode == WALL_TIME ? dstSavings :
489                                    (endTimeMode == UTC_TIME ? -rawOffset : 0),
490                                   endMode, (int8_t)endMonth, (int8_t)endDayOfWeek,
491                                   (int8_t)endDay, endTime);
492    }
493
494    // Check for both the northern and southern hemisphere cases.  We
495    // assume that in the northern hemisphere, the start rule is before the
496    // end rule within the calendar year, and vice versa for the southern
497    // hemisphere.
498    if ((!southern && (startCompare >= 0 && endCompare < 0)) ||
499        (southern && (startCompare >= 0 || endCompare < 0)))
500        result += dstSavings;
501
502    return result;
503}
504
505void
506SimpleTimeZone::getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
507                                   int32_t& rawOffsetGMT, int32_t& savingsDST, UErrorCode& status) /*const*/ {
508    if (U_FAILURE(status)) {
509        return;
510    }
511
512    rawOffsetGMT = getRawOffset();
513    int32_t year, month, dom, dow;
514    double day = uprv_floor(date / U_MILLIS_PER_DAY);
515    int32_t millis = (int32_t) (date - day * U_MILLIS_PER_DAY);
516
517    Grego::dayToFields(day, year, month, dom, dow);
518
519    savingsDST = getOffset(GregorianCalendar::AD, year, month, dom,
520                          (uint8_t) dow, millis,
521                          Grego::monthLength(year, month),
522                          status) - rawOffsetGMT;
523    if (U_FAILURE(status)) {
524        return;
525    }
526
527    UBool recalc = FALSE;
528
529    // Now we need some adjustment
530    if (savingsDST > 0) {
531        if ((nonExistingTimeOpt & kStdDstMask) == kStandard
532            || (nonExistingTimeOpt & kStdDstMask) != kDaylight && (nonExistingTimeOpt & kFormerLatterMask) != kLatter) {
533            date -= getDSTSavings();
534            recalc = TRUE;
535        }
536    } else {
537        if ((duplicatedTimeOpt & kStdDstMask) == kDaylight
538                || (duplicatedTimeOpt & kStdDstMask) != kStandard && (duplicatedTimeOpt & kFormerLatterMask) == kFormer) {
539            date -= getDSTSavings();
540            recalc = TRUE;
541        }
542    }
543    if (recalc) {
544        day = uprv_floor(date / U_MILLIS_PER_DAY);
545        millis = (int32_t) (date - day * U_MILLIS_PER_DAY);
546        Grego::dayToFields(day, year, month, dom, dow);
547        savingsDST = getOffset(GregorianCalendar::AD, year, month, dom,
548                          (uint8_t) dow, millis,
549                          Grego::monthLength(year, month),
550                          status) - rawOffsetGMT;
551    }
552}
553
554// -------------------------------------
555
556/**
557 * Compare a given date in the year to a rule. Return 1, 0, or -1, depending
558 * on whether the date is after, equal to, or before the rule date. The
559 * millis are compared directly against the ruleMillis, so any
560 * standard-daylight adjustments must be handled by the caller.
561 *
562 * @return  1 if the date is after the rule date, -1 if the date is before
563 *          the rule date, or 0 if the date is equal to the rule date.
564 */
565int32_t
566SimpleTimeZone::compareToRule(int8_t month, int8_t monthLen, int8_t prevMonthLen,
567                              int8_t dayOfMonth,
568                              int8_t dayOfWeek, int32_t millis, int32_t millisDelta,
569                              EMode ruleMode, int8_t ruleMonth, int8_t ruleDayOfWeek,
570                              int8_t ruleDay, int32_t ruleMillis)
571{
572    // Make adjustments for startTimeMode and endTimeMode
573    millis += millisDelta;
574    while (millis >= U_MILLIS_PER_DAY) {
575        millis -= U_MILLIS_PER_DAY;
576        ++dayOfMonth;
577        dayOfWeek = (int8_t)(1 + (dayOfWeek % 7)); // dayOfWeek is one-based
578        if (dayOfMonth > monthLen) {
579            dayOfMonth = 1;
580            /* When incrementing the month, it is desirible to overflow
581             * from DECEMBER to DECEMBER+1, since we use the result to
582             * compare against a real month. Wraparound of the value
583             * leads to bug 4173604. */
584            ++month;
585        }
586    }
587    while (millis < 0) {
588        millis += U_MILLIS_PER_DAY;
589        --dayOfMonth;
590        dayOfWeek = (int8_t)(1 + ((dayOfWeek+5) % 7)); // dayOfWeek is one-based
591        if (dayOfMonth < 1) {
592            dayOfMonth = prevMonthLen;
593            --month;
594        }
595    }
596
597    // first compare months.  If they're different, we don't have to worry about days
598    // and times
599    if (month < ruleMonth) return -1;
600    else if (month > ruleMonth) return 1;
601
602    // calculate the actual day of month for the rule
603    int32_t ruleDayOfMonth = 0;
604
605    // Adjust the ruleDay to the monthLen, for non-leap year February 29 rule days.
606    if (ruleDay > monthLen) {
607        ruleDay = monthLen;
608    }
609
610    switch (ruleMode)
611    {
612    // if the mode is day-of-month, the day of month is given
613    case DOM_MODE:
614        ruleDayOfMonth = ruleDay;
615        break;
616
617    // if the mode is day-of-week-in-month, calculate the day-of-month from it
618    case DOW_IN_MONTH_MODE:
619        // In this case ruleDay is the day-of-week-in-month (this code is using
620        // the dayOfWeek and dayOfMonth parameters to figure out the day-of-week
621        // of the first day of the month, so it's trusting that they're really
622        // consistent with each other)
623        if (ruleDay > 0)
624            ruleDayOfMonth = 1 + (ruleDay - 1) * 7 +
625                (7 + ruleDayOfWeek - (dayOfWeek - dayOfMonth + 1)) % 7;
626
627        // if ruleDay is negative (we assume it's not zero here), we have to do
628        // the same calculation figuring backward from the last day of the month.
629        else
630        {
631            // (again, this code is trusting that dayOfWeek and dayOfMonth are
632            // consistent with each other here, since we're using them to figure
633            // the day of week of the first of the month)
634            ruleDayOfMonth = monthLen + (ruleDay + 1) * 7 -
635                (7 + (dayOfWeek + monthLen - dayOfMonth) - ruleDayOfWeek) % 7;
636        }
637        break;
638
639    case DOW_GE_DOM_MODE:
640        ruleDayOfMonth = ruleDay +
641            (49 + ruleDayOfWeek - ruleDay - dayOfWeek + dayOfMonth) % 7;
642        break;
643
644    case DOW_LE_DOM_MODE:
645        ruleDayOfMonth = ruleDay -
646            (49 - ruleDayOfWeek + ruleDay + dayOfWeek - dayOfMonth) % 7;
647        // Note at this point ruleDayOfMonth may be <1, although it will
648        // be >=1 for well-formed rules.
649        break;
650    }
651
652    // now that we have a real day-in-month for the rule, we can compare days...
653    if (dayOfMonth < ruleDayOfMonth) return -1;
654    else if (dayOfMonth > ruleDayOfMonth) return 1;
655
656    // ...and if they're equal, we compare times
657    if (millis < ruleMillis) return -1;
658    else if (millis > ruleMillis) return 1;
659    else return 0;
660}
661
662// -------------------------------------
663
664int32_t
665SimpleTimeZone::getRawOffset() const
666{
667    return rawOffset;
668}
669
670// -------------------------------------
671
672void
673SimpleTimeZone::setRawOffset(int32_t offsetMillis)
674{
675    rawOffset = offsetMillis;
676    transitionRulesInitialized = FALSE;
677}
678
679// -------------------------------------
680
681void
682SimpleTimeZone::setDSTSavings(int32_t millisSavedDuringDST, UErrorCode& status)
683{
684    if (millisSavedDuringDST <= 0) {
685        status = U_ILLEGAL_ARGUMENT_ERROR;
686    }
687    else {
688        dstSavings = millisSavedDuringDST;
689    }
690    transitionRulesInitialized = FALSE;
691}
692
693// -------------------------------------
694
695int32_t
696SimpleTimeZone::getDSTSavings() const
697{
698    return dstSavings;
699}
700
701// -------------------------------------
702
703UBool
704SimpleTimeZone::useDaylightTime() const
705{
706    return useDaylight;
707}
708
709// -------------------------------------
710
711/**
712 * Overrides TimeZone
713 * Queries if the given date is in Daylight Savings Time.
714 */
715UBool SimpleTimeZone::inDaylightTime(UDate date, UErrorCode& status) const
716{
717    // This method is wasteful since it creates a new GregorianCalendar and
718    // deletes it each time it is called.  However, this is a deprecated method
719    // and provided only for Java compatibility as of 8/6/97 [LIU].
720    if (U_FAILURE(status)) return FALSE;
721    GregorianCalendar *gc = new GregorianCalendar(*this, status);
722    /* test for NULL */
723    if (gc == 0) {
724        status = U_MEMORY_ALLOCATION_ERROR;
725        return FALSE;
726    }
727    gc->setTime(date, status);
728    UBool result = gc->inDaylightTime(status);
729    delete gc;
730    return result;
731}
732
733// -------------------------------------
734
735/**
736 * Return true if this zone has the same rules and offset as another zone.
737 * @param other the TimeZone object to be compared with
738 * @return true if the given zone has the same rules and offset as this one
739 */
740UBool
741SimpleTimeZone::hasSameRules(const TimeZone& other) const
742{
743    if (this == &other) return TRUE;
744    if (other.getDynamicClassID() != SimpleTimeZone::getStaticClassID()) return FALSE;
745    SimpleTimeZone *that = (SimpleTimeZone*)&other;
746    return rawOffset     == that->rawOffset &&
747        useDaylight     == that->useDaylight &&
748        (!useDaylight
749         // Only check rules if using DST
750         || (dstSavings     == that->dstSavings &&
751             startMode      == that->startMode &&
752             startMonth     == that->startMonth &&
753             startDay       == that->startDay &&
754             startDayOfWeek == that->startDayOfWeek &&
755             startTime      == that->startTime &&
756             startTimeMode  == that->startTimeMode &&
757             endMode        == that->endMode &&
758             endMonth       == that->endMonth &&
759             endDay         == that->endDay &&
760             endDayOfWeek   == that->endDayOfWeek &&
761             endTime        == that->endTime &&
762             endTimeMode    == that->endTimeMode &&
763             startYear      == that->startYear));
764}
765
766// -------------------------------------
767
768//----------------------------------------------------------------------
769// Rule representation
770//
771// We represent the following flavors of rules:
772//       5        the fifth of the month
773//       lastSun  the last Sunday in the month
774//       lastMon  the last Monday in the month
775//       Sun>=8   first Sunday on or after the eighth
776//       Sun<=25  last Sunday on or before the 25th
777// This is further complicated by the fact that we need to remain
778// backward compatible with the 1.1 FCS.  Finally, we need to minimize
779// API changes.  In order to satisfy these requirements, we support
780// three representation systems, and we translate between them.
781//
782// INTERNAL REPRESENTATION
783// This is the format SimpleTimeZone objects take after construction or
784// streaming in is complete.  Rules are represented directly, using an
785// unencoded format.  We will discuss the start rule only below; the end
786// rule is analogous.
787//   startMode      Takes on enumerated values DAY_OF_MONTH,
788//                  DOW_IN_MONTH, DOW_AFTER_DOM, or DOW_BEFORE_DOM.
789//   startDay       The day of the month, or for DOW_IN_MONTH mode, a
790//                  value indicating which DOW, such as +1 for first,
791//                  +2 for second, -1 for last, etc.
792//   startDayOfWeek The day of the week.  Ignored for DAY_OF_MONTH.
793//
794// ENCODED REPRESENTATION
795// This is the format accepted by the constructor and by setStartRule()
796// and setEndRule().  It uses various combinations of positive, negative,
797// and zero values to encode the different rules.  This representation
798// allows us to specify all the different rule flavors without altering
799// the API.
800//   MODE              startMonth    startDay    startDayOfWeek
801//   DOW_IN_MONTH_MODE >=0           !=0         >0
802//   DOM_MODE          >=0           >0          ==0
803//   DOW_GE_DOM_MODE   >=0           >0          <0
804//   DOW_LE_DOM_MODE   >=0           <0          <0
805//   (no DST)          don't care    ==0         don't care
806//
807// STREAMED REPRESENTATION
808// We must retain binary compatibility with the 1.1 FCS.  The 1.1 code only
809// handles DOW_IN_MONTH_MODE and non-DST mode, the latter indicated by the
810// flag useDaylight.  When we stream an object out, we translate into an
811// approximate DOW_IN_MONTH_MODE representation so the object can be parsed
812// and used by 1.1 code.  Following that, we write out the full
813// representation separately so that contemporary code can recognize and
814// parse it.  The full representation is written in a "packed" format,
815// consisting of a version number, a length, and an array of bytes.  Future
816// versions of this class may specify different versions.  If they wish to
817// include additional data, they should do so by storing them after the
818// packed representation below.
819//----------------------------------------------------------------------
820
821/**
822 * Given a set of encoded rules in startDay and startDayOfMonth, decode
823 * them and set the startMode appropriately.  Do the same for endDay and
824 * endDayOfMonth.  Upon entry, the day of week variables may be zero or
825 * negative, in order to indicate special modes.  The day of month
826 * variables may also be negative.  Upon exit, the mode variables will be
827 * set, and the day of week and day of month variables will be positive.
828 * This method also recognizes a startDay or endDay of zero as indicating
829 * no DST.
830 */
831void
832SimpleTimeZone::decodeRules(UErrorCode& status)
833{
834    decodeStartRule(status);
835    decodeEndRule(status);
836}
837
838/**
839 * Decode the start rule and validate the parameters.  The parameters are
840 * expected to be in encoded form, which represents the various rule modes
841 * by negating or zeroing certain values.  Representation formats are:
842 * <p>
843 * <pre>
844 *            DOW_IN_MONTH  DOM    DOW>=DOM  DOW<=DOM  no DST
845 *            ------------  -----  --------  --------  ----------
846 * month       0..11        same    same      same     don't care
847 * day        -5..5         1..31   1..31    -1..-31   0
848 * dayOfWeek   1..7         0      -1..-7    -1..-7    don't care
849 * time        0..ONEDAY    same    same      same     don't care
850 * </pre>
851 * The range for month does not include UNDECIMBER since this class is
852 * really specific to GregorianCalendar, which does not use that month.
853 * The range for time includes ONEDAY (vs. ending at ONEDAY-1) because the
854 * end rule is an exclusive limit point.  That is, the range of times that
855 * are in DST include those >= the start and < the end.  For this reason,
856 * it should be possible to specify an end of ONEDAY in order to include the
857 * entire day.  Although this is equivalent to time 0 of the following day,
858 * it's not always possible to specify that, for example, on December 31.
859 * While arguably the start range should still be 0..ONEDAY-1, we keep
860 * the start and end ranges the same for consistency.
861 */
862void
863SimpleTimeZone::decodeStartRule(UErrorCode& status)
864{
865    if(U_FAILURE(status)) return;
866
867    useDaylight = (UBool)((startDay != 0) && (endDay != 0) ? TRUE : FALSE);
868    if (useDaylight && dstSavings == 0) {
869        dstSavings = U_MILLIS_PER_HOUR;
870    }
871    if (startDay != 0) {
872        if (startMonth < UCAL_JANUARY || startMonth > UCAL_DECEMBER) {
873            status = U_ILLEGAL_ARGUMENT_ERROR;
874            return;
875        }
876        if (startTime < 0 || startTime > U_MILLIS_PER_DAY ||
877            startTimeMode < WALL_TIME || startTimeMode > UTC_TIME) {
878            status = U_ILLEGAL_ARGUMENT_ERROR;
879            return;
880        }
881        if (startDayOfWeek == 0) {
882            startMode = DOM_MODE;
883        } else {
884            if (startDayOfWeek > 0) {
885                startMode = DOW_IN_MONTH_MODE;
886            } else {
887                startDayOfWeek = (int8_t)-startDayOfWeek;
888                if (startDay > 0) {
889                    startMode = DOW_GE_DOM_MODE;
890                } else {
891                    startDay = (int8_t)-startDay;
892                    startMode = DOW_LE_DOM_MODE;
893                }
894            }
895            if (startDayOfWeek > UCAL_SATURDAY) {
896                status = U_ILLEGAL_ARGUMENT_ERROR;
897                return;
898            }
899        }
900        if (startMode == DOW_IN_MONTH_MODE) {
901            if (startDay < -5 || startDay > 5) {
902                status = U_ILLEGAL_ARGUMENT_ERROR;
903                return;
904            }
905        } else if (startDay<1 || startDay > STATICMONTHLENGTH[startMonth]) {
906            status = U_ILLEGAL_ARGUMENT_ERROR;
907            return;
908        }
909    }
910}
911
912/**
913 * Decode the end rule and validate the parameters.  This method is exactly
914 * analogous to decodeStartRule().
915 * @see decodeStartRule
916 */
917void
918SimpleTimeZone::decodeEndRule(UErrorCode& status)
919{
920    if(U_FAILURE(status)) return;
921
922    useDaylight = (UBool)((startDay != 0) && (endDay != 0) ? TRUE : FALSE);
923    if (useDaylight && dstSavings == 0) {
924        dstSavings = U_MILLIS_PER_HOUR;
925    }
926    if (endDay != 0) {
927        if (endMonth < UCAL_JANUARY || endMonth > UCAL_DECEMBER) {
928            status = U_ILLEGAL_ARGUMENT_ERROR;
929            return;
930        }
931        if (endTime < 0 || endTime > U_MILLIS_PER_DAY ||
932            endTimeMode < WALL_TIME || endTimeMode > UTC_TIME) {
933            status = U_ILLEGAL_ARGUMENT_ERROR;
934            return;
935        }
936        if (endDayOfWeek == 0) {
937            endMode = DOM_MODE;
938        } else {
939            if (endDayOfWeek > 0) {
940                endMode = DOW_IN_MONTH_MODE;
941            } else {
942                endDayOfWeek = (int8_t)-endDayOfWeek;
943                if (endDay > 0) {
944                    endMode = DOW_GE_DOM_MODE;
945                } else {
946                    endDay = (int8_t)-endDay;
947                    endMode = DOW_LE_DOM_MODE;
948                }
949            }
950            if (endDayOfWeek > UCAL_SATURDAY) {
951                status = U_ILLEGAL_ARGUMENT_ERROR;
952                return;
953            }
954        }
955        if (endMode == DOW_IN_MONTH_MODE) {
956            if (endDay < -5 || endDay > 5) {
957                status = U_ILLEGAL_ARGUMENT_ERROR;
958                return;
959            }
960        } else if (endDay<1 || endDay > STATICMONTHLENGTH[endMonth]) {
961            status = U_ILLEGAL_ARGUMENT_ERROR;
962            return;
963        }
964    }
965}
966
967UBool
968SimpleTimeZone::getNextTransition(UDate base, UBool inclusive, TimeZoneTransition& result) /*const*/ {
969    if (!useDaylight) {
970        return FALSE;
971    }
972
973    UErrorCode status = U_ZERO_ERROR;
974    initTransitionRules(status);
975    if (U_FAILURE(status)) {
976        return FALSE;
977    }
978
979    UDate firstTransitionTime = firstTransition->getTime();
980    if (base < firstTransitionTime || (inclusive && base == firstTransitionTime)) {
981        result = *firstTransition;
982    }
983    UDate stdDate, dstDate;
984    UBool stdAvail = stdRule->getNextStart(base, dstRule->getRawOffset(), dstRule->getDSTSavings(), inclusive, stdDate);
985    UBool dstAvail = dstRule->getNextStart(base, stdRule->getRawOffset(), stdRule->getDSTSavings(), inclusive, dstDate);
986    if (stdAvail && (!dstAvail || stdDate < dstDate)) {
987        result.setTime(stdDate);
988        result.setFrom((const TimeZoneRule&)*dstRule);
989        result.setTo((const TimeZoneRule&)*stdRule);
990        return TRUE;
991    }
992    if (dstAvail && (!stdAvail || dstDate < stdDate)) {
993        result.setTime(dstDate);
994        result.setFrom((const TimeZoneRule&)*stdRule);
995        result.setTo((const TimeZoneRule&)*dstRule);
996        return TRUE;
997    }
998    return FALSE;
999}
1000
1001UBool
1002SimpleTimeZone::getPreviousTransition(UDate base, UBool inclusive, TimeZoneTransition& result) /*const*/ {
1003    if (!useDaylight) {
1004        return FALSE;
1005    }
1006
1007    UErrorCode status = U_ZERO_ERROR;
1008    initTransitionRules(status);
1009    if (U_FAILURE(status)) {
1010        return FALSE;
1011    }
1012
1013    UDate firstTransitionTime = firstTransition->getTime();
1014    if (base < firstTransitionTime || (!inclusive && base == firstTransitionTime)) {
1015        return FALSE;
1016    }
1017    UDate stdDate, dstDate;
1018    UBool stdAvail = stdRule->getPreviousStart(base, dstRule->getRawOffset(), dstRule->getDSTSavings(), inclusive, stdDate);
1019    UBool dstAvail = dstRule->getPreviousStart(base, stdRule->getRawOffset(), stdRule->getDSTSavings(), inclusive, dstDate);
1020    if (stdAvail && (!dstAvail || stdDate > dstDate)) {
1021        result.setTime(stdDate);
1022        result.setFrom((const TimeZoneRule&)*dstRule);
1023        result.setTo((const TimeZoneRule&)*stdRule);
1024        return TRUE;
1025    }
1026    if (dstAvail && (!stdAvail || dstDate > stdDate)) {
1027        result.setTime(dstDate);
1028        result.setFrom((const TimeZoneRule&)*stdRule);
1029        result.setTo((const TimeZoneRule&)*dstRule);
1030        return TRUE;
1031    }
1032    return FALSE;
1033}
1034
1035void
1036SimpleTimeZone::clearTransitionRules(void) {
1037    initialRule = NULL;
1038    firstTransition = NULL;
1039    stdRule = NULL;
1040    dstRule = NULL;
1041    transitionRulesInitialized = FALSE;
1042}
1043
1044void
1045SimpleTimeZone::deleteTransitionRules(void) {
1046    if (initialRule != NULL) {
1047        delete initialRule;
1048    }
1049    if (firstTransition != NULL) {
1050        delete firstTransition;
1051    }
1052    if (stdRule != NULL) {
1053        delete stdRule;
1054    }
1055    if (dstRule != NULL) {
1056        delete dstRule;
1057    }
1058    clearTransitionRules();
1059 }
1060
1061void
1062SimpleTimeZone::initTransitionRules(UErrorCode& status) {
1063    if (U_FAILURE(status)) {
1064        return;
1065    }
1066    if (transitionRulesInitialized) {
1067        return;
1068    }
1069    deleteTransitionRules();
1070    UnicodeString tzid;
1071    getID(tzid);
1072
1073    if (useDaylight) {
1074        DateTimeRule* dtRule;
1075        DateTimeRule::TimeRuleType timeRuleType;
1076        UDate firstStdStart, firstDstStart;
1077
1078        // Create a TimeZoneRule for daylight saving time
1079        timeRuleType = (startTimeMode == STANDARD_TIME) ? DateTimeRule::STANDARD_TIME :
1080            ((startTimeMode == UTC_TIME) ? DateTimeRule::UTC_TIME : DateTimeRule::WALL_TIME);
1081        switch (startMode) {
1082        case DOM_MODE:
1083            dtRule = new DateTimeRule(startMonth, startDay, startTime, timeRuleType);
1084            break;
1085        case DOW_IN_MONTH_MODE:
1086            dtRule = new DateTimeRule(startMonth, startDay, startDayOfWeek, startTime, timeRuleType);
1087            break;
1088        case DOW_GE_DOM_MODE:
1089            dtRule = new DateTimeRule(startMonth, startDay, startDayOfWeek, true, startTime, timeRuleType);
1090            break;
1091        case DOW_LE_DOM_MODE:
1092            dtRule = new DateTimeRule(startMonth, startDay, startDayOfWeek, false, startTime, timeRuleType);
1093            break;
1094        default:
1095            status = U_INVALID_STATE_ERROR;
1096            return;
1097        }
1098        // Check for Null pointer
1099        if (dtRule == NULL) {
1100        	status = U_MEMORY_ALLOCATION_ERROR;
1101        	return;
1102        }
1103        // For now, use ID + "(DST)" as the name
1104        dstRule = new AnnualTimeZoneRule(tzid+DST_STR, getRawOffset(), getDSTSavings(),
1105            dtRule, startYear, AnnualTimeZoneRule::MAX_YEAR);
1106
1107        // Check for Null pointer
1108        if (dstRule == NULL) {
1109        	status = U_MEMORY_ALLOCATION_ERROR;
1110        	deleteTransitionRules();
1111        	return;
1112        }
1113
1114        // Calculate the first DST start time
1115        dstRule->getFirstStart(getRawOffset(), 0, firstDstStart);
1116
1117        // Create a TimeZoneRule for standard time
1118        timeRuleType = (endTimeMode == STANDARD_TIME) ? DateTimeRule::STANDARD_TIME :
1119            ((endTimeMode == UTC_TIME) ? DateTimeRule::UTC_TIME : DateTimeRule::WALL_TIME);
1120        switch (endMode) {
1121        case DOM_MODE:
1122            dtRule = new DateTimeRule(endMonth, endDay, endTime, timeRuleType);
1123            break;
1124        case DOW_IN_MONTH_MODE:
1125            dtRule = new DateTimeRule(endMonth, endDay, endDayOfWeek, endTime, timeRuleType);
1126            break;
1127        case DOW_GE_DOM_MODE:
1128            dtRule = new DateTimeRule(endMonth, endDay, endDayOfWeek, true, endTime, timeRuleType);
1129            break;
1130        case DOW_LE_DOM_MODE:
1131            dtRule = new DateTimeRule(endMonth, endDay, endDayOfWeek, false, endTime, timeRuleType);
1132            break;
1133        }
1134
1135        // Check for Null pointer
1136        if (dtRule == NULL) {
1137        	status = U_MEMORY_ALLOCATION_ERROR;
1138        	deleteTransitionRules();
1139        	return;
1140        }
1141        // For now, use ID + "(STD)" as the name
1142        stdRule = new AnnualTimeZoneRule(tzid+STD_STR, getRawOffset(), 0,
1143            dtRule, startYear, AnnualTimeZoneRule::MAX_YEAR);
1144
1145        //Check for Null pointer
1146        if (stdRule == NULL) {
1147        	status = U_MEMORY_ALLOCATION_ERROR;
1148        	deleteTransitionRules();
1149        	return;
1150        }
1151
1152        // Calculate the first STD start time
1153        stdRule->getFirstStart(getRawOffset(), dstRule->getDSTSavings(), firstStdStart);
1154
1155        // Create a TimeZoneRule for initial time
1156        if (firstStdStart < firstDstStart) {
1157            initialRule = new InitialTimeZoneRule(tzid+DST_STR, getRawOffset(), dstRule->getDSTSavings());
1158            firstTransition = new TimeZoneTransition(firstStdStart, *initialRule, *stdRule);
1159        } else {
1160            initialRule = new InitialTimeZoneRule(tzid+STD_STR, getRawOffset(), 0);
1161            firstTransition = new TimeZoneTransition(firstDstStart, *initialRule, *dstRule);
1162        }
1163        // Check for null pointers.
1164        if (initialRule == NULL || firstTransition == NULL) {
1165        	status = U_MEMORY_ALLOCATION_ERROR;
1166        	deleteTransitionRules();
1167        	return;
1168        }
1169
1170    } else {
1171        // Create a TimeZoneRule for initial time
1172        initialRule = new InitialTimeZoneRule(tzid, getRawOffset(), 0);
1173        // Check for null pointer.
1174        if (initialRule == NULL) {
1175        	status = U_MEMORY_ALLOCATION_ERROR;
1176        	deleteTransitionRules();
1177        	return;
1178        }
1179    }
1180
1181    transitionRulesInitialized = true;
1182}
1183
1184int32_t
1185SimpleTimeZone::countTransitionRules(UErrorCode& /*status*/) /*const*/ {
1186    return (useDaylight) ? 2 : 0;
1187}
1188
1189void
1190SimpleTimeZone::getTimeZoneRules(const InitialTimeZoneRule*& initial,
1191                                 const TimeZoneRule* trsrules[],
1192                                 int32_t& trscount,
1193                                 UErrorCode& status) /*const*/ {
1194    if (U_FAILURE(status)) {
1195        return;
1196    }
1197    initTransitionRules(status);
1198    if (U_FAILURE(status)) {
1199        return;
1200    }
1201    initial = initialRule;
1202    int32_t cnt = 0;
1203    if (stdRule != NULL) {
1204        if (cnt < trscount) {
1205            trsrules[cnt++] = stdRule;
1206        }
1207        if (cnt < trscount) {
1208            trsrules[cnt++] = dstRule;
1209        }
1210    }
1211    trscount = cnt;
1212}
1213
1214
1215U_NAMESPACE_END
1216
1217#endif /* #if !UCONFIG_NO_FORMATTING */
1218
1219//eof
1220