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 36 U_NAMESPACE_BEGIN 37 38 UOBJECT_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. 50 const int8_t SimpleTimeZone::STATICMONTHLENGTH[] = {31,29,31,30,31,30,31,31,30,31,30,31}; 51 52 static const UChar DST_STR[] = {0x0028,0x0044,0x0053,0x0054,0x0029,0}; // "(DST)" 53 static const UChar STD_STR[] = {0x0028,0x0053,0x0054,0x0044,0x0029,0}; // "(STD)" 54 55 56 // ***************************************************************************** 57 // class SimpleTimeZone 58 // ***************************************************************************** 59 60 61 SimpleTimeZone::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 85 SimpleTimeZone::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 104 SimpleTimeZone::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 123 SimpleTimeZone::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 */ 145 void 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 184 SimpleTimeZone::~SimpleTimeZone() 185 { 186 deleteTransitionRules(); 187 } 188 189 // ------------------------------------- 190 191 // Called by TimeZone::createDefault(), then clone() inside a Mutex - be careful. 192 SimpleTimeZone::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. 201 SimpleTimeZone & 202 SimpleTimeZone::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 230 UBool 231 SimpleTimeZone::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. 242 TimeZone* 243 SimpleTimeZone::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 */ 257 void 258 SimpleTimeZone::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 305 void 306 SimpleTimeZone::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 320 void 321 SimpleTimeZone::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 329 void 330 SimpleTimeZone::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 357 void 358 SimpleTimeZone::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 372 void 373 SimpleTimeZone::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 381 void 382 SimpleTimeZone::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 391 int32_t 392 SimpleTimeZone::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 410 int32_t 411 SimpleTimeZone::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 437 int32_t 438 SimpleTimeZone::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 507 void 508 SimpleTimeZone::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 */ 567 int32_t 568 SimpleTimeZone::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 666 int32_t 667 SimpleTimeZone::getRawOffset() const 668 { 669 return rawOffset; 670 } 671 672 // ------------------------------------- 673 674 void 675 SimpleTimeZone::setRawOffset(int32_t offsetMillis) 676 { 677 rawOffset = offsetMillis; 678 transitionRulesInitialized = FALSE; 679 } 680 681 // ------------------------------------- 682 683 void 684 SimpleTimeZone::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 697 int32_t 698 SimpleTimeZone::getDSTSavings() const 699 { 700 return dstSavings; 701 } 702 703 // ------------------------------------- 704 705 UBool 706 SimpleTimeZone::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 */ 717 UBool 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 */ 742 UBool 743 SimpleTimeZone::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 */ 833 void 834 SimpleTimeZone::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 */ 864 void 865 SimpleTimeZone::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 */ 919 void 920 SimpleTimeZone::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 969 UBool 970 SimpleTimeZone::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 1003 UBool 1004 SimpleTimeZone::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 1037 void 1038 SimpleTimeZone::clearTransitionRules(void) { 1039 initialRule = NULL; 1040 firstTransition = NULL; 1041 stdRule = NULL; 1042 dstRule = NULL; 1043 transitionRulesInitialized = FALSE; 1044 } 1045 1046 void 1047 SimpleTimeZone::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 1063 void 1064 SimpleTimeZone::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 1186 int32_t 1187 SimpleTimeZone::countTransitionRules(UErrorCode& /*status*/) /*const*/ { 1188 return (useDaylight) ? 2 : 0; 1189 } 1190 1191 void 1192 SimpleTimeZone::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 1217 U_NAMESPACE_END 1218 1219 #endif /* #if !UCONFIG_NO_FORMATTING */ 1220 1221 //eof 1222