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