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