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