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