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