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