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