1/*
2*******************************************************************************
3* Copyright (C) 2007-2013, International Business Machines Corporation and
4* others. All Rights Reserved.
5*******************************************************************************
6*/
7
8#include "utypeinfo.h"  // for 'typeid' to work
9
10#include "unicode/utypes.h"
11
12#if !UCONFIG_NO_FORMATTING
13
14#include "unicode/vtzone.h"
15#include "unicode/rbtz.h"
16#include "unicode/ucal.h"
17#include "unicode/ures.h"
18#include "cmemory.h"
19#include "uvector.h"
20#include "gregoimp.h"
21#include "uassert.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, -1) == 0) {
418            // only support YEARLY frequency type
419            if (value.compare(ICAL_YEARLY, -1) == 0) {
420                yearly = TRUE;
421            } else {
422                goto rruleParseError;
423            }
424        } else if (attr.compare(ICAL_UNTIL, -1) == 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, -1) == 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, -1) == 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, -1) == 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);
890    //void write(const UChar* str, int32_t length);
891private:
892    UnicodeString* out;
893};
894
895VTZWriter::VTZWriter(UnicodeString& output) {
896    out = &output;
897}
898
899VTZWriter::~VTZWriter() {
900}
901
902void
903VTZWriter::write(const UnicodeString& str) {
904    out->append(str);
905}
906
907void
908VTZWriter::write(UChar ch) {
909    out->append(ch);
910}
911
912void
913VTZWriter::write(const UChar* str) {
914    out->append(str, -1);
915}
916
917/*
918void
919VTZWriter::write(const UChar* str, int32_t length) {
920    out->append(str, length);
921}
922*/
923
924class VTZReader {
925public:
926    VTZReader(const UnicodeString& input);
927    ~VTZReader();
928
929    UChar read(void);
930private:
931    const UnicodeString* in;
932    int32_t index;
933};
934
935VTZReader::VTZReader(const UnicodeString& input) {
936    in = &input;
937    index = 0;
938}
939
940VTZReader::~VTZReader() {
941}
942
943UChar
944VTZReader::read(void) {
945    UChar ch = 0xFFFF;
946    if (index < in->length()) {
947        ch = in->charAt(index);
948    }
949    index++;
950    return ch;
951}
952
953
954UOBJECT_DEFINE_RTTI_IMPLEMENTATION(VTimeZone)
955
956VTimeZone::VTimeZone()
957:   BasicTimeZone(), tz(NULL), vtzlines(NULL),
958    lastmod(MAX_MILLIS) {
959}
960
961VTimeZone::VTimeZone(const VTimeZone& source)
962:   BasicTimeZone(source), tz(NULL), vtzlines(NULL),
963    tzurl(source.tzurl), lastmod(source.lastmod),
964    olsonzid(source.olsonzid), icutzver(source.icutzver) {
965    if (source.tz != NULL) {
966        tz = (BasicTimeZone*)source.tz->clone();
967    }
968    if (source.vtzlines != NULL) {
969        UErrorCode status = U_ZERO_ERROR;
970        int32_t size = source.vtzlines->size();
971        vtzlines = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, size, status);
972        if (U_SUCCESS(status)) {
973            for (int32_t i = 0; i < size; i++) {
974                UnicodeString *line = (UnicodeString*)source.vtzlines->elementAt(i);
975                vtzlines->addElement(line->clone(), status);
976                if (U_FAILURE(status)) {
977                    break;
978                }
979            }
980        }
981        if (U_FAILURE(status) && vtzlines != NULL) {
982            delete vtzlines;
983        }
984    }
985}
986
987VTimeZone::~VTimeZone() {
988    if (tz != NULL) {
989        delete tz;
990    }
991    if (vtzlines != NULL) {
992        delete vtzlines;
993    }
994}
995
996VTimeZone&
997VTimeZone::operator=(const VTimeZone& right) {
998    if (this == &right) {
999        return *this;
1000    }
1001    if (*this != right) {
1002        BasicTimeZone::operator=(right);
1003        if (tz != NULL) {
1004            delete tz;
1005            tz = NULL;
1006        }
1007        if (right.tz != NULL) {
1008            tz = (BasicTimeZone*)right.tz->clone();
1009        }
1010        if (vtzlines != NULL) {
1011            delete vtzlines;
1012        }
1013        if (right.vtzlines != NULL) {
1014            UErrorCode status = U_ZERO_ERROR;
1015            int32_t size = right.vtzlines->size();
1016            vtzlines = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, size, status);
1017            if (U_SUCCESS(status)) {
1018                for (int32_t i = 0; i < size; i++) {
1019                    UnicodeString *line = (UnicodeString*)right.vtzlines->elementAt(i);
1020                    vtzlines->addElement(line->clone(), status);
1021                    if (U_FAILURE(status)) {
1022                        break;
1023                    }
1024                }
1025            }
1026            if (U_FAILURE(status) && vtzlines != NULL) {
1027                delete vtzlines;
1028                vtzlines = NULL;
1029            }
1030        }
1031        tzurl = right.tzurl;
1032        lastmod = right.lastmod;
1033        olsonzid = right.olsonzid;
1034        icutzver = right.icutzver;
1035    }
1036    return *this;
1037}
1038
1039UBool
1040VTimeZone::operator==(const TimeZone& that) const {
1041    if (this == &that) {
1042        return TRUE;
1043    }
1044    if (typeid(*this) != typeid(that) || !BasicTimeZone::operator==(that)) {
1045        return FALSE;
1046    }
1047    VTimeZone *vtz = (VTimeZone*)&that;
1048    if (*tz == *(vtz->tz)
1049        && tzurl == vtz->tzurl
1050        && lastmod == vtz->lastmod
1051        /* && olsonzid = that.olsonzid */
1052        /* && icutzver = that.icutzver */) {
1053        return TRUE;
1054    }
1055    return FALSE;
1056}
1057
1058UBool
1059VTimeZone::operator!=(const TimeZone& that) const {
1060    return !operator==(that);
1061}
1062
1063VTimeZone*
1064VTimeZone::createVTimeZoneByID(const UnicodeString& ID) {
1065    VTimeZone *vtz = new VTimeZone();
1066    vtz->tz = (BasicTimeZone*)TimeZone::createTimeZone(ID);
1067    vtz->tz->getID(vtz->olsonzid);
1068
1069    // Set ICU tzdata version
1070    UErrorCode status = U_ZERO_ERROR;
1071    UResourceBundle *bundle = NULL;
1072    const UChar* versionStr = NULL;
1073    int32_t len = 0;
1074    bundle = ures_openDirect(NULL, "zoneinfo64", &status);
1075    versionStr = ures_getStringByKey(bundle, "TZVersion", &len, &status);
1076    if (U_SUCCESS(status)) {
1077        vtz->icutzver.setTo(versionStr, len);
1078    }
1079    ures_close(bundle);
1080    return vtz;
1081}
1082
1083VTimeZone*
1084VTimeZone::createVTimeZoneFromBasicTimeZone(const BasicTimeZone& basic_time_zone, UErrorCode &status) {
1085    if (U_FAILURE(status)) {
1086        return NULL;
1087    }
1088    VTimeZone *vtz = new VTimeZone();
1089    if (vtz == NULL) {
1090        status = U_MEMORY_ALLOCATION_ERROR;
1091        return NULL;
1092    }
1093    vtz->tz = (BasicTimeZone *)basic_time_zone.clone();
1094    if (vtz->tz == NULL) {
1095        status = U_MEMORY_ALLOCATION_ERROR;
1096        delete vtz;
1097        return NULL;
1098    }
1099    vtz->tz->getID(vtz->olsonzid);
1100
1101    // Set ICU tzdata version
1102    UResourceBundle *bundle = NULL;
1103    const UChar* versionStr = NULL;
1104    int32_t len = 0;
1105    bundle = ures_openDirect(NULL, "zoneinfo64", &status);
1106    versionStr = ures_getStringByKey(bundle, "TZVersion", &len, &status);
1107    if (U_SUCCESS(status)) {
1108        vtz->icutzver.setTo(versionStr, len);
1109    }
1110    ures_close(bundle);
1111    return vtz;
1112}
1113
1114VTimeZone*
1115VTimeZone::createVTimeZone(const UnicodeString& vtzdata, UErrorCode& status) {
1116    if (U_FAILURE(status)) {
1117        return NULL;
1118    }
1119    VTZReader reader(vtzdata);
1120    VTimeZone *vtz = new VTimeZone();
1121    vtz->load(reader, status);
1122    if (U_FAILURE(status)) {
1123        delete vtz;
1124        return NULL;
1125    }
1126    return vtz;
1127}
1128
1129UBool
1130VTimeZone::getTZURL(UnicodeString& url) const {
1131    if (tzurl.length() > 0) {
1132        url = tzurl;
1133        return TRUE;
1134    }
1135    return FALSE;
1136}
1137
1138void
1139VTimeZone::setTZURL(const UnicodeString& url) {
1140    tzurl = url;
1141}
1142
1143UBool
1144VTimeZone::getLastModified(UDate& lastModified) const {
1145    if (lastmod != MAX_MILLIS) {
1146        lastModified = lastmod;
1147        return TRUE;
1148    }
1149    return FALSE;
1150}
1151
1152void
1153VTimeZone::setLastModified(UDate lastModified) {
1154    lastmod = lastModified;
1155}
1156
1157void
1158VTimeZone::write(UnicodeString& result, UErrorCode& status) const {
1159    result.remove();
1160    VTZWriter writer(result);
1161    write(writer, status);
1162}
1163
1164void
1165VTimeZone::write(UDate start, UnicodeString& result, UErrorCode& status) const {
1166    result.remove();
1167    VTZWriter writer(result);
1168    write(start, writer, status);
1169}
1170
1171void
1172VTimeZone::writeSimple(UDate time, UnicodeString& result, UErrorCode& status) const {
1173    result.remove();
1174    VTZWriter writer(result);
1175    writeSimple(time, writer, status);
1176}
1177
1178TimeZone*
1179VTimeZone::clone(void) const {
1180    return new VTimeZone(*this);
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, UErrorCode& status) const {
1186    return tz->getOffset(era, year, month, day, dayOfWeek, millis, status);
1187}
1188
1189int32_t
1190VTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day,
1191                     uint8_t dayOfWeek, int32_t millis,
1192                     int32_t monthLength, UErrorCode& status) const {
1193    return tz->getOffset(era, year, month, day, dayOfWeek, millis, monthLength, status);
1194}
1195
1196void
1197VTimeZone::getOffset(UDate date, UBool local, int32_t& rawOffset,
1198                     int32_t& dstOffset, UErrorCode& status) const {
1199    return tz->getOffset(date, local, rawOffset, dstOffset, status);
1200}
1201
1202void
1203VTimeZone::setRawOffset(int32_t offsetMillis) {
1204    tz->setRawOffset(offsetMillis);
1205}
1206
1207int32_t
1208VTimeZone::getRawOffset(void) const {
1209    return tz->getRawOffset();
1210}
1211
1212UBool
1213VTimeZone::useDaylightTime(void) const {
1214    return tz->useDaylightTime();
1215}
1216
1217UBool
1218VTimeZone::inDaylightTime(UDate date, UErrorCode& status) const {
1219    return tz->inDaylightTime(date, status);
1220}
1221
1222UBool
1223VTimeZone::hasSameRules(const TimeZone& other) const {
1224    return tz->hasSameRules(other);
1225}
1226
1227UBool
1228VTimeZone::getNextTransition(UDate base, UBool inclusive, TimeZoneTransition& result) const {
1229    return tz->getNextTransition(base, inclusive, result);
1230}
1231
1232UBool
1233VTimeZone::getPreviousTransition(UDate base, UBool inclusive, TimeZoneTransition& result) const {
1234    return tz->getPreviousTransition(base, inclusive, result);
1235}
1236
1237int32_t
1238VTimeZone::countTransitionRules(UErrorCode& status) const {
1239    return tz->countTransitionRules(status);
1240}
1241
1242void
1243VTimeZone::getTimeZoneRules(const InitialTimeZoneRule*& initial,
1244                            const TimeZoneRule* trsrules[], int32_t& trscount,
1245                            UErrorCode& status) const {
1246    tz->getTimeZoneRules(initial, trsrules, trscount, status);
1247}
1248
1249void
1250VTimeZone::load(VTZReader& reader, UErrorCode& status) {
1251    vtzlines = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, DEFAULT_VTIMEZONE_LINES, status);
1252    if (U_FAILURE(status)) {
1253        return;
1254    }
1255    UBool eol = FALSE;
1256    UBool start = FALSE;
1257    UBool success = FALSE;
1258    UnicodeString line;
1259
1260    while (TRUE) {
1261        UChar ch = reader.read();
1262        if (ch == 0xFFFF) {
1263            // end of file
1264            if (start && line.startsWith(ICAL_END_VTIMEZONE, -1)) {
1265                vtzlines->addElement(new UnicodeString(line), status);
1266                if (U_FAILURE(status)) {
1267                    goto cleanupVtzlines;
1268                }
1269                success = TRUE;
1270            }
1271            break;
1272        }
1273        if (ch == 0x000D) {
1274            // CR, must be followed by LF according to the definition in RFC2445
1275            continue;
1276        }
1277        if (eol) {
1278            if (ch != 0x0009 && ch != 0x0020) {
1279                // NOT followed by TAB/SP -> new line
1280                if (start) {
1281                    if (line.length() > 0) {
1282                        vtzlines->addElement(new UnicodeString(line), status);
1283                        if (U_FAILURE(status)) {
1284                            goto cleanupVtzlines;
1285                        }
1286                    }
1287                }
1288                line.remove();
1289                if (ch != 0x000A) {
1290                    line.append(ch);
1291                }
1292            }
1293            eol = FALSE;
1294        } else {
1295            if (ch == 0x000A) {
1296                // LF
1297                eol = TRUE;
1298                if (start) {
1299                    if (line.startsWith(ICAL_END_VTIMEZONE, -1)) {
1300                        vtzlines->addElement(new UnicodeString(line), status);
1301                        if (U_FAILURE(status)) {
1302                            goto cleanupVtzlines;
1303                        }
1304                        success = TRUE;
1305                        break;
1306                    }
1307                } else {
1308                    if (line.startsWith(ICAL_BEGIN_VTIMEZONE, -1)) {
1309                        vtzlines->addElement(new UnicodeString(line), status);
1310                        if (U_FAILURE(status)) {
1311                            goto cleanupVtzlines;
1312                        }
1313                        line.remove();
1314                        start = TRUE;
1315                        eol = FALSE;
1316                    }
1317                }
1318            } else {
1319                line.append(ch);
1320            }
1321        }
1322    }
1323    if (!success) {
1324        if (U_SUCCESS(status)) {
1325            status = U_INVALID_STATE_ERROR;
1326        }
1327        goto cleanupVtzlines;
1328    }
1329    parse(status);
1330    return;
1331
1332cleanupVtzlines:
1333    delete vtzlines;
1334    vtzlines = NULL;
1335}
1336
1337// parser state
1338#define INI 0   // Initial state
1339#define VTZ 1   // In VTIMEZONE
1340#define TZI 2   // In STANDARD or DAYLIGHT
1341
1342#define DEF_DSTSAVINGS (60*60*1000)
1343#define DEF_TZSTARTTIME (0.0)
1344
1345void
1346VTimeZone::parse(UErrorCode& status) {
1347    if (U_FAILURE(status)) {
1348        return;
1349    }
1350    if (vtzlines == NULL || vtzlines->size() == 0) {
1351        status = U_INVALID_STATE_ERROR;
1352        return;
1353    }
1354    InitialTimeZoneRule *initialRule = NULL;
1355    RuleBasedTimeZone *rbtz = NULL;
1356
1357    // timezone ID
1358    UnicodeString tzid;
1359
1360    int32_t state = INI;
1361    int32_t n = 0;
1362    UBool dst = FALSE;      // current zone type
1363    UnicodeString from;     // current zone from offset
1364    UnicodeString to;       // current zone offset
1365    UnicodeString zonename;   // current zone name
1366    UnicodeString dtstart;  // current zone starts
1367    UBool isRRULE = FALSE;  // true if the rule is described by RRULE
1368    int32_t initialRawOffset = 0;   // initial offset
1369    int32_t initialDSTSavings = 0;  // initial offset
1370    UDate firstStart = MAX_MILLIS;  // the earliest rule start time
1371    UnicodeString name;     // RFC2445 prop name
1372    UnicodeString value;    // RFC2445 prop value
1373
1374    UVector *dates = NULL;  // list of RDATE or RRULE strings
1375    UVector *rules = NULL;  // list of TimeZoneRule instances
1376
1377    int32_t finalRuleIdx = -1;
1378    int32_t finalRuleCount = 0;
1379
1380    rules = new UVector(status);
1381    if (U_FAILURE(status)) {
1382        goto cleanupParse;
1383    }
1384     // Set the deleter to remove TimeZoneRule vectors to avoid memory leaks due to unowned TimeZoneRules.
1385    rules->setDeleter(deleteTimeZoneRule);
1386
1387    dates = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, status);
1388    if (U_FAILURE(status)) {
1389        goto cleanupParse;
1390    }
1391    if (rules == NULL || dates == NULL) {
1392        status = U_MEMORY_ALLOCATION_ERROR;
1393        goto cleanupParse;
1394    }
1395
1396    for (n = 0; n < vtzlines->size(); n++) {
1397        UnicodeString *line = (UnicodeString*)vtzlines->elementAt(n);
1398        int32_t valueSep = line->indexOf(COLON);
1399        if (valueSep < 0) {
1400            continue;
1401        }
1402        name.setTo(*line, 0, valueSep);
1403        value.setTo(*line, valueSep + 1);
1404
1405        switch (state) {
1406        case INI:
1407            if (name.compare(ICAL_BEGIN, -1) == 0
1408                && value.compare(ICAL_VTIMEZONE, -1) == 0) {
1409                state = VTZ;
1410            }
1411            break;
1412
1413        case VTZ:
1414            if (name.compare(ICAL_TZID, -1) == 0) {
1415                tzid = value;
1416            } else if (name.compare(ICAL_TZURL, -1) == 0) {
1417                tzurl = value;
1418            } else if (name.compare(ICAL_LASTMOD, -1) == 0) {
1419                // Always in 'Z' format, so the offset argument for the parse method
1420                // can be any value.
1421                lastmod = parseDateTimeString(value, 0, status);
1422                if (U_FAILURE(status)) {
1423                    goto cleanupParse;
1424                }
1425            } else if (name.compare(ICAL_BEGIN, -1) == 0) {
1426                UBool isDST = (value.compare(ICAL_DAYLIGHT, -1) == 0);
1427                if (value.compare(ICAL_STANDARD, -1) == 0 || isDST) {
1428                    // tzid must be ready at this point
1429                    if (tzid.length() == 0) {
1430                        goto cleanupParse;
1431                    }
1432                    // initialize current zone properties
1433                    if (dates->size() != 0) {
1434                        dates->removeAllElements();
1435                    }
1436                    isRRULE = FALSE;
1437                    from.remove();
1438                    to.remove();
1439                    zonename.remove();
1440                    dst = isDST;
1441                    state = TZI;
1442                } else {
1443                    // BEGIN property other than STANDARD/DAYLIGHT
1444                    // must not be there.
1445                    goto cleanupParse;
1446                }
1447            } else if (name.compare(ICAL_END, -1) == 0) {
1448                break;
1449            }
1450            break;
1451        case TZI:
1452            if (name.compare(ICAL_DTSTART, -1) == 0) {
1453                dtstart = value;
1454            } else if (name.compare(ICAL_TZNAME, -1) == 0) {
1455                zonename = value;
1456            } else if (name.compare(ICAL_TZOFFSETFROM, -1) == 0) {
1457                from = value;
1458            } else if (name.compare(ICAL_TZOFFSETTO, -1) == 0) {
1459                to = value;
1460            } else if (name.compare(ICAL_RDATE, -1) == 0) {
1461                // RDATE mixed with RRULE is not supported
1462                if (isRRULE) {
1463                    goto cleanupParse;
1464                }
1465                // RDATE value may contain multiple date delimited
1466                // by comma
1467                UBool nextDate = TRUE;
1468                int32_t dstart = 0;
1469                UnicodeString *dstr;
1470                while (nextDate) {
1471                    int32_t dend = value.indexOf(COMMA, dstart);
1472                    if (dend == -1) {
1473                        dstr = new UnicodeString(value, dstart);
1474                        nextDate = FALSE;
1475                    } else {
1476                        dstr = new UnicodeString(value, dstart, dend - dstart);
1477                    }
1478                    dates->addElement(dstr, status);
1479                    if (U_FAILURE(status)) {
1480                        goto cleanupParse;
1481                    }
1482                    dstart = dend + 1;
1483                }
1484            } else if (name.compare(ICAL_RRULE, -1) == 0) {
1485                // RRULE mixed with RDATE is not supported
1486                if (!isRRULE && dates->size() != 0) {
1487                    goto cleanupParse;
1488                }
1489                isRRULE = true;
1490                dates->addElement(new UnicodeString(value), status);
1491                if (U_FAILURE(status)) {
1492                    goto cleanupParse;
1493                }
1494            } else if (name.compare(ICAL_END, -1) == 0) {
1495                // Mandatory properties
1496                if (dtstart.length() == 0 || from.length() == 0 || to.length() == 0) {
1497                    goto cleanupParse;
1498                }
1499                // if zonename is not available, create one from tzid
1500                if (zonename.length() == 0) {
1501                    getDefaultTZName(tzid, dst, zonename);
1502                }
1503
1504                // create a time zone rule
1505                TimeZoneRule *rule = NULL;
1506                int32_t fromOffset = 0;
1507                int32_t toOffset = 0;
1508                int32_t rawOffset = 0;
1509                int32_t dstSavings = 0;
1510                UDate start = 0;
1511
1512                // Parse TZOFFSETFROM/TZOFFSETTO
1513                fromOffset = offsetStrToMillis(from, status);
1514                toOffset = offsetStrToMillis(to, status);
1515                if (U_FAILURE(status)) {
1516                    goto cleanupParse;
1517                }
1518
1519                if (dst) {
1520                    // If daylight, use the previous offset as rawoffset if positive
1521                    if (toOffset - fromOffset > 0) {
1522                        rawOffset = fromOffset;
1523                        dstSavings = toOffset - fromOffset;
1524                    } else {
1525                        // This is rare case..  just use 1 hour DST savings
1526                        rawOffset = toOffset - DEF_DSTSAVINGS;
1527                        dstSavings = DEF_DSTSAVINGS;
1528                    }
1529                } else {
1530                    rawOffset = toOffset;
1531                    dstSavings = 0;
1532                }
1533
1534                // start time
1535                start = parseDateTimeString(dtstart, fromOffset, status);
1536                if (U_FAILURE(status)) {
1537                    goto cleanupParse;
1538                }
1539
1540                // Create the rule
1541                UDate actualStart = MAX_MILLIS;
1542                if (isRRULE) {
1543                    rule = createRuleByRRULE(zonename, rawOffset, dstSavings, start, dates, fromOffset, status);
1544                } else {
1545                    rule = createRuleByRDATE(zonename, rawOffset, dstSavings, start, dates, fromOffset, status);
1546                }
1547                if (U_FAILURE(status) || rule == NULL) {
1548                    goto cleanupParse;
1549                } else {
1550                    UBool startAvail = rule->getFirstStart(fromOffset, 0, actualStart);
1551                    if (startAvail && actualStart < firstStart) {
1552                        // save from offset information for the earliest rule
1553                        firstStart = actualStart;
1554                        // If this is STD, assume the time before this transtion
1555                        // is DST when the difference is 1 hour.  This might not be
1556                        // accurate, but VTIMEZONE data does not have such info.
1557                        if (dstSavings > 0) {
1558                            initialRawOffset = fromOffset;
1559                            initialDSTSavings = 0;
1560                        } else {
1561                            if (fromOffset - toOffset == DEF_DSTSAVINGS) {
1562                                initialRawOffset = fromOffset - DEF_DSTSAVINGS;
1563                                initialDSTSavings = DEF_DSTSAVINGS;
1564                            } else {
1565                                initialRawOffset = fromOffset;
1566                                initialDSTSavings = 0;
1567                            }
1568                        }
1569                    }
1570                }
1571                rules->addElement(rule, status);
1572                if (U_FAILURE(status)) {
1573                    goto cleanupParse;
1574                }
1575                state = VTZ;
1576            }
1577            break;
1578        }
1579    }
1580    // Must have at least one rule
1581    if (rules->size() == 0) {
1582        goto cleanupParse;
1583    }
1584
1585    // Create a initial rule
1586    getDefaultTZName(tzid, FALSE, zonename);
1587    initialRule = new InitialTimeZoneRule(zonename,
1588        initialRawOffset, initialDSTSavings);
1589    if (initialRule == NULL) {
1590        status = U_MEMORY_ALLOCATION_ERROR;
1591        goto cleanupParse;
1592    }
1593
1594    // Finally, create the RuleBasedTimeZone
1595    rbtz = new RuleBasedTimeZone(tzid, initialRule);
1596    if (rbtz == NULL) {
1597        status = U_MEMORY_ALLOCATION_ERROR;
1598        goto cleanupParse;
1599    }
1600    initialRule = NULL; // already adopted by RBTZ, no need to delete
1601
1602    for (n = 0; n < rules->size(); n++) {
1603        TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n);
1604        AnnualTimeZoneRule *atzrule = dynamic_cast<AnnualTimeZoneRule *>(r);
1605        if (atzrule != NULL) {
1606            if (atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR) {
1607                finalRuleCount++;
1608                finalRuleIdx = n;
1609            }
1610        }
1611    }
1612    if (finalRuleCount > 2) {
1613        // Too many final rules
1614        status = U_ILLEGAL_ARGUMENT_ERROR;
1615        goto cleanupParse;
1616    }
1617
1618    if (finalRuleCount == 1) {
1619        if (rules->size() == 1) {
1620            // Only one final rule, only governs the initial rule,
1621            // which is already initialized, thus, we do not need to
1622            // add this transition rule
1623            rules->removeAllElements();
1624        } else {
1625            // Normalize the final rule
1626            AnnualTimeZoneRule *finalRule = (AnnualTimeZoneRule*)rules->elementAt(finalRuleIdx);
1627            int32_t tmpRaw = finalRule->getRawOffset();
1628            int32_t tmpDST = finalRule->getDSTSavings();
1629
1630            // Find the last non-final rule
1631            UDate finalStart, start;
1632            finalRule->getFirstStart(initialRawOffset, initialDSTSavings, finalStart);
1633            start = finalStart;
1634            for (n = 0; n < rules->size(); n++) {
1635                if (finalRuleIdx == n) {
1636                    continue;
1637                }
1638                TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n);
1639                UDate lastStart;
1640                r->getFinalStart(tmpRaw, tmpDST, lastStart);
1641                if (lastStart > start) {
1642                    finalRule->getNextStart(lastStart,
1643                        r->getRawOffset(),
1644                        r->getDSTSavings(),
1645                        FALSE,
1646                        start);
1647                }
1648            }
1649
1650            TimeZoneRule *newRule;
1651            UnicodeString tznam;
1652            if (start == finalStart) {
1653                // Transform this into a single transition
1654                newRule = new TimeArrayTimeZoneRule(
1655                        finalRule->getName(tznam),
1656                        finalRule->getRawOffset(),
1657                        finalRule->getDSTSavings(),
1658                        &finalStart,
1659                        1,
1660                        DateTimeRule::UTC_TIME);
1661            } else {
1662                // Update the end year
1663                int32_t y, m, d, dow, doy, mid;
1664                Grego::timeToFields(start, y, m, d, dow, doy, mid);
1665                newRule = new AnnualTimeZoneRule(
1666                        finalRule->getName(tznam),
1667                        finalRule->getRawOffset(),
1668                        finalRule->getDSTSavings(),
1669                        *(finalRule->getRule()),
1670                        finalRule->getStartYear(),
1671                        y);
1672            }
1673            if (newRule == NULL) {
1674                status = U_MEMORY_ALLOCATION_ERROR;
1675                goto cleanupParse;
1676            }
1677            rules->removeElementAt(finalRuleIdx);
1678            rules->addElement(newRule, status);
1679            if (U_FAILURE(status)) {
1680                delete newRule;
1681                goto cleanupParse;
1682            }
1683        }
1684    }
1685
1686    while (!rules->isEmpty()) {
1687        TimeZoneRule *tzr = (TimeZoneRule*)rules->orphanElementAt(0);
1688        rbtz->addTransitionRule(tzr, status);
1689        if (U_FAILURE(status)) {
1690            goto cleanupParse;
1691        }
1692    }
1693    rbtz->complete(status);
1694    if (U_FAILURE(status)) {
1695        goto cleanupParse;
1696    }
1697    delete rules;
1698    delete dates;
1699
1700    tz = rbtz;
1701    setID(tzid);
1702    return;
1703
1704cleanupParse:
1705    if (rules != NULL) {
1706        while (!rules->isEmpty()) {
1707            TimeZoneRule *r = (TimeZoneRule*)rules->orphanElementAt(0);
1708            delete r;
1709        }
1710        delete rules;
1711    }
1712    if (dates != NULL) {
1713        delete dates;
1714    }
1715    if (initialRule != NULL) {
1716        delete initialRule;
1717    }
1718    if (rbtz != NULL) {
1719        delete rbtz;
1720    }
1721    return;
1722}
1723
1724void
1725VTimeZone::write(VTZWriter& writer, UErrorCode& status) const {
1726    if (vtzlines != NULL) {
1727        for (int32_t i = 0; i < vtzlines->size(); i++) {
1728            UnicodeString *line = (UnicodeString*)vtzlines->elementAt(i);
1729            if (line->startsWith(ICAL_TZURL, -1)
1730                && line->charAt(u_strlen(ICAL_TZURL)) == COLON) {
1731                writer.write(ICAL_TZURL);
1732                writer.write(COLON);
1733                writer.write(tzurl);
1734                writer.write(ICAL_NEWLINE);
1735            } else if (line->startsWith(ICAL_LASTMOD, -1)
1736                && line->charAt(u_strlen(ICAL_LASTMOD)) == COLON) {
1737                UnicodeString utcString;
1738                writer.write(ICAL_LASTMOD);
1739                writer.write(COLON);
1740                writer.write(getUTCDateTimeString(lastmod, utcString));
1741                writer.write(ICAL_NEWLINE);
1742            } else {
1743                writer.write(*line);
1744                writer.write(ICAL_NEWLINE);
1745            }
1746        }
1747    } else {
1748        UVector *customProps = NULL;
1749        if (olsonzid.length() > 0 && icutzver.length() > 0) {
1750            customProps = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, status);
1751            if (U_FAILURE(status)) {
1752                return;
1753            }
1754            UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP);
1755            icutzprop->append(olsonzid);
1756            icutzprop->append((UChar)0x005B/*'['*/);
1757            icutzprop->append(icutzver);
1758            icutzprop->append((UChar)0x005D/*']'*/);
1759            customProps->addElement(icutzprop, status);
1760            if (U_FAILURE(status)) {
1761                delete icutzprop;
1762                delete customProps;
1763                return;
1764            }
1765        }
1766        writeZone(writer, *tz, customProps, status);
1767        delete customProps;
1768    }
1769}
1770
1771void
1772VTimeZone::write(UDate start, VTZWriter& writer, UErrorCode& status) const {
1773    if (U_FAILURE(status)) {
1774        return;
1775    }
1776    InitialTimeZoneRule *initial = NULL;
1777    UVector *transitionRules = NULL;
1778    UVector customProps(uprv_deleteUObject, uhash_compareUnicodeString, status);
1779    UnicodeString tzid;
1780
1781    // Extract rules applicable to dates after the start time
1782    getTimeZoneRulesAfter(start, initial, transitionRules, status);
1783    if (U_FAILURE(status)) {
1784        return;
1785    }
1786
1787    // Create a RuleBasedTimeZone with the subset rule
1788    getID(tzid);
1789    RuleBasedTimeZone rbtz(tzid, initial);
1790    if (transitionRules != NULL) {
1791        while (!transitionRules->isEmpty()) {
1792            TimeZoneRule *tr = (TimeZoneRule*)transitionRules->orphanElementAt(0);
1793            rbtz.addTransitionRule(tr, status);
1794            if (U_FAILURE(status)) {
1795                goto cleanupWritePartial;
1796            }
1797        }
1798        delete transitionRules;
1799        transitionRules = NULL;
1800    }
1801    rbtz.complete(status);
1802    if (U_FAILURE(status)) {
1803        goto cleanupWritePartial;
1804    }
1805
1806    if (olsonzid.length() > 0 && icutzver.length() > 0) {
1807        UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP);
1808        icutzprop->append(olsonzid);
1809        icutzprop->append((UChar)0x005B/*'['*/);
1810        icutzprop->append(icutzver);
1811        icutzprop->append(ICU_TZINFO_PARTIAL, -1);
1812        appendMillis(start, *icutzprop);
1813        icutzprop->append((UChar)0x005D/*']'*/);
1814        customProps.addElement(icutzprop, status);
1815        if (U_FAILURE(status)) {
1816            delete icutzprop;
1817            goto cleanupWritePartial;
1818        }
1819    }
1820    writeZone(writer, rbtz, &customProps, status);
1821    return;
1822
1823cleanupWritePartial:
1824    if (initial != NULL) {
1825        delete initial;
1826    }
1827    if (transitionRules != NULL) {
1828        while (!transitionRules->isEmpty()) {
1829            TimeZoneRule *tr = (TimeZoneRule*)transitionRules->orphanElementAt(0);
1830            delete tr;
1831        }
1832        delete transitionRules;
1833    }
1834}
1835
1836void
1837VTimeZone::writeSimple(UDate time, VTZWriter& writer, UErrorCode& status) const {
1838    if (U_FAILURE(status)) {
1839        return;
1840    }
1841
1842    UVector customProps(uprv_deleteUObject, uhash_compareUnicodeString, status);
1843    UnicodeString tzid;
1844
1845    // Extract simple rules
1846    InitialTimeZoneRule *initial = NULL;
1847    AnnualTimeZoneRule *std = NULL, *dst = NULL;
1848    getSimpleRulesNear(time, initial, std, dst, status);
1849    if (U_SUCCESS(status)) {
1850        // Create a RuleBasedTimeZone with the subset rule
1851        getID(tzid);
1852        RuleBasedTimeZone rbtz(tzid, initial);
1853        if (std != NULL && dst != NULL) {
1854            rbtz.addTransitionRule(std, status);
1855            rbtz.addTransitionRule(dst, status);
1856        }
1857        if (U_FAILURE(status)) {
1858            goto cleanupWriteSimple;
1859        }
1860
1861        if (olsonzid.length() > 0 && icutzver.length() > 0) {
1862            UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP);
1863            icutzprop->append(olsonzid);
1864            icutzprop->append((UChar)0x005B/*'['*/);
1865            icutzprop->append(icutzver);
1866            icutzprop->append(ICU_TZINFO_SIMPLE, -1);
1867            appendMillis(time, *icutzprop);
1868            icutzprop->append((UChar)0x005D/*']'*/);
1869            customProps.addElement(icutzprop, status);
1870            if (U_FAILURE(status)) {
1871                delete icutzprop;
1872                goto cleanupWriteSimple;
1873            }
1874        }
1875        writeZone(writer, rbtz, &customProps, status);
1876    }
1877    return;
1878
1879cleanupWriteSimple:
1880    if (initial != NULL) {
1881        delete initial;
1882    }
1883    if (std != NULL) {
1884        delete std;
1885    }
1886    if (dst != NULL) {
1887        delete dst;
1888    }
1889}
1890
1891void
1892VTimeZone::writeZone(VTZWriter& w, BasicTimeZone& basictz,
1893                     UVector* customProps, UErrorCode& status) const {
1894    if (U_FAILURE(status)) {
1895        return;
1896    }
1897    writeHeaders(w, status);
1898    if (U_FAILURE(status)) {
1899        return;
1900    }
1901
1902    if (customProps != NULL) {
1903        for (int32_t i = 0; i < customProps->size(); i++) {
1904            UnicodeString *custprop = (UnicodeString*)customProps->elementAt(i);
1905            w.write(*custprop);
1906            w.write(ICAL_NEWLINE);
1907        }
1908    }
1909
1910    UDate t = MIN_MILLIS;
1911    UnicodeString dstName;
1912    int32_t dstFromOffset = 0;
1913    int32_t dstFromDSTSavings = 0;
1914    int32_t dstToOffset = 0;
1915    int32_t dstStartYear = 0;
1916    int32_t dstMonth = 0;
1917    int32_t dstDayOfWeek = 0;
1918    int32_t dstWeekInMonth = 0;
1919    int32_t dstMillisInDay = 0;
1920    UDate dstStartTime = 0.0;
1921    UDate dstUntilTime = 0.0;
1922    int32_t dstCount = 0;
1923    AnnualTimeZoneRule *finalDstRule = NULL;
1924
1925    UnicodeString stdName;
1926    int32_t stdFromOffset = 0;
1927    int32_t stdFromDSTSavings = 0;
1928    int32_t stdToOffset = 0;
1929    int32_t stdStartYear = 0;
1930    int32_t stdMonth = 0;
1931    int32_t stdDayOfWeek = 0;
1932    int32_t stdWeekInMonth = 0;
1933    int32_t stdMillisInDay = 0;
1934    UDate stdStartTime = 0.0;
1935    UDate stdUntilTime = 0.0;
1936    int32_t stdCount = 0;
1937    AnnualTimeZoneRule *finalStdRule = NULL;
1938
1939    int32_t year, month, dom, dow, doy, mid;
1940    UBool hasTransitions = FALSE;
1941    TimeZoneTransition tzt;
1942    UBool tztAvail;
1943    UnicodeString name;
1944    UBool isDst;
1945
1946    // Going through all transitions
1947    while (TRUE) {
1948        tztAvail = basictz.getNextTransition(t, FALSE, tzt);
1949        if (!tztAvail) {
1950            break;
1951        }
1952        hasTransitions = TRUE;
1953        t = tzt.getTime();
1954        tzt.getTo()->getName(name);
1955        isDst = (tzt.getTo()->getDSTSavings() != 0);
1956        int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings();
1957        int32_t fromDSTSavings = tzt.getFrom()->getDSTSavings();
1958        int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings();
1959        Grego::timeToFields(tzt.getTime() + fromOffset, year, month, dom, dow, doy, mid);
1960        int32_t weekInMonth = Grego::dayOfWeekInMonth(year, month, dom);
1961        UBool sameRule = FALSE;
1962        const AnnualTimeZoneRule *atzrule;
1963        if (isDst) {
1964            if (finalDstRule == NULL
1965                && (atzrule = dynamic_cast<const AnnualTimeZoneRule *>(tzt.getTo())) != NULL
1966                && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
1967            ) {
1968                finalDstRule = (AnnualTimeZoneRule*)tzt.getTo()->clone();
1969            }
1970            if (dstCount > 0) {
1971                if (year == dstStartYear + dstCount
1972                        && name.compare(dstName) == 0
1973                        && dstFromOffset == fromOffset
1974                        && dstToOffset == toOffset
1975                        && dstMonth == month
1976                        && dstDayOfWeek == dow
1977                        && dstWeekInMonth == weekInMonth
1978                        && dstMillisInDay == mid) {
1979                    // Update until time
1980                    dstUntilTime = t;
1981                    dstCount++;
1982                    sameRule = TRUE;
1983                }
1984                if (!sameRule) {
1985                    if (dstCount == 1) {
1986                        writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime,
1987                                TRUE, status);
1988                    } else {
1989                        writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
1990                                dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
1991                    }
1992                    if (U_FAILURE(status)) {
1993                        goto cleanupWriteZone;
1994                    }
1995                }
1996            }
1997            if (!sameRule) {
1998                // Reset this DST information
1999                dstName = name;
2000                dstFromOffset = fromOffset;
2001                dstFromDSTSavings = fromDSTSavings;
2002                dstToOffset = toOffset;
2003                dstStartYear = year;
2004                dstMonth = month;
2005                dstDayOfWeek = dow;
2006                dstWeekInMonth = weekInMonth;
2007                dstMillisInDay = mid;
2008                dstStartTime = dstUntilTime = t;
2009                dstCount = 1;
2010            }
2011            if (finalStdRule != NULL && finalDstRule != NULL) {
2012                break;
2013            }
2014        } else {
2015            if (finalStdRule == NULL
2016                && (atzrule = dynamic_cast<const AnnualTimeZoneRule *>(tzt.getTo())) != NULL
2017                && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
2018            ) {
2019                finalStdRule = (AnnualTimeZoneRule*)tzt.getTo()->clone();
2020            }
2021            if (stdCount > 0) {
2022                if (year == stdStartYear + stdCount
2023                        && name.compare(stdName) == 0
2024                        && stdFromOffset == fromOffset
2025                        && stdToOffset == toOffset
2026                        && stdMonth == month
2027                        && stdDayOfWeek == dow
2028                        && stdWeekInMonth == weekInMonth
2029                        && stdMillisInDay == mid) {
2030                    // Update until time
2031                    stdUntilTime = t;
2032                    stdCount++;
2033                    sameRule = TRUE;
2034                }
2035                if (!sameRule) {
2036                    if (stdCount == 1) {
2037                        writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime,
2038                                TRUE, status);
2039                    } else {
2040                        writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2041                                stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2042                    }
2043                    if (U_FAILURE(status)) {
2044                        goto cleanupWriteZone;
2045                    }
2046                }
2047            }
2048            if (!sameRule) {
2049                // Reset this STD information
2050                stdName = name;
2051                stdFromOffset = fromOffset;
2052                stdFromDSTSavings = fromDSTSavings;
2053                stdToOffset = toOffset;
2054                stdStartYear = year;
2055                stdMonth = month;
2056                stdDayOfWeek = dow;
2057                stdWeekInMonth = weekInMonth;
2058                stdMillisInDay = mid;
2059                stdStartTime = stdUntilTime = t;
2060                stdCount = 1;
2061            }
2062            if (finalStdRule != NULL && finalDstRule != NULL) {
2063                break;
2064            }
2065        }
2066    }
2067    if (!hasTransitions) {
2068        // No transition - put a single non transition RDATE
2069        int32_t raw, dst, offset;
2070        basictz.getOffset(0.0/*any time*/, FALSE, raw, dst, status);
2071        if (U_FAILURE(status)) {
2072            goto cleanupWriteZone;
2073        }
2074        offset = raw + dst;
2075        isDst = (dst != 0);
2076        UnicodeString tzid;
2077        basictz.getID(tzid);
2078        getDefaultTZName(tzid, isDst, name);
2079        writeZonePropsByTime(w, isDst, name,
2080                offset, offset, DEF_TZSTARTTIME - offset, FALSE, status);
2081        if (U_FAILURE(status)) {
2082            goto cleanupWriteZone;
2083        }
2084    } else {
2085        if (dstCount > 0) {
2086            if (finalDstRule == NULL) {
2087                if (dstCount == 1) {
2088                    writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime,
2089                            TRUE, status);
2090                } else {
2091                    writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
2092                            dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
2093                }
2094                if (U_FAILURE(status)) {
2095                    goto cleanupWriteZone;
2096                }
2097            } else {
2098                if (dstCount == 1) {
2099                    writeFinalRule(w, TRUE, finalDstRule,
2100                            dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, dstStartTime, status);
2101                } else {
2102                    // Use a single rule if possible
2103                    if (isEquivalentDateRule(dstMonth, dstWeekInMonth, dstDayOfWeek, finalDstRule->getRule())) {
2104                        writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
2105                                dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, MAX_MILLIS, status);
2106                    } else {
2107                        // Not equivalent rule - write out two different rules
2108                        writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
2109                                dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
2110                        if (U_FAILURE(status)) {
2111                            goto cleanupWriteZone;
2112                        }
2113                        UDate nextStart;
2114                        UBool nextStartAvail = finalDstRule->getNextStart(dstUntilTime, dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, false, nextStart);
2115                        U_ASSERT(nextStartAvail);
2116                        if (nextStartAvail) {
2117                            writeFinalRule(w, TRUE, finalDstRule,
2118                                    dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, nextStart, status);
2119                        }
2120                    }
2121                }
2122                if (U_FAILURE(status)) {
2123                    goto cleanupWriteZone;
2124                }
2125            }
2126        }
2127        if (stdCount > 0) {
2128            if (finalStdRule == NULL) {
2129                if (stdCount == 1) {
2130                    writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime,
2131                            TRUE, status);
2132                } else {
2133                    writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2134                            stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2135                }
2136                if (U_FAILURE(status)) {
2137                    goto cleanupWriteZone;
2138                }
2139            } else {
2140                if (stdCount == 1) {
2141                    writeFinalRule(w, FALSE, finalStdRule,
2142                            stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, stdStartTime, status);
2143                } else {
2144                    // Use a single rule if possible
2145                    if (isEquivalentDateRule(stdMonth, stdWeekInMonth, stdDayOfWeek, finalStdRule->getRule())) {
2146                        writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2147                                stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, MAX_MILLIS, status);
2148                    } else {
2149                        // Not equivalent rule - write out two different rules
2150                        writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2151                                stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2152                        if (U_FAILURE(status)) {
2153                            goto cleanupWriteZone;
2154                        }
2155                        UDate nextStart;
2156                        UBool nextStartAvail = finalStdRule->getNextStart(stdUntilTime, stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, false, nextStart);
2157                        U_ASSERT(nextStartAvail);
2158                        if (nextStartAvail) {
2159                            writeFinalRule(w, FALSE, finalStdRule,
2160                                    stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, nextStart, status);
2161                        }
2162                    }
2163                }
2164                if (U_FAILURE(status)) {
2165                    goto cleanupWriteZone;
2166                }
2167            }
2168        }
2169    }
2170    writeFooter(w, status);
2171
2172cleanupWriteZone:
2173
2174    if (finalStdRule != NULL) {
2175        delete finalStdRule;
2176    }
2177    if (finalDstRule != NULL) {
2178        delete finalDstRule;
2179    }
2180}
2181
2182void
2183VTimeZone::writeHeaders(VTZWriter& writer, UErrorCode& status) const {
2184    if (U_FAILURE(status)) {
2185        return;
2186    }
2187    UnicodeString tzid;
2188    tz->getID(tzid);
2189
2190    writer.write(ICAL_BEGIN);
2191    writer.write(COLON);
2192    writer.write(ICAL_VTIMEZONE);
2193    writer.write(ICAL_NEWLINE);
2194    writer.write(ICAL_TZID);
2195    writer.write(COLON);
2196    writer.write(tzid);
2197    writer.write(ICAL_NEWLINE);
2198    if (tzurl.length() != 0) {
2199        writer.write(ICAL_TZURL);
2200        writer.write(COLON);
2201        writer.write(tzurl);
2202        writer.write(ICAL_NEWLINE);
2203    }
2204    if (lastmod != MAX_MILLIS) {
2205        UnicodeString lastmodStr;
2206        writer.write(ICAL_LASTMOD);
2207        writer.write(COLON);
2208        writer.write(getUTCDateTimeString(lastmod, lastmodStr));
2209        writer.write(ICAL_NEWLINE);
2210    }
2211}
2212
2213/*
2214 * Write the closing section of the VTIMEZONE definition block
2215 */
2216void
2217VTimeZone::writeFooter(VTZWriter& writer, UErrorCode& status) const {
2218    if (U_FAILURE(status)) {
2219        return;
2220    }
2221    writer.write(ICAL_END);
2222    writer.write(COLON);
2223    writer.write(ICAL_VTIMEZONE);
2224    writer.write(ICAL_NEWLINE);
2225}
2226
2227/*
2228 * Write a single start time
2229 */
2230void
2231VTimeZone::writeZonePropsByTime(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2232                                int32_t fromOffset, int32_t toOffset, UDate time, UBool withRDATE,
2233                                UErrorCode& status) const {
2234    if (U_FAILURE(status)) {
2235        return;
2236    }
2237    beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, time, status);
2238    if (U_FAILURE(status)) {
2239        return;
2240    }
2241    if (withRDATE) {
2242        writer.write(ICAL_RDATE);
2243        writer.write(COLON);
2244        UnicodeString timestr;
2245        writer.write(getDateTimeString(time + fromOffset, timestr));
2246        writer.write(ICAL_NEWLINE);
2247    }
2248    endZoneProps(writer, isDst, status);
2249    if (U_FAILURE(status)) {
2250        return;
2251    }
2252}
2253
2254/*
2255 * Write start times defined by a DOM rule using VTIMEZONE RRULE
2256 */
2257void
2258VTimeZone::writeZonePropsByDOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2259                               int32_t fromOffset, int32_t toOffset,
2260                               int32_t month, int32_t dayOfMonth, UDate startTime, UDate untilTime,
2261                               UErrorCode& status) const {
2262    if (U_FAILURE(status)) {
2263        return;
2264    }
2265    beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
2266    if (U_FAILURE(status)) {
2267        return;
2268    }
2269    beginRRULE(writer, month, status);
2270    if (U_FAILURE(status)) {
2271        return;
2272    }
2273    writer.write(ICAL_BYMONTHDAY);
2274    writer.write(EQUALS_SIGN);
2275    UnicodeString dstr;
2276    appendAsciiDigits(dayOfMonth, 0, dstr);
2277    writer.write(dstr);
2278    if (untilTime != MAX_MILLIS) {
2279        appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2280        if (U_FAILURE(status)) {
2281            return;
2282        }
2283    }
2284    writer.write(ICAL_NEWLINE);
2285    endZoneProps(writer, isDst, status);
2286}
2287
2288/*
2289 * Write start times defined by a DOW rule using VTIMEZONE RRULE
2290 */
2291void
2292VTimeZone::writeZonePropsByDOW(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2293                               int32_t fromOffset, int32_t toOffset,
2294                               int32_t month, int32_t weekInMonth, int32_t dayOfWeek,
2295                               UDate startTime, UDate untilTime, UErrorCode& status) const {
2296    if (U_FAILURE(status)) {
2297        return;
2298    }
2299    beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
2300    if (U_FAILURE(status)) {
2301        return;
2302    }
2303    beginRRULE(writer, month, status);
2304    if (U_FAILURE(status)) {
2305        return;
2306    }
2307    writer.write(ICAL_BYDAY);
2308    writer.write(EQUALS_SIGN);
2309    UnicodeString dstr;
2310    appendAsciiDigits(weekInMonth, 0, dstr);
2311    writer.write(dstr);    // -4, -3, -2, -1, 1, 2, 3, 4
2312    writer.write(ICAL_DOW_NAMES[dayOfWeek - 1]);    // SU, MO, TU...
2313
2314    if (untilTime != MAX_MILLIS) {
2315        appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2316        if (U_FAILURE(status)) {
2317            return;
2318        }
2319    }
2320    writer.write(ICAL_NEWLINE);
2321    endZoneProps(writer, isDst, status);
2322}
2323
2324/*
2325 * Write start times defined by a DOW_GEQ_DOM rule using VTIMEZONE RRULE
2326 */
2327void
2328VTimeZone::writeZonePropsByDOW_GEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2329                                       int32_t fromOffset, int32_t toOffset,
2330                                       int32_t month, int32_t dayOfMonth, int32_t dayOfWeek,
2331                                       UDate startTime, UDate untilTime, UErrorCode& status) const {
2332    if (U_FAILURE(status)) {
2333        return;
2334    }
2335    // Check if this rule can be converted to DOW rule
2336    if (dayOfMonth%7 == 1) {
2337        // Can be represented by DOW rule
2338        writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2339                month, (dayOfMonth + 6)/7, dayOfWeek, startTime, untilTime, status);
2340        if (U_FAILURE(status)) {
2341            return;
2342        }
2343    } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 6) {
2344        // Can be represented by DOW rule with negative week number
2345        writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2346                month, -1*((MONTHLENGTH[month] - dayOfMonth + 1)/7), dayOfWeek, startTime, untilTime, status);
2347        if (U_FAILURE(status)) {
2348            return;
2349        }
2350    } else {
2351        // Otherwise, use BYMONTHDAY to include all possible dates
2352        beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
2353        if (U_FAILURE(status)) {
2354            return;
2355        }
2356        // Check if all days are in the same month
2357        int32_t startDay = dayOfMonth;
2358        int32_t currentMonthDays = 7;
2359
2360        if (dayOfMonth <= 0) {
2361            // The start day is in previous month
2362            int32_t prevMonthDays = 1 - dayOfMonth;
2363            currentMonthDays -= prevMonthDays;
2364
2365            int32_t prevMonth = (month - 1) < 0 ? 11 : month - 1;
2366
2367            // Note: When a rule is separated into two, UNTIL attribute needs to be
2368            // calculated for each of them.  For now, we skip this, because we basically use this method
2369            // only for final rules, which does not have the UNTIL attribute
2370            writeZonePropsByDOW_GEQ_DOM_sub(writer, prevMonth, -prevMonthDays, dayOfWeek, prevMonthDays,
2371                MAX_MILLIS /* Do not use UNTIL */, fromOffset, status);
2372            if (U_FAILURE(status)) {
2373                return;
2374            }
2375
2376            // Start from 1 for the rest
2377            startDay = 1;
2378        } else if (dayOfMonth + 6 > MONTHLENGTH[month]) {
2379            // Note: This code does not actually work well in February.  For now, days in month in
2380            // non-leap year.
2381            int32_t nextMonthDays = dayOfMonth + 6 - MONTHLENGTH[month];
2382            currentMonthDays -= nextMonthDays;
2383
2384            int32_t nextMonth = (month + 1) > 11 ? 0 : month + 1;
2385
2386            writeZonePropsByDOW_GEQ_DOM_sub(writer, nextMonth, 1, dayOfWeek, nextMonthDays,
2387                MAX_MILLIS /* Do not use UNTIL */, fromOffset, status);
2388            if (U_FAILURE(status)) {
2389                return;
2390            }
2391        }
2392        writeZonePropsByDOW_GEQ_DOM_sub(writer, month, startDay, dayOfWeek, currentMonthDays,
2393            untilTime, fromOffset, status);
2394        if (U_FAILURE(status)) {
2395            return;
2396        }
2397        endZoneProps(writer, isDst, status);
2398    }
2399}
2400
2401/*
2402 * Called from writeZonePropsByDOW_GEQ_DOM
2403 */
2404void
2405VTimeZone::writeZonePropsByDOW_GEQ_DOM_sub(VTZWriter& writer, int32_t month, int32_t dayOfMonth,
2406                                           int32_t dayOfWeek, int32_t numDays,
2407                                           UDate untilTime, int32_t fromOffset, UErrorCode& status) const {
2408
2409    if (U_FAILURE(status)) {
2410        return;
2411    }
2412    int32_t startDayNum = dayOfMonth;
2413    UBool isFeb = (month == UCAL_FEBRUARY);
2414    if (dayOfMonth < 0 && !isFeb) {
2415        // Use positive number if possible
2416        startDayNum = MONTHLENGTH[month] + dayOfMonth + 1;
2417    }
2418    beginRRULE(writer, month, status);
2419    if (U_FAILURE(status)) {
2420        return;
2421    }
2422    writer.write(ICAL_BYDAY);
2423    writer.write(EQUALS_SIGN);
2424    writer.write(ICAL_DOW_NAMES[dayOfWeek - 1]);    // SU, MO, TU...
2425    writer.write(SEMICOLON);
2426    writer.write(ICAL_BYMONTHDAY);
2427    writer.write(EQUALS_SIGN);
2428
2429    UnicodeString dstr;
2430    appendAsciiDigits(startDayNum, 0, dstr);
2431    writer.write(dstr);
2432    for (int32_t i = 1; i < numDays; i++) {
2433        writer.write(COMMA);
2434        dstr.remove();
2435        appendAsciiDigits(startDayNum + i, 0, dstr);
2436        writer.write(dstr);
2437    }
2438
2439    if (untilTime != MAX_MILLIS) {
2440        appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2441        if (U_FAILURE(status)) {
2442            return;
2443        }
2444    }
2445    writer.write(ICAL_NEWLINE);
2446}
2447
2448/*
2449 * Write start times defined by a DOW_LEQ_DOM rule using VTIMEZONE RRULE
2450 */
2451void
2452VTimeZone::writeZonePropsByDOW_LEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2453                                       int32_t fromOffset, int32_t toOffset,
2454                                       int32_t month, int32_t dayOfMonth, int32_t dayOfWeek,
2455                                       UDate startTime, UDate untilTime, UErrorCode& status) const {
2456    if (U_FAILURE(status)) {
2457        return;
2458    }
2459    // Check if this rule can be converted to DOW rule
2460    if (dayOfMonth%7 == 0) {
2461        // Can be represented by DOW rule
2462        writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2463                month, dayOfMonth/7, dayOfWeek, startTime, untilTime, status);
2464    } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 0){
2465        // Can be represented by DOW rule with negative week number
2466        writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2467                month, -1*((MONTHLENGTH[month] - dayOfMonth)/7 + 1), dayOfWeek, startTime, untilTime, status);
2468    } else if (month == UCAL_FEBRUARY && dayOfMonth == 29) {
2469        // Specical case for February
2470        writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2471                UCAL_FEBRUARY, -1, dayOfWeek, startTime, untilTime, status);
2472    } else {
2473        // Otherwise, convert this to DOW_GEQ_DOM rule
2474        writeZonePropsByDOW_GEQ_DOM(writer, isDst, zonename, fromOffset, toOffset,
2475                month, dayOfMonth - 6, dayOfWeek, startTime, untilTime, status);
2476    }
2477}
2478
2479/*
2480 * Write the final time zone rule using RRULE, with no UNTIL attribute
2481 */
2482void
2483VTimeZone::writeFinalRule(VTZWriter& writer, UBool isDst, const AnnualTimeZoneRule* rule,
2484                          int32_t fromRawOffset, int32_t fromDSTSavings,
2485                          UDate startTime, UErrorCode& status) const {
2486    if (U_FAILURE(status)) {
2487        return;
2488    }
2489    UBool modifiedRule = TRUE;
2490    const DateTimeRule *dtrule = toWallTimeRule(rule->getRule(), fromRawOffset, fromDSTSavings);
2491    if (dtrule == NULL) {
2492        modifiedRule = FALSE;
2493        dtrule = rule->getRule();
2494    }
2495
2496    // If the rule's mills in a day is out of range, adjust start time.
2497    // Olson tzdata supports 24:00 of a day, but VTIMEZONE does not.
2498    // See ticket#7008/#7518
2499
2500    int32_t timeInDay = dtrule->getRuleMillisInDay();
2501    if (timeInDay < 0) {
2502        startTime = startTime + (0 - timeInDay);
2503    } else if (timeInDay >= U_MILLIS_PER_DAY) {
2504        startTime = startTime - (timeInDay - (U_MILLIS_PER_DAY - 1));
2505    }
2506
2507    int32_t toOffset = rule->getRawOffset() + rule->getDSTSavings();
2508    UnicodeString name;
2509    rule->getName(name);
2510    switch (dtrule->getDateRuleType()) {
2511    case DateTimeRule::DOM:
2512        writeZonePropsByDOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2513                dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), startTime, MAX_MILLIS, status);
2514        break;
2515    case DateTimeRule::DOW:
2516        writeZonePropsByDOW(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2517                dtrule->getRuleMonth(), dtrule->getRuleWeekInMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
2518        break;
2519    case DateTimeRule::DOW_GEQ_DOM:
2520        writeZonePropsByDOW_GEQ_DOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2521                dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
2522        break;
2523    case DateTimeRule::DOW_LEQ_DOM:
2524        writeZonePropsByDOW_LEQ_DOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2525                dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
2526        break;
2527    }
2528    if (modifiedRule) {
2529        delete dtrule;
2530    }
2531}
2532
2533/*
2534 * Write the opening section of zone properties
2535 */
2536void
2537VTimeZone::beginZoneProps(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2538                          int32_t fromOffset, int32_t toOffset, UDate startTime, UErrorCode& status) const {
2539    if (U_FAILURE(status)) {
2540        return;
2541    }
2542    writer.write(ICAL_BEGIN);
2543    writer.write(COLON);
2544    if (isDst) {
2545        writer.write(ICAL_DAYLIGHT);
2546    } else {
2547        writer.write(ICAL_STANDARD);
2548    }
2549    writer.write(ICAL_NEWLINE);
2550
2551    UnicodeString dstr;
2552
2553    // TZOFFSETTO
2554    writer.write(ICAL_TZOFFSETTO);
2555    writer.write(COLON);
2556    millisToOffset(toOffset, dstr);
2557    writer.write(dstr);
2558    writer.write(ICAL_NEWLINE);
2559
2560    // TZOFFSETFROM
2561    writer.write(ICAL_TZOFFSETFROM);
2562    writer.write(COLON);
2563    millisToOffset(fromOffset, dstr);
2564    writer.write(dstr);
2565    writer.write(ICAL_NEWLINE);
2566
2567    // TZNAME
2568    writer.write(ICAL_TZNAME);
2569    writer.write(COLON);
2570    writer.write(zonename);
2571    writer.write(ICAL_NEWLINE);
2572
2573    // DTSTART
2574    writer.write(ICAL_DTSTART);
2575    writer.write(COLON);
2576    writer.write(getDateTimeString(startTime + fromOffset, dstr));
2577    writer.write(ICAL_NEWLINE);
2578}
2579
2580/*
2581 * Writes the closing section of zone properties
2582 */
2583void
2584VTimeZone::endZoneProps(VTZWriter& writer, UBool isDst, UErrorCode& status) const {
2585    if (U_FAILURE(status)) {
2586        return;
2587    }
2588    // END:STANDARD or END:DAYLIGHT
2589    writer.write(ICAL_END);
2590    writer.write(COLON);
2591    if (isDst) {
2592        writer.write(ICAL_DAYLIGHT);
2593    } else {
2594        writer.write(ICAL_STANDARD);
2595    }
2596    writer.write(ICAL_NEWLINE);
2597}
2598
2599/*
2600 * Write the beggining part of RRULE line
2601 */
2602void
2603VTimeZone::beginRRULE(VTZWriter& writer, int32_t month, UErrorCode& status) const {
2604    if (U_FAILURE(status)) {
2605        return;
2606    }
2607    UnicodeString dstr;
2608    writer.write(ICAL_RRULE);
2609    writer.write(COLON);
2610    writer.write(ICAL_FREQ);
2611    writer.write(EQUALS_SIGN);
2612    writer.write(ICAL_YEARLY);
2613    writer.write(SEMICOLON);
2614    writer.write(ICAL_BYMONTH);
2615    writer.write(EQUALS_SIGN);
2616    appendAsciiDigits(month + 1, 0, dstr);
2617    writer.write(dstr);
2618    writer.write(SEMICOLON);
2619}
2620
2621/*
2622 * Append the UNTIL attribute after RRULE line
2623 */
2624void
2625VTimeZone::appendUNTIL(VTZWriter& writer, const UnicodeString& until,  UErrorCode& status) const {
2626    if (U_FAILURE(status)) {
2627        return;
2628    }
2629    if (until.length() > 0) {
2630        writer.write(SEMICOLON);
2631        writer.write(ICAL_UNTIL);
2632        writer.write(EQUALS_SIGN);
2633        writer.write(until);
2634    }
2635}
2636
2637U_NAMESPACE_END
2638
2639#endif /* #if !UCONFIG_NO_FORMATTING */
2640
2641//eof
2642