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