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 <typeinfo> // 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