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