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