intl/icu/source/i18n/vtzone.cpp

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

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

mercurial