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