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