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 1039 if (typeid(*this) != typeid(that) || !BasicTimeZone::operator==(that)) { 1040 return FALSE; 1041 } 1042 VTimeZone *vtz = (VTimeZone*)&that; 1043 if (*tz == *(vtz->tz) 1044 && tzurl == vtz->tzurl 1045 && lastmod == vtz->lastmod 1046 /* && olsonzid = that.olsonzid */ 1047 /* && icutzver = that.icutzver */) { 1048 return TRUE; 1049 } 1050 return FALSE; 1051 } 1052 1053 UBool 1054 VTimeZone::operator!=(const TimeZone& that) const { 1055 return !operator==(that); 1056 } 1057 1058 VTimeZone* 1059 VTimeZone::createVTimeZoneByID(const UnicodeString& ID) { 1060 VTimeZone *vtz = new VTimeZone(); 1061 vtz->tz = (BasicTimeZone*)TimeZone::createTimeZone(ID); 1062 vtz->tz->getID(vtz->olsonzid); 1063 1064 // Set ICU tzdata version 1065 UErrorCode status = U_ZERO_ERROR; 1066 UResourceBundle *bundle = NULL; 1067 const UChar* versionStr = NULL; 1068 int32_t len = 0; 1069 bundle = ures_openDirect(NULL, "zoneinfo64", &status); 1070 versionStr = ures_getStringByKey(bundle, "TZVersion", &len, &status); 1071 if (U_SUCCESS(status)) { 1072 vtz->icutzver.setTo(versionStr, len); 1073 } 1074 ures_close(bundle); 1075 return vtz; 1076 } 1077 1078 VTimeZone* 1079 VTimeZone::createVTimeZoneFromBasicTimeZone(const BasicTimeZone& basic_time_zone, UErrorCode &status) { 1080 if (U_FAILURE(status)) { 1081 return NULL; 1082 } 1083 VTimeZone *vtz = new VTimeZone(); 1084 if (vtz == NULL) { 1085 status = U_MEMORY_ALLOCATION_ERROR; 1086 return NULL; 1087 } 1088 vtz->tz = (BasicTimeZone *)basic_time_zone.clone(); 1089 if (vtz->tz == NULL) { 1090 status = U_MEMORY_ALLOCATION_ERROR; 1091 delete vtz; 1092 return NULL; 1093 } 1094 vtz->tz->getID(vtz->olsonzid); 1095 1096 // Set ICU tzdata version 1097 UResourceBundle *bundle = NULL; 1098 const UChar* versionStr = NULL; 1099 int32_t len = 0; 1100 bundle = ures_openDirect(NULL, "zoneinfo64", &status); 1101 versionStr = ures_getStringByKey(bundle, "TZVersion", &len, &status); 1102 if (U_SUCCESS(status)) { 1103 vtz->icutzver.setTo(versionStr, len); 1104 } 1105 ures_close(bundle); 1106 return vtz; 1107 } 1108 1109 VTimeZone* 1110 VTimeZone::createVTimeZone(const UnicodeString& vtzdata, UErrorCode& status) { 1111 if (U_FAILURE(status)) { 1112 return NULL; 1113 } 1114 VTZReader reader(vtzdata); 1115 VTimeZone *vtz = new VTimeZone(); 1116 vtz->load(reader, status); 1117 if (U_FAILURE(status)) { 1118 delete vtz; 1119 return NULL; 1120 } 1121 return vtz; 1122 } 1123 1124 UBool 1125 VTimeZone::getTZURL(UnicodeString& url) const { 1126 if (tzurl.length() > 0) { 1127 url = tzurl; 1128 return TRUE; 1129 } 1130 return FALSE; 1131 } 1132 1133 void 1134 VTimeZone::setTZURL(const UnicodeString& url) { 1135 tzurl = url; 1136 } 1137 1138 UBool 1139 VTimeZone::getLastModified(UDate& lastModified) const { 1140 if (lastmod != MAX_MILLIS) { 1141 lastModified = lastmod; 1142 return TRUE; 1143 } 1144 return FALSE; 1145 } 1146 1147 void 1148 VTimeZone::setLastModified(UDate lastModified) { 1149 lastmod = lastModified; 1150 } 1151 1152 void 1153 VTimeZone::write(UnicodeString& result, UErrorCode& status) const { 1154 result.remove(); 1155 VTZWriter writer(result); 1156 write(writer, status); 1157 } 1158 1159 void 1160 VTimeZone::write(UDate start, UnicodeString& result, UErrorCode& status) /*const*/ { 1161 result.remove(); 1162 VTZWriter writer(result); 1163 write(start, writer, status); 1164 } 1165 1166 void 1167 VTimeZone::writeSimple(UDate time, UnicodeString& result, UErrorCode& status) /*const*/ { 1168 result.remove(); 1169 VTZWriter writer(result); 1170 writeSimple(time, writer, status); 1171 } 1172 1173 TimeZone* 1174 VTimeZone::clone(void) const { 1175 return new VTimeZone(*this); 1176 } 1177 1178 int32_t 1179 VTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day, 1180 uint8_t dayOfWeek, int32_t millis, UErrorCode& status) const { 1181 return tz->getOffset(era, year, month, day, dayOfWeek, millis, status); 1182 } 1183 1184 int32_t 1185 VTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day, 1186 uint8_t dayOfWeek, int32_t millis, 1187 int32_t monthLength, UErrorCode& status) const { 1188 return tz->getOffset(era, year, month, day, dayOfWeek, millis, monthLength, status); 1189 } 1190 1191 void 1192 VTimeZone::getOffset(UDate date, UBool local, int32_t& rawOffset, 1193 int32_t& dstOffset, UErrorCode& status) const { 1194 return tz->getOffset(date, local, rawOffset, dstOffset, status); 1195 } 1196 1197 void 1198 VTimeZone::setRawOffset(int32_t offsetMillis) { 1199 tz->setRawOffset(offsetMillis); 1200 } 1201 1202 int32_t 1203 VTimeZone::getRawOffset(void) const { 1204 return tz->getRawOffset(); 1205 } 1206 1207 UBool 1208 VTimeZone::useDaylightTime(void) const { 1209 return tz->useDaylightTime(); 1210 } 1211 1212 UBool 1213 VTimeZone::inDaylightTime(UDate date, UErrorCode& status) const { 1214 return tz->inDaylightTime(date, status); 1215 } 1216 1217 UBool 1218 VTimeZone::hasSameRules(const TimeZone& other) const { 1219 return tz->hasSameRules(other); 1220 } 1221 1222 UBool 1223 VTimeZone::getNextTransition(UDate base, UBool inclusive, TimeZoneTransition& result) /*const*/ { 1224 return tz->getNextTransition(base, inclusive, result); 1225 } 1226 1227 UBool 1228 VTimeZone::getPreviousTransition(UDate base, UBool inclusive, TimeZoneTransition& result) /*const*/ { 1229 return tz->getPreviousTransition(base, inclusive, result); 1230 } 1231 1232 int32_t 1233 VTimeZone::countTransitionRules(UErrorCode& status) /*const*/ { 1234 return tz->countTransitionRules(status); 1235 } 1236 1237 void 1238 VTimeZone::getTimeZoneRules(const InitialTimeZoneRule*& initial, 1239 const TimeZoneRule* trsrules[], int32_t& trscount, 1240 UErrorCode& status) /*const*/ { 1241 tz->getTimeZoneRules(initial, trsrules, trscount, status); 1242 } 1243 1244 void 1245 VTimeZone::load(VTZReader& reader, UErrorCode& status) { 1246 vtzlines = new UVector(uhash_deleteUnicodeString, uhash_compareUnicodeString, DEFAULT_VTIMEZONE_LINES, status); 1247 if (U_FAILURE(status)) { 1248 return; 1249 } 1250 UBool eol = FALSE; 1251 UBool start = FALSE; 1252 UBool success = FALSE; 1253 UnicodeString line; 1254 1255 while (TRUE) { 1256 UChar ch = reader.read(); 1257 if (ch == 0xFFFF) { 1258 // end of file 1259 if (start && line.startsWith(ICAL_END_VTIMEZONE)) { 1260 vtzlines->addElement(new UnicodeString(line), status); 1261 if (U_FAILURE(status)) { 1262 goto cleanupVtzlines; 1263 } 1264 success = TRUE; 1265 } 1266 break; 1267 } 1268 if (ch == 0x000D) { 1269 // CR, must be followed by LF according to the definition in RFC2445 1270 continue; 1271 } 1272 if (eol) { 1273 if (ch != 0x0009 && ch != 0x0020) { 1274 // NOT followed by TAB/SP -> new line 1275 if (start) { 1276 if (line.length() > 0) { 1277 vtzlines->addElement(new UnicodeString(line), status); 1278 if (U_FAILURE(status)) { 1279 goto cleanupVtzlines; 1280 } 1281 } 1282 } 1283 line.remove(); 1284 if (ch != 0x000A) { 1285 line.append(ch); 1286 } 1287 } 1288 eol = FALSE; 1289 } else { 1290 if (ch == 0x000A) { 1291 // LF 1292 eol = TRUE; 1293 if (start) { 1294 if (line.startsWith(ICAL_END_VTIMEZONE)) { 1295 vtzlines->addElement(new UnicodeString(line), status); 1296 if (U_FAILURE(status)) { 1297 goto cleanupVtzlines; 1298 } 1299 success = TRUE; 1300 break; 1301 } 1302 } else { 1303 if (line.startsWith(ICAL_BEGIN_VTIMEZONE)) { 1304 vtzlines->addElement(new UnicodeString(line), status); 1305 if (U_FAILURE(status)) { 1306 goto cleanupVtzlines; 1307 } 1308 line.remove(); 1309 start = TRUE; 1310 eol = FALSE; 1311 } 1312 } 1313 } else { 1314 line.append(ch); 1315 } 1316 } 1317 } 1318 if (!success) { 1319 if (U_SUCCESS(status)) { 1320 status = U_INVALID_STATE_ERROR; 1321 } 1322 goto cleanupVtzlines; 1323 } 1324 parse(status); 1325 return; 1326 1327 cleanupVtzlines: 1328 delete vtzlines; 1329 vtzlines = NULL; 1330 } 1331 1332 // parser state 1333 #define INI 0 // Initial state 1334 #define VTZ 1 // In VTIMEZONE 1335 #define TZI 2 // In STANDARD or DAYLIGHT 1336 1337 #define DEF_DSTSAVINGS (60*60*1000) 1338 #define DEF_TZSTARTTIME (0.0) 1339 1340 void 1341 VTimeZone::parse(UErrorCode& status) { 1342 if (U_FAILURE(status)) { 1343 return; 1344 } 1345 if (vtzlines == NULL || vtzlines->size() == 0) { 1346 status = U_INVALID_STATE_ERROR; 1347 return; 1348 } 1349 InitialTimeZoneRule *initialRule = NULL; 1350 RuleBasedTimeZone *rbtz = NULL; 1351 1352 // timezone ID 1353 UnicodeString tzid; 1354 1355 int32_t state = INI; 1356 int32_t n = 0; 1357 UBool dst = FALSE; // current zone type 1358 UnicodeString from; // current zone from offset 1359 UnicodeString to; // current zone offset 1360 UnicodeString zonename; // current zone name 1361 UnicodeString dtstart; // current zone starts 1362 UBool isRRULE = FALSE; // true if the rule is described by RRULE 1363 int32_t initialRawOffset = 0; // initial offset 1364 int32_t initialDSTSavings = 0; // initial offset 1365 UDate firstStart = MAX_MILLIS; // the earliest rule start time 1366 UnicodeString name; // RFC2445 prop name 1367 UnicodeString value; // RFC2445 prop value 1368 1369 UVector *dates = NULL; // list of RDATE or RRULE strings 1370 UVector *rules = NULL; // list of TimeZoneRule instances 1371 1372 int32_t finalRuleIdx = -1; 1373 int32_t finalRuleCount = 0; 1374 1375 rules = new UVector(status); 1376 if (U_FAILURE(status)) { 1377 goto cleanupParse; 1378 } 1379 // Set the deleter to remove TimeZoneRule vectors to avoid memory leaks due to unowned TimeZoneRules. 1380 rules->setDeleter(deleteTimeZoneRule); 1381 1382 dates = new UVector(uhash_deleteUnicodeString, uhash_compareUnicodeString, status); 1383 if (U_FAILURE(status)) { 1384 goto cleanupParse; 1385 } 1386 if (rules == NULL || dates == NULL) { 1387 status = U_MEMORY_ALLOCATION_ERROR; 1388 goto cleanupParse; 1389 } 1390 1391 for (n = 0; n < vtzlines->size(); n++) { 1392 UnicodeString *line = (UnicodeString*)vtzlines->elementAt(n); 1393 int32_t valueSep = line->indexOf(COLON); 1394 if (valueSep < 0) { 1395 continue; 1396 } 1397 name.setTo(*line, 0, valueSep); 1398 value.setTo(*line, valueSep + 1); 1399 1400 switch (state) { 1401 case INI: 1402 if (name.compare(ICAL_BEGIN) == 0 1403 && value.compare(ICAL_VTIMEZONE) == 0) { 1404 state = VTZ; 1405 } 1406 break; 1407 1408 case VTZ: 1409 if (name.compare(ICAL_TZID) == 0) { 1410 tzid = value; 1411 } else if (name.compare(ICAL_TZURL) == 0) { 1412 tzurl = value; 1413 } else if (name.compare(ICAL_LASTMOD) == 0) { 1414 // Always in 'Z' format, so the offset argument for the parse method 1415 // can be any value. 1416 lastmod = parseDateTimeString(value, 0, status); 1417 if (U_FAILURE(status)) { 1418 goto cleanupParse; 1419 } 1420 } else if (name.compare(ICAL_BEGIN) == 0) { 1421 UBool isDST = (value.compare(ICAL_DAYLIGHT) == 0); 1422 if (value.compare(ICAL_STANDARD) == 0 || isDST) { 1423 // tzid must be ready at this point 1424 if (tzid.length() == 0) { 1425 goto cleanupParse; 1426 } 1427 // initialize current zone properties 1428 if (dates->size() != 0) { 1429 dates->removeAllElements(); 1430 } 1431 isRRULE = FALSE; 1432 from.remove(); 1433 to.remove(); 1434 zonename.remove(); 1435 dst = isDST; 1436 state = TZI; 1437 } else { 1438 // BEGIN property other than STANDARD/DAYLIGHT 1439 // must not be there. 1440 goto cleanupParse; 1441 } 1442 } else if (name.compare(ICAL_END) == 0) { 1443 break; 1444 } 1445 break; 1446 case TZI: 1447 if (name.compare(ICAL_DTSTART) == 0) { 1448 dtstart = value; 1449 } else if (name.compare(ICAL_TZNAME) == 0) { 1450 zonename = value; 1451 } else if (name.compare(ICAL_TZOFFSETFROM) == 0) { 1452 from = value; 1453 } else if (name.compare(ICAL_TZOFFSETTO) == 0) { 1454 to = value; 1455 } else if (name.compare(ICAL_RDATE) == 0) { 1456 // RDATE mixed with RRULE is not supported 1457 if (isRRULE) { 1458 goto cleanupParse; 1459 } 1460 // RDATE value may contain multiple date delimited 1461 // by comma 1462 UBool nextDate = TRUE; 1463 int32_t dstart = 0; 1464 UnicodeString *dstr; 1465 while (nextDate) { 1466 int32_t dend = value.indexOf(COMMA, dstart); 1467 if (dend == -1) { 1468 dstr = new UnicodeString(value, dstart); 1469 nextDate = FALSE; 1470 } else { 1471 dstr = new UnicodeString(value, dstart, dend - dstart); 1472 } 1473 dates->addElement(dstr, status); 1474 if (U_FAILURE(status)) { 1475 goto cleanupParse; 1476 } 1477 dstart = dend + 1; 1478 } 1479 } else if (name.compare(ICAL_RRULE) == 0) { 1480 // RRULE mixed with RDATE is not supported 1481 if (!isRRULE && dates->size() != 0) { 1482 goto cleanupParse; 1483 } 1484 isRRULE = true; 1485 dates->addElement(new UnicodeString(value), status); 1486 if (U_FAILURE(status)) { 1487 goto cleanupParse; 1488 } 1489 } else if (name.compare(ICAL_END) == 0) { 1490 // Mandatory properties 1491 if (dtstart.length() == 0 || from.length() == 0 || to.length() == 0) { 1492 goto cleanupParse; 1493 } 1494 // if zonename is not available, create one from tzid 1495 if (zonename.length() == 0) { 1496 getDefaultTZName(tzid, dst, zonename); 1497 } 1498 1499 // create a time zone rule 1500 TimeZoneRule *rule = NULL; 1501 int32_t fromOffset = 0; 1502 int32_t toOffset = 0; 1503 int32_t rawOffset = 0; 1504 int32_t dstSavings = 0; 1505 UDate start = 0; 1506 1507 // Parse TZOFFSETFROM/TZOFFSETTO 1508 fromOffset = offsetStrToMillis(from, status); 1509 toOffset = offsetStrToMillis(to, status); 1510 if (U_FAILURE(status)) { 1511 goto cleanupParse; 1512 } 1513 1514 if (dst) { 1515 // If daylight, use the previous offset as rawoffset if positive 1516 if (toOffset - fromOffset > 0) { 1517 rawOffset = fromOffset; 1518 dstSavings = toOffset - fromOffset; 1519 } else { 1520 // This is rare case.. just use 1 hour DST savings 1521 rawOffset = toOffset - DEF_DSTSAVINGS; 1522 dstSavings = DEF_DSTSAVINGS; 1523 } 1524 } else { 1525 rawOffset = toOffset; 1526 dstSavings = 0; 1527 } 1528 1529 // start time 1530 start = parseDateTimeString(dtstart, fromOffset, status); 1531 if (U_FAILURE(status)) { 1532 goto cleanupParse; 1533 } 1534 1535 // Create the rule 1536 UDate actualStart = MAX_MILLIS; 1537 if (isRRULE) { 1538 rule = createRuleByRRULE(zonename, rawOffset, dstSavings, start, dates, fromOffset, status); 1539 } else { 1540 rule = createRuleByRDATE(zonename, rawOffset, dstSavings, start, dates, fromOffset, status); 1541 } 1542 if (U_FAILURE(status) || rule == NULL) { 1543 goto cleanupParse; 1544 } else { 1545 UBool startAvail = rule->getFirstStart(fromOffset, 0, actualStart); 1546 if (startAvail && actualStart < firstStart) { 1547 // save from offset information for the earliest rule 1548 firstStart = actualStart; 1549 // If this is STD, assume the time before this transtion 1550 // is DST when the difference is 1 hour. This might not be 1551 // accurate, but VTIMEZONE data does not have such info. 1552 if (dstSavings > 0) { 1553 initialRawOffset = fromOffset; 1554 initialDSTSavings = 0; 1555 } else { 1556 if (fromOffset - toOffset == DEF_DSTSAVINGS) { 1557 initialRawOffset = fromOffset - DEF_DSTSAVINGS; 1558 initialDSTSavings = DEF_DSTSAVINGS; 1559 } else { 1560 initialRawOffset = fromOffset; 1561 initialDSTSavings = 0; 1562 } 1563 } 1564 } 1565 } 1566 rules->addElement(rule, status); 1567 if (U_FAILURE(status)) { 1568 goto cleanupParse; 1569 } 1570 state = VTZ; 1571 } 1572 break; 1573 } 1574 } 1575 // Must have at least one rule 1576 if (rules->size() == 0) { 1577 goto cleanupParse; 1578 } 1579 1580 // Create a initial rule 1581 getDefaultTZName(tzid, FALSE, zonename); 1582 initialRule = new InitialTimeZoneRule(zonename, 1583 initialRawOffset, initialDSTSavings); 1584 if (initialRule == NULL) { 1585 status = U_MEMORY_ALLOCATION_ERROR; 1586 goto cleanupParse; 1587 } 1588 1589 // Finally, create the RuleBasedTimeZone 1590 rbtz = new RuleBasedTimeZone(tzid, initialRule); 1591 if (rbtz == NULL) { 1592 status = U_MEMORY_ALLOCATION_ERROR; 1593 goto cleanupParse; 1594 } 1595 initialRule = NULL; // already adopted by RBTZ, no need to delete 1596 1597 for (n = 0; n < rules->size(); n++) { 1598 TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n); 1599 AnnualTimeZoneRule *atzrule = dynamic_cast<AnnualTimeZoneRule *>(r); 1600 if (atzrule != NULL) { 1601 if (atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR) { 1602 finalRuleCount++; 1603 finalRuleIdx = n; 1604 } 1605 } 1606 } 1607 if (finalRuleCount > 2) { 1608 // Too many final rules 1609 status = U_ILLEGAL_ARGUMENT_ERROR; 1610 goto cleanupParse; 1611 } 1612 1613 if (finalRuleCount == 1) { 1614 if (rules->size() == 1) { 1615 // Only one final rule, only governs the initial rule, 1616 // which is already initialized, thus, we do not need to 1617 // add this transition rule 1618 rules->removeAllElements(); 1619 } else { 1620 // Normalize the final rule 1621 AnnualTimeZoneRule *finalRule = (AnnualTimeZoneRule*)rules->elementAt(finalRuleIdx); 1622 int32_t tmpRaw = finalRule->getRawOffset(); 1623 int32_t tmpDST = finalRule->getDSTSavings(); 1624 1625 // Find the last non-final rule 1626 UDate finalStart, start; 1627 finalRule->getFirstStart(initialRawOffset, initialDSTSavings, finalStart); 1628 start = finalStart; 1629 for (n = 0; n < rules->size(); n++) { 1630 if (finalRuleIdx == n) { 1631 continue; 1632 } 1633 TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n); 1634 UDate lastStart; 1635 r->getFinalStart(tmpRaw, tmpDST, lastStart); 1636 if (lastStart > start) { 1637 finalRule->getNextStart(lastStart, 1638 r->getRawOffset(), 1639 r->getDSTSavings(), 1640 FALSE, 1641 start); 1642 } 1643 } 1644 1645 TimeZoneRule *newRule; 1646 UnicodeString tznam; 1647 if (start == finalStart) { 1648 // Transform this into a single transition 1649 newRule = new TimeArrayTimeZoneRule( 1650 finalRule->getName(tznam), 1651 finalRule->getRawOffset(), 1652 finalRule->getDSTSavings(), 1653 &finalStart, 1654 1, 1655 DateTimeRule::UTC_TIME); 1656 } else { 1657 // Update the end year 1658 int32_t y, m, d, dow, doy, mid; 1659 Grego::timeToFields(start, y, m, d, dow, doy, mid); 1660 newRule = new AnnualTimeZoneRule( 1661 finalRule->getName(tznam), 1662 finalRule->getRawOffset(), 1663 finalRule->getDSTSavings(), 1664 *(finalRule->getRule()), 1665 finalRule->getStartYear(), 1666 y); 1667 } 1668 if (newRule == NULL) { 1669 status = U_MEMORY_ALLOCATION_ERROR; 1670 goto cleanupParse; 1671 } 1672 rules->removeElementAt(finalRuleIdx); 1673 rules->addElement(newRule, status); 1674 if (U_FAILURE(status)) { 1675 delete newRule; 1676 goto cleanupParse; 1677 } 1678 } 1679 } 1680 1681 while (!rules->isEmpty()) { 1682 TimeZoneRule *tzr = (TimeZoneRule*)rules->orphanElementAt(0); 1683 rbtz->addTransitionRule(tzr, status); 1684 if (U_FAILURE(status)) { 1685 goto cleanupParse; 1686 } 1687 } 1688 rbtz->complete(status); 1689 if (U_FAILURE(status)) { 1690 goto cleanupParse; 1691 } 1692 delete rules; 1693 delete dates; 1694 1695 tz = rbtz; 1696 setID(tzid); 1697 return; 1698 1699 cleanupParse: 1700 if (rules != NULL) { 1701 while (!rules->isEmpty()) { 1702 TimeZoneRule *r = (TimeZoneRule*)rules->orphanElementAt(0); 1703 delete r; 1704 } 1705 delete rules; 1706 } 1707 if (dates != NULL) { 1708 delete dates; 1709 } 1710 if (initialRule != NULL) { 1711 delete initialRule; 1712 } 1713 if (rbtz != NULL) { 1714 delete rbtz; 1715 } 1716 return; 1717 } 1718 1719 void 1720 VTimeZone::write(VTZWriter& writer, UErrorCode& status) const { 1721 if (vtzlines != NULL) { 1722 for (int32_t i = 0; i < vtzlines->size(); i++) { 1723 UnicodeString *line = (UnicodeString*)vtzlines->elementAt(i); 1724 if (line->startsWith(ICAL_TZURL) 1725 && line->charAt(u_strlen(ICAL_TZURL)) == COLON) { 1726 writer.write(ICAL_TZURL); 1727 writer.write(COLON); 1728 writer.write(tzurl); 1729 writer.write(ICAL_NEWLINE); 1730 } else if (line->startsWith(ICAL_LASTMOD) 1731 && line->charAt(u_strlen(ICAL_LASTMOD)) == COLON) { 1732 UnicodeString utcString; 1733 writer.write(ICAL_LASTMOD); 1734 writer.write(COLON); 1735 writer.write(getUTCDateTimeString(lastmod, utcString)); 1736 writer.write(ICAL_NEWLINE); 1737 } else { 1738 writer.write(*line); 1739 writer.write(ICAL_NEWLINE); 1740 } 1741 } 1742 } else { 1743 UVector *customProps = NULL; 1744 if (olsonzid.length() > 0 && icutzver.length() > 0) { 1745 customProps = new UVector(uhash_deleteUnicodeString, uhash_compareUnicodeString, status); 1746 if (U_FAILURE(status)) { 1747 return; 1748 } 1749 UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP); 1750 icutzprop->append(olsonzid); 1751 icutzprop->append((UChar)0x005B/*'['*/); 1752 icutzprop->append(icutzver); 1753 icutzprop->append((UChar)0x005D/*']'*/); 1754 customProps->addElement(icutzprop, status); 1755 if (U_FAILURE(status)) { 1756 delete icutzprop; 1757 delete customProps; 1758 return; 1759 } 1760 } 1761 writeZone(writer, *tz, customProps, status); 1762 delete customProps; 1763 } 1764 } 1765 1766 void 1767 VTimeZone::write(UDate start, VTZWriter& writer, UErrorCode& status) /*const*/ { 1768 if (U_FAILURE(status)) { 1769 return; 1770 } 1771 InitialTimeZoneRule *initial = NULL; 1772 UVector *transitionRules = NULL; 1773 UVector customProps(uhash_deleteUnicodeString, uhash_compareUnicodeString, status); 1774 UnicodeString tzid; 1775 1776 // Extract rules applicable to dates after the start time 1777 getTimeZoneRulesAfter(start, initial, transitionRules, status); 1778 if (U_FAILURE(status)) { 1779 return; 1780 } 1781 1782 // Create a RuleBasedTimeZone with the subset rule 1783 getID(tzid); 1784 RuleBasedTimeZone rbtz(tzid, initial); 1785 if (transitionRules != NULL) { 1786 while (!transitionRules->isEmpty()) { 1787 TimeZoneRule *tr = (TimeZoneRule*)transitionRules->orphanElementAt(0); 1788 rbtz.addTransitionRule(tr, status); 1789 if (U_FAILURE(status)) { 1790 goto cleanupWritePartial; 1791 } 1792 } 1793 delete transitionRules; 1794 transitionRules = NULL; 1795 } 1796 rbtz.complete(status); 1797 if (U_FAILURE(status)) { 1798 goto cleanupWritePartial; 1799 } 1800 1801 if (olsonzid.length() > 0 && icutzver.length() > 0) { 1802 UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP); 1803 icutzprop->append(olsonzid); 1804 icutzprop->append((UChar)0x005B/*'['*/); 1805 icutzprop->append(icutzver); 1806 icutzprop->append(ICU_TZINFO_PARTIAL); 1807 appendMillis(start, *icutzprop); 1808 icutzprop->append((UChar)0x005D/*']'*/); 1809 customProps.addElement(icutzprop, status); 1810 if (U_FAILURE(status)) { 1811 delete icutzprop; 1812 goto cleanupWritePartial; 1813 } 1814 } 1815 writeZone(writer, rbtz, &customProps, status); 1816 return; 1817 1818 cleanupWritePartial: 1819 if (initial != NULL) { 1820 delete initial; 1821 } 1822 if (transitionRules != NULL) { 1823 while (!transitionRules->isEmpty()) { 1824 TimeZoneRule *tr = (TimeZoneRule*)transitionRules->orphanElementAt(0); 1825 delete tr; 1826 } 1827 delete transitionRules; 1828 } 1829 } 1830 1831 void 1832 VTimeZone::writeSimple(UDate time, VTZWriter& writer, UErrorCode& status) /*const*/ { 1833 if (U_FAILURE(status)) { 1834 return; 1835 } 1836 1837 UVector customProps(uhash_deleteUnicodeString, uhash_compareUnicodeString, status); 1838 UnicodeString tzid; 1839 1840 // Extract simple rules 1841 InitialTimeZoneRule *initial = NULL; 1842 AnnualTimeZoneRule *std = NULL, *dst = NULL; 1843 getSimpleRulesNear(time, initial, std, dst, status); 1844 if (U_SUCCESS(status)) { 1845 // Create a RuleBasedTimeZone with the subset rule 1846 getID(tzid); 1847 RuleBasedTimeZone rbtz(tzid, initial); 1848 if (std != NULL && dst != NULL) { 1849 rbtz.addTransitionRule(std, status); 1850 rbtz.addTransitionRule(dst, status); 1851 } 1852 if (U_FAILURE(status)) { 1853 goto cleanupWriteSimple; 1854 } 1855 1856 if (olsonzid.length() > 0 && icutzver.length() > 0) { 1857 UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP); 1858 icutzprop->append(olsonzid); 1859 icutzprop->append((UChar)0x005B/*'['*/); 1860 icutzprop->append(icutzver); 1861 icutzprop->append(ICU_TZINFO_SIMPLE); 1862 appendMillis(time, *icutzprop); 1863 icutzprop->append((UChar)0x005D/*']'*/); 1864 customProps.addElement(icutzprop, status); 1865 if (U_FAILURE(status)) { 1866 delete icutzprop; 1867 goto cleanupWriteSimple; 1868 } 1869 } 1870 writeZone(writer, rbtz, &customProps, status); 1871 } 1872 return; 1873 1874 cleanupWriteSimple: 1875 if (initial != NULL) { 1876 delete initial; 1877 } 1878 if (std != NULL) { 1879 delete std; 1880 } 1881 if (dst != NULL) { 1882 delete dst; 1883 } 1884 } 1885 1886 void 1887 VTimeZone::writeZone(VTZWriter& w, BasicTimeZone& basictz, 1888 UVector* customProps, UErrorCode& status) const { 1889 if (U_FAILURE(status)) { 1890 return; 1891 } 1892 writeHeaders(w, status); 1893 if (U_FAILURE(status)) { 1894 return; 1895 } 1896 1897 if (customProps != NULL) { 1898 for (int32_t i = 0; i < customProps->size(); i++) { 1899 UnicodeString *custprop = (UnicodeString*)customProps->elementAt(i); 1900 w.write(*custprop); 1901 w.write(ICAL_NEWLINE); 1902 } 1903 } 1904 1905 UDate t = MIN_MILLIS; 1906 UnicodeString dstName; 1907 int32_t dstFromOffset = 0; 1908 int32_t dstFromDSTSavings = 0; 1909 int32_t dstToOffset = 0; 1910 int32_t dstStartYear = 0; 1911 int32_t dstMonth = 0; 1912 int32_t dstDayOfWeek = 0; 1913 int32_t dstWeekInMonth = 0; 1914 int32_t dstMillisInDay = 0; 1915 UDate dstStartTime = 0.0; 1916 UDate dstUntilTime = 0.0; 1917 int32_t dstCount = 0; 1918 AnnualTimeZoneRule *finalDstRule = NULL; 1919 1920 UnicodeString stdName; 1921 int32_t stdFromOffset = 0; 1922 int32_t stdFromDSTSavings = 0; 1923 int32_t stdToOffset = 0; 1924 int32_t stdStartYear = 0; 1925 int32_t stdMonth = 0; 1926 int32_t stdDayOfWeek = 0; 1927 int32_t stdWeekInMonth = 0; 1928 int32_t stdMillisInDay = 0; 1929 UDate stdStartTime = 0.0; 1930 UDate stdUntilTime = 0.0; 1931 int32_t stdCount = 0; 1932 AnnualTimeZoneRule *finalStdRule = NULL; 1933 1934 int32_t year, month, dom, dow, doy, mid; 1935 UBool hasTransitions = FALSE; 1936 TimeZoneTransition tzt; 1937 UBool tztAvail; 1938 UnicodeString name; 1939 UBool isDst; 1940 1941 // Going through all transitions 1942 while (TRUE) { 1943 tztAvail = basictz.getNextTransition(t, FALSE, tzt); 1944 if (!tztAvail) { 1945 break; 1946 } 1947 hasTransitions = TRUE; 1948 t = tzt.getTime(); 1949 tzt.getTo()->getName(name); 1950 isDst = (tzt.getTo()->getDSTSavings() != 0); 1951 int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings(); 1952 int32_t fromDSTSavings = tzt.getFrom()->getDSTSavings(); 1953 int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings(); 1954 Grego::timeToFields(tzt.getTime() + fromOffset, year, month, dom, dow, doy, mid); 1955 int32_t weekInMonth = Grego::dayOfWeekInMonth(year, month, dom); 1956 UBool sameRule = FALSE; 1957 const AnnualTimeZoneRule *atzrule; 1958 if (isDst) { 1959 if (finalDstRule == NULL 1960 && (atzrule = dynamic_cast<const AnnualTimeZoneRule *>(tzt.getTo())) != NULL 1961 && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR 1962 ) { 1963 finalDstRule = (AnnualTimeZoneRule*)tzt.getTo()->clone(); 1964 } 1965 if (dstCount > 0) { 1966 if (year == dstStartYear + dstCount 1967 && name.compare(dstName) == 0 1968 && dstFromOffset == fromOffset 1969 && dstToOffset == toOffset 1970 && dstMonth == month 1971 && dstDayOfWeek == dow 1972 && dstWeekInMonth == weekInMonth 1973 && dstMillisInDay == mid) { 1974 // Update until time 1975 dstUntilTime = t; 1976 dstCount++; 1977 sameRule = TRUE; 1978 } 1979 if (!sameRule) { 1980 if (dstCount == 1) { 1981 writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime, 1982 TRUE, status); 1983 } else { 1984 writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset, 1985 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status); 1986 } 1987 if (U_FAILURE(status)) { 1988 goto cleanupWriteZone; 1989 } 1990 } 1991 } 1992 if (!sameRule) { 1993 // Reset this DST information 1994 dstName = name; 1995 dstFromOffset = fromOffset; 1996 dstFromDSTSavings = fromDSTSavings; 1997 dstToOffset = toOffset; 1998 dstStartYear = year; 1999 dstMonth = month; 2000 dstDayOfWeek = dow; 2001 dstWeekInMonth = weekInMonth; 2002 dstMillisInDay = mid; 2003 dstStartTime = dstUntilTime = t; 2004 dstCount = 1; 2005 } 2006 if (finalStdRule != NULL && finalDstRule != NULL) { 2007 break; 2008 } 2009 } else { 2010 if (finalStdRule == NULL 2011 && (atzrule = dynamic_cast<const AnnualTimeZoneRule *>(tzt.getTo())) != NULL 2012 && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR 2013 ) { 2014 finalStdRule = (AnnualTimeZoneRule*)tzt.getTo()->clone(); 2015 } 2016 if (stdCount > 0) { 2017 if (year == stdStartYear + stdCount 2018 && name.compare(stdName) == 0 2019 && stdFromOffset == fromOffset 2020 && stdToOffset == toOffset 2021 && stdMonth == month 2022 && stdDayOfWeek == dow 2023 && stdWeekInMonth == weekInMonth 2024 && stdMillisInDay == mid) { 2025 // Update until time 2026 stdUntilTime = t; 2027 stdCount++; 2028 sameRule = TRUE; 2029 } 2030 if (!sameRule) { 2031 if (stdCount == 1) { 2032 writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime, 2033 TRUE, status); 2034 } else { 2035 writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset, 2036 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status); 2037 } 2038 if (U_FAILURE(status)) { 2039 goto cleanupWriteZone; 2040 } 2041 } 2042 } 2043 if (!sameRule) { 2044 // Reset this STD information 2045 stdName = name; 2046 stdFromOffset = fromOffset; 2047 stdFromDSTSavings = fromDSTSavings; 2048 stdToOffset = toOffset; 2049 stdStartYear = year; 2050 stdMonth = month; 2051 stdDayOfWeek = dow; 2052 stdWeekInMonth = weekInMonth; 2053 stdMillisInDay = mid; 2054 stdStartTime = stdUntilTime = t; 2055 stdCount = 1; 2056 } 2057 if (finalStdRule != NULL && finalDstRule != NULL) { 2058 break; 2059 } 2060 } 2061 } 2062 if (!hasTransitions) { 2063 // No transition - put a single non transition RDATE 2064 int32_t raw, dst, offset; 2065 basictz.getOffset(0.0/*any time*/, FALSE, raw, dst, status); 2066 if (U_FAILURE(status)) { 2067 goto cleanupWriteZone; 2068 } 2069 offset = raw + dst; 2070 isDst = (dst != 0); 2071 UnicodeString tzid; 2072 basictz.getID(tzid); 2073 getDefaultTZName(tzid, isDst, name); 2074 writeZonePropsByTime(w, isDst, name, 2075 offset, offset, DEF_TZSTARTTIME - offset, FALSE, status); 2076 if (U_FAILURE(status)) { 2077 goto cleanupWriteZone; 2078 } 2079 } else { 2080 if (dstCount > 0) { 2081 if (finalDstRule == NULL) { 2082 if (dstCount == 1) { 2083 writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime, 2084 TRUE, status); 2085 } else { 2086 writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset, 2087 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status); 2088 } 2089 if (U_FAILURE(status)) { 2090 goto cleanupWriteZone; 2091 } 2092 } else { 2093 if (dstCount == 1) { 2094 writeFinalRule(w, TRUE, finalDstRule, 2095 dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, dstStartTime, status); 2096 } else { 2097 // Use a single rule if possible 2098 if (isEquivalentDateRule(dstMonth, dstWeekInMonth, dstDayOfWeek, finalDstRule->getRule())) { 2099 writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset, 2100 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, MAX_MILLIS, status); 2101 } else { 2102 // Not equivalent rule - write out two different rules 2103 writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset, 2104 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status); 2105 if (U_FAILURE(status)) { 2106 goto cleanupWriteZone; 2107 } 2108 writeFinalRule(w, TRUE, finalDstRule, 2109 dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, dstStartTime, status); 2110 } 2111 } 2112 if (U_FAILURE(status)) { 2113 goto cleanupWriteZone; 2114 } 2115 } 2116 } 2117 if (stdCount > 0) { 2118 if (finalStdRule == NULL) { 2119 if (stdCount == 1) { 2120 writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime, 2121 TRUE, status); 2122 } else { 2123 writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset, 2124 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status); 2125 } 2126 if (U_FAILURE(status)) { 2127 goto cleanupWriteZone; 2128 } 2129 } else { 2130 if (stdCount == 1) { 2131 writeFinalRule(w, FALSE, finalStdRule, 2132 stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, stdStartTime, status); 2133 } else { 2134 // Use a single rule if possible 2135 if (isEquivalentDateRule(stdMonth, stdWeekInMonth, stdDayOfWeek, finalStdRule->getRule())) { 2136 writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset, 2137 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, MAX_MILLIS, status); 2138 } else { 2139 // Not equivalent rule - write out two different rules 2140 writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset, 2141 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status); 2142 if (U_FAILURE(status)) { 2143 goto cleanupWriteZone; 2144 } 2145 writeFinalRule(w, FALSE, finalStdRule, 2146 stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, stdStartTime, status); 2147 } 2148 } 2149 if (U_FAILURE(status)) { 2150 goto cleanupWriteZone; 2151 } 2152 } 2153 } 2154 } 2155 writeFooter(w, status); 2156 2157 cleanupWriteZone: 2158 2159 if (finalStdRule != NULL) { 2160 delete finalStdRule; 2161 } 2162 if (finalDstRule != NULL) { 2163 delete finalDstRule; 2164 } 2165 } 2166 2167 void 2168 VTimeZone::writeHeaders(VTZWriter& writer, UErrorCode& status) const { 2169 if (U_FAILURE(status)) { 2170 return; 2171 } 2172 UnicodeString tzid; 2173 tz->getID(tzid); 2174 2175 writer.write(ICAL_BEGIN); 2176 writer.write(COLON); 2177 writer.write(ICAL_VTIMEZONE); 2178 writer.write(ICAL_NEWLINE); 2179 writer.write(ICAL_TZID); 2180 writer.write(COLON); 2181 writer.write(tzid); 2182 writer.write(ICAL_NEWLINE); 2183 if (tzurl.length() != 0) { 2184 writer.write(ICAL_TZURL); 2185 writer.write(COLON); 2186 writer.write(tzurl); 2187 writer.write(ICAL_NEWLINE); 2188 } 2189 if (lastmod != MAX_MILLIS) { 2190 UnicodeString lastmodStr; 2191 writer.write(ICAL_LASTMOD); 2192 writer.write(COLON); 2193 writer.write(getUTCDateTimeString(lastmod, lastmodStr)); 2194 writer.write(ICAL_NEWLINE); 2195 } 2196 } 2197 2198 /* 2199 * Write the closing section of the VTIMEZONE definition block 2200 */ 2201 void 2202 VTimeZone::writeFooter(VTZWriter& writer, UErrorCode& status) const { 2203 if (U_FAILURE(status)) { 2204 return; 2205 } 2206 writer.write(ICAL_END); 2207 writer.write(COLON); 2208 writer.write(ICAL_VTIMEZONE); 2209 writer.write(ICAL_NEWLINE); 2210 } 2211 2212 /* 2213 * Write a single start time 2214 */ 2215 void 2216 VTimeZone::writeZonePropsByTime(VTZWriter& writer, UBool isDst, const UnicodeString& zonename, 2217 int32_t fromOffset, int32_t toOffset, UDate time, UBool withRDATE, 2218 UErrorCode& status) const { 2219 if (U_FAILURE(status)) { 2220 return; 2221 } 2222 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, time, status); 2223 if (U_FAILURE(status)) { 2224 return; 2225 } 2226 if (withRDATE) { 2227 writer.write(ICAL_RDATE); 2228 writer.write(COLON); 2229 UnicodeString timestr; 2230 writer.write(getDateTimeString(time + fromOffset, timestr)); 2231 writer.write(ICAL_NEWLINE); 2232 } 2233 endZoneProps(writer, isDst, status); 2234 if (U_FAILURE(status)) { 2235 return; 2236 } 2237 } 2238 2239 /* 2240 * Write start times defined by a DOM rule using VTIMEZONE RRULE 2241 */ 2242 void 2243 VTimeZone::writeZonePropsByDOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename, 2244 int32_t fromOffset, int32_t toOffset, 2245 int32_t month, int32_t dayOfMonth, UDate startTime, UDate untilTime, 2246 UErrorCode& status) const { 2247 if (U_FAILURE(status)) { 2248 return; 2249 } 2250 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status); 2251 if (U_FAILURE(status)) { 2252 return; 2253 } 2254 beginRRULE(writer, month, status); 2255 if (U_FAILURE(status)) { 2256 return; 2257 } 2258 writer.write(ICAL_BYMONTHDAY); 2259 writer.write(EQUALS_SIGN); 2260 UnicodeString dstr; 2261 appendAsciiDigits(dayOfMonth, 0, dstr); 2262 writer.write(dstr); 2263 if (untilTime != MAX_MILLIS) { 2264 appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status); 2265 if (U_FAILURE(status)) { 2266 return; 2267 } 2268 } 2269 writer.write(ICAL_NEWLINE); 2270 endZoneProps(writer, isDst, status); 2271 } 2272 2273 /* 2274 * Write start times defined by a DOW rule using VTIMEZONE RRULE 2275 */ 2276 void 2277 VTimeZone::writeZonePropsByDOW(VTZWriter& writer, UBool isDst, const UnicodeString& zonename, 2278 int32_t fromOffset, int32_t toOffset, 2279 int32_t month, int32_t weekInMonth, int32_t dayOfWeek, 2280 UDate startTime, UDate untilTime, UErrorCode& status) const { 2281 if (U_FAILURE(status)) { 2282 return; 2283 } 2284 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status); 2285 if (U_FAILURE(status)) { 2286 return; 2287 } 2288 beginRRULE(writer, month, status); 2289 if (U_FAILURE(status)) { 2290 return; 2291 } 2292 writer.write(ICAL_BYDAY); 2293 writer.write(EQUALS_SIGN); 2294 UnicodeString dstr; 2295 appendAsciiDigits(weekInMonth, 0, dstr); 2296 writer.write(dstr); // -4, -3, -2, -1, 1, 2, 3, 4 2297 writer.write(ICAL_DOW_NAMES[dayOfWeek - 1]); // SU, MO, TU... 2298 2299 if (untilTime != MAX_MILLIS) { 2300 appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status); 2301 if (U_FAILURE(status)) { 2302 return; 2303 } 2304 } 2305 writer.write(ICAL_NEWLINE); 2306 endZoneProps(writer, isDst, status); 2307 } 2308 2309 /* 2310 * Write start times defined by a DOW_GEQ_DOM rule using VTIMEZONE RRULE 2311 */ 2312 void 2313 VTimeZone::writeZonePropsByDOW_GEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename, 2314 int32_t fromOffset, int32_t toOffset, 2315 int32_t month, int32_t dayOfMonth, int32_t dayOfWeek, 2316 UDate startTime, UDate untilTime, UErrorCode& status) const { 2317 if (U_FAILURE(status)) { 2318 return; 2319 } 2320 // Check if this rule can be converted to DOW rule 2321 if (dayOfMonth%7 == 1) { 2322 // Can be represented by DOW rule 2323 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset, 2324 month, (dayOfMonth + 6)/7, dayOfWeek, startTime, untilTime, status); 2325 if (U_FAILURE(status)) { 2326 return; 2327 } 2328 } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 6) { 2329 // Can be represented by DOW rule with negative week number 2330 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset, 2331 month, -1*((MONTHLENGTH[month] - dayOfMonth + 1)/7), dayOfWeek, startTime, untilTime, status); 2332 if (U_FAILURE(status)) { 2333 return; 2334 } 2335 } else { 2336 // Otherwise, use BYMONTHDAY to include all possible dates 2337 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status); 2338 if (U_FAILURE(status)) { 2339 return; 2340 } 2341 // Check if all days are in the same month 2342 int32_t startDay = dayOfMonth; 2343 int32_t currentMonthDays = 7; 2344 2345 if (dayOfMonth <= 0) { 2346 // The start day is in previous month 2347 int32_t prevMonthDays = 1 - dayOfMonth; 2348 currentMonthDays -= prevMonthDays; 2349 2350 int32_t prevMonth = (month - 1) < 0 ? 11 : month - 1; 2351 2352 // Note: When a rule is separated into two, UNTIL attribute needs to be 2353 // calculated for each of them. For now, we skip this, because we basically use this method 2354 // only for final rules, which does not have the UNTIL attribute 2355 writeZonePropsByDOW_GEQ_DOM_sub(writer, prevMonth, -prevMonthDays, dayOfWeek, prevMonthDays, 2356 MAX_MILLIS /* Do not use UNTIL */, fromOffset, status); 2357 if (U_FAILURE(status)) { 2358 return; 2359 } 2360 2361 // Start from 1 for the rest 2362 startDay = 1; 2363 } else if (dayOfMonth + 6 > MONTHLENGTH[month]) { 2364 // Note: This code does not actually work well in February. For now, days in month in 2365 // non-leap year. 2366 int32_t nextMonthDays = dayOfMonth + 6 - MONTHLENGTH[month]; 2367 currentMonthDays -= nextMonthDays; 2368 2369 int32_t nextMonth = (month + 1) > 11 ? 0 : month + 1; 2370 2371 writeZonePropsByDOW_GEQ_DOM_sub(writer, nextMonth, 1, dayOfWeek, nextMonthDays, 2372 MAX_MILLIS /* Do not use UNTIL */, fromOffset, status); 2373 if (U_FAILURE(status)) { 2374 return; 2375 } 2376 } 2377 writeZonePropsByDOW_GEQ_DOM_sub(writer, month, startDay, dayOfWeek, currentMonthDays, 2378 untilTime, fromOffset, status); 2379 if (U_FAILURE(status)) { 2380 return; 2381 } 2382 endZoneProps(writer, isDst, status); 2383 } 2384 } 2385 2386 /* 2387 * Called from writeZonePropsByDOW_GEQ_DOM 2388 */ 2389 void 2390 VTimeZone::writeZonePropsByDOW_GEQ_DOM_sub(VTZWriter& writer, int32_t month, int32_t dayOfMonth, 2391 int32_t dayOfWeek, int32_t numDays, 2392 UDate untilTime, int32_t fromOffset, UErrorCode& status) const { 2393 2394 if (U_FAILURE(status)) { 2395 return; 2396 } 2397 int32_t startDayNum = dayOfMonth; 2398 UBool isFeb = (month == UCAL_FEBRUARY); 2399 if (dayOfMonth < 0 && !isFeb) { 2400 // Use positive number if possible 2401 startDayNum = MONTHLENGTH[month] + dayOfMonth + 1; 2402 } 2403 beginRRULE(writer, month, status); 2404 if (U_FAILURE(status)) { 2405 return; 2406 } 2407 writer.write(ICAL_BYDAY); 2408 writer.write(EQUALS_SIGN); 2409 writer.write(ICAL_DOW_NAMES[dayOfWeek - 1]); // SU, MO, TU... 2410 writer.write(SEMICOLON); 2411 writer.write(ICAL_BYMONTHDAY); 2412 writer.write(EQUALS_SIGN); 2413 2414 UnicodeString dstr; 2415 appendAsciiDigits(startDayNum, 0, dstr); 2416 writer.write(dstr); 2417 for (int32_t i = 1; i < numDays; i++) { 2418 writer.write(COMMA); 2419 dstr.remove(); 2420 appendAsciiDigits(startDayNum + i, 0, dstr); 2421 writer.write(dstr); 2422 } 2423 2424 if (untilTime != MAX_MILLIS) { 2425 appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status); 2426 if (U_FAILURE(status)) { 2427 return; 2428 } 2429 } 2430 writer.write(ICAL_NEWLINE); 2431 } 2432 2433 /* 2434 * Write start times defined by a DOW_LEQ_DOM rule using VTIMEZONE RRULE 2435 */ 2436 void 2437 VTimeZone::writeZonePropsByDOW_LEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename, 2438 int32_t fromOffset, int32_t toOffset, 2439 int32_t month, int32_t dayOfMonth, int32_t dayOfWeek, 2440 UDate startTime, UDate untilTime, UErrorCode& status) const { 2441 if (U_FAILURE(status)) { 2442 return; 2443 } 2444 // Check if this rule can be converted to DOW rule 2445 if (dayOfMonth%7 == 0) { 2446 // Can be represented by DOW rule 2447 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset, 2448 month, dayOfMonth/7, dayOfWeek, startTime, untilTime, status); 2449 } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 0){ 2450 // Can be represented by DOW rule with negative week number 2451 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset, 2452 month, -1*((MONTHLENGTH[month] - dayOfMonth)/7 + 1), dayOfWeek, startTime, untilTime, status); 2453 } else if (month == UCAL_FEBRUARY && dayOfMonth == 29) { 2454 // Specical case for February 2455 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset, 2456 UCAL_FEBRUARY, -1, dayOfWeek, startTime, untilTime, status); 2457 } else { 2458 // Otherwise, convert this to DOW_GEQ_DOM rule 2459 writeZonePropsByDOW_GEQ_DOM(writer, isDst, zonename, fromOffset, toOffset, 2460 month, dayOfMonth - 6, dayOfWeek, startTime, untilTime, status); 2461 } 2462 } 2463 2464 /* 2465 * Write the final time zone rule using RRULE, with no UNTIL attribute 2466 */ 2467 void 2468 VTimeZone::writeFinalRule(VTZWriter& writer, UBool isDst, const AnnualTimeZoneRule* rule, 2469 int32_t fromRawOffset, int32_t fromDSTSavings, 2470 UDate startTime, UErrorCode& status) const { 2471 if (U_FAILURE(status)) { 2472 return; 2473 } 2474 UBool modifiedRule = TRUE; 2475 const DateTimeRule *dtrule = toWallTimeRule(rule->getRule(), fromRawOffset, fromDSTSavings); 2476 if (dtrule == NULL) { 2477 modifiedRule = FALSE; 2478 dtrule = rule->getRule(); 2479 } 2480 2481 // If the rule's mills in a day is out of range, adjust start time. 2482 // Olson tzdata supports 24:00 of a day, but VTIMEZONE does not. 2483 // See ticket#7008/#7518 2484 2485 int32_t timeInDay = dtrule->getRuleMillisInDay(); 2486 if (timeInDay < 0) { 2487 startTime = startTime + (0 - timeInDay); 2488 } else if (timeInDay >= U_MILLIS_PER_DAY) { 2489 startTime = startTime - (timeInDay - (U_MILLIS_PER_DAY - 1)); 2490 } 2491 2492 int32_t toOffset = rule->getRawOffset() + rule->getDSTSavings(); 2493 UnicodeString name; 2494 rule->getName(name); 2495 switch (dtrule->getDateRuleType()) { 2496 case DateTimeRule::DOM: 2497 writeZonePropsByDOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset, 2498 dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), startTime, MAX_MILLIS, status); 2499 break; 2500 case DateTimeRule::DOW: 2501 writeZonePropsByDOW(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset, 2502 dtrule->getRuleMonth(), dtrule->getRuleWeekInMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status); 2503 break; 2504 case DateTimeRule::DOW_GEQ_DOM: 2505 writeZonePropsByDOW_GEQ_DOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset, 2506 dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status); 2507 break; 2508 case DateTimeRule::DOW_LEQ_DOM: 2509 writeZonePropsByDOW_LEQ_DOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset, 2510 dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status); 2511 break; 2512 } 2513 if (modifiedRule) { 2514 delete dtrule; 2515 } 2516 } 2517 2518 /* 2519 * Write the opening section of zone properties 2520 */ 2521 void 2522 VTimeZone::beginZoneProps(VTZWriter& writer, UBool isDst, const UnicodeString& zonename, 2523 int32_t fromOffset, int32_t toOffset, UDate startTime, UErrorCode& status) const { 2524 if (U_FAILURE(status)) { 2525 return; 2526 } 2527 writer.write(ICAL_BEGIN); 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 UnicodeString dstr; 2537 2538 // TZOFFSETTO 2539 writer.write(ICAL_TZOFFSETTO); 2540 writer.write(COLON); 2541 millisToOffset(toOffset, dstr); 2542 writer.write(dstr); 2543 writer.write(ICAL_NEWLINE); 2544 2545 // TZOFFSETFROM 2546 writer.write(ICAL_TZOFFSETFROM); 2547 writer.write(COLON); 2548 millisToOffset(fromOffset, dstr); 2549 writer.write(dstr); 2550 writer.write(ICAL_NEWLINE); 2551 2552 // TZNAME 2553 writer.write(ICAL_TZNAME); 2554 writer.write(COLON); 2555 writer.write(zonename); 2556 writer.write(ICAL_NEWLINE); 2557 2558 // DTSTART 2559 writer.write(ICAL_DTSTART); 2560 writer.write(COLON); 2561 writer.write(getDateTimeString(startTime + fromOffset, dstr)); 2562 writer.write(ICAL_NEWLINE); 2563 } 2564 2565 /* 2566 * Writes the closing section of zone properties 2567 */ 2568 void 2569 VTimeZone::endZoneProps(VTZWriter& writer, UBool isDst, UErrorCode& status) const { 2570 if (U_FAILURE(status)) { 2571 return; 2572 } 2573 // END:STANDARD or END:DAYLIGHT 2574 writer.write(ICAL_END); 2575 writer.write(COLON); 2576 if (isDst) { 2577 writer.write(ICAL_DAYLIGHT); 2578 } else { 2579 writer.write(ICAL_STANDARD); 2580 } 2581 writer.write(ICAL_NEWLINE); 2582 } 2583 2584 /* 2585 * Write the beggining part of RRULE line 2586 */ 2587 void 2588 VTimeZone::beginRRULE(VTZWriter& writer, int32_t month, UErrorCode& status) const { 2589 if (U_FAILURE(status)) { 2590 return; 2591 } 2592 UnicodeString dstr; 2593 writer.write(ICAL_RRULE); 2594 writer.write(COLON); 2595 writer.write(ICAL_FREQ); 2596 writer.write(EQUALS_SIGN); 2597 writer.write(ICAL_YEARLY); 2598 writer.write(SEMICOLON); 2599 writer.write(ICAL_BYMONTH); 2600 writer.write(EQUALS_SIGN); 2601 appendAsciiDigits(month + 1, 0, dstr); 2602 writer.write(dstr); 2603 writer.write(SEMICOLON); 2604 } 2605 2606 /* 2607 * Append the UNTIL attribute after RRULE line 2608 */ 2609 void 2610 VTimeZone::appendUNTIL(VTZWriter& writer, const UnicodeString& until, UErrorCode& status) const { 2611 if (U_FAILURE(status)) { 2612 return; 2613 } 2614 if (until.length() > 0) { 2615 writer.write(SEMICOLON); 2616 writer.write(ICAL_UNTIL); 2617 writer.write(EQUALS_SIGN); 2618 writer.write(until); 2619 } 2620 } 2621 2622 U_NAMESPACE_END 2623 2624 #endif /* #if !UCONFIG_NO_FORMATTING */ 2625 2626 //eof 2627