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