Wed, 31 Dec 2014 07:22:50 +0100
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 |