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