intl/icu/source/i18n/vtzone.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/intl/icu/source/i18n/vtzone.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,2641 @@
     1.4 +/*
     1.5 +*******************************************************************************
     1.6 +* Copyright (C) 2007-2013, International Business Machines Corporation and
     1.7 +* others. All Rights Reserved.
     1.8 +*******************************************************************************
     1.9 +*/
    1.10 +
    1.11 +#include "utypeinfo.h"  // for 'typeid' to work
    1.12 +
    1.13 +#include "unicode/utypes.h"
    1.14 +
    1.15 +#if !UCONFIG_NO_FORMATTING
    1.16 +
    1.17 +#include "unicode/vtzone.h"
    1.18 +#include "unicode/rbtz.h"
    1.19 +#include "unicode/ucal.h"
    1.20 +#include "unicode/ures.h"
    1.21 +#include "cmemory.h"
    1.22 +#include "uvector.h"
    1.23 +#include "gregoimp.h"
    1.24 +#include "uassert.h"
    1.25 +
    1.26 +U_NAMESPACE_BEGIN
    1.27 +
    1.28 +// This is the deleter that will be use to remove TimeZoneRule
    1.29 +U_CDECL_BEGIN
    1.30 +static void U_CALLCONV
    1.31 +deleteTimeZoneRule(void* obj) {
    1.32 +    delete (TimeZoneRule*) obj;
    1.33 +}
    1.34 +U_CDECL_END
    1.35 +
    1.36 +// Smybol characters used by RFC2445 VTIMEZONE
    1.37 +static const UChar COLON = 0x3A; /* : */
    1.38 +static const UChar SEMICOLON = 0x3B; /* ; */
    1.39 +static const UChar EQUALS_SIGN = 0x3D; /* = */
    1.40 +static const UChar COMMA = 0x2C; /* , */
    1.41 +static const UChar PLUS = 0x2B; /* + */
    1.42 +static const UChar MINUS = 0x2D; /* - */
    1.43 +
    1.44 +// RFC2445 VTIMEZONE tokens
    1.45 +static const UChar ICAL_BEGIN_VTIMEZONE[] = {0x42, 0x45, 0x47, 0x49, 0x4E, 0x3A, 0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "BEGIN:VTIMEZONE" */
    1.46 +static const UChar ICAL_END_VTIMEZONE[] = {0x45, 0x4E, 0x44, 0x3A, 0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "END:VTIMEZONE" */
    1.47 +static const UChar ICAL_BEGIN[] = {0x42, 0x45, 0x47, 0x49, 0x4E, 0}; /* "BEGIN" */
    1.48 +static const UChar ICAL_END[] = {0x45, 0x4E, 0x44, 0}; /* "END" */
    1.49 +static const UChar ICAL_VTIMEZONE[] = {0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "VTIMEZONE" */
    1.50 +static const UChar ICAL_TZID[] = {0x54, 0x5A, 0x49, 0x44, 0}; /* "TZID" */
    1.51 +static const UChar ICAL_STANDARD[] = {0x53, 0x54, 0x41, 0x4E, 0x44, 0x41, 0x52, 0x44, 0}; /* "STANDARD" */
    1.52 +static const UChar ICAL_DAYLIGHT[] = {0x44, 0x41, 0x59, 0x4C, 0x49, 0x47, 0x48, 0x54, 0}; /* "DAYLIGHT" */
    1.53 +static const UChar ICAL_DTSTART[] = {0x44, 0x54, 0x53, 0x54, 0x41, 0x52, 0x54, 0}; /* "DTSTART" */
    1.54 +static const UChar ICAL_TZOFFSETFROM[] = {0x54, 0x5A, 0x4F, 0x46, 0x46, 0x53, 0x45, 0x54, 0x46, 0x52, 0x4F, 0x4D, 0}; /* "TZOFFSETFROM" */
    1.55 +static const UChar ICAL_TZOFFSETTO[] = {0x54, 0x5A, 0x4F, 0x46, 0x46, 0x53, 0x45, 0x54, 0x54, 0x4F, 0}; /* "TZOFFSETTO" */
    1.56 +static const UChar ICAL_RDATE[] = {0x52, 0x44, 0x41, 0x54, 0x45, 0}; /* "RDATE" */
    1.57 +static const UChar ICAL_RRULE[] = {0x52, 0x52, 0x55, 0x4C, 0x45, 0}; /* "RRULE" */
    1.58 +static const UChar ICAL_TZNAME[] = {0x54, 0x5A, 0x4E, 0x41, 0x4D, 0x45, 0}; /* "TZNAME" */
    1.59 +static const UChar ICAL_TZURL[] = {0x54, 0x5A, 0x55, 0x52, 0x4C, 0}; /* "TZURL" */
    1.60 +static const UChar ICAL_LASTMOD[] = {0x4C, 0x41, 0x53, 0x54, 0x2D, 0x4D, 0x4F, 0x44, 0x49, 0x46, 0x49, 0x45, 0x44, 0}; /* "LAST-MODIFIED" */
    1.61 +
    1.62 +static const UChar ICAL_FREQ[] = {0x46, 0x52, 0x45, 0x51, 0}; /* "FREQ" */
    1.63 +static const UChar ICAL_UNTIL[] = {0x55, 0x4E, 0x54, 0x49, 0x4C, 0}; /* "UNTIL" */
    1.64 +static const UChar ICAL_YEARLY[] = {0x59, 0x45, 0x41, 0x52, 0x4C, 0x59, 0}; /* "YEARLY" */
    1.65 +static const UChar ICAL_BYMONTH[] = {0x42, 0x59, 0x4D, 0x4F, 0x4E, 0x54, 0x48, 0}; /* "BYMONTH" */
    1.66 +static const UChar ICAL_BYDAY[] = {0x42, 0x59, 0x44, 0x41, 0x59, 0}; /* "BYDAY" */
    1.67 +static const UChar ICAL_BYMONTHDAY[] = {0x42, 0x59, 0x4D, 0x4F, 0x4E, 0x54, 0x48, 0x44, 0x41, 0x59, 0}; /* "BYMONTHDAY" */
    1.68 +
    1.69 +static const UChar ICAL_NEWLINE[] = {0x0D, 0x0A, 0}; /* CRLF */
    1.70 +
    1.71 +static const UChar ICAL_DOW_NAMES[7][3] = {
    1.72 +    {0x53, 0x55, 0}, /* "SU" */
    1.73 +    {0x4D, 0x4F, 0}, /* "MO" */
    1.74 +    {0x54, 0x55, 0}, /* "TU" */
    1.75 +    {0x57, 0x45, 0}, /* "WE" */
    1.76 +    {0x54, 0x48, 0}, /* "TH" */
    1.77 +    {0x46, 0x52, 0}, /* "FR" */
    1.78 +    {0x53, 0x41, 0}  /* "SA" */};
    1.79 +
    1.80 +// Month length for non-leap year
    1.81 +static const int32_t MONTHLENGTH[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    1.82 +
    1.83 +// ICU custom property
    1.84 +static const UChar ICU_TZINFO_PROP[] = {0x58, 0x2D, 0x54, 0x5A, 0x49, 0x4E, 0x46, 0x4F, 0x3A, 0}; /* "X-TZINFO:" */
    1.85 +static const UChar ICU_TZINFO_PARTIAL[] = {0x2F, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6C, 0x40, 0}; /* "/Partial@" */
    1.86 +static const UChar ICU_TZINFO_SIMPLE[] = {0x2F, 0x53, 0x69, 0x6D, 0x70, 0x6C, 0x65, 0x40, 0}; /* "/Simple@" */
    1.87 +
    1.88 +
    1.89 +/*
    1.90 + * Simple fixed digit ASCII number to integer converter
    1.91 + */
    1.92 +static int32_t parseAsciiDigits(const UnicodeString& str, int32_t start, int32_t length, UErrorCode& status) {
    1.93 +    if (U_FAILURE(status)) {
    1.94 +        return 0;
    1.95 +    }
    1.96 +    if (length <= 0 || str.length() < start || (start + length) > str.length()) {
    1.97 +        status = U_INVALID_FORMAT_ERROR;
    1.98 +        return 0;
    1.99 +    }
   1.100 +    int32_t sign = 1;
   1.101 +    if (str.charAt(start) == PLUS) {
   1.102 +        start++;
   1.103 +        length--;
   1.104 +    } else if (str.charAt(start) == MINUS) {
   1.105 +        sign = -1;
   1.106 +        start++;
   1.107 +        length--;
   1.108 +    }
   1.109 +    int32_t num = 0;
   1.110 +    for (int32_t i = 0; i < length; i++) {
   1.111 +        int32_t digit = str.charAt(start + i) - 0x0030;
   1.112 +        if (digit < 0 || digit > 9) {
   1.113 +            status = U_INVALID_FORMAT_ERROR;
   1.114 +            return 0;
   1.115 +        }
   1.116 +        num = 10 * num + digit;
   1.117 +    }
   1.118 +    return sign * num;    
   1.119 +}
   1.120 +
   1.121 +static UnicodeString& appendAsciiDigits(int32_t number, uint8_t length, UnicodeString& str) {
   1.122 +    UBool negative = FALSE;
   1.123 +    int32_t digits[10]; // max int32_t is 10 decimal digits
   1.124 +    int32_t i;
   1.125 +
   1.126 +    if (number < 0) {
   1.127 +        negative = TRUE;
   1.128 +        number *= -1;
   1.129 +    }
   1.130 +
   1.131 +    length = length > 10 ? 10 : length;
   1.132 +    if (length == 0) {
   1.133 +        // variable length
   1.134 +        i = 0;
   1.135 +        do {
   1.136 +            digits[i++] = number % 10;
   1.137 +            number /= 10;
   1.138 +        } while (number != 0);
   1.139 +        length = i;
   1.140 +    } else {
   1.141 +        // fixed digits
   1.142 +        for (i = 0; i < length; i++) {
   1.143 +           digits[i] = number % 10;
   1.144 +           number /= 10;
   1.145 +        }
   1.146 +    }
   1.147 +    if (negative) {
   1.148 +        str.append(MINUS);
   1.149 +    }
   1.150 +    for (i = length - 1; i >= 0; i--) {
   1.151 +        str.append((UChar)(digits[i] + 0x0030));
   1.152 +    }
   1.153 +    return str;
   1.154 +}
   1.155 +
   1.156 +static UnicodeString& appendMillis(UDate date, UnicodeString& str) {
   1.157 +    UBool negative = FALSE;
   1.158 +    int32_t digits[20]; // max int64_t is 20 decimal digits
   1.159 +    int32_t i;
   1.160 +    int64_t number;
   1.161 +
   1.162 +    if (date < MIN_MILLIS) {
   1.163 +        number = (int64_t)MIN_MILLIS;
   1.164 +    } else if (date > MAX_MILLIS) {
   1.165 +        number = (int64_t)MAX_MILLIS;
   1.166 +    } else {
   1.167 +        number = (int64_t)date;
   1.168 +    }
   1.169 +    if (number < 0) {
   1.170 +        negative = TRUE;
   1.171 +        number *= -1;
   1.172 +    }
   1.173 +    i = 0;
   1.174 +    do {
   1.175 +        digits[i++] = (int32_t)(number % 10);
   1.176 +        number /= 10;
   1.177 +    } while (number != 0);
   1.178 +
   1.179 +    if (negative) {
   1.180 +        str.append(MINUS);
   1.181 +    }
   1.182 +    i--;
   1.183 +    while (i >= 0) {
   1.184 +        str.append((UChar)(digits[i--] + 0x0030));
   1.185 +    }
   1.186 +    return str;
   1.187 +}
   1.188 +
   1.189 +/*
   1.190 + * Convert date/time to RFC2445 Date-Time form #1 DATE WITH LOCAL TIME
   1.191 + */
   1.192 +static UnicodeString& getDateTimeString(UDate time, UnicodeString& str) {
   1.193 +    int32_t year, month, dom, dow, doy, mid;
   1.194 +    Grego::timeToFields(time, year, month, dom, dow, doy, mid);
   1.195 +
   1.196 +    str.remove();
   1.197 +    appendAsciiDigits(year, 4, str);
   1.198 +    appendAsciiDigits(month + 1, 2, str);
   1.199 +    appendAsciiDigits(dom, 2, str);
   1.200 +    str.append((UChar)0x0054 /*'T'*/);
   1.201 +
   1.202 +    int32_t t = mid;
   1.203 +    int32_t hour = t / U_MILLIS_PER_HOUR;
   1.204 +    t %= U_MILLIS_PER_HOUR;
   1.205 +    int32_t min = t / U_MILLIS_PER_MINUTE;
   1.206 +    t %= U_MILLIS_PER_MINUTE;
   1.207 +    int32_t sec = t / U_MILLIS_PER_SECOND;
   1.208 +
   1.209 +    appendAsciiDigits(hour, 2, str);
   1.210 +    appendAsciiDigits(min, 2, str);
   1.211 +    appendAsciiDigits(sec, 2, str);
   1.212 +    return str;
   1.213 +}
   1.214 +
   1.215 +/*
   1.216 + * Convert date/time to RFC2445 Date-Time form #2 DATE WITH UTC TIME
   1.217 + */
   1.218 +static UnicodeString& getUTCDateTimeString(UDate time, UnicodeString& str) {
   1.219 +    getDateTimeString(time, str);
   1.220 +    str.append((UChar)0x005A /*'Z'*/);
   1.221 +    return str;
   1.222 +}
   1.223 +
   1.224 +/*
   1.225 + * Parse RFC2445 Date-Time form #1 DATE WITH LOCAL TIME and
   1.226 + * #2 DATE WITH UTC TIME
   1.227 + */
   1.228 +static UDate parseDateTimeString(const UnicodeString& str, int32_t offset, UErrorCode& status) {
   1.229 +    if (U_FAILURE(status)) {
   1.230 +        return 0.0;
   1.231 +    }
   1.232 +
   1.233 +    int32_t year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0;
   1.234 +    UBool isUTC = FALSE;
   1.235 +    UBool isValid = FALSE;
   1.236 +    do {
   1.237 +        int length = str.length();
   1.238 +        if (length != 15 && length != 16) {
   1.239 +            // FORM#1 15 characters, such as "20060317T142115"
   1.240 +            // FORM#2 16 characters, such as "20060317T142115Z"
   1.241 +            break;
   1.242 +        }
   1.243 +        if (str.charAt(8) != 0x0054) {
   1.244 +            // charcter "T" must be used for separating date and time
   1.245 +            break;
   1.246 +        }
   1.247 +        if (length == 16) {
   1.248 +            if (str.charAt(15) != 0x005A) {
   1.249 +                // invalid format
   1.250 +                break;
   1.251 +            }
   1.252 +            isUTC = TRUE;
   1.253 +        }
   1.254 +
   1.255 +        year = parseAsciiDigits(str, 0, 4, status);
   1.256 +        month = parseAsciiDigits(str, 4, 2, status) - 1;  // 0-based
   1.257 +        day = parseAsciiDigits(str, 6, 2, status);
   1.258 +        hour = parseAsciiDigits(str, 9, 2, status);
   1.259 +        min = parseAsciiDigits(str, 11, 2, status);
   1.260 +        sec = parseAsciiDigits(str, 13, 2, status);
   1.261 +
   1.262 +        if (U_FAILURE(status)) {
   1.263 +            break;
   1.264 +        }
   1.265 +
   1.266 +        // check valid range
   1.267 +        int32_t maxDayOfMonth = Grego::monthLength(year, month);
   1.268 +        if (year < 0 || month < 0 || month > 11 || day < 1 || day > maxDayOfMonth ||
   1.269 +                hour < 0 || hour >= 24 || min < 0 || min >= 60 || sec < 0 || sec >= 60) {
   1.270 +            break;
   1.271 +        }
   1.272 +
   1.273 +        isValid = TRUE;
   1.274 +    } while(false);
   1.275 +
   1.276 +    if (!isValid) {
   1.277 +        status = U_INVALID_FORMAT_ERROR;
   1.278 +        return 0.0;
   1.279 +    }
   1.280 +    // Calculate the time
   1.281 +    UDate time = Grego::fieldsToDay(year, month, day) * U_MILLIS_PER_DAY;
   1.282 +    time += (hour * U_MILLIS_PER_HOUR + min * U_MILLIS_PER_MINUTE + sec * U_MILLIS_PER_SECOND);
   1.283 +    if (!isUTC) {
   1.284 +        time -= offset;
   1.285 +    }
   1.286 +    return time;
   1.287 +}
   1.288 +
   1.289 +/*
   1.290 + * Convert RFC2445 utc-offset string to milliseconds
   1.291 + */
   1.292 +static int32_t offsetStrToMillis(const UnicodeString& str, UErrorCode& status) {
   1.293 +    if (U_FAILURE(status)) {
   1.294 +        return 0;
   1.295 +    }
   1.296 +
   1.297 +    UBool isValid = FALSE;
   1.298 +    int32_t sign = 0, hour = 0, min = 0, sec = 0;
   1.299 +
   1.300 +    do {
   1.301 +        int length = str.length();
   1.302 +        if (length != 5 && length != 7) {
   1.303 +            // utf-offset must be 5 or 7 characters
   1.304 +            break;
   1.305 +        }
   1.306 +        // sign
   1.307 +        UChar s = str.charAt(0);
   1.308 +        if (s == PLUS) {
   1.309 +            sign = 1;
   1.310 +        } else if (s == MINUS) {
   1.311 +            sign = -1;
   1.312 +        } else {
   1.313 +            // utf-offset must start with "+" or "-"
   1.314 +            break;
   1.315 +        }
   1.316 +        hour = parseAsciiDigits(str, 1, 2, status);
   1.317 +        min = parseAsciiDigits(str, 3, 2, status);
   1.318 +        if (length == 7) {
   1.319 +            sec = parseAsciiDigits(str, 5, 2, status);
   1.320 +        }
   1.321 +        if (U_FAILURE(status)) {
   1.322 +            break;
   1.323 +        }
   1.324 +        isValid = true;
   1.325 +    } while(false);
   1.326 +
   1.327 +    if (!isValid) {
   1.328 +        status = U_INVALID_FORMAT_ERROR;
   1.329 +        return 0;
   1.330 +    }
   1.331 +    int32_t millis = sign * ((hour * 60 + min) * 60 + sec) * 1000;
   1.332 +    return millis;
   1.333 +}
   1.334 +
   1.335 +/*
   1.336 + * Convert milliseconds to RFC2445 utc-offset string
   1.337 + */
   1.338 +static void millisToOffset(int32_t millis, UnicodeString& str) {
   1.339 +    str.remove();
   1.340 +    if (millis >= 0) {
   1.341 +        str.append(PLUS);
   1.342 +    } else {
   1.343 +        str.append(MINUS);
   1.344 +        millis = -millis;
   1.345 +    }
   1.346 +    int32_t hour, min, sec;
   1.347 +    int32_t t = millis / 1000;
   1.348 +
   1.349 +    sec = t % 60;
   1.350 +    t = (t - sec) / 60;
   1.351 +    min = t % 60;
   1.352 +    hour = t / 60;
   1.353 +
   1.354 +    appendAsciiDigits(hour, 2, str);
   1.355 +    appendAsciiDigits(min, 2, str);
   1.356 +    appendAsciiDigits(sec, 2, str);
   1.357 +}
   1.358 +
   1.359 +/*
   1.360 + * Create a default TZNAME from TZID
   1.361 + */
   1.362 +static void getDefaultTZName(const UnicodeString tzid, UBool isDST, UnicodeString& zonename) {
   1.363 +    zonename = tzid;
   1.364 +    if (isDST) {
   1.365 +        zonename += UNICODE_STRING_SIMPLE("(DST)");
   1.366 +    } else {
   1.367 +        zonename += UNICODE_STRING_SIMPLE("(STD)");
   1.368 +    }
   1.369 +}
   1.370 +
   1.371 +/*
   1.372 + * Parse individual RRULE
   1.373 + * 
   1.374 + * On return -
   1.375 + * 
   1.376 + * month    calculated by BYMONTH-1, or -1 when not found
   1.377 + * dow      day of week in BYDAY, or 0 when not found
   1.378 + * wim      day of week ordinal number in BYDAY, or 0 when not found
   1.379 + * dom      an array of day of month
   1.380 + * domCount number of availble days in dom (domCount is specifying the size of dom on input)
   1.381 + * until    time defined by UNTIL attribute or MIN_MILLIS if not available
   1.382 + */
   1.383 +static void parseRRULE(const UnicodeString& rrule, int32_t& month, int32_t& dow, int32_t& wim,
   1.384 +                       int32_t* dom, int32_t& domCount, UDate& until, UErrorCode& status) {
   1.385 +    if (U_FAILURE(status)) {
   1.386 +        return;
   1.387 +    }
   1.388 +    int32_t numDom = 0;
   1.389 +
   1.390 +    month = -1;
   1.391 +    dow = 0;
   1.392 +    wim = 0;
   1.393 +    until = MIN_MILLIS;
   1.394 +
   1.395 +    UBool yearly = FALSE;
   1.396 +    //UBool parseError = FALSE;
   1.397 +
   1.398 +    int32_t prop_start = 0;
   1.399 +    int32_t prop_end;
   1.400 +    UnicodeString prop, attr, value;
   1.401 +    UBool nextProp = TRUE;
   1.402 +
   1.403 +    while (nextProp) {
   1.404 +        prop_end = rrule.indexOf(SEMICOLON, prop_start);
   1.405 +        if (prop_end == -1) {
   1.406 +            prop.setTo(rrule, prop_start);
   1.407 +            nextProp = FALSE;
   1.408 +        } else {
   1.409 +            prop.setTo(rrule, prop_start, prop_end - prop_start);
   1.410 +            prop_start = prop_end + 1;
   1.411 +        }
   1.412 +        int32_t eql = prop.indexOf(EQUALS_SIGN);
   1.413 +        if (eql != -1) {
   1.414 +            attr.setTo(prop, 0, eql);
   1.415 +            value.setTo(prop, eql + 1);
   1.416 +        } else {
   1.417 +            goto rruleParseError;
   1.418 +        }
   1.419 +
   1.420 +        if (attr.compare(ICAL_FREQ, -1) == 0) {
   1.421 +            // only support YEARLY frequency type
   1.422 +            if (value.compare(ICAL_YEARLY, -1) == 0) {
   1.423 +                yearly = TRUE;
   1.424 +            } else {
   1.425 +                goto rruleParseError;
   1.426 +            }
   1.427 +        } else if (attr.compare(ICAL_UNTIL, -1) == 0) {
   1.428 +            // ISO8601 UTC format, for example, "20060315T020000Z"
   1.429 +            until = parseDateTimeString(value, 0, status);
   1.430 +            if (U_FAILURE(status)) {
   1.431 +                goto rruleParseError;
   1.432 +            }
   1.433 +        } else if (attr.compare(ICAL_BYMONTH, -1) == 0) {
   1.434 +            // Note: BYMONTH may contain multiple months, but only single month make sense for
   1.435 +            // VTIMEZONE property.
   1.436 +            if (value.length() > 2) {
   1.437 +                goto rruleParseError;
   1.438 +            }
   1.439 +            month = parseAsciiDigits(value, 0, value.length(), status) - 1;
   1.440 +            if (U_FAILURE(status) || month < 0 || month >= 12) {
   1.441 +                goto rruleParseError;
   1.442 +            }
   1.443 +        } else if (attr.compare(ICAL_BYDAY, -1) == 0) {
   1.444 +            // Note: BYDAY may contain multiple day of week separated by comma.  It is unlikely used for
   1.445 +            // VTIMEZONE property.  We do not support the case.
   1.446 +
   1.447 +            // 2-letter format is used just for representing a day of week, for example, "SU" for Sunday
   1.448 +            // 3 or 4-letter format is used for represeinging Nth day of week, for example, "-1SA" for last Saturday
   1.449 +            int32_t length = value.length();
   1.450 +            if (length < 2 || length > 4) {
   1.451 +                goto rruleParseError;
   1.452 +            }
   1.453 +            if (length > 2) {
   1.454 +                // Nth day of week
   1.455 +                int32_t sign = 1;
   1.456 +                if (value.charAt(0) == PLUS) {
   1.457 +                    sign = 1;
   1.458 +                } else if (value.charAt(0) == MINUS) {
   1.459 +                    sign = -1;
   1.460 +                } else if (length == 4) {
   1.461 +                    goto rruleParseError;
   1.462 +                }
   1.463 +                int32_t n = parseAsciiDigits(value, length - 3, 1, status);
   1.464 +                if (U_FAILURE(status) || n == 0 || n > 4) {
   1.465 +                    goto rruleParseError;
   1.466 +                }
   1.467 +                wim = n * sign;
   1.468 +                value.remove(0, length - 2);
   1.469 +            }
   1.470 +            int32_t wday;
   1.471 +            for (wday = 0; wday < 7; wday++) {
   1.472 +                if (value.compare(ICAL_DOW_NAMES[wday], 2) == 0) {
   1.473 +                    break;
   1.474 +                }
   1.475 +            }
   1.476 +            if (wday < 7) {
   1.477 +                // Sunday(1) - Saturday(7)
   1.478 +                dow = wday + 1;
   1.479 +            } else {
   1.480 +                goto rruleParseError;
   1.481 +            }
   1.482 +        } else if (attr.compare(ICAL_BYMONTHDAY, -1) == 0) {
   1.483 +            // Note: BYMONTHDAY may contain multiple days delimitted by comma
   1.484 +            //
   1.485 +            // A value of BYMONTHDAY could be negative, for example, -1 means
   1.486 +            // the last day in a month
   1.487 +            int32_t dom_idx = 0;
   1.488 +            int32_t dom_start = 0;
   1.489 +            int32_t dom_end;
   1.490 +            UBool nextDOM = TRUE;
   1.491 +            while (nextDOM) {
   1.492 +                dom_end = value.indexOf(COMMA, dom_start);
   1.493 +                if (dom_end == -1) {
   1.494 +                    dom_end = value.length();
   1.495 +                    nextDOM = FALSE;
   1.496 +                }
   1.497 +                if (dom_idx < domCount) {
   1.498 +                    dom[dom_idx] = parseAsciiDigits(value, dom_start, dom_end - dom_start, status);
   1.499 +                    if (U_FAILURE(status)) {
   1.500 +                        goto rruleParseError;
   1.501 +                    }
   1.502 +                    dom_idx++;
   1.503 +                } else {
   1.504 +                    status = U_BUFFER_OVERFLOW_ERROR;
   1.505 +                    goto rruleParseError;
   1.506 +                }
   1.507 +                dom_start = dom_end + 1;
   1.508 +            }
   1.509 +            numDom = dom_idx;
   1.510 +        }
   1.511 +    }
   1.512 +    if (!yearly) {
   1.513 +        // FREQ=YEARLY must be set
   1.514 +        goto rruleParseError;
   1.515 +    }
   1.516 +    // Set actual number of parsed DOM (ICAL_BYMONTHDAY)
   1.517 +    domCount = numDom;
   1.518 +    return;
   1.519 +
   1.520 +rruleParseError:
   1.521 +    if (U_SUCCESS(status)) {
   1.522 +        // Set error status
   1.523 +        status = U_INVALID_FORMAT_ERROR;
   1.524 +    }
   1.525 +}
   1.526 +
   1.527 +static TimeZoneRule* createRuleByRRULE(const UnicodeString& zonename, int rawOffset, int dstSavings, UDate start,
   1.528 +                                       UVector* dates, int fromOffset, UErrorCode& status) {
   1.529 +    if (U_FAILURE(status)) {
   1.530 +        return NULL;
   1.531 +    }
   1.532 +    if (dates == NULL || dates->size() == 0) {
   1.533 +        status = U_ILLEGAL_ARGUMENT_ERROR;
   1.534 +        return NULL;
   1.535 +    }
   1.536 +
   1.537 +    int32_t i, j;
   1.538 +    DateTimeRule *adtr = NULL;
   1.539 +
   1.540 +    // Parse the first rule
   1.541 +    UnicodeString rrule = *((UnicodeString*)dates->elementAt(0));
   1.542 +    int32_t month, dayOfWeek, nthDayOfWeek, dayOfMonth = 0;
   1.543 +    int32_t days[7];
   1.544 +    int32_t daysCount = sizeof(days)/sizeof(days[0]);
   1.545 +    UDate until;
   1.546 +
   1.547 +    parseRRULE(rrule, month, dayOfWeek, nthDayOfWeek, days, daysCount, until, status);
   1.548 +    if (U_FAILURE(status)) {
   1.549 +        return NULL;
   1.550 +    }
   1.551 +
   1.552 +    if (dates->size() == 1) {
   1.553 +        // No more rules
   1.554 +        if (daysCount > 1) {
   1.555 +            // Multiple BYMONTHDAY values
   1.556 +            if (daysCount != 7 || month == -1 || dayOfWeek == 0) {
   1.557 +                // Only support the rule using 7 continuous days
   1.558 +                // BYMONTH and BYDAY must be set at the same time
   1.559 +                goto unsupportedRRule;
   1.560 +            }
   1.561 +            int32_t firstDay = 31; // max possible number of dates in a month
   1.562 +            for (i = 0; i < 7; i++) {
   1.563 +                // Resolve negative day numbers.  A negative day number should
   1.564 +                // not be used in February, but if we see such case, we use 28
   1.565 +                // as the base.
   1.566 +                if (days[i] < 0) {
   1.567 +                    days[i] = MONTHLENGTH[month] + days[i] + 1;
   1.568 +                }
   1.569 +                if (days[i] < firstDay) {
   1.570 +                    firstDay = days[i];
   1.571 +                }
   1.572 +            }
   1.573 +            // Make sure days are continuous
   1.574 +            for (i = 1; i < 7; i++) {
   1.575 +                UBool found = FALSE;
   1.576 +                for (j = 0; j < 7; j++) {
   1.577 +                    if (days[j] == firstDay + i) {
   1.578 +                        found = TRUE;
   1.579 +                        break;
   1.580 +                    }
   1.581 +                }
   1.582 +                if (!found) {
   1.583 +                    // days are not continuous
   1.584 +                    goto unsupportedRRule;
   1.585 +                }
   1.586 +            }
   1.587 +            // Use DOW_GEQ_DOM rule with firstDay as the start date
   1.588 +            dayOfMonth = firstDay;
   1.589 +        }
   1.590 +    } else {
   1.591 +        // Check if BYMONTH + BYMONTHDAY + BYDAY rule with multiple RRULE lines.
   1.592 +        // Otherwise, not supported.
   1.593 +        if (month == -1 || dayOfWeek == 0 || daysCount == 0) {
   1.594 +            // This is not the case
   1.595 +            goto unsupportedRRule;
   1.596 +        }
   1.597 +        // Parse the rest of rules if number of rules is not exceeding 7.
   1.598 +        // We can only support 7 continuous days starting from a day of month.
   1.599 +        if (dates->size() > 7) {
   1.600 +            goto unsupportedRRule;
   1.601 +        }
   1.602 +
   1.603 +        // Note: To check valid date range across multiple rule is a little
   1.604 +        // bit complicated.  For now, this code is not doing strict range
   1.605 +        // checking across month boundary
   1.606 +
   1.607 +        int32_t earliestMonth = month;
   1.608 +        int32_t earliestDay = 31;
   1.609 +        for (i = 0; i < daysCount; i++) {
   1.610 +            int32_t dom = days[i];
   1.611 +            dom = dom > 0 ? dom : MONTHLENGTH[month] + dom + 1;
   1.612 +            earliestDay = dom < earliestDay ? dom : earliestDay;
   1.613 +        }
   1.614 +
   1.615 +        int32_t anotherMonth = -1;
   1.616 +        for (i = 1; i < dates->size(); i++) {
   1.617 +            rrule = *((UnicodeString*)dates->elementAt(i));
   1.618 +            UDate tmp_until;
   1.619 +            int32_t tmp_month, tmp_dayOfWeek, tmp_nthDayOfWeek;
   1.620 +            int32_t tmp_days[7];
   1.621 +            int32_t tmp_daysCount = sizeof(tmp_days)/sizeof(tmp_days[0]);
   1.622 +            parseRRULE(rrule, tmp_month, tmp_dayOfWeek, tmp_nthDayOfWeek, tmp_days, tmp_daysCount, tmp_until, status);
   1.623 +            if (U_FAILURE(status)) {
   1.624 +                return NULL;
   1.625 +            }
   1.626 +            // If UNTIL is newer than previous one, use the one
   1.627 +            if (tmp_until > until) {
   1.628 +                until = tmp_until;
   1.629 +            }
   1.630 +            
   1.631 +            // Check if BYMONTH + BYMONTHDAY + BYDAY rule
   1.632 +            if (tmp_month == -1 || tmp_dayOfWeek == 0 || tmp_daysCount == 0) {
   1.633 +                goto unsupportedRRule;
   1.634 +            }
   1.635 +            // Count number of BYMONTHDAY
   1.636 +            if (daysCount + tmp_daysCount > 7) {
   1.637 +                // We cannot support BYMONTHDAY more than 7
   1.638 +                goto unsupportedRRule;
   1.639 +            }
   1.640 +            // Check if the same BYDAY is used.  Otherwise, we cannot
   1.641 +            // support the rule
   1.642 +            if (tmp_dayOfWeek != dayOfWeek) {
   1.643 +                goto unsupportedRRule;
   1.644 +            }
   1.645 +            // Check if the month is same or right next to the primary month
   1.646 +            if (tmp_month != month) {
   1.647 +                if (anotherMonth == -1) {
   1.648 +                    int32_t diff = tmp_month - month;
   1.649 +                    if (diff == -11 || diff == -1) {
   1.650 +                        // Previous month
   1.651 +                        anotherMonth = tmp_month;
   1.652 +                        earliestMonth = anotherMonth;
   1.653 +                        // Reset earliest day
   1.654 +                        earliestDay = 31;
   1.655 +                    } else if (diff == 11 || diff == 1) {
   1.656 +                        // Next month
   1.657 +                        anotherMonth = tmp_month;
   1.658 +                    } else {
   1.659 +                        // The day range cannot exceed more than 2 months
   1.660 +                        goto unsupportedRRule;
   1.661 +                    }
   1.662 +                } else if (tmp_month != month && tmp_month != anotherMonth) {
   1.663 +                    // The day range cannot exceed more than 2 months
   1.664 +                    goto unsupportedRRule;
   1.665 +                }
   1.666 +            }
   1.667 +            // If ealier month, go through days to find the earliest day
   1.668 +            if (tmp_month == earliestMonth) {
   1.669 +                for (j = 0; j < tmp_daysCount; j++) {
   1.670 +                    tmp_days[j] = tmp_days[j] > 0 ? tmp_days[j] : MONTHLENGTH[tmp_month] + tmp_days[j] + 1;
   1.671 +                    earliestDay = tmp_days[j] < earliestDay ? tmp_days[j] : earliestDay;
   1.672 +                }
   1.673 +            }
   1.674 +            daysCount += tmp_daysCount;
   1.675 +        }
   1.676 +        if (daysCount != 7) {
   1.677 +            // Number of BYMONTHDAY entries must be 7
   1.678 +            goto unsupportedRRule;
   1.679 +        }
   1.680 +        month = earliestMonth;
   1.681 +        dayOfMonth = earliestDay;
   1.682 +    }
   1.683 +
   1.684 +    // Calculate start/end year and missing fields
   1.685 +    int32_t startYear, startMonth, startDOM, startDOW, startDOY, startMID;
   1.686 +    Grego::timeToFields(start + fromOffset, startYear, startMonth, startDOM,
   1.687 +        startDOW, startDOY, startMID);
   1.688 +    if (month == -1) {
   1.689 +        // If BYMONTH is not set, use the month of DTSTART
   1.690 +        month = startMonth;
   1.691 +    }
   1.692 +    if (dayOfWeek == 0 && nthDayOfWeek == 0 && dayOfMonth == 0) {
   1.693 +        // If only YEARLY is set, use the day of DTSTART as BYMONTHDAY
   1.694 +        dayOfMonth = startDOM;
   1.695 +    }
   1.696 +
   1.697 +    int32_t endYear;
   1.698 +    if (until != MIN_MILLIS) {
   1.699 +        int32_t endMonth, endDOM, endDOW, endDOY, endMID;
   1.700 +        Grego::timeToFields(until, endYear, endMonth, endDOM, endDOW, endDOY, endMID);
   1.701 +    } else {
   1.702 +        endYear = AnnualTimeZoneRule::MAX_YEAR;
   1.703 +    }
   1.704 +
   1.705 +    // Create the AnnualDateTimeRule
   1.706 +    if (dayOfWeek == 0 && nthDayOfWeek == 0 && dayOfMonth != 0) {
   1.707 +        // Day in month rule, for example, 15th day in the month
   1.708 +        adtr = new DateTimeRule(month, dayOfMonth, startMID, DateTimeRule::WALL_TIME);
   1.709 +    } else if (dayOfWeek != 0 && nthDayOfWeek != 0 && dayOfMonth == 0) {
   1.710 +        // Nth day of week rule, for example, last Sunday
   1.711 +        adtr = new DateTimeRule(month, nthDayOfWeek, dayOfWeek, startMID, DateTimeRule::WALL_TIME);
   1.712 +    } else if (dayOfWeek != 0 && nthDayOfWeek == 0 && dayOfMonth != 0) {
   1.713 +        // First day of week after day of month rule, for example,
   1.714 +        // first Sunday after 15th day in the month
   1.715 +        adtr = new DateTimeRule(month, dayOfMonth, dayOfWeek, TRUE, startMID, DateTimeRule::WALL_TIME);
   1.716 +    }
   1.717 +    if (adtr == NULL) {
   1.718 +        goto unsupportedRRule;
   1.719 +    }
   1.720 +    return new AnnualTimeZoneRule(zonename, rawOffset, dstSavings, adtr, startYear, endYear);
   1.721 +
   1.722 +unsupportedRRule:
   1.723 +    status = U_INVALID_STATE_ERROR;
   1.724 +    return NULL;
   1.725 +}
   1.726 +
   1.727 +/*
   1.728 + * Create a TimeZoneRule by the RDATE definition
   1.729 + */
   1.730 +static TimeZoneRule* createRuleByRDATE(const UnicodeString& zonename, int32_t rawOffset, int32_t dstSavings,
   1.731 +                                       UDate start, UVector* dates, int32_t fromOffset, UErrorCode& status) {
   1.732 +    if (U_FAILURE(status)) {
   1.733 +        return NULL;
   1.734 +    }
   1.735 +    TimeArrayTimeZoneRule *retVal = NULL;
   1.736 +    if (dates == NULL || dates->size() == 0) {
   1.737 +        // When no RDATE line is provided, use start (DTSTART)
   1.738 +        // as the transition time
   1.739 +        retVal = new TimeArrayTimeZoneRule(zonename, rawOffset, dstSavings,
   1.740 +            &start, 1, DateTimeRule::UTC_TIME);
   1.741 +    } else {
   1.742 +        // Create an array of transition times
   1.743 +        int32_t size = dates->size();
   1.744 +        UDate* times = (UDate*)uprv_malloc(sizeof(UDate) * size);
   1.745 +        if (times == NULL) {
   1.746 +            status = U_MEMORY_ALLOCATION_ERROR;
   1.747 +            return NULL;
   1.748 +        }
   1.749 +        for (int32_t i = 0; i < size; i++) {
   1.750 +            UnicodeString *datestr = (UnicodeString*)dates->elementAt(i);
   1.751 +            times[i] = parseDateTimeString(*datestr, fromOffset, status);
   1.752 +            if (U_FAILURE(status)) {
   1.753 +                uprv_free(times);
   1.754 +                return NULL;
   1.755 +            }
   1.756 +        }
   1.757 +        retVal = new TimeArrayTimeZoneRule(zonename, rawOffset, dstSavings,
   1.758 +            times, size, DateTimeRule::UTC_TIME);
   1.759 +        uprv_free(times);
   1.760 +    }
   1.761 +    return retVal;
   1.762 +}
   1.763 +
   1.764 +/*
   1.765 + * Check if the DOW rule specified by month, weekInMonth and dayOfWeek is equivalent
   1.766 + * to the DateTimerule.
   1.767 + */
   1.768 +static UBool isEquivalentDateRule(int32_t month, int32_t weekInMonth, int32_t dayOfWeek, const DateTimeRule *dtrule) {
   1.769 +    if (month != dtrule->getRuleMonth() || dayOfWeek != dtrule->getRuleDayOfWeek()) {
   1.770 +        return FALSE;
   1.771 +    }
   1.772 +    if (dtrule->getTimeRuleType() != DateTimeRule::WALL_TIME) {
   1.773 +        // Do not try to do more intelligent comparison for now.
   1.774 +        return FALSE;
   1.775 +    }
   1.776 +    if (dtrule->getDateRuleType() == DateTimeRule::DOW
   1.777 +            && dtrule->getRuleWeekInMonth() == weekInMonth) {
   1.778 +        return TRUE;
   1.779 +    }
   1.780 +    int32_t ruleDOM = dtrule->getRuleDayOfMonth();
   1.781 +    if (dtrule->getDateRuleType() == DateTimeRule::DOW_GEQ_DOM) {
   1.782 +        if (ruleDOM%7 == 1 && (ruleDOM + 6)/7 == weekInMonth) {
   1.783 +            return TRUE;
   1.784 +        }
   1.785 +        if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - ruleDOM)%7 == 6
   1.786 +                && weekInMonth == -1*((MONTHLENGTH[month]-ruleDOM+1)/7)) {
   1.787 +            return TRUE;
   1.788 +        }
   1.789 +    }
   1.790 +    if (dtrule->getDateRuleType() == DateTimeRule::DOW_LEQ_DOM) {
   1.791 +        if (ruleDOM%7 == 0 && ruleDOM/7 == weekInMonth) {
   1.792 +            return TRUE;
   1.793 +        }
   1.794 +        if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - ruleDOM)%7 == 0
   1.795 +                && weekInMonth == -1*((MONTHLENGTH[month] - ruleDOM)/7 + 1)) {
   1.796 +            return TRUE;
   1.797 +        }
   1.798 +    }
   1.799 +    return FALSE;
   1.800 +}
   1.801 +
   1.802 +/*
   1.803 + * Convert the rule to its equivalent rule using WALL_TIME mode.
   1.804 + * This function returns NULL when the specified DateTimeRule is already
   1.805 + * using WALL_TIME mode.
   1.806 + */
   1.807 +static DateTimeRule* toWallTimeRule(const DateTimeRule* rule, int32_t rawOffset, int32_t dstSavings) {
   1.808 +    if (rule->getTimeRuleType() == DateTimeRule::WALL_TIME) {
   1.809 +        return NULL;
   1.810 +    }
   1.811 +    int32_t wallt = rule->getRuleMillisInDay();
   1.812 +    if (rule->getTimeRuleType() == DateTimeRule::UTC_TIME) {
   1.813 +        wallt += (rawOffset + dstSavings);
   1.814 +    } else if (rule->getTimeRuleType() == DateTimeRule::STANDARD_TIME) {
   1.815 +        wallt += dstSavings;
   1.816 +    }
   1.817 +
   1.818 +    int32_t month = -1, dom = 0, dow = 0;
   1.819 +    DateTimeRule::DateRuleType dtype;
   1.820 +    int32_t dshift = 0;
   1.821 +    if (wallt < 0) {
   1.822 +        dshift = -1;
   1.823 +        wallt += U_MILLIS_PER_DAY;
   1.824 +    } else if (wallt >= U_MILLIS_PER_DAY) {
   1.825 +        dshift = 1;
   1.826 +        wallt -= U_MILLIS_PER_DAY;
   1.827 +    }
   1.828 +
   1.829 +    month = rule->getRuleMonth();
   1.830 +    dom = rule->getRuleDayOfMonth();
   1.831 +    dow = rule->getRuleDayOfWeek();
   1.832 +    dtype = rule->getDateRuleType();
   1.833 +
   1.834 +    if (dshift != 0) {
   1.835 +        if (dtype == DateTimeRule::DOW) {
   1.836 +            // Convert to DOW_GEW_DOM or DOW_LEQ_DOM rule first
   1.837 +            int32_t wim = rule->getRuleWeekInMonth();
   1.838 +            if (wim > 0) {
   1.839 +                dtype = DateTimeRule::DOW_GEQ_DOM;
   1.840 +                dom = 7 * (wim - 1) + 1;
   1.841 +            } else {
   1.842 +                dtype = DateTimeRule::DOW_LEQ_DOM;
   1.843 +                dom = MONTHLENGTH[month] + 7 * (wim + 1);
   1.844 +            }
   1.845 +        }
   1.846 +        // Shift one day before or after
   1.847 +        dom += dshift;
   1.848 +        if (dom == 0) {
   1.849 +            month--;
   1.850 +            month = month < UCAL_JANUARY ? UCAL_DECEMBER : month;
   1.851 +            dom = MONTHLENGTH[month];
   1.852 +        } else if (dom > MONTHLENGTH[month]) {
   1.853 +            month++;
   1.854 +            month = month > UCAL_DECEMBER ? UCAL_JANUARY : month;
   1.855 +            dom = 1;
   1.856 +        }
   1.857 +        if (dtype != DateTimeRule::DOM) {
   1.858 +            // Adjust day of week
   1.859 +            dow += dshift;
   1.860 +            if (dow < UCAL_SUNDAY) {
   1.861 +                dow = UCAL_SATURDAY;
   1.862 +            } else if (dow > UCAL_SATURDAY) {
   1.863 +                dow = UCAL_SUNDAY;
   1.864 +            }
   1.865 +        }
   1.866 +    }
   1.867 +    // Create a new rule
   1.868 +    DateTimeRule *modifiedRule;
   1.869 +    if (dtype == DateTimeRule::DOM) {
   1.870 +        modifiedRule = new DateTimeRule(month, dom, wallt, DateTimeRule::WALL_TIME);
   1.871 +    } else {
   1.872 +        modifiedRule = new DateTimeRule(month, dom, dow,
   1.873 +            (dtype == DateTimeRule::DOW_GEQ_DOM), wallt, DateTimeRule::WALL_TIME);
   1.874 +    }
   1.875 +    return modifiedRule;
   1.876 +}
   1.877 +
   1.878 +/*
   1.879 + * Minumum implementations of stream writer/reader, writing/reading
   1.880 + * UnicodeString.  For now, we do not want to introduce the dependency
   1.881 + * on the ICU I/O stream in this module.  But we want to keep the code
   1.882 + * equivalent to the ICU4J implementation, which utilizes java.io.Writer/
   1.883 + * Reader.
   1.884 + */
   1.885 +class VTZWriter {
   1.886 +public:
   1.887 +    VTZWriter(UnicodeString& out);
   1.888 +    ~VTZWriter();
   1.889 +
   1.890 +    void write(const UnicodeString& str);
   1.891 +    void write(UChar ch);
   1.892 +    void write(const UChar* str);
   1.893 +    //void write(const UChar* str, int32_t length);
   1.894 +private:
   1.895 +    UnicodeString* out;
   1.896 +};
   1.897 +
   1.898 +VTZWriter::VTZWriter(UnicodeString& output) {
   1.899 +    out = &output;
   1.900 +}
   1.901 +
   1.902 +VTZWriter::~VTZWriter() {
   1.903 +}
   1.904 +
   1.905 +void
   1.906 +VTZWriter::write(const UnicodeString& str) {
   1.907 +    out->append(str);
   1.908 +}
   1.909 +
   1.910 +void
   1.911 +VTZWriter::write(UChar ch) {
   1.912 +    out->append(ch);
   1.913 +}
   1.914 +
   1.915 +void
   1.916 +VTZWriter::write(const UChar* str) {
   1.917 +    out->append(str, -1);
   1.918 +}
   1.919 +
   1.920 +/*
   1.921 +void
   1.922 +VTZWriter::write(const UChar* str, int32_t length) {
   1.923 +    out->append(str, length);
   1.924 +}
   1.925 +*/
   1.926 +
   1.927 +class VTZReader {
   1.928 +public:
   1.929 +    VTZReader(const UnicodeString& input);
   1.930 +    ~VTZReader();
   1.931 +
   1.932 +    UChar read(void);
   1.933 +private:
   1.934 +    const UnicodeString* in;
   1.935 +    int32_t index;
   1.936 +};
   1.937 +
   1.938 +VTZReader::VTZReader(const UnicodeString& input) {
   1.939 +    in = &input;
   1.940 +    index = 0;
   1.941 +}
   1.942 +
   1.943 +VTZReader::~VTZReader() {
   1.944 +}
   1.945 +
   1.946 +UChar
   1.947 +VTZReader::read(void) {
   1.948 +    UChar ch = 0xFFFF;
   1.949 +    if (index < in->length()) {
   1.950 +        ch = in->charAt(index);
   1.951 +    }
   1.952 +    index++;
   1.953 +    return ch;
   1.954 +}
   1.955 +
   1.956 +
   1.957 +UOBJECT_DEFINE_RTTI_IMPLEMENTATION(VTimeZone)
   1.958 +
   1.959 +VTimeZone::VTimeZone()
   1.960 +:   BasicTimeZone(), tz(NULL), vtzlines(NULL),
   1.961 +    lastmod(MAX_MILLIS) {
   1.962 +}
   1.963 +
   1.964 +VTimeZone::VTimeZone(const VTimeZone& source)
   1.965 +:   BasicTimeZone(source), tz(NULL), vtzlines(NULL),
   1.966 +    tzurl(source.tzurl), lastmod(source.lastmod),
   1.967 +    olsonzid(source.olsonzid), icutzver(source.icutzver) {
   1.968 +    if (source.tz != NULL) {
   1.969 +        tz = (BasicTimeZone*)source.tz->clone();
   1.970 +    }
   1.971 +    if (source.vtzlines != NULL) {
   1.972 +        UErrorCode status = U_ZERO_ERROR;
   1.973 +        int32_t size = source.vtzlines->size();
   1.974 +        vtzlines = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, size, status);
   1.975 +        if (U_SUCCESS(status)) {
   1.976 +            for (int32_t i = 0; i < size; i++) {
   1.977 +                UnicodeString *line = (UnicodeString*)source.vtzlines->elementAt(i);
   1.978 +                vtzlines->addElement(line->clone(), status);
   1.979 +                if (U_FAILURE(status)) {
   1.980 +                    break;
   1.981 +                }
   1.982 +            }
   1.983 +        }
   1.984 +        if (U_FAILURE(status) && vtzlines != NULL) {
   1.985 +            delete vtzlines;
   1.986 +        }
   1.987 +    }
   1.988 +}
   1.989 +
   1.990 +VTimeZone::~VTimeZone() {
   1.991 +    if (tz != NULL) {
   1.992 +        delete tz;
   1.993 +    }
   1.994 +    if (vtzlines != NULL) {
   1.995 +        delete vtzlines;
   1.996 +    }
   1.997 +}
   1.998 +
   1.999 +VTimeZone&
  1.1000 +VTimeZone::operator=(const VTimeZone& right) {
  1.1001 +    if (this == &right) {
  1.1002 +        return *this;
  1.1003 +    }
  1.1004 +    if (*this != right) {
  1.1005 +        BasicTimeZone::operator=(right);
  1.1006 +        if (tz != NULL) {
  1.1007 +            delete tz;
  1.1008 +            tz = NULL;
  1.1009 +        }
  1.1010 +        if (right.tz != NULL) {
  1.1011 +            tz = (BasicTimeZone*)right.tz->clone();
  1.1012 +        }
  1.1013 +        if (vtzlines != NULL) {
  1.1014 +            delete vtzlines;
  1.1015 +        }
  1.1016 +        if (right.vtzlines != NULL) {
  1.1017 +            UErrorCode status = U_ZERO_ERROR;
  1.1018 +            int32_t size = right.vtzlines->size();
  1.1019 +            vtzlines = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, size, status);
  1.1020 +            if (U_SUCCESS(status)) {
  1.1021 +                for (int32_t i = 0; i < size; i++) {
  1.1022 +                    UnicodeString *line = (UnicodeString*)right.vtzlines->elementAt(i);
  1.1023 +                    vtzlines->addElement(line->clone(), status);
  1.1024 +                    if (U_FAILURE(status)) {
  1.1025 +                        break;
  1.1026 +                    }
  1.1027 +                }
  1.1028 +            }
  1.1029 +            if (U_FAILURE(status) && vtzlines != NULL) {
  1.1030 +                delete vtzlines;
  1.1031 +                vtzlines = NULL;
  1.1032 +            }
  1.1033 +        }
  1.1034 +        tzurl = right.tzurl;
  1.1035 +        lastmod = right.lastmod;
  1.1036 +        olsonzid = right.olsonzid;
  1.1037 +        icutzver = right.icutzver;
  1.1038 +    }
  1.1039 +    return *this;
  1.1040 +}
  1.1041 +
  1.1042 +UBool
  1.1043 +VTimeZone::operator==(const TimeZone& that) const {
  1.1044 +    if (this == &that) {
  1.1045 +        return TRUE;
  1.1046 +    }
  1.1047 +    if (typeid(*this) != typeid(that) || !BasicTimeZone::operator==(that)) {
  1.1048 +        return FALSE;
  1.1049 +    }
  1.1050 +    VTimeZone *vtz = (VTimeZone*)&that;
  1.1051 +    if (*tz == *(vtz->tz)
  1.1052 +        && tzurl == vtz->tzurl
  1.1053 +        && lastmod == vtz->lastmod
  1.1054 +        /* && olsonzid = that.olsonzid */
  1.1055 +        /* && icutzver = that.icutzver */) {
  1.1056 +        return TRUE;
  1.1057 +    }
  1.1058 +    return FALSE;
  1.1059 +}
  1.1060 +
  1.1061 +UBool
  1.1062 +VTimeZone::operator!=(const TimeZone& that) const {
  1.1063 +    return !operator==(that);
  1.1064 +}
  1.1065 +
  1.1066 +VTimeZone*
  1.1067 +VTimeZone::createVTimeZoneByID(const UnicodeString& ID) {
  1.1068 +    VTimeZone *vtz = new VTimeZone();
  1.1069 +    vtz->tz = (BasicTimeZone*)TimeZone::createTimeZone(ID);
  1.1070 +    vtz->tz->getID(vtz->olsonzid);
  1.1071 +
  1.1072 +    // Set ICU tzdata version
  1.1073 +    UErrorCode status = U_ZERO_ERROR;
  1.1074 +    UResourceBundle *bundle = NULL;
  1.1075 +    const UChar* versionStr = NULL;
  1.1076 +    int32_t len = 0;
  1.1077 +    bundle = ures_openDirect(NULL, "zoneinfo64", &status);
  1.1078 +    versionStr = ures_getStringByKey(bundle, "TZVersion", &len, &status);
  1.1079 +    if (U_SUCCESS(status)) {
  1.1080 +        vtz->icutzver.setTo(versionStr, len);
  1.1081 +    }
  1.1082 +    ures_close(bundle);
  1.1083 +    return vtz;
  1.1084 +}
  1.1085 +
  1.1086 +VTimeZone*
  1.1087 +VTimeZone::createVTimeZoneFromBasicTimeZone(const BasicTimeZone& basic_time_zone, UErrorCode &status) {
  1.1088 +    if (U_FAILURE(status)) {
  1.1089 +        return NULL;
  1.1090 +    }
  1.1091 +    VTimeZone *vtz = new VTimeZone();
  1.1092 +    if (vtz == NULL) {
  1.1093 +        status = U_MEMORY_ALLOCATION_ERROR;
  1.1094 +        return NULL;
  1.1095 +    }
  1.1096 +    vtz->tz = (BasicTimeZone *)basic_time_zone.clone();
  1.1097 +    if (vtz->tz == NULL) {
  1.1098 +        status = U_MEMORY_ALLOCATION_ERROR;
  1.1099 +        delete vtz;
  1.1100 +        return NULL;
  1.1101 +    }
  1.1102 +    vtz->tz->getID(vtz->olsonzid);
  1.1103 +
  1.1104 +    // Set ICU tzdata version
  1.1105 +    UResourceBundle *bundle = NULL;
  1.1106 +    const UChar* versionStr = NULL;
  1.1107 +    int32_t len = 0;
  1.1108 +    bundle = ures_openDirect(NULL, "zoneinfo64", &status);
  1.1109 +    versionStr = ures_getStringByKey(bundle, "TZVersion", &len, &status);
  1.1110 +    if (U_SUCCESS(status)) {
  1.1111 +        vtz->icutzver.setTo(versionStr, len);
  1.1112 +    }
  1.1113 +    ures_close(bundle);
  1.1114 +    return vtz;
  1.1115 +}
  1.1116 +
  1.1117 +VTimeZone*
  1.1118 +VTimeZone::createVTimeZone(const UnicodeString& vtzdata, UErrorCode& status) {
  1.1119 +    if (U_FAILURE(status)) {
  1.1120 +        return NULL;
  1.1121 +    }
  1.1122 +    VTZReader reader(vtzdata);
  1.1123 +    VTimeZone *vtz = new VTimeZone();
  1.1124 +    vtz->load(reader, status);
  1.1125 +    if (U_FAILURE(status)) {
  1.1126 +        delete vtz;
  1.1127 +        return NULL;
  1.1128 +    }
  1.1129 +    return vtz;
  1.1130 +}
  1.1131 +
  1.1132 +UBool
  1.1133 +VTimeZone::getTZURL(UnicodeString& url) const {
  1.1134 +    if (tzurl.length() > 0) {
  1.1135 +        url = tzurl;
  1.1136 +        return TRUE;
  1.1137 +    }
  1.1138 +    return FALSE;
  1.1139 +}
  1.1140 +
  1.1141 +void
  1.1142 +VTimeZone::setTZURL(const UnicodeString& url) {
  1.1143 +    tzurl = url;
  1.1144 +}
  1.1145 +
  1.1146 +UBool
  1.1147 +VTimeZone::getLastModified(UDate& lastModified) const {
  1.1148 +    if (lastmod != MAX_MILLIS) {
  1.1149 +        lastModified = lastmod;
  1.1150 +        return TRUE;
  1.1151 +    }
  1.1152 +    return FALSE;
  1.1153 +}
  1.1154 +
  1.1155 +void
  1.1156 +VTimeZone::setLastModified(UDate lastModified) {
  1.1157 +    lastmod = lastModified;
  1.1158 +}
  1.1159 +
  1.1160 +void
  1.1161 +VTimeZone::write(UnicodeString& result, UErrorCode& status) const {
  1.1162 +    result.remove();
  1.1163 +    VTZWriter writer(result);
  1.1164 +    write(writer, status);
  1.1165 +}
  1.1166 +
  1.1167 +void
  1.1168 +VTimeZone::write(UDate start, UnicodeString& result, UErrorCode& status) const {
  1.1169 +    result.remove();
  1.1170 +    VTZWriter writer(result);
  1.1171 +    write(start, writer, status);
  1.1172 +}
  1.1173 +
  1.1174 +void
  1.1175 +VTimeZone::writeSimple(UDate time, UnicodeString& result, UErrorCode& status) const {
  1.1176 +    result.remove();
  1.1177 +    VTZWriter writer(result);
  1.1178 +    writeSimple(time, writer, status);
  1.1179 +}
  1.1180 +
  1.1181 +TimeZone*
  1.1182 +VTimeZone::clone(void) const {
  1.1183 +    return new VTimeZone(*this);
  1.1184 +}
  1.1185 +
  1.1186 +int32_t
  1.1187 +VTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day,
  1.1188 +                     uint8_t dayOfWeek, int32_t millis, UErrorCode& status) const {
  1.1189 +    return tz->getOffset(era, year, month, day, dayOfWeek, millis, status);
  1.1190 +}
  1.1191 +
  1.1192 +int32_t
  1.1193 +VTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day,
  1.1194 +                     uint8_t dayOfWeek, int32_t millis,
  1.1195 +                     int32_t monthLength, UErrorCode& status) const {
  1.1196 +    return tz->getOffset(era, year, month, day, dayOfWeek, millis, monthLength, status);
  1.1197 +}
  1.1198 +
  1.1199 +void
  1.1200 +VTimeZone::getOffset(UDate date, UBool local, int32_t& rawOffset,
  1.1201 +                     int32_t& dstOffset, UErrorCode& status) const {
  1.1202 +    return tz->getOffset(date, local, rawOffset, dstOffset, status);
  1.1203 +}
  1.1204 +
  1.1205 +void
  1.1206 +VTimeZone::setRawOffset(int32_t offsetMillis) {
  1.1207 +    tz->setRawOffset(offsetMillis);
  1.1208 +}
  1.1209 +
  1.1210 +int32_t
  1.1211 +VTimeZone::getRawOffset(void) const {
  1.1212 +    return tz->getRawOffset();
  1.1213 +}
  1.1214 +
  1.1215 +UBool
  1.1216 +VTimeZone::useDaylightTime(void) const {
  1.1217 +    return tz->useDaylightTime();
  1.1218 +}
  1.1219 +
  1.1220 +UBool
  1.1221 +VTimeZone::inDaylightTime(UDate date, UErrorCode& status) const {
  1.1222 +    return tz->inDaylightTime(date, status);
  1.1223 +}
  1.1224 +
  1.1225 +UBool
  1.1226 +VTimeZone::hasSameRules(const TimeZone& other) const {
  1.1227 +    return tz->hasSameRules(other);
  1.1228 +}
  1.1229 +
  1.1230 +UBool
  1.1231 +VTimeZone::getNextTransition(UDate base, UBool inclusive, TimeZoneTransition& result) const {
  1.1232 +    return tz->getNextTransition(base, inclusive, result);
  1.1233 +}
  1.1234 +
  1.1235 +UBool
  1.1236 +VTimeZone::getPreviousTransition(UDate base, UBool inclusive, TimeZoneTransition& result) const {
  1.1237 +    return tz->getPreviousTransition(base, inclusive, result);
  1.1238 +}
  1.1239 +
  1.1240 +int32_t
  1.1241 +VTimeZone::countTransitionRules(UErrorCode& status) const {
  1.1242 +    return tz->countTransitionRules(status);
  1.1243 +}
  1.1244 +
  1.1245 +void
  1.1246 +VTimeZone::getTimeZoneRules(const InitialTimeZoneRule*& initial,
  1.1247 +                            const TimeZoneRule* trsrules[], int32_t& trscount,
  1.1248 +                            UErrorCode& status) const {
  1.1249 +    tz->getTimeZoneRules(initial, trsrules, trscount, status);
  1.1250 +}
  1.1251 +
  1.1252 +void
  1.1253 +VTimeZone::load(VTZReader& reader, UErrorCode& status) {
  1.1254 +    vtzlines = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, DEFAULT_VTIMEZONE_LINES, status);
  1.1255 +    if (U_FAILURE(status)) {
  1.1256 +        return;
  1.1257 +    }
  1.1258 +    UBool eol = FALSE;
  1.1259 +    UBool start = FALSE;
  1.1260 +    UBool success = FALSE;
  1.1261 +    UnicodeString line;
  1.1262 +
  1.1263 +    while (TRUE) {
  1.1264 +        UChar ch = reader.read();
  1.1265 +        if (ch == 0xFFFF) {
  1.1266 +            // end of file
  1.1267 +            if (start && line.startsWith(ICAL_END_VTIMEZONE, -1)) {
  1.1268 +                vtzlines->addElement(new UnicodeString(line), status);
  1.1269 +                if (U_FAILURE(status)) {
  1.1270 +                    goto cleanupVtzlines;
  1.1271 +                }
  1.1272 +                success = TRUE;
  1.1273 +            }
  1.1274 +            break;
  1.1275 +        }
  1.1276 +        if (ch == 0x000D) {
  1.1277 +            // CR, must be followed by LF according to the definition in RFC2445
  1.1278 +            continue;
  1.1279 +        }
  1.1280 +        if (eol) {
  1.1281 +            if (ch != 0x0009 && ch != 0x0020) {
  1.1282 +                // NOT followed by TAB/SP -> new line
  1.1283 +                if (start) {
  1.1284 +                    if (line.length() > 0) {
  1.1285 +                        vtzlines->addElement(new UnicodeString(line), status);
  1.1286 +                        if (U_FAILURE(status)) {
  1.1287 +                            goto cleanupVtzlines;
  1.1288 +                        }
  1.1289 +                    }
  1.1290 +                }
  1.1291 +                line.remove();
  1.1292 +                if (ch != 0x000A) {
  1.1293 +                    line.append(ch);
  1.1294 +                }
  1.1295 +            }
  1.1296 +            eol = FALSE;
  1.1297 +        } else {
  1.1298 +            if (ch == 0x000A) {
  1.1299 +                // LF
  1.1300 +                eol = TRUE;
  1.1301 +                if (start) {
  1.1302 +                    if (line.startsWith(ICAL_END_VTIMEZONE, -1)) {
  1.1303 +                        vtzlines->addElement(new UnicodeString(line), status);
  1.1304 +                        if (U_FAILURE(status)) {
  1.1305 +                            goto cleanupVtzlines;
  1.1306 +                        }
  1.1307 +                        success = TRUE;
  1.1308 +                        break;
  1.1309 +                    }
  1.1310 +                } else {
  1.1311 +                    if (line.startsWith(ICAL_BEGIN_VTIMEZONE, -1)) {
  1.1312 +                        vtzlines->addElement(new UnicodeString(line), status);
  1.1313 +                        if (U_FAILURE(status)) {
  1.1314 +                            goto cleanupVtzlines;
  1.1315 +                        }
  1.1316 +                        line.remove();
  1.1317 +                        start = TRUE;
  1.1318 +                        eol = FALSE;
  1.1319 +                    }
  1.1320 +                }
  1.1321 +            } else {
  1.1322 +                line.append(ch);
  1.1323 +            }
  1.1324 +        }
  1.1325 +    }
  1.1326 +    if (!success) {
  1.1327 +        if (U_SUCCESS(status)) {
  1.1328 +            status = U_INVALID_STATE_ERROR;
  1.1329 +        }
  1.1330 +        goto cleanupVtzlines;
  1.1331 +    }
  1.1332 +    parse(status);
  1.1333 +    return;
  1.1334 +
  1.1335 +cleanupVtzlines:
  1.1336 +    delete vtzlines;
  1.1337 +    vtzlines = NULL;
  1.1338 +}
  1.1339 +
  1.1340 +// parser state
  1.1341 +#define INI 0   // Initial state
  1.1342 +#define VTZ 1   // In VTIMEZONE
  1.1343 +#define TZI 2   // In STANDARD or DAYLIGHT
  1.1344 +
  1.1345 +#define DEF_DSTSAVINGS (60*60*1000)
  1.1346 +#define DEF_TZSTARTTIME (0.0)
  1.1347 +
  1.1348 +void
  1.1349 +VTimeZone::parse(UErrorCode& status) {
  1.1350 +    if (U_FAILURE(status)) {
  1.1351 +        return;
  1.1352 +    }
  1.1353 +    if (vtzlines == NULL || vtzlines->size() == 0) {
  1.1354 +        status = U_INVALID_STATE_ERROR;
  1.1355 +        return;
  1.1356 +    }
  1.1357 +    InitialTimeZoneRule *initialRule = NULL;
  1.1358 +    RuleBasedTimeZone *rbtz = NULL;
  1.1359 +
  1.1360 +    // timezone ID
  1.1361 +    UnicodeString tzid;
  1.1362 +
  1.1363 +    int32_t state = INI;
  1.1364 +    int32_t n = 0;
  1.1365 +    UBool dst = FALSE;      // current zone type
  1.1366 +    UnicodeString from;     // current zone from offset
  1.1367 +    UnicodeString to;       // current zone offset
  1.1368 +    UnicodeString zonename;   // current zone name
  1.1369 +    UnicodeString dtstart;  // current zone starts
  1.1370 +    UBool isRRULE = FALSE;  // true if the rule is described by RRULE
  1.1371 +    int32_t initialRawOffset = 0;   // initial offset
  1.1372 +    int32_t initialDSTSavings = 0;  // initial offset
  1.1373 +    UDate firstStart = MAX_MILLIS;  // the earliest rule start time
  1.1374 +    UnicodeString name;     // RFC2445 prop name
  1.1375 +    UnicodeString value;    // RFC2445 prop value
  1.1376 +
  1.1377 +    UVector *dates = NULL;  // list of RDATE or RRULE strings
  1.1378 +    UVector *rules = NULL;  // list of TimeZoneRule instances
  1.1379 +
  1.1380 +    int32_t finalRuleIdx = -1;
  1.1381 +    int32_t finalRuleCount = 0;
  1.1382 +
  1.1383 +    rules = new UVector(status);
  1.1384 +    if (U_FAILURE(status)) {
  1.1385 +        goto cleanupParse;
  1.1386 +    }
  1.1387 +     // Set the deleter to remove TimeZoneRule vectors to avoid memory leaks due to unowned TimeZoneRules.
  1.1388 +    rules->setDeleter(deleteTimeZoneRule);
  1.1389 +    
  1.1390 +    dates = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, status);
  1.1391 +    if (U_FAILURE(status)) {
  1.1392 +        goto cleanupParse;
  1.1393 +    }
  1.1394 +    if (rules == NULL || dates == NULL) {
  1.1395 +        status = U_MEMORY_ALLOCATION_ERROR;
  1.1396 +        goto cleanupParse;
  1.1397 +    }
  1.1398 +
  1.1399 +    for (n = 0; n < vtzlines->size(); n++) {
  1.1400 +        UnicodeString *line = (UnicodeString*)vtzlines->elementAt(n);
  1.1401 +        int32_t valueSep = line->indexOf(COLON);
  1.1402 +        if (valueSep < 0) {
  1.1403 +            continue;
  1.1404 +        }
  1.1405 +        name.setTo(*line, 0, valueSep);
  1.1406 +        value.setTo(*line, valueSep + 1);
  1.1407 +
  1.1408 +        switch (state) {
  1.1409 +        case INI:
  1.1410 +            if (name.compare(ICAL_BEGIN, -1) == 0
  1.1411 +                && value.compare(ICAL_VTIMEZONE, -1) == 0) {
  1.1412 +                state = VTZ;
  1.1413 +            }
  1.1414 +            break;
  1.1415 +
  1.1416 +        case VTZ:
  1.1417 +            if (name.compare(ICAL_TZID, -1) == 0) {
  1.1418 +                tzid = value;
  1.1419 +            } else if (name.compare(ICAL_TZURL, -1) == 0) {
  1.1420 +                tzurl = value;
  1.1421 +            } else if (name.compare(ICAL_LASTMOD, -1) == 0) {
  1.1422 +                // Always in 'Z' format, so the offset argument for the parse method
  1.1423 +                // can be any value.
  1.1424 +                lastmod = parseDateTimeString(value, 0, status);
  1.1425 +                if (U_FAILURE(status)) {
  1.1426 +                    goto cleanupParse;
  1.1427 +                }
  1.1428 +            } else if (name.compare(ICAL_BEGIN, -1) == 0) {
  1.1429 +                UBool isDST = (value.compare(ICAL_DAYLIGHT, -1) == 0);
  1.1430 +                if (value.compare(ICAL_STANDARD, -1) == 0 || isDST) {
  1.1431 +                    // tzid must be ready at this point
  1.1432 +                    if (tzid.length() == 0) {
  1.1433 +                        goto cleanupParse;
  1.1434 +                    }
  1.1435 +                    // initialize current zone properties
  1.1436 +                    if (dates->size() != 0) {
  1.1437 +                        dates->removeAllElements();
  1.1438 +                    }
  1.1439 +                    isRRULE = FALSE;
  1.1440 +                    from.remove();
  1.1441 +                    to.remove();
  1.1442 +                    zonename.remove();
  1.1443 +                    dst = isDST;
  1.1444 +                    state = TZI;
  1.1445 +                } else {
  1.1446 +                    // BEGIN property other than STANDARD/DAYLIGHT
  1.1447 +                    // must not be there.
  1.1448 +                    goto cleanupParse;
  1.1449 +                }
  1.1450 +            } else if (name.compare(ICAL_END, -1) == 0) {
  1.1451 +                break;
  1.1452 +            }
  1.1453 +            break;
  1.1454 +        case TZI:
  1.1455 +            if (name.compare(ICAL_DTSTART, -1) == 0) {
  1.1456 +                dtstart = value;
  1.1457 +            } else if (name.compare(ICAL_TZNAME, -1) == 0) {
  1.1458 +                zonename = value;
  1.1459 +            } else if (name.compare(ICAL_TZOFFSETFROM, -1) == 0) {
  1.1460 +                from = value;
  1.1461 +            } else if (name.compare(ICAL_TZOFFSETTO, -1) == 0) {
  1.1462 +                to = value;
  1.1463 +            } else if (name.compare(ICAL_RDATE, -1) == 0) {
  1.1464 +                // RDATE mixed with RRULE is not supported
  1.1465 +                if (isRRULE) {
  1.1466 +                    goto cleanupParse;
  1.1467 +                }
  1.1468 +                // RDATE value may contain multiple date delimited
  1.1469 +                // by comma
  1.1470 +                UBool nextDate = TRUE;
  1.1471 +                int32_t dstart = 0;
  1.1472 +                UnicodeString *dstr;
  1.1473 +                while (nextDate) {
  1.1474 +                    int32_t dend = value.indexOf(COMMA, dstart);
  1.1475 +                    if (dend == -1) {
  1.1476 +                        dstr = new UnicodeString(value, dstart);
  1.1477 +                        nextDate = FALSE;
  1.1478 +                    } else {
  1.1479 +                        dstr = new UnicodeString(value, dstart, dend - dstart);
  1.1480 +                    }
  1.1481 +                    dates->addElement(dstr, status);
  1.1482 +                    if (U_FAILURE(status)) {
  1.1483 +                        goto cleanupParse;
  1.1484 +                    }
  1.1485 +                    dstart = dend + 1;
  1.1486 +                }
  1.1487 +            } else if (name.compare(ICAL_RRULE, -1) == 0) {
  1.1488 +                // RRULE mixed with RDATE is not supported
  1.1489 +                if (!isRRULE && dates->size() != 0) {
  1.1490 +                    goto cleanupParse;
  1.1491 +                }
  1.1492 +                isRRULE = true;
  1.1493 +                dates->addElement(new UnicodeString(value), status);
  1.1494 +                if (U_FAILURE(status)) {
  1.1495 +                    goto cleanupParse;
  1.1496 +                }
  1.1497 +            } else if (name.compare(ICAL_END, -1) == 0) {
  1.1498 +                // Mandatory properties
  1.1499 +                if (dtstart.length() == 0 || from.length() == 0 || to.length() == 0) {
  1.1500 +                    goto cleanupParse;
  1.1501 +                }
  1.1502 +                // if zonename is not available, create one from tzid
  1.1503 +                if (zonename.length() == 0) {
  1.1504 +                    getDefaultTZName(tzid, dst, zonename);
  1.1505 +                }
  1.1506 +
  1.1507 +                // create a time zone rule
  1.1508 +                TimeZoneRule *rule = NULL;
  1.1509 +                int32_t fromOffset = 0;
  1.1510 +                int32_t toOffset = 0;
  1.1511 +                int32_t rawOffset = 0;
  1.1512 +                int32_t dstSavings = 0;
  1.1513 +                UDate start = 0;
  1.1514 +
  1.1515 +                // Parse TZOFFSETFROM/TZOFFSETTO
  1.1516 +                fromOffset = offsetStrToMillis(from, status);
  1.1517 +                toOffset = offsetStrToMillis(to, status);
  1.1518 +                if (U_FAILURE(status)) {
  1.1519 +                    goto cleanupParse;
  1.1520 +                }
  1.1521 +
  1.1522 +                if (dst) {
  1.1523 +                    // If daylight, use the previous offset as rawoffset if positive
  1.1524 +                    if (toOffset - fromOffset > 0) {
  1.1525 +                        rawOffset = fromOffset;
  1.1526 +                        dstSavings = toOffset - fromOffset;
  1.1527 +                    } else {
  1.1528 +                        // This is rare case..  just use 1 hour DST savings
  1.1529 +                        rawOffset = toOffset - DEF_DSTSAVINGS;
  1.1530 +                        dstSavings = DEF_DSTSAVINGS;                                
  1.1531 +                    }
  1.1532 +                } else {
  1.1533 +                    rawOffset = toOffset;
  1.1534 +                    dstSavings = 0;
  1.1535 +                }
  1.1536 +
  1.1537 +                // start time
  1.1538 +                start = parseDateTimeString(dtstart, fromOffset, status);
  1.1539 +                if (U_FAILURE(status)) {
  1.1540 +                    goto cleanupParse;
  1.1541 +                }
  1.1542 +
  1.1543 +                // Create the rule
  1.1544 +                UDate actualStart = MAX_MILLIS;
  1.1545 +                if (isRRULE) {
  1.1546 +                    rule = createRuleByRRULE(zonename, rawOffset, dstSavings, start, dates, fromOffset, status);
  1.1547 +                } else {
  1.1548 +                    rule = createRuleByRDATE(zonename, rawOffset, dstSavings, start, dates, fromOffset, status);
  1.1549 +                }
  1.1550 +                if (U_FAILURE(status) || rule == NULL) {
  1.1551 +                    goto cleanupParse;
  1.1552 +                } else {
  1.1553 +                    UBool startAvail = rule->getFirstStart(fromOffset, 0, actualStart);
  1.1554 +                    if (startAvail && actualStart < firstStart) {
  1.1555 +                        // save from offset information for the earliest rule
  1.1556 +                        firstStart = actualStart;
  1.1557 +                        // If this is STD, assume the time before this transtion
  1.1558 +                        // is DST when the difference is 1 hour.  This might not be
  1.1559 +                        // accurate, but VTIMEZONE data does not have such info.
  1.1560 +                        if (dstSavings > 0) {
  1.1561 +                            initialRawOffset = fromOffset;
  1.1562 +                            initialDSTSavings = 0;
  1.1563 +                        } else {
  1.1564 +                            if (fromOffset - toOffset == DEF_DSTSAVINGS) {
  1.1565 +                                initialRawOffset = fromOffset - DEF_DSTSAVINGS;
  1.1566 +                                initialDSTSavings = DEF_DSTSAVINGS;
  1.1567 +                            } else {
  1.1568 +                                initialRawOffset = fromOffset;
  1.1569 +                                initialDSTSavings = 0;
  1.1570 +                            }
  1.1571 +                        }
  1.1572 +                    }
  1.1573 +                }
  1.1574 +                rules->addElement(rule, status);
  1.1575 +                if (U_FAILURE(status)) {
  1.1576 +                    goto cleanupParse;
  1.1577 +                }
  1.1578 +                state = VTZ;
  1.1579 +            }
  1.1580 +            break;
  1.1581 +        }
  1.1582 +    }
  1.1583 +    // Must have at least one rule
  1.1584 +    if (rules->size() == 0) {
  1.1585 +        goto cleanupParse;
  1.1586 +    }
  1.1587 +
  1.1588 +    // Create a initial rule
  1.1589 +    getDefaultTZName(tzid, FALSE, zonename);
  1.1590 +    initialRule = new InitialTimeZoneRule(zonename,
  1.1591 +        initialRawOffset, initialDSTSavings);
  1.1592 +    if (initialRule == NULL) {
  1.1593 +        status = U_MEMORY_ALLOCATION_ERROR;
  1.1594 +        goto cleanupParse;
  1.1595 +    }
  1.1596 +
  1.1597 +    // Finally, create the RuleBasedTimeZone
  1.1598 +    rbtz = new RuleBasedTimeZone(tzid, initialRule);
  1.1599 +    if (rbtz == NULL) {
  1.1600 +        status = U_MEMORY_ALLOCATION_ERROR;
  1.1601 +        goto cleanupParse;
  1.1602 +    }
  1.1603 +    initialRule = NULL; // already adopted by RBTZ, no need to delete
  1.1604 +
  1.1605 +    for (n = 0; n < rules->size(); n++) {
  1.1606 +        TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n);
  1.1607 +        AnnualTimeZoneRule *atzrule = dynamic_cast<AnnualTimeZoneRule *>(r);
  1.1608 +        if (atzrule != NULL) {
  1.1609 +            if (atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR) {
  1.1610 +                finalRuleCount++;
  1.1611 +                finalRuleIdx = n;
  1.1612 +            }
  1.1613 +        }
  1.1614 +    }
  1.1615 +    if (finalRuleCount > 2) {
  1.1616 +        // Too many final rules
  1.1617 +        status = U_ILLEGAL_ARGUMENT_ERROR;
  1.1618 +        goto cleanupParse;
  1.1619 +    }
  1.1620 +
  1.1621 +    if (finalRuleCount == 1) {
  1.1622 +        if (rules->size() == 1) {
  1.1623 +            // Only one final rule, only governs the initial rule,
  1.1624 +            // which is already initialized, thus, we do not need to
  1.1625 +            // add this transition rule
  1.1626 +            rules->removeAllElements();
  1.1627 +        } else {
  1.1628 +            // Normalize the final rule
  1.1629 +            AnnualTimeZoneRule *finalRule = (AnnualTimeZoneRule*)rules->elementAt(finalRuleIdx);
  1.1630 +            int32_t tmpRaw = finalRule->getRawOffset();
  1.1631 +            int32_t tmpDST = finalRule->getDSTSavings();
  1.1632 +
  1.1633 +            // Find the last non-final rule
  1.1634 +            UDate finalStart, start;
  1.1635 +            finalRule->getFirstStart(initialRawOffset, initialDSTSavings, finalStart);
  1.1636 +            start = finalStart;
  1.1637 +            for (n = 0; n < rules->size(); n++) {
  1.1638 +                if (finalRuleIdx == n) {
  1.1639 +                    continue;
  1.1640 +                }
  1.1641 +                TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n);
  1.1642 +                UDate lastStart;
  1.1643 +                r->getFinalStart(tmpRaw, tmpDST, lastStart);
  1.1644 +                if (lastStart > start) {
  1.1645 +                    finalRule->getNextStart(lastStart,
  1.1646 +                        r->getRawOffset(),
  1.1647 +                        r->getDSTSavings(),
  1.1648 +                        FALSE,
  1.1649 +                        start);
  1.1650 +                }
  1.1651 +            }
  1.1652 +
  1.1653 +            TimeZoneRule *newRule;
  1.1654 +            UnicodeString tznam;
  1.1655 +            if (start == finalStart) {
  1.1656 +                // Transform this into a single transition
  1.1657 +                newRule = new TimeArrayTimeZoneRule(
  1.1658 +                        finalRule->getName(tznam),
  1.1659 +                        finalRule->getRawOffset(),
  1.1660 +                        finalRule->getDSTSavings(),
  1.1661 +                        &finalStart,
  1.1662 +                        1,
  1.1663 +                        DateTimeRule::UTC_TIME);
  1.1664 +            } else {
  1.1665 +                // Update the end year
  1.1666 +                int32_t y, m, d, dow, doy, mid;
  1.1667 +                Grego::timeToFields(start, y, m, d, dow, doy, mid);
  1.1668 +                newRule = new AnnualTimeZoneRule(
  1.1669 +                        finalRule->getName(tznam),
  1.1670 +                        finalRule->getRawOffset(),
  1.1671 +                        finalRule->getDSTSavings(),
  1.1672 +                        *(finalRule->getRule()),
  1.1673 +                        finalRule->getStartYear(),
  1.1674 +                        y);
  1.1675 +            }
  1.1676 +            if (newRule == NULL) {
  1.1677 +                status = U_MEMORY_ALLOCATION_ERROR;
  1.1678 +                goto cleanupParse;
  1.1679 +            }
  1.1680 +            rules->removeElementAt(finalRuleIdx);
  1.1681 +            rules->addElement(newRule, status);
  1.1682 +            if (U_FAILURE(status)) {
  1.1683 +                delete newRule;
  1.1684 +                goto cleanupParse;
  1.1685 +            }
  1.1686 +        }
  1.1687 +    }
  1.1688 +
  1.1689 +    while (!rules->isEmpty()) {
  1.1690 +        TimeZoneRule *tzr = (TimeZoneRule*)rules->orphanElementAt(0);
  1.1691 +        rbtz->addTransitionRule(tzr, status);
  1.1692 +        if (U_FAILURE(status)) {
  1.1693 +            goto cleanupParse;
  1.1694 +        }
  1.1695 +    }
  1.1696 +    rbtz->complete(status);
  1.1697 +    if (U_FAILURE(status)) {
  1.1698 +        goto cleanupParse;
  1.1699 +    }
  1.1700 +    delete rules;
  1.1701 +    delete dates;
  1.1702 +
  1.1703 +    tz = rbtz;
  1.1704 +    setID(tzid);
  1.1705 +    return;
  1.1706 +
  1.1707 +cleanupParse:
  1.1708 +    if (rules != NULL) {
  1.1709 +        while (!rules->isEmpty()) {
  1.1710 +            TimeZoneRule *r = (TimeZoneRule*)rules->orphanElementAt(0);
  1.1711 +            delete r;
  1.1712 +        }
  1.1713 +        delete rules;
  1.1714 +    }
  1.1715 +    if (dates != NULL) {
  1.1716 +        delete dates;
  1.1717 +    }
  1.1718 +    if (initialRule != NULL) {
  1.1719 +        delete initialRule;
  1.1720 +    }
  1.1721 +    if (rbtz != NULL) {
  1.1722 +        delete rbtz;
  1.1723 +    }
  1.1724 +    return;
  1.1725 +}
  1.1726 +
  1.1727 +void
  1.1728 +VTimeZone::write(VTZWriter& writer, UErrorCode& status) const {
  1.1729 +    if (vtzlines != NULL) {
  1.1730 +        for (int32_t i = 0; i < vtzlines->size(); i++) {
  1.1731 +            UnicodeString *line = (UnicodeString*)vtzlines->elementAt(i);
  1.1732 +            if (line->startsWith(ICAL_TZURL, -1)
  1.1733 +                && line->charAt(u_strlen(ICAL_TZURL)) == COLON) {
  1.1734 +                writer.write(ICAL_TZURL);
  1.1735 +                writer.write(COLON);
  1.1736 +                writer.write(tzurl);
  1.1737 +                writer.write(ICAL_NEWLINE);
  1.1738 +            } else if (line->startsWith(ICAL_LASTMOD, -1)
  1.1739 +                && line->charAt(u_strlen(ICAL_LASTMOD)) == COLON) {
  1.1740 +                UnicodeString utcString;
  1.1741 +                writer.write(ICAL_LASTMOD);
  1.1742 +                writer.write(COLON);
  1.1743 +                writer.write(getUTCDateTimeString(lastmod, utcString));
  1.1744 +                writer.write(ICAL_NEWLINE);
  1.1745 +            } else {
  1.1746 +                writer.write(*line);
  1.1747 +                writer.write(ICAL_NEWLINE);
  1.1748 +            }
  1.1749 +        }
  1.1750 +    } else {
  1.1751 +        UVector *customProps = NULL;
  1.1752 +        if (olsonzid.length() > 0 && icutzver.length() > 0) {
  1.1753 +            customProps = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, status);
  1.1754 +            if (U_FAILURE(status)) {
  1.1755 +                return;
  1.1756 +            }
  1.1757 +            UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP);
  1.1758 +            icutzprop->append(olsonzid);
  1.1759 +            icutzprop->append((UChar)0x005B/*'['*/);
  1.1760 +            icutzprop->append(icutzver);
  1.1761 +            icutzprop->append((UChar)0x005D/*']'*/);
  1.1762 +            customProps->addElement(icutzprop, status);
  1.1763 +            if (U_FAILURE(status)) {
  1.1764 +                delete icutzprop;
  1.1765 +                delete customProps;
  1.1766 +                return;
  1.1767 +            }
  1.1768 +        }
  1.1769 +        writeZone(writer, *tz, customProps, status);
  1.1770 +        delete customProps;
  1.1771 +    }
  1.1772 +}
  1.1773 +
  1.1774 +void
  1.1775 +VTimeZone::write(UDate start, VTZWriter& writer, UErrorCode& status) const {
  1.1776 +    if (U_FAILURE(status)) {
  1.1777 +        return;
  1.1778 +    }
  1.1779 +    InitialTimeZoneRule *initial = NULL;
  1.1780 +    UVector *transitionRules = NULL;
  1.1781 +    UVector customProps(uprv_deleteUObject, uhash_compareUnicodeString, status);
  1.1782 +    UnicodeString tzid;
  1.1783 +
  1.1784 +    // Extract rules applicable to dates after the start time
  1.1785 +    getTimeZoneRulesAfter(start, initial, transitionRules, status);
  1.1786 +    if (U_FAILURE(status)) {
  1.1787 +        return;
  1.1788 +    }
  1.1789 +
  1.1790 +    // Create a RuleBasedTimeZone with the subset rule
  1.1791 +    getID(tzid);
  1.1792 +    RuleBasedTimeZone rbtz(tzid, initial);
  1.1793 +    if (transitionRules != NULL) {
  1.1794 +        while (!transitionRules->isEmpty()) {
  1.1795 +            TimeZoneRule *tr = (TimeZoneRule*)transitionRules->orphanElementAt(0);
  1.1796 +            rbtz.addTransitionRule(tr, status);
  1.1797 +            if (U_FAILURE(status)) {
  1.1798 +                goto cleanupWritePartial;
  1.1799 +            }
  1.1800 +        }
  1.1801 +        delete transitionRules;
  1.1802 +        transitionRules = NULL;
  1.1803 +    }
  1.1804 +    rbtz.complete(status);
  1.1805 +    if (U_FAILURE(status)) {
  1.1806 +        goto cleanupWritePartial;
  1.1807 +    }
  1.1808 +
  1.1809 +    if (olsonzid.length() > 0 && icutzver.length() > 0) {
  1.1810 +        UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP);
  1.1811 +        icutzprop->append(olsonzid);
  1.1812 +        icutzprop->append((UChar)0x005B/*'['*/);
  1.1813 +        icutzprop->append(icutzver);
  1.1814 +        icutzprop->append(ICU_TZINFO_PARTIAL, -1);
  1.1815 +        appendMillis(start, *icutzprop);
  1.1816 +        icutzprop->append((UChar)0x005D/*']'*/);
  1.1817 +        customProps.addElement(icutzprop, status);
  1.1818 +        if (U_FAILURE(status)) {
  1.1819 +            delete icutzprop;
  1.1820 +            goto cleanupWritePartial;
  1.1821 +        }
  1.1822 +    }
  1.1823 +    writeZone(writer, rbtz, &customProps, status);
  1.1824 +    return;
  1.1825 +
  1.1826 +cleanupWritePartial:
  1.1827 +    if (initial != NULL) {
  1.1828 +        delete initial;
  1.1829 +    }
  1.1830 +    if (transitionRules != NULL) {
  1.1831 +        while (!transitionRules->isEmpty()) {
  1.1832 +            TimeZoneRule *tr = (TimeZoneRule*)transitionRules->orphanElementAt(0);
  1.1833 +            delete tr;
  1.1834 +        }
  1.1835 +        delete transitionRules;
  1.1836 +    }
  1.1837 +}
  1.1838 +
  1.1839 +void
  1.1840 +VTimeZone::writeSimple(UDate time, VTZWriter& writer, UErrorCode& status) const {
  1.1841 +    if (U_FAILURE(status)) {
  1.1842 +        return;
  1.1843 +    }
  1.1844 +
  1.1845 +    UVector customProps(uprv_deleteUObject, uhash_compareUnicodeString, status);
  1.1846 +    UnicodeString tzid;
  1.1847 +
  1.1848 +    // Extract simple rules
  1.1849 +    InitialTimeZoneRule *initial = NULL;
  1.1850 +    AnnualTimeZoneRule *std = NULL, *dst = NULL;
  1.1851 +    getSimpleRulesNear(time, initial, std, dst, status);
  1.1852 +    if (U_SUCCESS(status)) {
  1.1853 +        // Create a RuleBasedTimeZone with the subset rule
  1.1854 +        getID(tzid);
  1.1855 +        RuleBasedTimeZone rbtz(tzid, initial);
  1.1856 +        if (std != NULL && dst != NULL) {
  1.1857 +            rbtz.addTransitionRule(std, status);
  1.1858 +            rbtz.addTransitionRule(dst, status);
  1.1859 +        }
  1.1860 +        if (U_FAILURE(status)) {
  1.1861 +            goto cleanupWriteSimple;
  1.1862 +        }
  1.1863 +
  1.1864 +        if (olsonzid.length() > 0 && icutzver.length() > 0) {
  1.1865 +            UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP);
  1.1866 +            icutzprop->append(olsonzid);
  1.1867 +            icutzprop->append((UChar)0x005B/*'['*/);
  1.1868 +            icutzprop->append(icutzver);
  1.1869 +            icutzprop->append(ICU_TZINFO_SIMPLE, -1);
  1.1870 +            appendMillis(time, *icutzprop);
  1.1871 +            icutzprop->append((UChar)0x005D/*']'*/);
  1.1872 +            customProps.addElement(icutzprop, status);
  1.1873 +            if (U_FAILURE(status)) {
  1.1874 +                delete icutzprop;
  1.1875 +                goto cleanupWriteSimple;
  1.1876 +            }
  1.1877 +        }
  1.1878 +        writeZone(writer, rbtz, &customProps, status);
  1.1879 +    }
  1.1880 +    return;
  1.1881 +
  1.1882 +cleanupWriteSimple:
  1.1883 +    if (initial != NULL) {
  1.1884 +        delete initial;
  1.1885 +    }
  1.1886 +    if (std != NULL) {
  1.1887 +        delete std;
  1.1888 +    }
  1.1889 +    if (dst != NULL) {
  1.1890 +        delete dst;
  1.1891 +    }
  1.1892 +}
  1.1893 +
  1.1894 +void
  1.1895 +VTimeZone::writeZone(VTZWriter& w, BasicTimeZone& basictz,
  1.1896 +                     UVector* customProps, UErrorCode& status) const {
  1.1897 +    if (U_FAILURE(status)) {
  1.1898 +        return;
  1.1899 +    }
  1.1900 +    writeHeaders(w, status);
  1.1901 +    if (U_FAILURE(status)) {
  1.1902 +        return;
  1.1903 +    }
  1.1904 +
  1.1905 +    if (customProps != NULL) {
  1.1906 +        for (int32_t i = 0; i < customProps->size(); i++) {
  1.1907 +            UnicodeString *custprop = (UnicodeString*)customProps->elementAt(i);
  1.1908 +            w.write(*custprop);
  1.1909 +            w.write(ICAL_NEWLINE);
  1.1910 +        }
  1.1911 +    }
  1.1912 +
  1.1913 +    UDate t = MIN_MILLIS;
  1.1914 +    UnicodeString dstName;
  1.1915 +    int32_t dstFromOffset = 0;
  1.1916 +    int32_t dstFromDSTSavings = 0;
  1.1917 +    int32_t dstToOffset = 0;
  1.1918 +    int32_t dstStartYear = 0;
  1.1919 +    int32_t dstMonth = 0;
  1.1920 +    int32_t dstDayOfWeek = 0;
  1.1921 +    int32_t dstWeekInMonth = 0;
  1.1922 +    int32_t dstMillisInDay = 0;
  1.1923 +    UDate dstStartTime = 0.0;
  1.1924 +    UDate dstUntilTime = 0.0;
  1.1925 +    int32_t dstCount = 0;
  1.1926 +    AnnualTimeZoneRule *finalDstRule = NULL;
  1.1927 +
  1.1928 +    UnicodeString stdName;
  1.1929 +    int32_t stdFromOffset = 0;
  1.1930 +    int32_t stdFromDSTSavings = 0;
  1.1931 +    int32_t stdToOffset = 0;
  1.1932 +    int32_t stdStartYear = 0;
  1.1933 +    int32_t stdMonth = 0;
  1.1934 +    int32_t stdDayOfWeek = 0;
  1.1935 +    int32_t stdWeekInMonth = 0;
  1.1936 +    int32_t stdMillisInDay = 0;
  1.1937 +    UDate stdStartTime = 0.0;
  1.1938 +    UDate stdUntilTime = 0.0;
  1.1939 +    int32_t stdCount = 0;
  1.1940 +    AnnualTimeZoneRule *finalStdRule = NULL;
  1.1941 +
  1.1942 +    int32_t year, month, dom, dow, doy, mid;
  1.1943 +    UBool hasTransitions = FALSE;
  1.1944 +    TimeZoneTransition tzt;
  1.1945 +    UBool tztAvail;
  1.1946 +    UnicodeString name;
  1.1947 +    UBool isDst;
  1.1948 +
  1.1949 +    // Going through all transitions
  1.1950 +    while (TRUE) {
  1.1951 +        tztAvail = basictz.getNextTransition(t, FALSE, tzt);
  1.1952 +        if (!tztAvail) {
  1.1953 +            break;
  1.1954 +        }
  1.1955 +        hasTransitions = TRUE;
  1.1956 +        t = tzt.getTime();
  1.1957 +        tzt.getTo()->getName(name);
  1.1958 +        isDst = (tzt.getTo()->getDSTSavings() != 0);
  1.1959 +        int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings();
  1.1960 +        int32_t fromDSTSavings = tzt.getFrom()->getDSTSavings();
  1.1961 +        int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings();
  1.1962 +        Grego::timeToFields(tzt.getTime() + fromOffset, year, month, dom, dow, doy, mid);
  1.1963 +        int32_t weekInMonth = Grego::dayOfWeekInMonth(year, month, dom);
  1.1964 +        UBool sameRule = FALSE;
  1.1965 +        const AnnualTimeZoneRule *atzrule;
  1.1966 +        if (isDst) {
  1.1967 +            if (finalDstRule == NULL
  1.1968 +                && (atzrule = dynamic_cast<const AnnualTimeZoneRule *>(tzt.getTo())) != NULL
  1.1969 +                && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
  1.1970 +            ) {
  1.1971 +                finalDstRule = (AnnualTimeZoneRule*)tzt.getTo()->clone();
  1.1972 +            }
  1.1973 +            if (dstCount > 0) {
  1.1974 +                if (year == dstStartYear + dstCount
  1.1975 +                        && name.compare(dstName) == 0
  1.1976 +                        && dstFromOffset == fromOffset
  1.1977 +                        && dstToOffset == toOffset
  1.1978 +                        && dstMonth == month
  1.1979 +                        && dstDayOfWeek == dow
  1.1980 +                        && dstWeekInMonth == weekInMonth
  1.1981 +                        && dstMillisInDay == mid) {
  1.1982 +                    // Update until time
  1.1983 +                    dstUntilTime = t;
  1.1984 +                    dstCount++;
  1.1985 +                    sameRule = TRUE;
  1.1986 +                }
  1.1987 +                if (!sameRule) {
  1.1988 +                    if (dstCount == 1) {
  1.1989 +                        writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime,
  1.1990 +                                TRUE, status);
  1.1991 +                    } else {
  1.1992 +                        writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
  1.1993 +                                dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
  1.1994 +                    }
  1.1995 +                    if (U_FAILURE(status)) {
  1.1996 +                        goto cleanupWriteZone;
  1.1997 +                    }
  1.1998 +                }
  1.1999 +            } 
  1.2000 +            if (!sameRule) {
  1.2001 +                // Reset this DST information
  1.2002 +                dstName = name;
  1.2003 +                dstFromOffset = fromOffset;
  1.2004 +                dstFromDSTSavings = fromDSTSavings;
  1.2005 +                dstToOffset = toOffset;
  1.2006 +                dstStartYear = year;
  1.2007 +                dstMonth = month;
  1.2008 +                dstDayOfWeek = dow;
  1.2009 +                dstWeekInMonth = weekInMonth;
  1.2010 +                dstMillisInDay = mid;
  1.2011 +                dstStartTime = dstUntilTime = t;
  1.2012 +                dstCount = 1;
  1.2013 +            }
  1.2014 +            if (finalStdRule != NULL && finalDstRule != NULL) {
  1.2015 +                break;
  1.2016 +            }
  1.2017 +        } else {
  1.2018 +            if (finalStdRule == NULL
  1.2019 +                && (atzrule = dynamic_cast<const AnnualTimeZoneRule *>(tzt.getTo())) != NULL
  1.2020 +                && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
  1.2021 +            ) {
  1.2022 +                finalStdRule = (AnnualTimeZoneRule*)tzt.getTo()->clone();
  1.2023 +            }
  1.2024 +            if (stdCount > 0) {
  1.2025 +                if (year == stdStartYear + stdCount
  1.2026 +                        && name.compare(stdName) == 0
  1.2027 +                        && stdFromOffset == fromOffset
  1.2028 +                        && stdToOffset == toOffset
  1.2029 +                        && stdMonth == month
  1.2030 +                        && stdDayOfWeek == dow
  1.2031 +                        && stdWeekInMonth == weekInMonth
  1.2032 +                        && stdMillisInDay == mid) {
  1.2033 +                    // Update until time
  1.2034 +                    stdUntilTime = t;
  1.2035 +                    stdCount++;
  1.2036 +                    sameRule = TRUE;
  1.2037 +                }
  1.2038 +                if (!sameRule) {
  1.2039 +                    if (stdCount == 1) {
  1.2040 +                        writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime,
  1.2041 +                                TRUE, status);
  1.2042 +                    } else {
  1.2043 +                        writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
  1.2044 +                                stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
  1.2045 +                    }
  1.2046 +                    if (U_FAILURE(status)) {
  1.2047 +                        goto cleanupWriteZone;
  1.2048 +                    }
  1.2049 +                }
  1.2050 +            }
  1.2051 +            if (!sameRule) {
  1.2052 +                // Reset this STD information
  1.2053 +                stdName = name;
  1.2054 +                stdFromOffset = fromOffset;
  1.2055 +                stdFromDSTSavings = fromDSTSavings;
  1.2056 +                stdToOffset = toOffset;
  1.2057 +                stdStartYear = year;
  1.2058 +                stdMonth = month;
  1.2059 +                stdDayOfWeek = dow;
  1.2060 +                stdWeekInMonth = weekInMonth;
  1.2061 +                stdMillisInDay = mid;
  1.2062 +                stdStartTime = stdUntilTime = t;
  1.2063 +                stdCount = 1;
  1.2064 +            }
  1.2065 +            if (finalStdRule != NULL && finalDstRule != NULL) {
  1.2066 +                break;
  1.2067 +            }
  1.2068 +        }
  1.2069 +    }
  1.2070 +    if (!hasTransitions) {
  1.2071 +        // No transition - put a single non transition RDATE
  1.2072 +        int32_t raw, dst, offset;
  1.2073 +        basictz.getOffset(0.0/*any time*/, FALSE, raw, dst, status);
  1.2074 +        if (U_FAILURE(status)) {
  1.2075 +            goto cleanupWriteZone;
  1.2076 +        }
  1.2077 +        offset = raw + dst;
  1.2078 +        isDst = (dst != 0);
  1.2079 +        UnicodeString tzid;
  1.2080 +        basictz.getID(tzid);
  1.2081 +        getDefaultTZName(tzid, isDst, name);        
  1.2082 +        writeZonePropsByTime(w, isDst, name,
  1.2083 +                offset, offset, DEF_TZSTARTTIME - offset, FALSE, status);    
  1.2084 +        if (U_FAILURE(status)) {
  1.2085 +            goto cleanupWriteZone;
  1.2086 +        }
  1.2087 +    } else {
  1.2088 +        if (dstCount > 0) {
  1.2089 +            if (finalDstRule == NULL) {
  1.2090 +                if (dstCount == 1) {
  1.2091 +                    writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime,
  1.2092 +                            TRUE, status);
  1.2093 +                } else {
  1.2094 +                    writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
  1.2095 +                            dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
  1.2096 +                }
  1.2097 +                if (U_FAILURE(status)) {
  1.2098 +                    goto cleanupWriteZone;
  1.2099 +                }
  1.2100 +            } else {
  1.2101 +                if (dstCount == 1) {
  1.2102 +                    writeFinalRule(w, TRUE, finalDstRule,
  1.2103 +                            dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, dstStartTime, status);
  1.2104 +                } else {
  1.2105 +                    // Use a single rule if possible
  1.2106 +                    if (isEquivalentDateRule(dstMonth, dstWeekInMonth, dstDayOfWeek, finalDstRule->getRule())) {
  1.2107 +                        writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
  1.2108 +                                dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, MAX_MILLIS, status);
  1.2109 +                    } else {
  1.2110 +                        // Not equivalent rule - write out two different rules
  1.2111 +                        writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
  1.2112 +                                dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
  1.2113 +                        if (U_FAILURE(status)) {
  1.2114 +                            goto cleanupWriteZone;
  1.2115 +                        }
  1.2116 +                        UDate nextStart;
  1.2117 +                        UBool nextStartAvail = finalDstRule->getNextStart(dstUntilTime, dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, false, nextStart);
  1.2118 +                        U_ASSERT(nextStartAvail);
  1.2119 +                        if (nextStartAvail) {
  1.2120 +                            writeFinalRule(w, TRUE, finalDstRule,
  1.2121 +                                    dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, nextStart, status);
  1.2122 +                        }
  1.2123 +                    }
  1.2124 +                }
  1.2125 +                if (U_FAILURE(status)) {
  1.2126 +                    goto cleanupWriteZone;
  1.2127 +                }
  1.2128 +            }
  1.2129 +        }
  1.2130 +        if (stdCount > 0) {
  1.2131 +            if (finalStdRule == NULL) {
  1.2132 +                if (stdCount == 1) {
  1.2133 +                    writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime,
  1.2134 +                            TRUE, status);
  1.2135 +                } else {
  1.2136 +                    writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
  1.2137 +                            stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
  1.2138 +                }
  1.2139 +                if (U_FAILURE(status)) {
  1.2140 +                    goto cleanupWriteZone;
  1.2141 +                }
  1.2142 +            } else {
  1.2143 +                if (stdCount == 1) {
  1.2144 +                    writeFinalRule(w, FALSE, finalStdRule,
  1.2145 +                            stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, stdStartTime, status);
  1.2146 +                } else {
  1.2147 +                    // Use a single rule if possible
  1.2148 +                    if (isEquivalentDateRule(stdMonth, stdWeekInMonth, stdDayOfWeek, finalStdRule->getRule())) {
  1.2149 +                        writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
  1.2150 +                                stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, MAX_MILLIS, status);
  1.2151 +                    } else {
  1.2152 +                        // Not equivalent rule - write out two different rules
  1.2153 +                        writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
  1.2154 +                                stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
  1.2155 +                        if (U_FAILURE(status)) {
  1.2156 +                            goto cleanupWriteZone;
  1.2157 +                        }
  1.2158 +                        UDate nextStart;
  1.2159 +                        UBool nextStartAvail = finalStdRule->getNextStart(stdUntilTime, stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, false, nextStart);
  1.2160 +                        U_ASSERT(nextStartAvail);
  1.2161 +                        if (nextStartAvail) {
  1.2162 +                            writeFinalRule(w, FALSE, finalStdRule,
  1.2163 +                                    stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, nextStart, status);
  1.2164 +                        }
  1.2165 +                    }
  1.2166 +                }
  1.2167 +                if (U_FAILURE(status)) {
  1.2168 +                    goto cleanupWriteZone;
  1.2169 +                }
  1.2170 +            }
  1.2171 +        }            
  1.2172 +    }
  1.2173 +    writeFooter(w, status);
  1.2174 +
  1.2175 +cleanupWriteZone:
  1.2176 +
  1.2177 +    if (finalStdRule != NULL) {
  1.2178 +        delete finalStdRule;
  1.2179 +    }
  1.2180 +    if (finalDstRule != NULL) {
  1.2181 +        delete finalDstRule;
  1.2182 +    }
  1.2183 +}
  1.2184 +
  1.2185 +void
  1.2186 +VTimeZone::writeHeaders(VTZWriter& writer, UErrorCode& status) const {
  1.2187 +    if (U_FAILURE(status)) {
  1.2188 +        return;
  1.2189 +    }
  1.2190 +    UnicodeString tzid;
  1.2191 +    tz->getID(tzid);
  1.2192 +
  1.2193 +    writer.write(ICAL_BEGIN);
  1.2194 +    writer.write(COLON);
  1.2195 +    writer.write(ICAL_VTIMEZONE);
  1.2196 +    writer.write(ICAL_NEWLINE);
  1.2197 +    writer.write(ICAL_TZID);
  1.2198 +    writer.write(COLON);
  1.2199 +    writer.write(tzid);
  1.2200 +    writer.write(ICAL_NEWLINE);
  1.2201 +    if (tzurl.length() != 0) {
  1.2202 +        writer.write(ICAL_TZURL);
  1.2203 +        writer.write(COLON);
  1.2204 +        writer.write(tzurl);
  1.2205 +        writer.write(ICAL_NEWLINE);
  1.2206 +    }
  1.2207 +    if (lastmod != MAX_MILLIS) {
  1.2208 +        UnicodeString lastmodStr;
  1.2209 +        writer.write(ICAL_LASTMOD);
  1.2210 +        writer.write(COLON);
  1.2211 +        writer.write(getUTCDateTimeString(lastmod, lastmodStr));
  1.2212 +        writer.write(ICAL_NEWLINE);
  1.2213 +    }
  1.2214 +}
  1.2215 +
  1.2216 +/*
  1.2217 + * Write the closing section of the VTIMEZONE definition block
  1.2218 + */
  1.2219 +void
  1.2220 +VTimeZone::writeFooter(VTZWriter& writer, UErrorCode& status) const {
  1.2221 +    if (U_FAILURE(status)) {
  1.2222 +        return;
  1.2223 +    }
  1.2224 +    writer.write(ICAL_END);
  1.2225 +    writer.write(COLON);
  1.2226 +    writer.write(ICAL_VTIMEZONE);
  1.2227 +    writer.write(ICAL_NEWLINE);
  1.2228 +}
  1.2229 +
  1.2230 +/*
  1.2231 + * Write a single start time
  1.2232 + */
  1.2233 +void
  1.2234 +VTimeZone::writeZonePropsByTime(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
  1.2235 +                                int32_t fromOffset, int32_t toOffset, UDate time, UBool withRDATE,
  1.2236 +                                UErrorCode& status) const {
  1.2237 +    if (U_FAILURE(status)) {
  1.2238 +        return;
  1.2239 +    }
  1.2240 +    beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, time, status);
  1.2241 +    if (U_FAILURE(status)) {
  1.2242 +        return;
  1.2243 +    }
  1.2244 +    if (withRDATE) {
  1.2245 +        writer.write(ICAL_RDATE);
  1.2246 +        writer.write(COLON);
  1.2247 +        UnicodeString timestr;
  1.2248 +        writer.write(getDateTimeString(time + fromOffset, timestr));
  1.2249 +        writer.write(ICAL_NEWLINE);
  1.2250 +    }
  1.2251 +    endZoneProps(writer, isDst, status);
  1.2252 +    if (U_FAILURE(status)) {
  1.2253 +        return;
  1.2254 +    }
  1.2255 +}
  1.2256 +
  1.2257 +/*
  1.2258 + * Write start times defined by a DOM rule using VTIMEZONE RRULE
  1.2259 + */
  1.2260 +void
  1.2261 +VTimeZone::writeZonePropsByDOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
  1.2262 +                               int32_t fromOffset, int32_t toOffset,
  1.2263 +                               int32_t month, int32_t dayOfMonth, UDate startTime, UDate untilTime,
  1.2264 +                               UErrorCode& status) const {
  1.2265 +    if (U_FAILURE(status)) {
  1.2266 +        return;
  1.2267 +    }
  1.2268 +    beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
  1.2269 +    if (U_FAILURE(status)) {
  1.2270 +        return;
  1.2271 +    }
  1.2272 +    beginRRULE(writer, month, status);
  1.2273 +    if (U_FAILURE(status)) {
  1.2274 +        return;
  1.2275 +    }
  1.2276 +    writer.write(ICAL_BYMONTHDAY);
  1.2277 +    writer.write(EQUALS_SIGN);
  1.2278 +    UnicodeString dstr;
  1.2279 +    appendAsciiDigits(dayOfMonth, 0, dstr);
  1.2280 +    writer.write(dstr);
  1.2281 +    if (untilTime != MAX_MILLIS) {
  1.2282 +        appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
  1.2283 +        if (U_FAILURE(status)) {
  1.2284 +            return;
  1.2285 +        }
  1.2286 +    }
  1.2287 +    writer.write(ICAL_NEWLINE);
  1.2288 +    endZoneProps(writer, isDst, status);
  1.2289 +}
  1.2290 +
  1.2291 +/*
  1.2292 + * Write start times defined by a DOW rule using VTIMEZONE RRULE
  1.2293 + */
  1.2294 +void
  1.2295 +VTimeZone::writeZonePropsByDOW(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
  1.2296 +                               int32_t fromOffset, int32_t toOffset,
  1.2297 +                               int32_t month, int32_t weekInMonth, int32_t dayOfWeek,
  1.2298 +                               UDate startTime, UDate untilTime, UErrorCode& status) const {
  1.2299 +    if (U_FAILURE(status)) {
  1.2300 +        return;
  1.2301 +    }
  1.2302 +    beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
  1.2303 +    if (U_FAILURE(status)) {
  1.2304 +        return;
  1.2305 +    }
  1.2306 +    beginRRULE(writer, month, status);
  1.2307 +    if (U_FAILURE(status)) {
  1.2308 +        return;
  1.2309 +    }
  1.2310 +    writer.write(ICAL_BYDAY);
  1.2311 +    writer.write(EQUALS_SIGN);
  1.2312 +    UnicodeString dstr;
  1.2313 +    appendAsciiDigits(weekInMonth, 0, dstr);
  1.2314 +    writer.write(dstr);    // -4, -3, -2, -1, 1, 2, 3, 4
  1.2315 +    writer.write(ICAL_DOW_NAMES[dayOfWeek - 1]);    // SU, MO, TU...
  1.2316 +
  1.2317 +    if (untilTime != MAX_MILLIS) {
  1.2318 +        appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
  1.2319 +        if (U_FAILURE(status)) {
  1.2320 +            return;
  1.2321 +        }
  1.2322 +    }
  1.2323 +    writer.write(ICAL_NEWLINE);
  1.2324 +    endZoneProps(writer, isDst, status);
  1.2325 +}
  1.2326 +
  1.2327 +/*
  1.2328 + * Write start times defined by a DOW_GEQ_DOM rule using VTIMEZONE RRULE
  1.2329 + */
  1.2330 +void
  1.2331 +VTimeZone::writeZonePropsByDOW_GEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
  1.2332 +                                       int32_t fromOffset, int32_t toOffset,
  1.2333 +                                       int32_t month, int32_t dayOfMonth, int32_t dayOfWeek,
  1.2334 +                                       UDate startTime, UDate untilTime, UErrorCode& status) const {
  1.2335 +    if (U_FAILURE(status)) {
  1.2336 +        return;
  1.2337 +    }
  1.2338 +    // Check if this rule can be converted to DOW rule
  1.2339 +    if (dayOfMonth%7 == 1) {
  1.2340 +        // Can be represented by DOW rule
  1.2341 +        writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
  1.2342 +                month, (dayOfMonth + 6)/7, dayOfWeek, startTime, untilTime, status);
  1.2343 +        if (U_FAILURE(status)) {
  1.2344 +            return;
  1.2345 +        }
  1.2346 +    } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 6) {
  1.2347 +        // Can be represented by DOW rule with negative week number
  1.2348 +        writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
  1.2349 +                month, -1*((MONTHLENGTH[month] - dayOfMonth + 1)/7), dayOfWeek, startTime, untilTime, status);
  1.2350 +        if (U_FAILURE(status)) {
  1.2351 +            return;
  1.2352 +        }
  1.2353 +    } else {
  1.2354 +        // Otherwise, use BYMONTHDAY to include all possible dates
  1.2355 +        beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
  1.2356 +        if (U_FAILURE(status)) {
  1.2357 +            return;
  1.2358 +        }
  1.2359 +        // Check if all days are in the same month
  1.2360 +        int32_t startDay = dayOfMonth;
  1.2361 +        int32_t currentMonthDays = 7;
  1.2362 +    
  1.2363 +        if (dayOfMonth <= 0) {
  1.2364 +            // The start day is in previous month
  1.2365 +            int32_t prevMonthDays = 1 - dayOfMonth;
  1.2366 +            currentMonthDays -= prevMonthDays;
  1.2367 +
  1.2368 +            int32_t prevMonth = (month - 1) < 0 ? 11 : month - 1;
  1.2369 +
  1.2370 +            // Note: When a rule is separated into two, UNTIL attribute needs to be
  1.2371 +            // calculated for each of them.  For now, we skip this, because we basically use this method
  1.2372 +            // only for final rules, which does not have the UNTIL attribute
  1.2373 +            writeZonePropsByDOW_GEQ_DOM_sub(writer, prevMonth, -prevMonthDays, dayOfWeek, prevMonthDays,
  1.2374 +                MAX_MILLIS /* Do not use UNTIL */, fromOffset, status);
  1.2375 +            if (U_FAILURE(status)) {
  1.2376 +                return;
  1.2377 +            }
  1.2378 +
  1.2379 +            // Start from 1 for the rest
  1.2380 +            startDay = 1;
  1.2381 +        } else if (dayOfMonth + 6 > MONTHLENGTH[month]) {
  1.2382 +            // Note: This code does not actually work well in February.  For now, days in month in
  1.2383 +            // non-leap year.
  1.2384 +            int32_t nextMonthDays = dayOfMonth + 6 - MONTHLENGTH[month];
  1.2385 +            currentMonthDays -= nextMonthDays;
  1.2386 +
  1.2387 +            int32_t nextMonth = (month + 1) > 11 ? 0 : month + 1;
  1.2388 +            
  1.2389 +            writeZonePropsByDOW_GEQ_DOM_sub(writer, nextMonth, 1, dayOfWeek, nextMonthDays,
  1.2390 +                MAX_MILLIS /* Do not use UNTIL */, fromOffset, status);
  1.2391 +            if (U_FAILURE(status)) {
  1.2392 +                return;
  1.2393 +            }
  1.2394 +        }
  1.2395 +        writeZonePropsByDOW_GEQ_DOM_sub(writer, month, startDay, dayOfWeek, currentMonthDays,
  1.2396 +            untilTime, fromOffset, status);
  1.2397 +        if (U_FAILURE(status)) {
  1.2398 +            return;
  1.2399 +        }
  1.2400 +        endZoneProps(writer, isDst, status);
  1.2401 +    }
  1.2402 +}
  1.2403 +
  1.2404 +/*
  1.2405 + * Called from writeZonePropsByDOW_GEQ_DOM
  1.2406 + */
  1.2407 +void
  1.2408 +VTimeZone::writeZonePropsByDOW_GEQ_DOM_sub(VTZWriter& writer, int32_t month, int32_t dayOfMonth,
  1.2409 +                                           int32_t dayOfWeek, int32_t numDays,
  1.2410 +                                           UDate untilTime, int32_t fromOffset, UErrorCode& status) const {
  1.2411 +
  1.2412 +    if (U_FAILURE(status)) {
  1.2413 +        return;
  1.2414 +    }
  1.2415 +    int32_t startDayNum = dayOfMonth;
  1.2416 +    UBool isFeb = (month == UCAL_FEBRUARY);
  1.2417 +    if (dayOfMonth < 0 && !isFeb) {
  1.2418 +        // Use positive number if possible
  1.2419 +        startDayNum = MONTHLENGTH[month] + dayOfMonth + 1;
  1.2420 +    }
  1.2421 +    beginRRULE(writer, month, status);
  1.2422 +    if (U_FAILURE(status)) {
  1.2423 +        return;
  1.2424 +    }
  1.2425 +    writer.write(ICAL_BYDAY);
  1.2426 +    writer.write(EQUALS_SIGN);
  1.2427 +    writer.write(ICAL_DOW_NAMES[dayOfWeek - 1]);    // SU, MO, TU...
  1.2428 +    writer.write(SEMICOLON);
  1.2429 +    writer.write(ICAL_BYMONTHDAY);
  1.2430 +    writer.write(EQUALS_SIGN);
  1.2431 +
  1.2432 +    UnicodeString dstr;
  1.2433 +    appendAsciiDigits(startDayNum, 0, dstr);
  1.2434 +    writer.write(dstr);
  1.2435 +    for (int32_t i = 1; i < numDays; i++) {
  1.2436 +        writer.write(COMMA);
  1.2437 +        dstr.remove();
  1.2438 +        appendAsciiDigits(startDayNum + i, 0, dstr);
  1.2439 +        writer.write(dstr);
  1.2440 +    }
  1.2441 +
  1.2442 +    if (untilTime != MAX_MILLIS) {
  1.2443 +        appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
  1.2444 +        if (U_FAILURE(status)) {
  1.2445 +            return;
  1.2446 +        }
  1.2447 +    }
  1.2448 +    writer.write(ICAL_NEWLINE);
  1.2449 +}
  1.2450 +
  1.2451 +/*
  1.2452 + * Write start times defined by a DOW_LEQ_DOM rule using VTIMEZONE RRULE
  1.2453 + */
  1.2454 +void
  1.2455 +VTimeZone::writeZonePropsByDOW_LEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
  1.2456 +                                       int32_t fromOffset, int32_t toOffset,
  1.2457 +                                       int32_t month, int32_t dayOfMonth, int32_t dayOfWeek,
  1.2458 +                                       UDate startTime, UDate untilTime, UErrorCode& status) const {
  1.2459 +    if (U_FAILURE(status)) {
  1.2460 +        return;
  1.2461 +    }
  1.2462 +    // Check if this rule can be converted to DOW rule
  1.2463 +    if (dayOfMonth%7 == 0) {
  1.2464 +        // Can be represented by DOW rule
  1.2465 +        writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
  1.2466 +                month, dayOfMonth/7, dayOfWeek, startTime, untilTime, status);
  1.2467 +    } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 0){
  1.2468 +        // Can be represented by DOW rule with negative week number
  1.2469 +        writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
  1.2470 +                month, -1*((MONTHLENGTH[month] - dayOfMonth)/7 + 1), dayOfWeek, startTime, untilTime, status);
  1.2471 +    } else if (month == UCAL_FEBRUARY && dayOfMonth == 29) {
  1.2472 +        // Specical case for February
  1.2473 +        writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
  1.2474 +                UCAL_FEBRUARY, -1, dayOfWeek, startTime, untilTime, status);
  1.2475 +    } else {
  1.2476 +        // Otherwise, convert this to DOW_GEQ_DOM rule
  1.2477 +        writeZonePropsByDOW_GEQ_DOM(writer, isDst, zonename, fromOffset, toOffset,
  1.2478 +                month, dayOfMonth - 6, dayOfWeek, startTime, untilTime, status);
  1.2479 +    }
  1.2480 +}
  1.2481 +
  1.2482 +/*
  1.2483 + * Write the final time zone rule using RRULE, with no UNTIL attribute
  1.2484 + */
  1.2485 +void
  1.2486 +VTimeZone::writeFinalRule(VTZWriter& writer, UBool isDst, const AnnualTimeZoneRule* rule,
  1.2487 +                          int32_t fromRawOffset, int32_t fromDSTSavings,
  1.2488 +                          UDate startTime, UErrorCode& status) const {
  1.2489 +    if (U_FAILURE(status)) {
  1.2490 +        return;
  1.2491 +    }
  1.2492 +    UBool modifiedRule = TRUE;
  1.2493 +    const DateTimeRule *dtrule = toWallTimeRule(rule->getRule(), fromRawOffset, fromDSTSavings);
  1.2494 +    if (dtrule == NULL) {
  1.2495 +        modifiedRule = FALSE;
  1.2496 +        dtrule = rule->getRule();
  1.2497 +    }
  1.2498 +
  1.2499 +    // If the rule's mills in a day is out of range, adjust start time.
  1.2500 +    // Olson tzdata supports 24:00 of a day, but VTIMEZONE does not.
  1.2501 +    // See ticket#7008/#7518
  1.2502 +
  1.2503 +    int32_t timeInDay = dtrule->getRuleMillisInDay();
  1.2504 +    if (timeInDay < 0) {
  1.2505 +        startTime = startTime + (0 - timeInDay);
  1.2506 +    } else if (timeInDay >= U_MILLIS_PER_DAY) {
  1.2507 +        startTime = startTime - (timeInDay - (U_MILLIS_PER_DAY - 1));
  1.2508 +    }
  1.2509 +
  1.2510 +    int32_t toOffset = rule->getRawOffset() + rule->getDSTSavings();
  1.2511 +    UnicodeString name;
  1.2512 +    rule->getName(name);
  1.2513 +    switch (dtrule->getDateRuleType()) {
  1.2514 +    case DateTimeRule::DOM:
  1.2515 +        writeZonePropsByDOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
  1.2516 +                dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), startTime, MAX_MILLIS, status);
  1.2517 +        break;
  1.2518 +    case DateTimeRule::DOW:
  1.2519 +        writeZonePropsByDOW(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
  1.2520 +                dtrule->getRuleMonth(), dtrule->getRuleWeekInMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
  1.2521 +        break;
  1.2522 +    case DateTimeRule::DOW_GEQ_DOM:
  1.2523 +        writeZonePropsByDOW_GEQ_DOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
  1.2524 +                dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
  1.2525 +        break;
  1.2526 +    case DateTimeRule::DOW_LEQ_DOM:
  1.2527 +        writeZonePropsByDOW_LEQ_DOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
  1.2528 +                dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
  1.2529 +        break;
  1.2530 +    }
  1.2531 +    if (modifiedRule) {
  1.2532 +        delete dtrule;
  1.2533 +    }
  1.2534 +}
  1.2535 +
  1.2536 +/*
  1.2537 + * Write the opening section of zone properties
  1.2538 + */
  1.2539 +void
  1.2540 +VTimeZone::beginZoneProps(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
  1.2541 +                          int32_t fromOffset, int32_t toOffset, UDate startTime, UErrorCode& status) const {
  1.2542 +    if (U_FAILURE(status)) {
  1.2543 +        return;
  1.2544 +    }
  1.2545 +    writer.write(ICAL_BEGIN);
  1.2546 +    writer.write(COLON);
  1.2547 +    if (isDst) {
  1.2548 +        writer.write(ICAL_DAYLIGHT);
  1.2549 +    } else {
  1.2550 +        writer.write(ICAL_STANDARD);
  1.2551 +    }
  1.2552 +    writer.write(ICAL_NEWLINE);
  1.2553 +
  1.2554 +    UnicodeString dstr;
  1.2555 +
  1.2556 +    // TZOFFSETTO
  1.2557 +    writer.write(ICAL_TZOFFSETTO);
  1.2558 +    writer.write(COLON);
  1.2559 +    millisToOffset(toOffset, dstr);
  1.2560 +    writer.write(dstr);
  1.2561 +    writer.write(ICAL_NEWLINE);
  1.2562 +
  1.2563 +    // TZOFFSETFROM
  1.2564 +    writer.write(ICAL_TZOFFSETFROM);
  1.2565 +    writer.write(COLON);
  1.2566 +    millisToOffset(fromOffset, dstr);
  1.2567 +    writer.write(dstr);
  1.2568 +    writer.write(ICAL_NEWLINE);
  1.2569 +
  1.2570 +    // TZNAME
  1.2571 +    writer.write(ICAL_TZNAME);
  1.2572 +    writer.write(COLON);
  1.2573 +    writer.write(zonename);
  1.2574 +    writer.write(ICAL_NEWLINE);
  1.2575 +    
  1.2576 +    // DTSTART
  1.2577 +    writer.write(ICAL_DTSTART);
  1.2578 +    writer.write(COLON);
  1.2579 +    writer.write(getDateTimeString(startTime + fromOffset, dstr));
  1.2580 +    writer.write(ICAL_NEWLINE);        
  1.2581 +}
  1.2582 +
  1.2583 +/*
  1.2584 + * Writes the closing section of zone properties
  1.2585 + */
  1.2586 +void
  1.2587 +VTimeZone::endZoneProps(VTZWriter& writer, UBool isDst, UErrorCode& status) const {
  1.2588 +    if (U_FAILURE(status)) {
  1.2589 +        return;
  1.2590 +    }
  1.2591 +    // END:STANDARD or END:DAYLIGHT
  1.2592 +    writer.write(ICAL_END);
  1.2593 +    writer.write(COLON);
  1.2594 +    if (isDst) {
  1.2595 +        writer.write(ICAL_DAYLIGHT);
  1.2596 +    } else {
  1.2597 +        writer.write(ICAL_STANDARD);
  1.2598 +    }
  1.2599 +    writer.write(ICAL_NEWLINE);
  1.2600 +}
  1.2601 +
  1.2602 +/*
  1.2603 + * Write the beggining part of RRULE line
  1.2604 + */
  1.2605 +void
  1.2606 +VTimeZone::beginRRULE(VTZWriter& writer, int32_t month, UErrorCode& status) const {
  1.2607 +    if (U_FAILURE(status)) {
  1.2608 +        return;
  1.2609 +    }
  1.2610 +    UnicodeString dstr;
  1.2611 +    writer.write(ICAL_RRULE);
  1.2612 +    writer.write(COLON);
  1.2613 +    writer.write(ICAL_FREQ);
  1.2614 +    writer.write(EQUALS_SIGN);
  1.2615 +    writer.write(ICAL_YEARLY);
  1.2616 +    writer.write(SEMICOLON);
  1.2617 +    writer.write(ICAL_BYMONTH);
  1.2618 +    writer.write(EQUALS_SIGN);
  1.2619 +    appendAsciiDigits(month + 1, 0, dstr);
  1.2620 +    writer.write(dstr);
  1.2621 +    writer.write(SEMICOLON);
  1.2622 +}
  1.2623 +
  1.2624 +/*
  1.2625 + * Append the UNTIL attribute after RRULE line
  1.2626 + */
  1.2627 +void
  1.2628 +VTimeZone::appendUNTIL(VTZWriter& writer, const UnicodeString& until,  UErrorCode& status) const {
  1.2629 +    if (U_FAILURE(status)) {
  1.2630 +        return;
  1.2631 +    }
  1.2632 +    if (until.length() > 0) {
  1.2633 +        writer.write(SEMICOLON);
  1.2634 +        writer.write(ICAL_UNTIL);
  1.2635 +        writer.write(EQUALS_SIGN);
  1.2636 +        writer.write(until);
  1.2637 +    }
  1.2638 +}
  1.2639 +
  1.2640 +U_NAMESPACE_END
  1.2641 +
  1.2642 +#endif /* #if !UCONFIG_NO_FORMATTING */
  1.2643 +
  1.2644 +//eof

mercurial