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