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