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