1 // Copyright (C) 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 /* 4 ******************************************************************************* 5 * Copyright (C) 2007-2016, International Business Machines Corporation and 6 * others. All Rights Reserved. 7 ******************************************************************************* 8 */ 9 10 #include "utypeinfo.h" // for 'typeid' to work 11 12 #include "unicode/utypes.h" 13 14 #if !UCONFIG_NO_FORMATTING 15 16 #include "unicode/vtzone.h" 17 #include "unicode/rbtz.h" 18 #include "unicode/ucal.h" 19 #include "unicode/ures.h" 20 #include "cmemory.h" 21 #include "uvector.h" 22 #include "gregoimp.h" 23 #include "uassert.h" 24 25 U_NAMESPACE_BEGIN 26 27 // This is the deleter that will be use to remove TimeZoneRule 28 U_CDECL_BEGIN 29 static void U_CALLCONV 30 deleteTimeZoneRule(void* obj) { 31 delete (TimeZoneRule*) obj; 32 } 33 U_CDECL_END 34 35 // Smybol characters used by RFC2445 VTIMEZONE 36 static const UChar COLON = 0x3A; /* : */ 37 static const UChar SEMICOLON = 0x3B; /* ; */ 38 static const UChar EQUALS_SIGN = 0x3D; /* = */ 39 static const UChar COMMA = 0x2C; /* , */ 40 static const UChar PLUS = 0x2B; /* + */ 41 static const UChar MINUS = 0x2D; /* - */ 42 43 // RFC2445 VTIMEZONE tokens 44 static const UChar ICAL_BEGIN_VTIMEZONE[] = {0x42, 0x45, 0x47, 0x49, 0x4E, 0x3A, 0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "BEGIN:VTIMEZONE" */ 45 static const UChar ICAL_END_VTIMEZONE[] = {0x45, 0x4E, 0x44, 0x3A, 0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "END:VTIMEZONE" */ 46 static const UChar ICAL_BEGIN[] = {0x42, 0x45, 0x47, 0x49, 0x4E, 0}; /* "BEGIN" */ 47 static const UChar ICAL_END[] = {0x45, 0x4E, 0x44, 0}; /* "END" */ 48 static const UChar ICAL_VTIMEZONE[] = {0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "VTIMEZONE" */ 49 static const UChar ICAL_TZID[] = {0x54, 0x5A, 0x49, 0x44, 0}; /* "TZID" */ 50 static const UChar ICAL_STANDARD[] = {0x53, 0x54, 0x41, 0x4E, 0x44, 0x41, 0x52, 0x44, 0}; /* "STANDARD" */ 51 static const UChar ICAL_DAYLIGHT[] = {0x44, 0x41, 0x59, 0x4C, 0x49, 0x47, 0x48, 0x54, 0}; /* "DAYLIGHT" */ 52 static const UChar ICAL_DTSTART[] = {0x44, 0x54, 0x53, 0x54, 0x41, 0x52, 0x54, 0}; /* "DTSTART" */ 53 static const UChar ICAL_TZOFFSETFROM[] = {0x54, 0x5A, 0x4F, 0x46, 0x46, 0x53, 0x45, 0x54, 0x46, 0x52, 0x4F, 0x4D, 0}; /* "TZOFFSETFROM" */ 54 static const UChar ICAL_TZOFFSETTO[] = {0x54, 0x5A, 0x4F, 0x46, 0x46, 0x53, 0x45, 0x54, 0x54, 0x4F, 0}; /* "TZOFFSETTO" */ 55 static const UChar ICAL_RDATE[] = {0x52, 0x44, 0x41, 0x54, 0x45, 0}; /* "RDATE" */ 56 static const UChar ICAL_RRULE[] = {0x52, 0x52, 0x55, 0x4C, 0x45, 0}; /* "RRULE" */ 57 static const UChar ICAL_TZNAME[] = {0x54, 0x5A, 0x4E, 0x41, 0x4D, 0x45, 0}; /* "TZNAME" */ 58 static const UChar ICAL_TZURL[] = {0x54, 0x5A, 0x55, 0x52, 0x4C, 0}; /* "TZURL" */ 59 static const UChar ICAL_LASTMOD[] = {0x4C, 0x41, 0x53, 0x54, 0x2D, 0x4D, 0x4F, 0x44, 0x49, 0x46, 0x49, 0x45, 0x44, 0}; /* "LAST-MODIFIED" */ 60 61 static const UChar ICAL_FREQ[] = {0x46, 0x52, 0x45, 0x51, 0}; /* "FREQ" */ 62 static const UChar ICAL_UNTIL[] = {0x55, 0x4E, 0x54, 0x49, 0x4C, 0}; /* "UNTIL" */ 63 static const UChar ICAL_YEARLY[] = {0x59, 0x45, 0x41, 0x52, 0x4C, 0x59, 0}; /* "YEARLY" */ 64 static const UChar ICAL_BYMONTH[] = {0x42, 0x59, 0x4D, 0x4F, 0x4E, 0x54, 0x48, 0}; /* "BYMONTH" */ 65 static const UChar ICAL_BYDAY[] = {0x42, 0x59, 0x44, 0x41, 0x59, 0}; /* "BYDAY" */ 66 static const UChar ICAL_BYMONTHDAY[] = {0x42, 0x59, 0x4D, 0x4F, 0x4E, 0x54, 0x48, 0x44, 0x41, 0x59, 0}; /* "BYMONTHDAY" */ 67 68 static const UChar ICAL_NEWLINE[] = {0x0D, 0x0A, 0}; /* CRLF */ 69 70 static const UChar ICAL_DOW_NAMES[7][3] = { 71 {0x53, 0x55, 0}, /* "SU" */ 72 {0x4D, 0x4F, 0}, /* "MO" */ 73 {0x54, 0x55, 0}, /* "TU" */ 74 {0x57, 0x45, 0}, /* "WE" */ 75 {0x54, 0x48, 0}, /* "TH" */ 76 {0x46, 0x52, 0}, /* "FR" */ 77 {0x53, 0x41, 0} /* "SA" */}; 78 79 // Month length for non-leap year 80 static const int32_t MONTHLENGTH[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 81 82 // ICU custom property 83 static const UChar ICU_TZINFO_PROP[] = {0x58, 0x2D, 0x54, 0x5A, 0x49, 0x4E, 0x46, 0x4F, 0x3A, 0}; /* "X-TZINFO:" */ 84 static const UChar ICU_TZINFO_PARTIAL[] = {0x2F, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6C, 0x40, 0}; /* "/Partial@" */ 85 static const UChar ICU_TZINFO_SIMPLE[] = {0x2F, 0x53, 0x69, 0x6D, 0x70, 0x6C, 0x65, 0x40, 0}; /* "/Simple@" */ 86 87 88 /* 89 * Simple fixed digit ASCII number to integer converter 90 */ 91 static int32_t parseAsciiDigits(const UnicodeString& str, int32_t start, int32_t length, UErrorCode& status) { 92 if (U_FAILURE(status)) { 93 return 0; 94 } 95 if (length <= 0 || str.length() < start || (start + length) > str.length()) { 96 status = U_INVALID_FORMAT_ERROR; 97 return 0; 98 } 99 int32_t sign = 1; 100 if (str.charAt(start) == PLUS) { 101 start++; 102 length--; 103 } else if (str.charAt(start) == MINUS) { 104 sign = -1; 105 start++; 106 length--; 107 } 108 int32_t num = 0; 109 for (int32_t i = 0; i < length; i++) { 110 int32_t digit = str.charAt(start + i) - 0x0030; 111 if (digit < 0 || digit > 9) { 112 status = U_INVALID_FORMAT_ERROR; 113 return 0; 114 } 115 num = 10 * num + digit; 116 } 117 return sign * num; 118 } 119 120 static UnicodeString& appendAsciiDigits(int32_t number, uint8_t length, UnicodeString& str) { 121 UBool negative = FALSE; 122 int32_t digits[10]; // max int32_t is 10 decimal digits 123 int32_t i; 124 125 if (number < 0) { 126 negative = TRUE; 127 number *= -1; 128 } 129 130 length = length > 10 ? 10 : length; 131 if (length == 0) { 132 // variable length 133 i = 0; 134 do { 135 digits[i++] = number % 10; 136 number /= 10; 137 } while (number != 0); 138 length = i; 139 } else { 140 // fixed digits 141 for (i = 0; i < length; i++) { 142 digits[i] = number % 10; 143 number /= 10; 144 } 145 } 146 if (negative) { 147 str.append(MINUS); 148 } 149 for (i = length - 1; i >= 0; i--) { 150 str.append((UChar)(digits[i] + 0x0030)); 151 } 152 return str; 153 } 154 155 static UnicodeString& appendMillis(UDate date, UnicodeString& str) { 156 UBool negative = FALSE; 157 int32_t digits[20]; // max int64_t is 20 decimal digits 158 int32_t i; 159 int64_t number; 160 161 if (date < MIN_MILLIS) { 162 number = (int64_t)MIN_MILLIS; 163 } else if (date > MAX_MILLIS) { 164 number = (int64_t)MAX_MILLIS; 165 } else { 166 number = (int64_t)date; 167 } 168 if (number < 0) { 169 negative = TRUE; 170 number *= -1; 171 } 172 i = 0; 173 do { 174 digits[i++] = (int32_t)(number % 10); 175 number /= 10; 176 } while (number != 0); 177 178 if (negative) { 179 str.append(MINUS); 180 } 181 i--; 182 while (i >= 0) { 183 str.append((UChar)(digits[i--] + 0x0030)); 184 } 185 return str; 186 } 187 188 /* 189 * Convert date/time to RFC2445 Date-Time form #1 DATE WITH LOCAL TIME 190 */ 191 static UnicodeString& getDateTimeString(UDate time, UnicodeString& str) { 192 int32_t year, month, dom, dow, doy, mid; 193 Grego::timeToFields(time, year, month, dom, dow, doy, mid); 194 195 str.remove(); 196 appendAsciiDigits(year, 4, str); 197 appendAsciiDigits(month + 1, 2, str); 198 appendAsciiDigits(dom, 2, str); 199 str.append((UChar)0x0054 /*'T'*/); 200 201 int32_t t = mid; 202 int32_t hour = t / U_MILLIS_PER_HOUR; 203 t %= U_MILLIS_PER_HOUR; 204 int32_t min = t / U_MILLIS_PER_MINUTE; 205 t %= U_MILLIS_PER_MINUTE; 206 int32_t sec = t / U_MILLIS_PER_SECOND; 207 208 appendAsciiDigits(hour, 2, str); 209 appendAsciiDigits(min, 2, str); 210 appendAsciiDigits(sec, 2, str); 211 return str; 212 } 213 214 /* 215 * Convert date/time to RFC2445 Date-Time form #2 DATE WITH UTC TIME 216 */ 217 static UnicodeString& getUTCDateTimeString(UDate time, UnicodeString& str) { 218 getDateTimeString(time, str); 219 str.append((UChar)0x005A /*'Z'*/); 220 return str; 221 } 222 223 /* 224 * Parse RFC2445 Date-Time form #1 DATE WITH LOCAL TIME and 225 * #2 DATE WITH UTC TIME 226 */ 227 static UDate parseDateTimeString(const UnicodeString& str, int32_t offset, UErrorCode& status) { 228 if (U_FAILURE(status)) { 229 return 0.0; 230 } 231 232 int32_t year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0; 233 UBool isUTC = FALSE; 234 UBool isValid = FALSE; 235 do { 236 int length = str.length(); 237 if (length != 15 && length != 16) { 238 // FORM#1 15 characters, such as "20060317T142115" 239 // FORM#2 16 characters, such as "20060317T142115Z" 240 break; 241 } 242 if (str.charAt(8) != 0x0054) { 243 // charcter "T" must be used for separating date and time 244 break; 245 } 246 if (length == 16) { 247 if (str.charAt(15) != 0x005A) { 248 // invalid format 249 break; 250 } 251 isUTC = TRUE; 252 } 253 254 year = parseAsciiDigits(str, 0, 4, status); 255 month = parseAsciiDigits(str, 4, 2, status) - 1; // 0-based 256 day = parseAsciiDigits(str, 6, 2, status); 257 hour = parseAsciiDigits(str, 9, 2, status); 258 min = parseAsciiDigits(str, 11, 2, status); 259 sec = parseAsciiDigits(str, 13, 2, status); 260 261 if (U_FAILURE(status)) { 262 break; 263 } 264 265 // check valid range 266 int32_t maxDayOfMonth = Grego::monthLength(year, month); 267 if (year < 0 || month < 0 || month > 11 || day < 1 || day > maxDayOfMonth || 268 hour < 0 || hour >= 24 || min < 0 || min >= 60 || sec < 0 || sec >= 60) { 269 break; 270 } 271 272 isValid = TRUE; 273 } while(false); 274 275 if (!isValid) { 276 status = U_INVALID_FORMAT_ERROR; 277 return 0.0; 278 } 279 // Calculate the time 280 UDate time = Grego::fieldsToDay(year, month, day) * U_MILLIS_PER_DAY; 281 time += (hour * U_MILLIS_PER_HOUR + min * U_MILLIS_PER_MINUTE + sec * U_MILLIS_PER_SECOND); 282 if (!isUTC) { 283 time -= offset; 284 } 285 return time; 286 } 287 288 /* 289 * Convert RFC2445 utc-offset string to milliseconds 290 */ 291 static int32_t offsetStrToMillis(const UnicodeString& str, UErrorCode& status) { 292 if (U_FAILURE(status)) { 293 return 0; 294 } 295 296 UBool isValid = FALSE; 297 int32_t sign = 0, hour = 0, min = 0, sec = 0; 298 299 do { 300 int length = str.length(); 301 if (length != 5 && length != 7) { 302 // utf-offset must be 5 or 7 characters 303 break; 304 } 305 // sign 306 UChar s = str.charAt(0); 307 if (s == PLUS) { 308 sign = 1; 309 } else if (s == MINUS) { 310 sign = -1; 311 } else { 312 // utf-offset must start with "+" or "-" 313 break; 314 } 315 hour = parseAsciiDigits(str, 1, 2, status); 316 min = parseAsciiDigits(str, 3, 2, status); 317 if (length == 7) { 318 sec = parseAsciiDigits(str, 5, 2, status); 319 } 320 if (U_FAILURE(status)) { 321 break; 322 } 323 isValid = true; 324 } while(false); 325 326 if (!isValid) { 327 status = U_INVALID_FORMAT_ERROR; 328 return 0; 329 } 330 int32_t millis = sign * ((hour * 60 + min) * 60 + sec) * 1000; 331 return millis; 332 } 333 334 /* 335 * Convert milliseconds to RFC2445 utc-offset string 336 */ 337 static void millisToOffset(int32_t millis, UnicodeString& str) { 338 str.remove(); 339 if (millis >= 0) { 340 str.append(PLUS); 341 } else { 342 str.append(MINUS); 343 millis = -millis; 344 } 345 int32_t hour, min, sec; 346 int32_t t = millis / 1000; 347 348 sec = t % 60; 349 t = (t - sec) / 60; 350 min = t % 60; 351 hour = t / 60; 352 353 appendAsciiDigits(hour, 2, str); 354 appendAsciiDigits(min, 2, str); 355 appendAsciiDigits(sec, 2, str); 356 } 357 358 /* 359 * Create a default TZNAME from TZID 360 */ 361 static void getDefaultTZName(const UnicodeString tzid, UBool isDST, UnicodeString& zonename) { 362 zonename = tzid; 363 if (isDST) { 364 zonename += UNICODE_STRING_SIMPLE("(DST)"); 365 } else { 366 zonename += UNICODE_STRING_SIMPLE("(STD)"); 367 } 368 } 369 370 /* 371 * Parse individual RRULE 372 * 373 * On return - 374 * 375 * month calculated by BYMONTH-1, or -1 when not found 376 * dow day of week in BYDAY, or 0 when not found 377 * wim day of week ordinal number in BYDAY, or 0 when not found 378 * dom an array of day of month 379 * domCount number of availble days in dom (domCount is specifying the size of dom on input) 380 * until time defined by UNTIL attribute or MIN_MILLIS if not available 381 */ 382 static void parseRRULE(const UnicodeString& rrule, int32_t& month, int32_t& dow, int32_t& wim, 383 int32_t* dom, int32_t& domCount, UDate& until, UErrorCode& status) { 384 if (U_FAILURE(status)) { 385 return; 386 } 387 int32_t numDom = 0; 388 389 month = -1; 390 dow = 0; 391 wim = 0; 392 until = MIN_MILLIS; 393 394 UBool yearly = FALSE; 395 //UBool parseError = FALSE; 396 397 int32_t prop_start = 0; 398 int32_t prop_end; 399 UnicodeString prop, attr, value; 400 UBool nextProp = TRUE; 401 402 while (nextProp) { 403 prop_end = rrule.indexOf(SEMICOLON, prop_start); 404 if (prop_end == -1) { 405 prop.setTo(rrule, prop_start); 406 nextProp = FALSE; 407 } else { 408 prop.setTo(rrule, prop_start, prop_end - prop_start); 409 prop_start = prop_end + 1; 410 } 411 int32_t eql = prop.indexOf(EQUALS_SIGN); 412 if (eql != -1) { 413 attr.setTo(prop, 0, eql); 414 value.setTo(prop, eql + 1); 415 } else { 416 goto rruleParseError; 417 } 418 419 if (attr.compare(ICAL_FREQ, -1) == 0) { 420 // only support YEARLY frequency type 421 if (value.compare(ICAL_YEARLY, -1) == 0) { 422 yearly = TRUE; 423 } else { 424 goto rruleParseError; 425 } 426 } else if (attr.compare(ICAL_UNTIL, -1) == 0) { 427 // ISO8601 UTC format, for example, "20060315T020000Z" 428 until = parseDateTimeString(value, 0, status); 429 if (U_FAILURE(status)) { 430 goto rruleParseError; 431 } 432 } else if (attr.compare(ICAL_BYMONTH, -1) == 0) { 433 // Note: BYMONTH may contain multiple months, but only single month make sense for 434 // VTIMEZONE property. 435 if (value.length() > 2) { 436 goto rruleParseError; 437 } 438 month = parseAsciiDigits(value, 0, value.length(), status) - 1; 439 if (U_FAILURE(status) || month < 0 || month >= 12) { 440 goto rruleParseError; 441 } 442 } else if (attr.compare(ICAL_BYDAY, -1) == 0) { 443 // Note: BYDAY may contain multiple day of week separated by comma. It is unlikely used for 444 // VTIMEZONE property. We do not support the case. 445 446 // 2-letter format is used just for representing a day of week, for example, "SU" for Sunday 447 // 3 or 4-letter format is used for represeinging Nth day of week, for example, "-1SA" for last Saturday 448 int32_t length = value.length(); 449 if (length < 2 || length > 4) { 450 goto rruleParseError; 451 } 452 if (length > 2) { 453 // Nth day of week 454 int32_t sign = 1; 455 if (value.charAt(0) == PLUS) { 456 sign = 1; 457 } else if (value.charAt(0) == MINUS) { 458 sign = -1; 459 } else if (length == 4) { 460 goto rruleParseError; 461 } 462 int32_t n = parseAsciiDigits(value, length - 3, 1, status); 463 if (U_FAILURE(status) || n == 0 || n > 4) { 464 goto rruleParseError; 465 } 466 wim = n * sign; 467 value.remove(0, length - 2); 468 } 469 int32_t wday; 470 for (wday = 0; wday < 7; wday++) { 471 if (value.compare(ICAL_DOW_NAMES[wday], 2) == 0) { 472 break; 473 } 474 } 475 if (wday < 7) { 476 // Sunday(1) - Saturday(7) 477 dow = wday + 1; 478 } else { 479 goto rruleParseError; 480 } 481 } else if (attr.compare(ICAL_BYMONTHDAY, -1) == 0) { 482 // Note: BYMONTHDAY may contain multiple days delimitted by comma 483 // 484 // A value of BYMONTHDAY could be negative, for example, -1 means 485 // the last day in a month 486 int32_t dom_idx = 0; 487 int32_t dom_start = 0; 488 int32_t dom_end; 489 UBool nextDOM = TRUE; 490 while (nextDOM) { 491 dom_end = value.indexOf(COMMA, dom_start); 492 if (dom_end == -1) { 493 dom_end = value.length(); 494 nextDOM = FALSE; 495 } 496 if (dom_idx < domCount) { 497 dom[dom_idx] = parseAsciiDigits(value, dom_start, dom_end - dom_start, status); 498 if (U_FAILURE(status)) { 499 goto rruleParseError; 500 } 501 dom_idx++; 502 } else { 503 status = U_BUFFER_OVERFLOW_ERROR; 504 goto rruleParseError; 505 } 506 dom_start = dom_end + 1; 507 } 508 numDom = dom_idx; 509 } 510 } 511 if (!yearly) { 512 // FREQ=YEARLY must be set 513 goto rruleParseError; 514 } 515 // Set actual number of parsed DOM (ICAL_BYMONTHDAY) 516 domCount = numDom; 517 return; 518 519 rruleParseError: 520 if (U_SUCCESS(status)) { 521 // Set error status 522 status = U_INVALID_FORMAT_ERROR; 523 } 524 } 525 526 static TimeZoneRule* createRuleByRRULE(const UnicodeString& zonename, int rawOffset, int dstSavings, UDate start, 527 UVector* dates, int fromOffset, UErrorCode& status) { 528 if (U_FAILURE(status)) { 529 return NULL; 530 } 531 if (dates == NULL || dates->size() == 0) { 532 status = U_ILLEGAL_ARGUMENT_ERROR; 533 return NULL; 534 } 535 536 int32_t i, j; 537 DateTimeRule *adtr = NULL; 538 539 // Parse the first rule 540 UnicodeString rrule = *((UnicodeString*)dates->elementAt(0)); 541 int32_t month, dayOfWeek, nthDayOfWeek, dayOfMonth = 0; 542 int32_t days[7]; 543 int32_t daysCount = UPRV_LENGTHOF(days); 544 UDate until; 545 546 parseRRULE(rrule, month, dayOfWeek, nthDayOfWeek, days, daysCount, until, status); 547 if (U_FAILURE(status)) { 548 return NULL; 549 } 550 551 if (dates->size() == 1) { 552 // No more rules 553 if (daysCount > 1) { 554 // Multiple BYMONTHDAY values 555 if (daysCount != 7 || month == -1 || dayOfWeek == 0) { 556 // Only support the rule using 7 continuous days 557 // BYMONTH and BYDAY must be set at the same time 558 goto unsupportedRRule; 559 } 560 int32_t firstDay = 31; // max possible number of dates in a month 561 for (i = 0; i < 7; i++) { 562 // Resolve negative day numbers. A negative day number should 563 // not be used in February, but if we see such case, we use 28 564 // as the base. 565 if (days[i] < 0) { 566 days[i] = MONTHLENGTH[month] + days[i] + 1; 567 } 568 if (days[i] < firstDay) { 569 firstDay = days[i]; 570 } 571 } 572 // Make sure days are continuous 573 for (i = 1; i < 7; i++) { 574 UBool found = FALSE; 575 for (j = 0; j < 7; j++) { 576 if (days[j] == firstDay + i) { 577 found = TRUE; 578 break; 579 } 580 } 581 if (!found) { 582 // days are not continuous 583 goto unsupportedRRule; 584 } 585 } 586 // Use DOW_GEQ_DOM rule with firstDay as the start date 587 dayOfMonth = firstDay; 588 } 589 } else { 590 // Check if BYMONTH + BYMONTHDAY + BYDAY rule with multiple RRULE lines. 591 // Otherwise, not supported. 592 if (month == -1 || dayOfWeek == 0 || daysCount == 0) { 593 // This is not the case 594 goto unsupportedRRule; 595 } 596 // Parse the rest of rules if number of rules is not exceeding 7. 597 // We can only support 7 continuous days starting from a day of month. 598 if (dates->size() > 7) { 599 goto unsupportedRRule; 600 } 601 602 // Note: To check valid date range across multiple rule is a little 603 // bit complicated. For now, this code is not doing strict range 604 // checking across month boundary 605 606 int32_t earliestMonth = month; 607 int32_t earliestDay = 31; 608 for (i = 0; i < daysCount; i++) { 609 int32_t dom = days[i]; 610 dom = dom > 0 ? dom : MONTHLENGTH[month] + dom + 1; 611 earliestDay = dom < earliestDay ? dom : earliestDay; 612 } 613 614 int32_t anotherMonth = -1; 615 for (i = 1; i < dates->size(); i++) { 616 rrule = *((UnicodeString*)dates->elementAt(i)); 617 UDate tmp_until; 618 int32_t tmp_month, tmp_dayOfWeek, tmp_nthDayOfWeek; 619 int32_t tmp_days[7]; 620 int32_t tmp_daysCount = UPRV_LENGTHOF(tmp_days); 621 parseRRULE(rrule, tmp_month, tmp_dayOfWeek, tmp_nthDayOfWeek, tmp_days, tmp_daysCount, tmp_until, status); 622 if (U_FAILURE(status)) { 623 return NULL; 624 } 625 // If UNTIL is newer than previous one, use the one 626 if (tmp_until > until) { 627 until = tmp_until; 628 } 629 630 // Check if BYMONTH + BYMONTHDAY + BYDAY rule 631 if (tmp_month == -1 || tmp_dayOfWeek == 0 || tmp_daysCount == 0) { 632 goto unsupportedRRule; 633 } 634 // Count number of BYMONTHDAY 635 if (daysCount + tmp_daysCount > 7) { 636 // We cannot support BYMONTHDAY more than 7 637 goto unsupportedRRule; 638 } 639 // Check if the same BYDAY is used. Otherwise, we cannot 640 // support the rule 641 if (tmp_dayOfWeek != dayOfWeek) { 642 goto unsupportedRRule; 643 } 644 // Check if the month is same or right next to the primary month 645 if (tmp_month != month) { 646 if (anotherMonth == -1) { 647 int32_t diff = tmp_month - month; 648 if (diff == -11 || diff == -1) { 649 // Previous month 650 anotherMonth = tmp_month; 651 earliestMonth = anotherMonth; 652 // Reset earliest day 653 earliestDay = 31; 654 } else if (diff == 11 || diff == 1) { 655 // Next month 656 anotherMonth = tmp_month; 657 } else { 658 // The day range cannot exceed more than 2 months 659 goto unsupportedRRule; 660 } 661 } else if (tmp_month != month && tmp_month != anotherMonth) { 662 // The day range cannot exceed more than 2 months 663 goto unsupportedRRule; 664 } 665 } 666 // If ealier month, go through days to find the earliest day 667 if (tmp_month == earliestMonth) { 668 for (j = 0; j < tmp_daysCount; j++) { 669 tmp_days[j] = tmp_days[j] > 0 ? tmp_days[j] : MONTHLENGTH[tmp_month] + tmp_days[j] + 1; 670 earliestDay = tmp_days[j] < earliestDay ? tmp_days[j] : earliestDay; 671 } 672 } 673 daysCount += tmp_daysCount; 674 } 675 if (daysCount != 7) { 676 // Number of BYMONTHDAY entries must be 7 677 goto unsupportedRRule; 678 } 679 month = earliestMonth; 680 dayOfMonth = earliestDay; 681 } 682 683 // Calculate start/end year and missing fields 684 int32_t startYear, startMonth, startDOM, startDOW, startDOY, startMID; 685 Grego::timeToFields(start + fromOffset, startYear, startMonth, startDOM, 686 startDOW, startDOY, startMID); 687 if (month == -1) { 688 // If BYMONTH is not set, use the month of DTSTART 689 month = startMonth; 690 } 691 if (dayOfWeek == 0 && nthDayOfWeek == 0 && dayOfMonth == 0) { 692 // If only YEARLY is set, use the day of DTSTART as BYMONTHDAY 693 dayOfMonth = startDOM; 694 } 695 696 int32_t endYear; 697 if (until != MIN_MILLIS) { 698 int32_t endMonth, endDOM, endDOW, endDOY, endMID; 699 Grego::timeToFields(until, endYear, endMonth, endDOM, endDOW, endDOY, endMID); 700 } else { 701 endYear = AnnualTimeZoneRule::MAX_YEAR; 702 } 703 704 // Create the AnnualDateTimeRule 705 if (dayOfWeek == 0 && nthDayOfWeek == 0 && dayOfMonth != 0) { 706 // Day in month rule, for example, 15th day in the month 707 adtr = new DateTimeRule(month, dayOfMonth, startMID, DateTimeRule::WALL_TIME); 708 } else if (dayOfWeek != 0 && nthDayOfWeek != 0 && dayOfMonth == 0) { 709 // Nth day of week rule, for example, last Sunday 710 adtr = new DateTimeRule(month, nthDayOfWeek, dayOfWeek, startMID, DateTimeRule::WALL_TIME); 711 } else if (dayOfWeek != 0 && nthDayOfWeek == 0 && dayOfMonth != 0) { 712 // First day of week after day of month rule, for example, 713 // first Sunday after 15th day in the month 714 adtr = new DateTimeRule(month, dayOfMonth, dayOfWeek, TRUE, startMID, DateTimeRule::WALL_TIME); 715 } 716 if (adtr == NULL) { 717 goto unsupportedRRule; 718 } 719 return new AnnualTimeZoneRule(zonename, rawOffset, dstSavings, adtr, startYear, endYear); 720 721 unsupportedRRule: 722 status = U_INVALID_STATE_ERROR; 723 return NULL; 724 } 725 726 /* 727 * Create a TimeZoneRule by the RDATE definition 728 */ 729 static TimeZoneRule* createRuleByRDATE(const UnicodeString& zonename, int32_t rawOffset, int32_t dstSavings, 730 UDate start, UVector* dates, int32_t fromOffset, UErrorCode& status) { 731 if (U_FAILURE(status)) { 732 return NULL; 733 } 734 TimeArrayTimeZoneRule *retVal = NULL; 735 if (dates == NULL || dates->size() == 0) { 736 // When no RDATE line is provided, use start (DTSTART) 737 // as the transition time 738 retVal = new TimeArrayTimeZoneRule(zonename, rawOffset, dstSavings, 739 &start, 1, DateTimeRule::UTC_TIME); 740 } else { 741 // Create an array of transition times 742 int32_t size = dates->size(); 743 UDate* times = (UDate*)uprv_malloc(sizeof(UDate) * size); 744 if (times == NULL) { 745 status = U_MEMORY_ALLOCATION_ERROR; 746 return NULL; 747 } 748 for (int32_t i = 0; i < size; i++) { 749 UnicodeString *datestr = (UnicodeString*)dates->elementAt(i); 750 times[i] = parseDateTimeString(*datestr, fromOffset, status); 751 if (U_FAILURE(status)) { 752 uprv_free(times); 753 return NULL; 754 } 755 } 756 retVal = new TimeArrayTimeZoneRule(zonename, rawOffset, dstSavings, 757 times, size, DateTimeRule::UTC_TIME); 758 uprv_free(times); 759 } 760 return retVal; 761 } 762 763 /* 764 * Check if the DOW rule specified by month, weekInMonth and dayOfWeek is equivalent 765 * to the DateTimerule. 766 */ 767 static UBool isEquivalentDateRule(int32_t month, int32_t weekInMonth, int32_t dayOfWeek, const DateTimeRule *dtrule) { 768 if (month != dtrule->getRuleMonth() || dayOfWeek != dtrule->getRuleDayOfWeek()) { 769 return FALSE; 770 } 771 if (dtrule->getTimeRuleType() != DateTimeRule::WALL_TIME) { 772 // Do not try to do more intelligent comparison for now. 773 return FALSE; 774 } 775 if (dtrule->getDateRuleType() == DateTimeRule::DOW 776 && dtrule->getRuleWeekInMonth() == weekInMonth) { 777 return TRUE; 778 } 779 int32_t ruleDOM = dtrule->getRuleDayOfMonth(); 780 if (dtrule->getDateRuleType() == DateTimeRule::DOW_GEQ_DOM) { 781 if (ruleDOM%7 == 1 && (ruleDOM + 6)/7 == weekInMonth) { 782 return TRUE; 783 } 784 if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - ruleDOM)%7 == 6 785 && weekInMonth == -1*((MONTHLENGTH[month]-ruleDOM+1)/7)) { 786 return TRUE; 787 } 788 } 789 if (dtrule->getDateRuleType() == DateTimeRule::DOW_LEQ_DOM) { 790 if (ruleDOM%7 == 0 && ruleDOM/7 == weekInMonth) { 791 return TRUE; 792 } 793 if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - ruleDOM)%7 == 0 794 && weekInMonth == -1*((MONTHLENGTH[month] - ruleDOM)/7 + 1)) { 795 return TRUE; 796 } 797 } 798 return FALSE; 799 } 800 801 /* 802 * Convert the rule to its equivalent rule using WALL_TIME mode. 803 * This function returns NULL when the specified DateTimeRule is already 804 * using WALL_TIME mode. 805 */ 806 static DateTimeRule* toWallTimeRule(const DateTimeRule* rule, int32_t rawOffset, int32_t dstSavings) { 807 if (rule->getTimeRuleType() == DateTimeRule::WALL_TIME) { 808 return NULL; 809 } 810 int32_t wallt = rule->getRuleMillisInDay(); 811 if (rule->getTimeRuleType() == DateTimeRule::UTC_TIME) { 812 wallt += (rawOffset + dstSavings); 813 } else if (rule->getTimeRuleType() == DateTimeRule::STANDARD_TIME) { 814 wallt += dstSavings; 815 } 816 817 int32_t month = -1, dom = 0, dow = 0; 818 DateTimeRule::DateRuleType dtype; 819 int32_t dshift = 0; 820 if (wallt < 0) { 821 dshift = -1; 822 wallt += U_MILLIS_PER_DAY; 823 } else if (wallt >= U_MILLIS_PER_DAY) { 824 dshift = 1; 825 wallt -= U_MILLIS_PER_DAY; 826 } 827 828 month = rule->getRuleMonth(); 829 dom = rule->getRuleDayOfMonth(); 830 dow = rule->getRuleDayOfWeek(); 831 dtype = rule->getDateRuleType(); 832 833 if (dshift != 0) { 834 if (dtype == DateTimeRule::DOW) { 835 // Convert to DOW_GEW_DOM or DOW_LEQ_DOM rule first 836 int32_t wim = rule->getRuleWeekInMonth(); 837 if (wim > 0) { 838 dtype = DateTimeRule::DOW_GEQ_DOM; 839 dom = 7 * (wim - 1) + 1; 840 } else { 841 dtype = DateTimeRule::DOW_LEQ_DOM; 842 dom = MONTHLENGTH[month] + 7 * (wim + 1); 843 } 844 } 845 // Shift one day before or after 846 dom += dshift; 847 if (dom == 0) { 848 month--; 849 month = month < UCAL_JANUARY ? UCAL_DECEMBER : month; 850 dom = MONTHLENGTH[month]; 851 } else if (dom > MONTHLENGTH[month]) { 852 month++; 853 month = month > UCAL_DECEMBER ? UCAL_JANUARY : month; 854 dom = 1; 855 } 856 if (dtype != DateTimeRule::DOM) { 857 // Adjust day of week 858 dow += dshift; 859 if (dow < UCAL_SUNDAY) { 860 dow = UCAL_SATURDAY; 861 } else if (dow > UCAL_SATURDAY) { 862 dow = UCAL_SUNDAY; 863 } 864 } 865 } 866 // Create a new rule 867 DateTimeRule *modifiedRule; 868 if (dtype == DateTimeRule::DOM) { 869 modifiedRule = new DateTimeRule(month, dom, wallt, DateTimeRule::WALL_TIME); 870 } else { 871 modifiedRule = new DateTimeRule(month, dom, dow, 872 (dtype == DateTimeRule::DOW_GEQ_DOM), wallt, DateTimeRule::WALL_TIME); 873 } 874 return modifiedRule; 875 } 876 877 /* 878 * Minumum implementations of stream writer/reader, writing/reading 879 * UnicodeString. For now, we do not want to introduce the dependency 880 * on the ICU I/O stream in this module. But we want to keep the code 881 * equivalent to the ICU4J implementation, which utilizes java.io.Writer/ 882 * Reader. 883 */ 884 class VTZWriter { 885 public: 886 VTZWriter(UnicodeString& out); 887 ~VTZWriter(); 888 889 void write(const UnicodeString& str); 890 void write(UChar ch); 891 void write(const UChar* str); 892 //void write(const UChar* str, int32_t length); 893 private: 894 UnicodeString* out; 895 }; 896 897 VTZWriter::VTZWriter(UnicodeString& output) { 898 out = &output; 899 } 900 901 VTZWriter::~VTZWriter() { 902 } 903 904 void 905 VTZWriter::write(const UnicodeString& str) { 906 out->append(str); 907 } 908 909 void 910 VTZWriter::write(UChar ch) { 911 out->append(ch); 912 } 913 914 void 915 VTZWriter::write(const UChar* str) { 916 out->append(str, -1); 917 } 918 919 /* 920 void 921 VTZWriter::write(const UChar* str, int32_t length) { 922 out->append(str, length); 923 } 924 */ 925 926 class VTZReader { 927 public: 928 VTZReader(const UnicodeString& input); 929 ~VTZReader(); 930 931 UChar read(void); 932 private: 933 const UnicodeString* in; 934 int32_t index; 935 }; 936 937 VTZReader::VTZReader(const UnicodeString& input) { 938 in = &input; 939 index = 0; 940 } 941 942 VTZReader::~VTZReader() { 943 } 944 945 UChar 946 VTZReader::read(void) { 947 UChar ch = 0xFFFF; 948 if (index < in->length()) { 949 ch = in->charAt(index); 950 } 951 index++; 952 return ch; 953 } 954 955 956 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(VTimeZone) 957 958 VTimeZone::VTimeZone() 959 : BasicTimeZone(), tz(NULL), vtzlines(NULL), 960 lastmod(MAX_MILLIS) { 961 } 962 963 VTimeZone::VTimeZone(const VTimeZone& source) 964 : BasicTimeZone(source), tz(NULL), vtzlines(NULL), 965 tzurl(source.tzurl), lastmod(source.lastmod), 966 olsonzid(source.olsonzid), icutzver(source.icutzver) { 967 if (source.tz != NULL) { 968 tz = (BasicTimeZone*)source.tz->clone(); 969 } 970 if (source.vtzlines != NULL) { 971 UErrorCode status = U_ZERO_ERROR; 972 int32_t size = source.vtzlines->size(); 973 vtzlines = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, size, status); 974 if (U_SUCCESS(status)) { 975 for (int32_t i = 0; i < size; i++) { 976 UnicodeString *line = (UnicodeString*)source.vtzlines->elementAt(i); 977 vtzlines->addElement(line->clone(), status); 978 if (U_FAILURE(status)) { 979 break; 980 } 981 } 982 } 983 if (U_FAILURE(status) && vtzlines != NULL) { 984 delete vtzlines; 985 } 986 } 987 } 988 989 VTimeZone::~VTimeZone() { 990 if (tz != NULL) { 991 delete tz; 992 } 993 if (vtzlines != NULL) { 994 delete vtzlines; 995 } 996 } 997 998 VTimeZone& 999 VTimeZone::operator=(const VTimeZone& right) { 1000 if (this == &right) { 1001 return *this; 1002 } 1003 if (*this != right) { 1004 BasicTimeZone::operator=(right); 1005 if (tz != NULL) { 1006 delete tz; 1007 tz = NULL; 1008 } 1009 if (right.tz != NULL) { 1010 tz = (BasicTimeZone*)right.tz->clone(); 1011 } 1012 if (vtzlines != NULL) { 1013 delete vtzlines; 1014 } 1015 if (right.vtzlines != NULL) { 1016 UErrorCode status = U_ZERO_ERROR; 1017 int32_t size = right.vtzlines->size(); 1018 vtzlines = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, size, status); 1019 if (U_SUCCESS(status)) { 1020 for (int32_t i = 0; i < size; i++) { 1021 UnicodeString *line = (UnicodeString*)right.vtzlines->elementAt(i); 1022 vtzlines->addElement(line->clone(), status); 1023 if (U_FAILURE(status)) { 1024 break; 1025 } 1026 } 1027 } 1028 if (U_FAILURE(status) && vtzlines != NULL) { 1029 delete vtzlines; 1030 vtzlines = NULL; 1031 } 1032 } 1033 tzurl = right.tzurl; 1034 lastmod = right.lastmod; 1035 olsonzid = right.olsonzid; 1036 icutzver = right.icutzver; 1037 } 1038 return *this; 1039 } 1040 1041 UBool 1042 VTimeZone::operator==(const TimeZone& that) const { 1043 if (this == &that) { 1044 return TRUE; 1045 } 1046 if (typeid(*this) != typeid(that) || !BasicTimeZone::operator==(that)) { 1047 return FALSE; 1048 } 1049 VTimeZone *vtz = (VTimeZone*)&that; 1050 if (*tz == *(vtz->tz) 1051 && tzurl == vtz->tzurl 1052 && lastmod == vtz->lastmod 1053 /* && olsonzid = that.olsonzid */ 1054 /* && icutzver = that.icutzver */) { 1055 return TRUE; 1056 } 1057 return FALSE; 1058 } 1059 1060 UBool 1061 VTimeZone::operator!=(const TimeZone& that) const { 1062 return !operator==(that); 1063 } 1064 1065 VTimeZone* 1066 VTimeZone::createVTimeZoneByID(const UnicodeString& ID) { 1067 VTimeZone *vtz = new VTimeZone(); 1068 vtz->tz = (BasicTimeZone*)TimeZone::createTimeZone(ID); 1069 vtz->tz->getID(vtz->olsonzid); 1070 1071 // Set ICU tzdata version 1072 UErrorCode status = U_ZERO_ERROR; 1073 UResourceBundle *bundle = NULL; 1074 const UChar* versionStr = NULL; 1075 int32_t len = 0; 1076 bundle = ures_openDirect(NULL, "zoneinfo64", &status); 1077 versionStr = ures_getStringByKey(bundle, "TZVersion", &len, &status); 1078 if (U_SUCCESS(status)) { 1079 vtz->icutzver.setTo(versionStr, len); 1080 } 1081 ures_close(bundle); 1082 return vtz; 1083 } 1084 1085 VTimeZone* 1086 VTimeZone::createVTimeZoneFromBasicTimeZone(const BasicTimeZone& basic_time_zone, UErrorCode &status) { 1087 if (U_FAILURE(status)) { 1088 return NULL; 1089 } 1090 VTimeZone *vtz = new VTimeZone(); 1091 if (vtz == NULL) { 1092 status = U_MEMORY_ALLOCATION_ERROR; 1093 return NULL; 1094 } 1095 vtz->tz = (BasicTimeZone *)basic_time_zone.clone(); 1096 if (vtz->tz == NULL) { 1097 status = U_MEMORY_ALLOCATION_ERROR; 1098 delete vtz; 1099 return NULL; 1100 } 1101 vtz->tz->getID(vtz->olsonzid); 1102 1103 // Set ICU tzdata version 1104 UResourceBundle *bundle = NULL; 1105 const UChar* versionStr = NULL; 1106 int32_t len = 0; 1107 bundle = ures_openDirect(NULL, "zoneinfo64", &status); 1108 versionStr = ures_getStringByKey(bundle, "TZVersion", &len, &status); 1109 if (U_SUCCESS(status)) { 1110 vtz->icutzver.setTo(versionStr, len); 1111 } 1112 ures_close(bundle); 1113 return vtz; 1114 } 1115 1116 VTimeZone* 1117 VTimeZone::createVTimeZone(const UnicodeString& vtzdata, UErrorCode& status) { 1118 if (U_FAILURE(status)) { 1119 return NULL; 1120 } 1121 VTZReader reader(vtzdata); 1122 VTimeZone *vtz = new VTimeZone(); 1123 vtz->load(reader, status); 1124 if (U_FAILURE(status)) { 1125 delete vtz; 1126 return NULL; 1127 } 1128 return vtz; 1129 } 1130 1131 UBool 1132 VTimeZone::getTZURL(UnicodeString& url) const { 1133 if (tzurl.length() > 0) { 1134 url = tzurl; 1135 return TRUE; 1136 } 1137 return FALSE; 1138 } 1139 1140 void 1141 VTimeZone::setTZURL(const UnicodeString& url) { 1142 tzurl = url; 1143 } 1144 1145 UBool 1146 VTimeZone::getLastModified(UDate& lastModified) const { 1147 if (lastmod != MAX_MILLIS) { 1148 lastModified = lastmod; 1149 return TRUE; 1150 } 1151 return FALSE; 1152 } 1153 1154 void 1155 VTimeZone::setLastModified(UDate lastModified) { 1156 lastmod = lastModified; 1157 } 1158 1159 void 1160 VTimeZone::write(UnicodeString& result, UErrorCode& status) const { 1161 result.remove(); 1162 VTZWriter writer(result); 1163 write(writer, status); 1164 } 1165 1166 void 1167 VTimeZone::write(UDate start, UnicodeString& result, UErrorCode& status) const { 1168 result.remove(); 1169 VTZWriter writer(result); 1170 write(start, writer, status); 1171 } 1172 1173 void 1174 VTimeZone::writeSimple(UDate time, UnicodeString& result, UErrorCode& status) const { 1175 result.remove(); 1176 VTZWriter writer(result); 1177 writeSimple(time, writer, status); 1178 } 1179 1180 TimeZone* 1181 VTimeZone::clone(void) const { 1182 return new VTimeZone(*this); 1183 } 1184 1185 int32_t 1186 VTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day, 1187 uint8_t dayOfWeek, int32_t millis, UErrorCode& status) const { 1188 return tz->getOffset(era, year, month, day, dayOfWeek, millis, status); 1189 } 1190 1191 int32_t 1192 VTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day, 1193 uint8_t dayOfWeek, int32_t millis, 1194 int32_t monthLength, UErrorCode& status) const { 1195 return tz->getOffset(era, year, month, day, dayOfWeek, millis, monthLength, status); 1196 } 1197 1198 void 1199 VTimeZone::getOffset(UDate date, UBool local, int32_t& rawOffset, 1200 int32_t& dstOffset, UErrorCode& status) const { 1201 return tz->getOffset(date, local, rawOffset, dstOffset, status); 1202 } 1203 1204 void 1205 VTimeZone::setRawOffset(int32_t offsetMillis) { 1206 tz->setRawOffset(offsetMillis); 1207 } 1208 1209 int32_t 1210 VTimeZone::getRawOffset(void) const { 1211 return tz->getRawOffset(); 1212 } 1213 1214 UBool 1215 VTimeZone::useDaylightTime(void) const { 1216 return tz->useDaylightTime(); 1217 } 1218 1219 UBool 1220 VTimeZone::inDaylightTime(UDate date, UErrorCode& status) const { 1221 return tz->inDaylightTime(date, status); 1222 } 1223 1224 UBool 1225 VTimeZone::hasSameRules(const TimeZone& other) const { 1226 return tz->hasSameRules(other); 1227 } 1228 1229 UBool 1230 VTimeZone::getNextTransition(UDate base, UBool inclusive, TimeZoneTransition& result) const { 1231 return tz->getNextTransition(base, inclusive, result); 1232 } 1233 1234 UBool 1235 VTimeZone::getPreviousTransition(UDate base, UBool inclusive, TimeZoneTransition& result) const { 1236 return tz->getPreviousTransition(base, inclusive, result); 1237 } 1238 1239 int32_t 1240 VTimeZone::countTransitionRules(UErrorCode& status) const { 1241 return tz->countTransitionRules(status); 1242 } 1243 1244 void 1245 VTimeZone::getTimeZoneRules(const InitialTimeZoneRule*& initial, 1246 const TimeZoneRule* trsrules[], int32_t& trscount, 1247 UErrorCode& status) const { 1248 tz->getTimeZoneRules(initial, trsrules, trscount, status); 1249 } 1250 1251 void 1252 VTimeZone::load(VTZReader& reader, UErrorCode& status) { 1253 vtzlines = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, DEFAULT_VTIMEZONE_LINES, status); 1254 if (U_FAILURE(status)) { 1255 return; 1256 } 1257 UBool eol = FALSE; 1258 UBool start = FALSE; 1259 UBool success = FALSE; 1260 UnicodeString line; 1261 1262 while (TRUE) { 1263 UChar ch = reader.read(); 1264 if (ch == 0xFFFF) { 1265 // end of file 1266 if (start && line.startsWith(ICAL_END_VTIMEZONE, -1)) { 1267 vtzlines->addElement(new UnicodeString(line), status); 1268 if (U_FAILURE(status)) { 1269 goto cleanupVtzlines; 1270 } 1271 success = TRUE; 1272 } 1273 break; 1274 } 1275 if (ch == 0x000D) { 1276 // CR, must be followed by LF according to the definition in RFC2445 1277 continue; 1278 } 1279 if (eol) { 1280 if (ch != 0x0009 && ch != 0x0020) { 1281 // NOT followed by TAB/SP -> new line 1282 if (start) { 1283 if (line.length() > 0) { 1284 vtzlines->addElement(new UnicodeString(line), status); 1285 if (U_FAILURE(status)) { 1286 goto cleanupVtzlines; 1287 } 1288 } 1289 } 1290 line.remove(); 1291 if (ch != 0x000A) { 1292 line.append(ch); 1293 } 1294 } 1295 eol = FALSE; 1296 } else { 1297 if (ch == 0x000A) { 1298 // LF 1299 eol = TRUE; 1300 if (start) { 1301 if (line.startsWith(ICAL_END_VTIMEZONE, -1)) { 1302 vtzlines->addElement(new UnicodeString(line), status); 1303 if (U_FAILURE(status)) { 1304 goto cleanupVtzlines; 1305 } 1306 success = TRUE; 1307 break; 1308 } 1309 } else { 1310 if (line.startsWith(ICAL_BEGIN_VTIMEZONE, -1)) { 1311 vtzlines->addElement(new UnicodeString(line), status); 1312 if (U_FAILURE(status)) { 1313 goto cleanupVtzlines; 1314 } 1315 line.remove(); 1316 start = TRUE; 1317 eol = FALSE; 1318 } 1319 } 1320 } else { 1321 line.append(ch); 1322 } 1323 } 1324 } 1325 if (!success) { 1326 if (U_SUCCESS(status)) { 1327 status = U_INVALID_STATE_ERROR; 1328 } 1329 goto cleanupVtzlines; 1330 } 1331 parse(status); 1332 return; 1333 1334 cleanupVtzlines: 1335 delete vtzlines; 1336 vtzlines = NULL; 1337 } 1338 1339 // parser state 1340 #define INI 0 // Initial state 1341 #define VTZ 1 // In VTIMEZONE 1342 #define TZI 2 // In STANDARD or DAYLIGHT 1343 1344 #define DEF_DSTSAVINGS (60*60*1000) 1345 #define DEF_TZSTARTTIME (0.0) 1346 1347 void 1348 VTimeZone::parse(UErrorCode& status) { 1349 if (U_FAILURE(status)) { 1350 return; 1351 } 1352 if (vtzlines == NULL || vtzlines->size() == 0) { 1353 status = U_INVALID_STATE_ERROR; 1354 return; 1355 } 1356 InitialTimeZoneRule *initialRule = NULL; 1357 RuleBasedTimeZone *rbtz = NULL; 1358 1359 // timezone ID 1360 UnicodeString tzid; 1361 1362 int32_t state = INI; 1363 int32_t n = 0; 1364 UBool dst = FALSE; // current zone type 1365 UnicodeString from; // current zone from offset 1366 UnicodeString to; // current zone offset 1367 UnicodeString zonename; // current zone name 1368 UnicodeString dtstart; // current zone starts 1369 UBool isRRULE = FALSE; // true if the rule is described by RRULE 1370 int32_t initialRawOffset = 0; // initial offset 1371 int32_t initialDSTSavings = 0; // initial offset 1372 UDate firstStart = MAX_MILLIS; // the earliest rule start time 1373 UnicodeString name; // RFC2445 prop name 1374 UnicodeString value; // RFC2445 prop value 1375 1376 UVector *dates = NULL; // list of RDATE or RRULE strings 1377 UVector *rules = NULL; // list of TimeZoneRule instances 1378 1379 int32_t finalRuleIdx = -1; 1380 int32_t finalRuleCount = 0; 1381 1382 rules = new UVector(status); 1383 if (U_FAILURE(status)) { 1384 goto cleanupParse; 1385 } 1386 // Set the deleter to remove TimeZoneRule vectors to avoid memory leaks due to unowned TimeZoneRules. 1387 rules->setDeleter(deleteTimeZoneRule); 1388 1389 dates = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, status); 1390 if (U_FAILURE(status)) { 1391 goto cleanupParse; 1392 } 1393 if (rules == NULL || dates == NULL) { 1394 status = U_MEMORY_ALLOCATION_ERROR; 1395 goto cleanupParse; 1396 } 1397 1398 for (n = 0; n < vtzlines->size(); n++) { 1399 UnicodeString *line = (UnicodeString*)vtzlines->elementAt(n); 1400 int32_t valueSep = line->indexOf(COLON); 1401 if (valueSep < 0) { 1402 continue; 1403 } 1404 name.setTo(*line, 0, valueSep); 1405 value.setTo(*line, valueSep + 1); 1406 1407 switch (state) { 1408 case INI: 1409 if (name.compare(ICAL_BEGIN, -1) == 0 1410 && value.compare(ICAL_VTIMEZONE, -1) == 0) { 1411 state = VTZ; 1412 } 1413 break; 1414 1415 case VTZ: 1416 if (name.compare(ICAL_TZID, -1) == 0) { 1417 tzid = value; 1418 } else if (name.compare(ICAL_TZURL, -1) == 0) { 1419 tzurl = value; 1420 } else if (name.compare(ICAL_LASTMOD, -1) == 0) { 1421 // Always in 'Z' format, so the offset argument for the parse method 1422 // can be any value. 1423 lastmod = parseDateTimeString(value, 0, status); 1424 if (U_FAILURE(status)) { 1425 goto cleanupParse; 1426 } 1427 } else if (name.compare(ICAL_BEGIN, -1) == 0) { 1428 UBool isDST = (value.compare(ICAL_DAYLIGHT, -1) == 0); 1429 if (value.compare(ICAL_STANDARD, -1) == 0 || isDST) { 1430 // tzid must be ready at this point 1431 if (tzid.length() == 0) { 1432 goto cleanupParse; 1433 } 1434 // initialize current zone properties 1435 if (dates->size() != 0) { 1436 dates->removeAllElements(); 1437 } 1438 isRRULE = FALSE; 1439 from.remove(); 1440 to.remove(); 1441 zonename.remove(); 1442 dst = isDST; 1443 state = TZI; 1444 } else { 1445 // BEGIN property other than STANDARD/DAYLIGHT 1446 // must not be there. 1447 goto cleanupParse; 1448 } 1449 } else if (name.compare(ICAL_END, -1) == 0) { 1450 break; 1451 } 1452 break; 1453 case TZI: 1454 if (name.compare(ICAL_DTSTART, -1) == 0) { 1455 dtstart = value; 1456 } else if (name.compare(ICAL_TZNAME, -1) == 0) { 1457 zonename = value; 1458 } else if (name.compare(ICAL_TZOFFSETFROM, -1) == 0) { 1459 from = value; 1460 } else if (name.compare(ICAL_TZOFFSETTO, -1) == 0) { 1461 to = value; 1462 } else if (name.compare(ICAL_RDATE, -1) == 0) { 1463 // RDATE mixed with RRULE is not supported 1464 if (isRRULE) { 1465 goto cleanupParse; 1466 } 1467 // RDATE value may contain multiple date delimited 1468 // by comma 1469 UBool nextDate = TRUE; 1470 int32_t dstart = 0; 1471 UnicodeString *dstr; 1472 while (nextDate) { 1473 int32_t dend = value.indexOf(COMMA, dstart); 1474 if (dend == -1) { 1475 dstr = new UnicodeString(value, dstart); 1476 nextDate = FALSE; 1477 } else { 1478 dstr = new UnicodeString(value, dstart, dend - dstart); 1479 } 1480 dates->addElement(dstr, status); 1481 if (U_FAILURE(status)) { 1482 goto cleanupParse; 1483 } 1484 dstart = dend + 1; 1485 } 1486 } else if (name.compare(ICAL_RRULE, -1) == 0) { 1487 // RRULE mixed with RDATE is not supported 1488 if (!isRRULE && dates->size() != 0) { 1489 goto cleanupParse; 1490 } 1491 isRRULE = true; 1492 dates->addElement(new UnicodeString(value), status); 1493 if (U_FAILURE(status)) { 1494 goto cleanupParse; 1495 } 1496 } else if (name.compare(ICAL_END, -1) == 0) { 1497 // Mandatory properties 1498 if (dtstart.length() == 0 || from.length() == 0 || to.length() == 0) { 1499 goto cleanupParse; 1500 } 1501 // if zonename is not available, create one from tzid 1502 if (zonename.length() == 0) { 1503 getDefaultTZName(tzid, dst, zonename); 1504 } 1505 1506 // create a time zone rule 1507 TimeZoneRule *rule = NULL; 1508 int32_t fromOffset = 0; 1509 int32_t toOffset = 0; 1510 int32_t rawOffset = 0; 1511 int32_t dstSavings = 0; 1512 UDate start = 0; 1513 1514 // Parse TZOFFSETFROM/TZOFFSETTO 1515 fromOffset = offsetStrToMillis(from, status); 1516 toOffset = offsetStrToMillis(to, status); 1517 if (U_FAILURE(status)) { 1518 goto cleanupParse; 1519 } 1520 1521 if (dst) { 1522 // If daylight, use the previous offset as rawoffset if positive 1523 if (toOffset - fromOffset > 0) { 1524 rawOffset = fromOffset; 1525 dstSavings = toOffset - fromOffset; 1526 } else { 1527 // This is rare case.. just use 1 hour DST savings 1528 rawOffset = toOffset - DEF_DSTSAVINGS; 1529 dstSavings = DEF_DSTSAVINGS; 1530 } 1531 } else { 1532 rawOffset = toOffset; 1533 dstSavings = 0; 1534 } 1535 1536 // start time 1537 start = parseDateTimeString(dtstart, fromOffset, status); 1538 if (U_FAILURE(status)) { 1539 goto cleanupParse; 1540 } 1541 1542 // Create the rule 1543 UDate actualStart = MAX_MILLIS; 1544 if (isRRULE) { 1545 rule = createRuleByRRULE(zonename, rawOffset, dstSavings, start, dates, fromOffset, status); 1546 } else { 1547 rule = createRuleByRDATE(zonename, rawOffset, dstSavings, start, dates, fromOffset, status); 1548 } 1549 if (U_FAILURE(status) || rule == NULL) { 1550 goto cleanupParse; 1551 } else { 1552 UBool startAvail = rule->getFirstStart(fromOffset, 0, actualStart); 1553 if (startAvail && actualStart < firstStart) { 1554 // save from offset information for the earliest rule 1555 firstStart = actualStart; 1556 // If this is STD, assume the time before this transtion 1557 // is DST when the difference is 1 hour. This might not be 1558 // accurate, but VTIMEZONE data does not have such info. 1559 if (dstSavings > 0) { 1560 initialRawOffset = fromOffset; 1561 initialDSTSavings = 0; 1562 } else { 1563 if (fromOffset - toOffset == DEF_DSTSAVINGS) { 1564 initialRawOffset = fromOffset - DEF_DSTSAVINGS; 1565 initialDSTSavings = DEF_DSTSAVINGS; 1566 } else { 1567 initialRawOffset = fromOffset; 1568 initialDSTSavings = 0; 1569 } 1570 } 1571 } 1572 } 1573 rules->addElement(rule, status); 1574 if (U_FAILURE(status)) { 1575 goto cleanupParse; 1576 } 1577 state = VTZ; 1578 } 1579 break; 1580 } 1581 } 1582 // Must have at least one rule 1583 if (rules->size() == 0) { 1584 goto cleanupParse; 1585 } 1586 1587 // Create a initial rule 1588 getDefaultTZName(tzid, FALSE, zonename); 1589 initialRule = new InitialTimeZoneRule(zonename, 1590 initialRawOffset, initialDSTSavings); 1591 if (initialRule == NULL) { 1592 status = U_MEMORY_ALLOCATION_ERROR; 1593 goto cleanupParse; 1594 } 1595 1596 // Finally, create the RuleBasedTimeZone 1597 rbtz = new RuleBasedTimeZone(tzid, initialRule); 1598 if (rbtz == NULL) { 1599 status = U_MEMORY_ALLOCATION_ERROR; 1600 goto cleanupParse; 1601 } 1602 initialRule = NULL; // already adopted by RBTZ, no need to delete 1603 1604 for (n = 0; n < rules->size(); n++) { 1605 TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n); 1606 AnnualTimeZoneRule *atzrule = dynamic_cast<AnnualTimeZoneRule *>(r); 1607 if (atzrule != NULL) { 1608 if (atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR) { 1609 finalRuleCount++; 1610 finalRuleIdx = n; 1611 } 1612 } 1613 } 1614 if (finalRuleCount > 2) { 1615 // Too many final rules 1616 status = U_ILLEGAL_ARGUMENT_ERROR; 1617 goto cleanupParse; 1618 } 1619 1620 if (finalRuleCount == 1) { 1621 if (rules->size() == 1) { 1622 // Only one final rule, only governs the initial rule, 1623 // which is already initialized, thus, we do not need to 1624 // add this transition rule 1625 rules->removeAllElements(); 1626 } else { 1627 // Normalize the final rule 1628 AnnualTimeZoneRule *finalRule = (AnnualTimeZoneRule*)rules->elementAt(finalRuleIdx); 1629 int32_t tmpRaw = finalRule->getRawOffset(); 1630 int32_t tmpDST = finalRule->getDSTSavings(); 1631 1632 // Find the last non-final rule 1633 UDate finalStart, start; 1634 finalRule->getFirstStart(initialRawOffset, initialDSTSavings, finalStart); 1635 start = finalStart; 1636 for (n = 0; n < rules->size(); n++) { 1637 if (finalRuleIdx == n) { 1638 continue; 1639 } 1640 TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n); 1641 UDate lastStart; 1642 r->getFinalStart(tmpRaw, tmpDST, lastStart); 1643 if (lastStart > start) { 1644 finalRule->getNextStart(lastStart, 1645 r->getRawOffset(), 1646 r->getDSTSavings(), 1647 FALSE, 1648 start); 1649 } 1650 } 1651 1652 TimeZoneRule *newRule; 1653 UnicodeString tznam; 1654 if (start == finalStart) { 1655 // Transform this into a single transition 1656 newRule = new TimeArrayTimeZoneRule( 1657 finalRule->getName(tznam), 1658 finalRule->getRawOffset(), 1659 finalRule->getDSTSavings(), 1660 &finalStart, 1661 1, 1662 DateTimeRule::UTC_TIME); 1663 } else { 1664 // Update the end year 1665 int32_t y, m, d, dow, doy, mid; 1666 Grego::timeToFields(start, y, m, d, dow, doy, mid); 1667 newRule = new AnnualTimeZoneRule( 1668 finalRule->getName(tznam), 1669 finalRule->getRawOffset(), 1670 finalRule->getDSTSavings(), 1671 *(finalRule->getRule()), 1672 finalRule->getStartYear(), 1673 y); 1674 } 1675 if (newRule == NULL) { 1676 status = U_MEMORY_ALLOCATION_ERROR; 1677 goto cleanupParse; 1678 } 1679 rules->removeElementAt(finalRuleIdx); 1680 rules->addElement(newRule, status); 1681 if (U_FAILURE(status)) { 1682 delete newRule; 1683 goto cleanupParse; 1684 } 1685 } 1686 } 1687 1688 while (!rules->isEmpty()) { 1689 TimeZoneRule *tzr = (TimeZoneRule*)rules->orphanElementAt(0); 1690 rbtz->addTransitionRule(tzr, status); 1691 if (U_FAILURE(status)) { 1692 goto cleanupParse; 1693 } 1694 } 1695 rbtz->complete(status); 1696 if (U_FAILURE(status)) { 1697 goto cleanupParse; 1698 } 1699 delete rules; 1700 delete dates; 1701 1702 tz = rbtz; 1703 setID(tzid); 1704 return; 1705 1706 cleanupParse: 1707 if (rules != NULL) { 1708 while (!rules->isEmpty()) { 1709 TimeZoneRule *r = (TimeZoneRule*)rules->orphanElementAt(0); 1710 delete r; 1711 } 1712 delete rules; 1713 } 1714 if (dates != NULL) { 1715 delete dates; 1716 } 1717 if (initialRule != NULL) { 1718 delete initialRule; 1719 } 1720 if (rbtz != NULL) { 1721 delete rbtz; 1722 } 1723 return; 1724 } 1725 1726 void 1727 VTimeZone::write(VTZWriter& writer, UErrorCode& status) const { 1728 if (vtzlines != NULL) { 1729 for (int32_t i = 0; i < vtzlines->size(); i++) { 1730 UnicodeString *line = (UnicodeString*)vtzlines->elementAt(i); 1731 if (line->startsWith(ICAL_TZURL, -1) 1732 && line->charAt(u_strlen(ICAL_TZURL)) == COLON) { 1733 writer.write(ICAL_TZURL); 1734 writer.write(COLON); 1735 writer.write(tzurl); 1736 writer.write(ICAL_NEWLINE); 1737 } else if (line->startsWith(ICAL_LASTMOD, -1) 1738 && line->charAt(u_strlen(ICAL_LASTMOD)) == COLON) { 1739 UnicodeString utcString; 1740 writer.write(ICAL_LASTMOD); 1741 writer.write(COLON); 1742 writer.write(getUTCDateTimeString(lastmod, utcString)); 1743 writer.write(ICAL_NEWLINE); 1744 } else { 1745 writer.write(*line); 1746 writer.write(ICAL_NEWLINE); 1747 } 1748 } 1749 } else { 1750 UVector *customProps = NULL; 1751 if (olsonzid.length() > 0 && icutzver.length() > 0) { 1752 customProps = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, status); 1753 if (U_FAILURE(status)) { 1754 return; 1755 } 1756 UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP); 1757 icutzprop->append(olsonzid); 1758 icutzprop->append((UChar)0x005B/*'['*/); 1759 icutzprop->append(icutzver); 1760 icutzprop->append((UChar)0x005D/*']'*/); 1761 customProps->addElement(icutzprop, status); 1762 if (U_FAILURE(status)) { 1763 delete icutzprop; 1764 delete customProps; 1765 return; 1766 } 1767 } 1768 writeZone(writer, *tz, customProps, status); 1769 delete customProps; 1770 } 1771 } 1772 1773 void 1774 VTimeZone::write(UDate start, VTZWriter& writer, UErrorCode& status) const { 1775 if (U_FAILURE(status)) { 1776 return; 1777 } 1778 InitialTimeZoneRule *initial = NULL; 1779 UVector *transitionRules = NULL; 1780 UVector customProps(uprv_deleteUObject, uhash_compareUnicodeString, status); 1781 UnicodeString tzid; 1782 1783 // Extract rules applicable to dates after the start time 1784 getTimeZoneRulesAfter(start, initial, transitionRules, status); 1785 if (U_FAILURE(status)) { 1786 return; 1787 } 1788 1789 // Create a RuleBasedTimeZone with the subset rule 1790 getID(tzid); 1791 RuleBasedTimeZone rbtz(tzid, initial); 1792 if (transitionRules != NULL) { 1793 while (!transitionRules->isEmpty()) { 1794 TimeZoneRule *tr = (TimeZoneRule*)transitionRules->orphanElementAt(0); 1795 rbtz.addTransitionRule(tr, status); 1796 if (U_FAILURE(status)) { 1797 goto cleanupWritePartial; 1798 } 1799 } 1800 delete transitionRules; 1801 transitionRules = NULL; 1802 } 1803 rbtz.complete(status); 1804 if (U_FAILURE(status)) { 1805 goto cleanupWritePartial; 1806 } 1807 1808 if (olsonzid.length() > 0 && icutzver.length() > 0) { 1809 UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP); 1810 icutzprop->append(olsonzid); 1811 icutzprop->append((UChar)0x005B/*'['*/); 1812 icutzprop->append(icutzver); 1813 icutzprop->append(ICU_TZINFO_PARTIAL, -1); 1814 appendMillis(start, *icutzprop); 1815 icutzprop->append((UChar)0x005D/*']'*/); 1816 customProps.addElement(icutzprop, status); 1817 if (U_FAILURE(status)) { 1818 delete icutzprop; 1819 goto cleanupWritePartial; 1820 } 1821 } 1822 writeZone(writer, rbtz, &customProps, status); 1823 return; 1824 1825 cleanupWritePartial: 1826 if (initial != NULL) { 1827 delete initial; 1828 } 1829 if (transitionRules != NULL) { 1830 while (!transitionRules->isEmpty()) { 1831 TimeZoneRule *tr = (TimeZoneRule*)transitionRules->orphanElementAt(0); 1832 delete tr; 1833 } 1834 delete transitionRules; 1835 } 1836 } 1837 1838 void 1839 VTimeZone::writeSimple(UDate time, VTZWriter& writer, UErrorCode& status) const { 1840 if (U_FAILURE(status)) { 1841 return; 1842 } 1843 1844 UVector customProps(uprv_deleteUObject, uhash_compareUnicodeString, status); 1845 UnicodeString tzid; 1846 1847 // Extract simple rules 1848 InitialTimeZoneRule *initial = NULL; 1849 AnnualTimeZoneRule *std = NULL, *dst = NULL; 1850 getSimpleRulesNear(time, initial, std, dst, status); 1851 if (U_SUCCESS(status)) { 1852 // Create a RuleBasedTimeZone with the subset rule 1853 getID(tzid); 1854 RuleBasedTimeZone rbtz(tzid, initial); 1855 if (std != NULL && dst != NULL) { 1856 rbtz.addTransitionRule(std, status); 1857 rbtz.addTransitionRule(dst, status); 1858 } 1859 if (U_FAILURE(status)) { 1860 goto cleanupWriteSimple; 1861 } 1862 1863 if (olsonzid.length() > 0 && icutzver.length() > 0) { 1864 UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP); 1865 icutzprop->append(olsonzid); 1866 icutzprop->append((UChar)0x005B/*'['*/); 1867 icutzprop->append(icutzver); 1868 icutzprop->append(ICU_TZINFO_SIMPLE, -1); 1869 appendMillis(time, *icutzprop); 1870 icutzprop->append((UChar)0x005D/*']'*/); 1871 customProps.addElement(icutzprop, status); 1872 if (U_FAILURE(status)) { 1873 delete icutzprop; 1874 goto cleanupWriteSimple; 1875 } 1876 } 1877 writeZone(writer, rbtz, &customProps, status); 1878 } 1879 return; 1880 1881 cleanupWriteSimple: 1882 if (initial != NULL) { 1883 delete initial; 1884 } 1885 if (std != NULL) { 1886 delete std; 1887 } 1888 if (dst != NULL) { 1889 delete dst; 1890 } 1891 } 1892 1893 void 1894 VTimeZone::writeZone(VTZWriter& w, BasicTimeZone& basictz, 1895 UVector* customProps, UErrorCode& status) const { 1896 if (U_FAILURE(status)) { 1897 return; 1898 } 1899 writeHeaders(w, status); 1900 if (U_FAILURE(status)) { 1901 return; 1902 } 1903 1904 if (customProps != NULL) { 1905 for (int32_t i = 0; i < customProps->size(); i++) { 1906 UnicodeString *custprop = (UnicodeString*)customProps->elementAt(i); 1907 w.write(*custprop); 1908 w.write(ICAL_NEWLINE); 1909 } 1910 } 1911 1912 UDate t = MIN_MILLIS; 1913 UnicodeString dstName; 1914 int32_t dstFromOffset = 0; 1915 int32_t dstFromDSTSavings = 0; 1916 int32_t dstToOffset = 0; 1917 int32_t dstStartYear = 0; 1918 int32_t dstMonth = 0; 1919 int32_t dstDayOfWeek = 0; 1920 int32_t dstWeekInMonth = 0; 1921 int32_t dstMillisInDay = 0; 1922 UDate dstStartTime = 0.0; 1923 UDate dstUntilTime = 0.0; 1924 int32_t dstCount = 0; 1925 AnnualTimeZoneRule *finalDstRule = NULL; 1926 1927 UnicodeString stdName; 1928 int32_t stdFromOffset = 0; 1929 int32_t stdFromDSTSavings = 0; 1930 int32_t stdToOffset = 0; 1931 int32_t stdStartYear = 0; 1932 int32_t stdMonth = 0; 1933 int32_t stdDayOfWeek = 0; 1934 int32_t stdWeekInMonth = 0; 1935 int32_t stdMillisInDay = 0; 1936 UDate stdStartTime = 0.0; 1937 UDate stdUntilTime = 0.0; 1938 int32_t stdCount = 0; 1939 AnnualTimeZoneRule *finalStdRule = NULL; 1940 1941 int32_t year, month, dom, dow, doy, mid; 1942 UBool hasTransitions = FALSE; 1943 TimeZoneTransition tzt; 1944 UBool tztAvail; 1945 UnicodeString name; 1946 UBool isDst; 1947 1948 // Going through all transitions 1949 while (TRUE) { 1950 tztAvail = basictz.getNextTransition(t, FALSE, tzt); 1951 if (!tztAvail) { 1952 break; 1953 } 1954 hasTransitions = TRUE; 1955 t = tzt.getTime(); 1956 tzt.getTo()->getName(name); 1957 isDst = (tzt.getTo()->getDSTSavings() != 0); 1958 int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings(); 1959 int32_t fromDSTSavings = tzt.getFrom()->getDSTSavings(); 1960 int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings(); 1961 Grego::timeToFields(tzt.getTime() + fromOffset, year, month, dom, dow, doy, mid); 1962 int32_t weekInMonth = Grego::dayOfWeekInMonth(year, month, dom); 1963 UBool sameRule = FALSE; 1964 const AnnualTimeZoneRule *atzrule; 1965 if (isDst) { 1966 if (finalDstRule == NULL 1967 && (atzrule = dynamic_cast<const AnnualTimeZoneRule *>(tzt.getTo())) != NULL 1968 && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR 1969 ) { 1970 finalDstRule = (AnnualTimeZoneRule*)tzt.getTo()->clone(); 1971 } 1972 if (dstCount > 0) { 1973 if (year == dstStartYear + dstCount 1974 && name.compare(dstName) == 0 1975 && dstFromOffset == fromOffset 1976 && dstToOffset == toOffset 1977 && dstMonth == month 1978 && dstDayOfWeek == dow 1979 && dstWeekInMonth == weekInMonth 1980 && dstMillisInDay == mid) { 1981 // Update until time 1982 dstUntilTime = t; 1983 dstCount++; 1984 sameRule = TRUE; 1985 } 1986 if (!sameRule) { 1987 if (dstCount == 1) { 1988 writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime, 1989 TRUE, status); 1990 } else { 1991 writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset, 1992 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status); 1993 } 1994 if (U_FAILURE(status)) { 1995 goto cleanupWriteZone; 1996 } 1997 } 1998 } 1999 if (!sameRule) { 2000 // Reset this DST information 2001 dstName = name; 2002 dstFromOffset = fromOffset; 2003 dstFromDSTSavings = fromDSTSavings; 2004 dstToOffset = toOffset; 2005 dstStartYear = year; 2006 dstMonth = month; 2007 dstDayOfWeek = dow; 2008 dstWeekInMonth = weekInMonth; 2009 dstMillisInDay = mid; 2010 dstStartTime = dstUntilTime = t; 2011 dstCount = 1; 2012 } 2013 if (finalStdRule != NULL && finalDstRule != NULL) { 2014 break; 2015 } 2016 } else { 2017 if (finalStdRule == NULL 2018 && (atzrule = dynamic_cast<const AnnualTimeZoneRule *>(tzt.getTo())) != NULL 2019 && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR 2020 ) { 2021 finalStdRule = (AnnualTimeZoneRule*)tzt.getTo()->clone(); 2022 } 2023 if (stdCount > 0) { 2024 if (year == stdStartYear + stdCount 2025 && name.compare(stdName) == 0 2026 && stdFromOffset == fromOffset 2027 && stdToOffset == toOffset 2028 && stdMonth == month 2029 && stdDayOfWeek == dow 2030 && stdWeekInMonth == weekInMonth 2031 && stdMillisInDay == mid) { 2032 // Update until time 2033 stdUntilTime = t; 2034 stdCount++; 2035 sameRule = TRUE; 2036 } 2037 if (!sameRule) { 2038 if (stdCount == 1) { 2039 writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime, 2040 TRUE, status); 2041 } else { 2042 writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset, 2043 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status); 2044 } 2045 if (U_FAILURE(status)) { 2046 goto cleanupWriteZone; 2047 } 2048 } 2049 } 2050 if (!sameRule) { 2051 // Reset this STD information 2052 stdName = name; 2053 stdFromOffset = fromOffset; 2054 stdFromDSTSavings = fromDSTSavings; 2055 stdToOffset = toOffset; 2056 stdStartYear = year; 2057 stdMonth = month; 2058 stdDayOfWeek = dow; 2059 stdWeekInMonth = weekInMonth; 2060 stdMillisInDay = mid; 2061 stdStartTime = stdUntilTime = t; 2062 stdCount = 1; 2063 } 2064 if (finalStdRule != NULL && finalDstRule != NULL) { 2065 break; 2066 } 2067 } 2068 } 2069 if (!hasTransitions) { 2070 // No transition - put a single non transition RDATE 2071 int32_t raw, dst, offset; 2072 basictz.getOffset(0.0/*any time*/, FALSE, raw, dst, status); 2073 if (U_FAILURE(status)) { 2074 goto cleanupWriteZone; 2075 } 2076 offset = raw + dst; 2077 isDst = (dst != 0); 2078 UnicodeString tzid; 2079 basictz.getID(tzid); 2080 getDefaultTZName(tzid, isDst, name); 2081 writeZonePropsByTime(w, isDst, name, 2082 offset, offset, DEF_TZSTARTTIME - offset, FALSE, status); 2083 if (U_FAILURE(status)) { 2084 goto cleanupWriteZone; 2085 } 2086 } else { 2087 if (dstCount > 0) { 2088 if (finalDstRule == NULL) { 2089 if (dstCount == 1) { 2090 writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime, 2091 TRUE, status); 2092 } else { 2093 writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset, 2094 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status); 2095 } 2096 if (U_FAILURE(status)) { 2097 goto cleanupWriteZone; 2098 } 2099 } else { 2100 if (dstCount == 1) { 2101 writeFinalRule(w, TRUE, finalDstRule, 2102 dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, dstStartTime, status); 2103 } else { 2104 // Use a single rule if possible 2105 if (isEquivalentDateRule(dstMonth, dstWeekInMonth, dstDayOfWeek, finalDstRule->getRule())) { 2106 writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset, 2107 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, MAX_MILLIS, status); 2108 } else { 2109 // Not equivalent rule - write out two different rules 2110 writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset, 2111 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status); 2112 if (U_FAILURE(status)) { 2113 goto cleanupWriteZone; 2114 } 2115 UDate nextStart; 2116 UBool nextStartAvail = finalDstRule->getNextStart(dstUntilTime, dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, false, nextStart); 2117 U_ASSERT(nextStartAvail); 2118 if (nextStartAvail) { 2119 writeFinalRule(w, TRUE, finalDstRule, 2120 dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, nextStart, status); 2121 } 2122 } 2123 } 2124 if (U_FAILURE(status)) { 2125 goto cleanupWriteZone; 2126 } 2127 } 2128 } 2129 if (stdCount > 0) { 2130 if (finalStdRule == NULL) { 2131 if (stdCount == 1) { 2132 writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime, 2133 TRUE, status); 2134 } else { 2135 writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset, 2136 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status); 2137 } 2138 if (U_FAILURE(status)) { 2139 goto cleanupWriteZone; 2140 } 2141 } else { 2142 if (stdCount == 1) { 2143 writeFinalRule(w, FALSE, finalStdRule, 2144 stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, stdStartTime, status); 2145 } else { 2146 // Use a single rule if possible 2147 if (isEquivalentDateRule(stdMonth, stdWeekInMonth, stdDayOfWeek, finalStdRule->getRule())) { 2148 writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset, 2149 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, MAX_MILLIS, status); 2150 } else { 2151 // Not equivalent rule - write out two different rules 2152 writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset, 2153 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status); 2154 if (U_FAILURE(status)) { 2155 goto cleanupWriteZone; 2156 } 2157 UDate nextStart; 2158 UBool nextStartAvail = finalStdRule->getNextStart(stdUntilTime, stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, false, nextStart); 2159 U_ASSERT(nextStartAvail); 2160 if (nextStartAvail) { 2161 writeFinalRule(w, FALSE, finalStdRule, 2162 stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, nextStart, status); 2163 } 2164 } 2165 } 2166 if (U_FAILURE(status)) { 2167 goto cleanupWriteZone; 2168 } 2169 } 2170 } 2171 } 2172 writeFooter(w, status); 2173 2174 cleanupWriteZone: 2175 2176 if (finalStdRule != NULL) { 2177 delete finalStdRule; 2178 } 2179 if (finalDstRule != NULL) { 2180 delete finalDstRule; 2181 } 2182 } 2183 2184 void 2185 VTimeZone::writeHeaders(VTZWriter& writer, UErrorCode& status) const { 2186 if (U_FAILURE(status)) { 2187 return; 2188 } 2189 UnicodeString tzid; 2190 tz->getID(tzid); 2191 2192 writer.write(ICAL_BEGIN); 2193 writer.write(COLON); 2194 writer.write(ICAL_VTIMEZONE); 2195 writer.write(ICAL_NEWLINE); 2196 writer.write(ICAL_TZID); 2197 writer.write(COLON); 2198 writer.write(tzid); 2199 writer.write(ICAL_NEWLINE); 2200 if (tzurl.length() != 0) { 2201 writer.write(ICAL_TZURL); 2202 writer.write(COLON); 2203 writer.write(tzurl); 2204 writer.write(ICAL_NEWLINE); 2205 } 2206 if (lastmod != MAX_MILLIS) { 2207 UnicodeString lastmodStr; 2208 writer.write(ICAL_LASTMOD); 2209 writer.write(COLON); 2210 writer.write(getUTCDateTimeString(lastmod, lastmodStr)); 2211 writer.write(ICAL_NEWLINE); 2212 } 2213 } 2214 2215 /* 2216 * Write the closing section of the VTIMEZONE definition block 2217 */ 2218 void 2219 VTimeZone::writeFooter(VTZWriter& writer, UErrorCode& status) const { 2220 if (U_FAILURE(status)) { 2221 return; 2222 } 2223 writer.write(ICAL_END); 2224 writer.write(COLON); 2225 writer.write(ICAL_VTIMEZONE); 2226 writer.write(ICAL_NEWLINE); 2227 } 2228 2229 /* 2230 * Write a single start time 2231 */ 2232 void 2233 VTimeZone::writeZonePropsByTime(VTZWriter& writer, UBool isDst, const UnicodeString& zonename, 2234 int32_t fromOffset, int32_t toOffset, UDate time, UBool withRDATE, 2235 UErrorCode& status) const { 2236 if (U_FAILURE(status)) { 2237 return; 2238 } 2239 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, time, status); 2240 if (U_FAILURE(status)) { 2241 return; 2242 } 2243 if (withRDATE) { 2244 writer.write(ICAL_RDATE); 2245 writer.write(COLON); 2246 UnicodeString timestr; 2247 writer.write(getDateTimeString(time + fromOffset, timestr)); 2248 writer.write(ICAL_NEWLINE); 2249 } 2250 endZoneProps(writer, isDst, status); 2251 if (U_FAILURE(status)) { 2252 return; 2253 } 2254 } 2255 2256 /* 2257 * Write start times defined by a DOM rule using VTIMEZONE RRULE 2258 */ 2259 void 2260 VTimeZone::writeZonePropsByDOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename, 2261 int32_t fromOffset, int32_t toOffset, 2262 int32_t month, int32_t dayOfMonth, UDate startTime, UDate untilTime, 2263 UErrorCode& status) const { 2264 if (U_FAILURE(status)) { 2265 return; 2266 } 2267 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status); 2268 if (U_FAILURE(status)) { 2269 return; 2270 } 2271 beginRRULE(writer, month, status); 2272 if (U_FAILURE(status)) { 2273 return; 2274 } 2275 writer.write(ICAL_BYMONTHDAY); 2276 writer.write(EQUALS_SIGN); 2277 UnicodeString dstr; 2278 appendAsciiDigits(dayOfMonth, 0, dstr); 2279 writer.write(dstr); 2280 if (untilTime != MAX_MILLIS) { 2281 appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status); 2282 if (U_FAILURE(status)) { 2283 return; 2284 } 2285 } 2286 writer.write(ICAL_NEWLINE); 2287 endZoneProps(writer, isDst, status); 2288 } 2289 2290 /* 2291 * Write start times defined by a DOW rule using VTIMEZONE RRULE 2292 */ 2293 void 2294 VTimeZone::writeZonePropsByDOW(VTZWriter& writer, UBool isDst, const UnicodeString& zonename, 2295 int32_t fromOffset, int32_t toOffset, 2296 int32_t month, int32_t weekInMonth, int32_t dayOfWeek, 2297 UDate startTime, UDate untilTime, UErrorCode& status) const { 2298 if (U_FAILURE(status)) { 2299 return; 2300 } 2301 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status); 2302 if (U_FAILURE(status)) { 2303 return; 2304 } 2305 beginRRULE(writer, month, status); 2306 if (U_FAILURE(status)) { 2307 return; 2308 } 2309 writer.write(ICAL_BYDAY); 2310 writer.write(EQUALS_SIGN); 2311 UnicodeString dstr; 2312 appendAsciiDigits(weekInMonth, 0, dstr); 2313 writer.write(dstr); // -4, -3, -2, -1, 1, 2, 3, 4 2314 writer.write(ICAL_DOW_NAMES[dayOfWeek - 1]); // SU, MO, TU... 2315 2316 if (untilTime != MAX_MILLIS) { 2317 appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status); 2318 if (U_FAILURE(status)) { 2319 return; 2320 } 2321 } 2322 writer.write(ICAL_NEWLINE); 2323 endZoneProps(writer, isDst, status); 2324 } 2325 2326 /* 2327 * Write start times defined by a DOW_GEQ_DOM rule using VTIMEZONE RRULE 2328 */ 2329 void 2330 VTimeZone::writeZonePropsByDOW_GEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename, 2331 int32_t fromOffset, int32_t toOffset, 2332 int32_t month, int32_t dayOfMonth, int32_t dayOfWeek, 2333 UDate startTime, UDate untilTime, UErrorCode& status) const { 2334 if (U_FAILURE(status)) { 2335 return; 2336 } 2337 // Check if this rule can be converted to DOW rule 2338 if (dayOfMonth%7 == 1) { 2339 // Can be represented by DOW rule 2340 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset, 2341 month, (dayOfMonth + 6)/7, dayOfWeek, startTime, untilTime, status); 2342 if (U_FAILURE(status)) { 2343 return; 2344 } 2345 } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 6) { 2346 // Can be represented by DOW rule with negative week number 2347 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset, 2348 month, -1*((MONTHLENGTH[month] - dayOfMonth + 1)/7), dayOfWeek, startTime, untilTime, status); 2349 if (U_FAILURE(status)) { 2350 return; 2351 } 2352 } else { 2353 // Otherwise, use BYMONTHDAY to include all possible dates 2354 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status); 2355 if (U_FAILURE(status)) { 2356 return; 2357 } 2358 // Check if all days are in the same month 2359 int32_t startDay = dayOfMonth; 2360 int32_t currentMonthDays = 7; 2361 2362 if (dayOfMonth <= 0) { 2363 // The start day is in previous month 2364 int32_t prevMonthDays = 1 - dayOfMonth; 2365 currentMonthDays -= prevMonthDays; 2366 2367 int32_t prevMonth = (month - 1) < 0 ? 11 : month - 1; 2368 2369 // Note: When a rule is separated into two, UNTIL attribute needs to be 2370 // calculated for each of them. For now, we skip this, because we basically use this method 2371 // only for final rules, which does not have the UNTIL attribute 2372 writeZonePropsByDOW_GEQ_DOM_sub(writer, prevMonth, -prevMonthDays, dayOfWeek, prevMonthDays, 2373 MAX_MILLIS /* Do not use UNTIL */, fromOffset, status); 2374 if (U_FAILURE(status)) { 2375 return; 2376 } 2377 2378 // Start from 1 for the rest 2379 startDay = 1; 2380 } else if (dayOfMonth + 6 > MONTHLENGTH[month]) { 2381 // Note: This code does not actually work well in February. For now, days in month in 2382 // non-leap year. 2383 int32_t nextMonthDays = dayOfMonth + 6 - MONTHLENGTH[month]; 2384 currentMonthDays -= nextMonthDays; 2385 2386 int32_t nextMonth = (month + 1) > 11 ? 0 : month + 1; 2387 2388 writeZonePropsByDOW_GEQ_DOM_sub(writer, nextMonth, 1, dayOfWeek, nextMonthDays, 2389 MAX_MILLIS /* Do not use UNTIL */, fromOffset, status); 2390 if (U_FAILURE(status)) { 2391 return; 2392 } 2393 } 2394 writeZonePropsByDOW_GEQ_DOM_sub(writer, month, startDay, dayOfWeek, currentMonthDays, 2395 untilTime, fromOffset, status); 2396 if (U_FAILURE(status)) { 2397 return; 2398 } 2399 endZoneProps(writer, isDst, status); 2400 } 2401 } 2402 2403 /* 2404 * Called from writeZonePropsByDOW_GEQ_DOM 2405 */ 2406 void 2407 VTimeZone::writeZonePropsByDOW_GEQ_DOM_sub(VTZWriter& writer, int32_t month, int32_t dayOfMonth, 2408 int32_t dayOfWeek, int32_t numDays, 2409 UDate untilTime, int32_t fromOffset, UErrorCode& status) const { 2410 2411 if (U_FAILURE(status)) { 2412 return; 2413 } 2414 int32_t startDayNum = dayOfMonth; 2415 UBool isFeb = (month == UCAL_FEBRUARY); 2416 if (dayOfMonth < 0 && !isFeb) { 2417 // Use positive number if possible 2418 startDayNum = MONTHLENGTH[month] + dayOfMonth + 1; 2419 } 2420 beginRRULE(writer, month, status); 2421 if (U_FAILURE(status)) { 2422 return; 2423 } 2424 writer.write(ICAL_BYDAY); 2425 writer.write(EQUALS_SIGN); 2426 writer.write(ICAL_DOW_NAMES[dayOfWeek - 1]); // SU, MO, TU... 2427 writer.write(SEMICOLON); 2428 writer.write(ICAL_BYMONTHDAY); 2429 writer.write(EQUALS_SIGN); 2430 2431 UnicodeString dstr; 2432 appendAsciiDigits(startDayNum, 0, dstr); 2433 writer.write(dstr); 2434 for (int32_t i = 1; i < numDays; i++) { 2435 writer.write(COMMA); 2436 dstr.remove(); 2437 appendAsciiDigits(startDayNum + i, 0, dstr); 2438 writer.write(dstr); 2439 } 2440 2441 if (untilTime != MAX_MILLIS) { 2442 appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status); 2443 if (U_FAILURE(status)) { 2444 return; 2445 } 2446 } 2447 writer.write(ICAL_NEWLINE); 2448 } 2449 2450 /* 2451 * Write start times defined by a DOW_LEQ_DOM rule using VTIMEZONE RRULE 2452 */ 2453 void 2454 VTimeZone::writeZonePropsByDOW_LEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename, 2455 int32_t fromOffset, int32_t toOffset, 2456 int32_t month, int32_t dayOfMonth, int32_t dayOfWeek, 2457 UDate startTime, UDate untilTime, UErrorCode& status) const { 2458 if (U_FAILURE(status)) { 2459 return; 2460 } 2461 // Check if this rule can be converted to DOW rule 2462 if (dayOfMonth%7 == 0) { 2463 // Can be represented by DOW rule 2464 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset, 2465 month, dayOfMonth/7, dayOfWeek, startTime, untilTime, status); 2466 } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 0){ 2467 // Can be represented by DOW rule with negative week number 2468 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset, 2469 month, -1*((MONTHLENGTH[month] - dayOfMonth)/7 + 1), dayOfWeek, startTime, untilTime, status); 2470 } else if (month == UCAL_FEBRUARY && dayOfMonth == 29) { 2471 // Specical case for February 2472 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset, 2473 UCAL_FEBRUARY, -1, dayOfWeek, startTime, untilTime, status); 2474 } else { 2475 // Otherwise, convert this to DOW_GEQ_DOM rule 2476 writeZonePropsByDOW_GEQ_DOM(writer, isDst, zonename, fromOffset, toOffset, 2477 month, dayOfMonth - 6, dayOfWeek, startTime, untilTime, status); 2478 } 2479 } 2480 2481 /* 2482 * Write the final time zone rule using RRULE, with no UNTIL attribute 2483 */ 2484 void 2485 VTimeZone::writeFinalRule(VTZWriter& writer, UBool isDst, const AnnualTimeZoneRule* rule, 2486 int32_t fromRawOffset, int32_t fromDSTSavings, 2487 UDate startTime, UErrorCode& status) const { 2488 if (U_FAILURE(status)) { 2489 return; 2490 } 2491 UBool modifiedRule = TRUE; 2492 const DateTimeRule *dtrule = toWallTimeRule(rule->getRule(), fromRawOffset, fromDSTSavings); 2493 if (dtrule == NULL) { 2494 modifiedRule = FALSE; 2495 dtrule = rule->getRule(); 2496 } 2497 2498 // If the rule's mills in a day is out of range, adjust start time. 2499 // Olson tzdata supports 24:00 of a day, but VTIMEZONE does not. 2500 // See ticket#7008/#7518 2501 2502 int32_t timeInDay = dtrule->getRuleMillisInDay(); 2503 if (timeInDay < 0) { 2504 startTime = startTime + (0 - timeInDay); 2505 } else if (timeInDay >= U_MILLIS_PER_DAY) { 2506 startTime = startTime - (timeInDay - (U_MILLIS_PER_DAY - 1)); 2507 } 2508 2509 int32_t toOffset = rule->getRawOffset() + rule->getDSTSavings(); 2510 UnicodeString name; 2511 rule->getName(name); 2512 switch (dtrule->getDateRuleType()) { 2513 case DateTimeRule::DOM: 2514 writeZonePropsByDOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset, 2515 dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), startTime, MAX_MILLIS, status); 2516 break; 2517 case DateTimeRule::DOW: 2518 writeZonePropsByDOW(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset, 2519 dtrule->getRuleMonth(), dtrule->getRuleWeekInMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status); 2520 break; 2521 case DateTimeRule::DOW_GEQ_DOM: 2522 writeZonePropsByDOW_GEQ_DOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset, 2523 dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status); 2524 break; 2525 case DateTimeRule::DOW_LEQ_DOM: 2526 writeZonePropsByDOW_LEQ_DOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset, 2527 dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status); 2528 break; 2529 } 2530 if (modifiedRule) { 2531 delete dtrule; 2532 } 2533 } 2534 2535 /* 2536 * Write the opening section of zone properties 2537 */ 2538 void 2539 VTimeZone::beginZoneProps(VTZWriter& writer, UBool isDst, const UnicodeString& zonename, 2540 int32_t fromOffset, int32_t toOffset, UDate startTime, UErrorCode& status) const { 2541 if (U_FAILURE(status)) { 2542 return; 2543 } 2544 writer.write(ICAL_BEGIN); 2545 writer.write(COLON); 2546 if (isDst) { 2547 writer.write(ICAL_DAYLIGHT); 2548 } else { 2549 writer.write(ICAL_STANDARD); 2550 } 2551 writer.write(ICAL_NEWLINE); 2552 2553 UnicodeString dstr; 2554 2555 // TZOFFSETTO 2556 writer.write(ICAL_TZOFFSETTO); 2557 writer.write(COLON); 2558 millisToOffset(toOffset, dstr); 2559 writer.write(dstr); 2560 writer.write(ICAL_NEWLINE); 2561 2562 // TZOFFSETFROM 2563 writer.write(ICAL_TZOFFSETFROM); 2564 writer.write(COLON); 2565 millisToOffset(fromOffset, dstr); 2566 writer.write(dstr); 2567 writer.write(ICAL_NEWLINE); 2568 2569 // TZNAME 2570 writer.write(ICAL_TZNAME); 2571 writer.write(COLON); 2572 writer.write(zonename); 2573 writer.write(ICAL_NEWLINE); 2574 2575 // DTSTART 2576 writer.write(ICAL_DTSTART); 2577 writer.write(COLON); 2578 writer.write(getDateTimeString(startTime + fromOffset, dstr)); 2579 writer.write(ICAL_NEWLINE); 2580 } 2581 2582 /* 2583 * Writes the closing section of zone properties 2584 */ 2585 void 2586 VTimeZone::endZoneProps(VTZWriter& writer, UBool isDst, UErrorCode& status) const { 2587 if (U_FAILURE(status)) { 2588 return; 2589 } 2590 // END:STANDARD or END:DAYLIGHT 2591 writer.write(ICAL_END); 2592 writer.write(COLON); 2593 if (isDst) { 2594 writer.write(ICAL_DAYLIGHT); 2595 } else { 2596 writer.write(ICAL_STANDARD); 2597 } 2598 writer.write(ICAL_NEWLINE); 2599 } 2600 2601 /* 2602 * Write the beggining part of RRULE line 2603 */ 2604 void 2605 VTimeZone::beginRRULE(VTZWriter& writer, int32_t month, UErrorCode& status) const { 2606 if (U_FAILURE(status)) { 2607 return; 2608 } 2609 UnicodeString dstr; 2610 writer.write(ICAL_RRULE); 2611 writer.write(COLON); 2612 writer.write(ICAL_FREQ); 2613 writer.write(EQUALS_SIGN); 2614 writer.write(ICAL_YEARLY); 2615 writer.write(SEMICOLON); 2616 writer.write(ICAL_BYMONTH); 2617 writer.write(EQUALS_SIGN); 2618 appendAsciiDigits(month + 1, 0, dstr); 2619 writer.write(dstr); 2620 writer.write(SEMICOLON); 2621 } 2622 2623 /* 2624 * Append the UNTIL attribute after RRULE line 2625 */ 2626 void 2627 VTimeZone::appendUNTIL(VTZWriter& writer, const UnicodeString& until, UErrorCode& status) const { 2628 if (U_FAILURE(status)) { 2629 return; 2630 } 2631 if (until.length() > 0) { 2632 writer.write(SEMICOLON); 2633 writer.write(ICAL_UNTIL); 2634 writer.write(EQUALS_SIGN); 2635 writer.write(until); 2636 } 2637 } 2638 2639 U_NAMESPACE_END 2640 2641 #endif /* #if !UCONFIG_NO_FORMATTING */ 2642 2643 //eof 2644