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