tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

vtzone.cpp (91663B)


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