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