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 available 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 
getOffsetFromLocal(UDate date,UTimeZoneLocalOption nonExistingTimeOpt,UTimeZoneLocalOption duplicatedTimeOpt,int32_t & rawOffset,int32_t & dstOffset,UErrorCode & status) const1220 void VTimeZone::getOffsetFromLocal(UDate date, UTimeZoneLocalOption nonExistingTimeOpt,
1221                                    UTimeZoneLocalOption duplicatedTimeOpt,
1222                                    int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) const {
1223     tz->getOffsetFromLocal(date, nonExistingTimeOpt, duplicatedTimeOpt, rawOffset, dstOffset, status);
1224 }
1225 
1226 void
setRawOffset(int32_t offsetMillis)1227 VTimeZone::setRawOffset(int32_t offsetMillis) {
1228     tz->setRawOffset(offsetMillis);
1229 }
1230 
1231 int32_t
getRawOffset(void) const1232 VTimeZone::getRawOffset(void) const {
1233     return tz->getRawOffset();
1234 }
1235 
1236 UBool
useDaylightTime(void) const1237 VTimeZone::useDaylightTime(void) const {
1238     return tz->useDaylightTime();
1239 }
1240 
1241 UBool
inDaylightTime(UDate date,UErrorCode & status) const1242 VTimeZone::inDaylightTime(UDate date, UErrorCode& status) const {
1243     return tz->inDaylightTime(date, status);
1244 }
1245 
1246 UBool
hasSameRules(const TimeZone & other) const1247 VTimeZone::hasSameRules(const TimeZone& other) const {
1248     return tz->hasSameRules(other);
1249 }
1250 
1251 UBool
getNextTransition(UDate base,UBool inclusive,TimeZoneTransition & result) const1252 VTimeZone::getNextTransition(UDate base, UBool inclusive, TimeZoneTransition& result) const {
1253     return tz->getNextTransition(base, inclusive, result);
1254 }
1255 
1256 UBool
getPreviousTransition(UDate base,UBool inclusive,TimeZoneTransition & result) const1257 VTimeZone::getPreviousTransition(UDate base, UBool inclusive, TimeZoneTransition& result) const {
1258     return tz->getPreviousTransition(base, inclusive, result);
1259 }
1260 
1261 int32_t
countTransitionRules(UErrorCode & status) const1262 VTimeZone::countTransitionRules(UErrorCode& status) const {
1263     return tz->countTransitionRules(status);
1264 }
1265 
1266 void
getTimeZoneRules(const InitialTimeZoneRule * & initial,const TimeZoneRule * trsrules[],int32_t & trscount,UErrorCode & status) const1267 VTimeZone::getTimeZoneRules(const InitialTimeZoneRule*& initial,
1268                             const TimeZoneRule* trsrules[], int32_t& trscount,
1269                             UErrorCode& status) const {
1270     tz->getTimeZoneRules(initial, trsrules, trscount, status);
1271 }
1272 
1273 void
load(VTZReader & reader,UErrorCode & status)1274 VTimeZone::load(VTZReader& reader, UErrorCode& status) {
1275     vtzlines = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, DEFAULT_VTIMEZONE_LINES, status);
1276     if (vtzlines == nullptr) {
1277         status = U_MEMORY_ALLOCATION_ERROR;
1278     }
1279     if (U_FAILURE(status)) {
1280         return;
1281     }
1282     UBool eol = FALSE;
1283     UBool start = FALSE;
1284     UBool success = FALSE;
1285     UnicodeString line;
1286 
1287     while (TRUE) {
1288         UChar ch = reader.read();
1289         if (ch == 0xFFFF) {
1290             // end of file
1291             if (start && line.startsWith(ICAL_END_VTIMEZONE, -1)) {
1292                 LocalPointer<UnicodeString> element(new UnicodeString(line), status);
1293                 if (U_FAILURE(status)) {
1294                     goto cleanupVtzlines;
1295                 }
1296                 vtzlines->addElement(element.getAlias(), status);
1297                 if (U_FAILURE(status)) {
1298                     goto cleanupVtzlines;
1299                 }
1300                 element.orphan(); // on success, vtzlines owns the object.
1301                 success = TRUE;
1302             }
1303             break;
1304         }
1305         if (ch == 0x000D) {
1306             // CR, must be followed by LF according to the definition in RFC2445
1307             continue;
1308         }
1309         if (eol) {
1310             if (ch != 0x0009 && ch != 0x0020) {
1311                 // NOT followed by TAB/SP -> new line
1312                 if (start) {
1313                     if (line.length() > 0) {
1314                         LocalPointer<UnicodeString> element(new UnicodeString(line), status);
1315                         if (U_FAILURE(status)) {
1316                             goto cleanupVtzlines;
1317                         }
1318                         vtzlines->addElement(element.getAlias(), status);
1319                         if (U_FAILURE(status)) {
1320                             goto cleanupVtzlines;
1321                         }
1322                         element.orphan(); // on success, vtzlines owns the object.
1323                     }
1324                 }
1325                 line.remove();
1326                 if (ch != 0x000A) {
1327                     line.append(ch);
1328                 }
1329             }
1330             eol = FALSE;
1331         } else {
1332             if (ch == 0x000A) {
1333                 // LF
1334                 eol = TRUE;
1335                 if (start) {
1336                     if (line.startsWith(ICAL_END_VTIMEZONE, -1)) {
1337                         LocalPointer<UnicodeString> element(new UnicodeString(line), status);
1338                         if (U_FAILURE(status)) {
1339                             goto cleanupVtzlines;
1340                         }
1341                         vtzlines->addElement(element.getAlias(), status);
1342                         if (U_FAILURE(status)) {
1343                             goto cleanupVtzlines;
1344                         }
1345                         element.orphan(); // on success, vtzlines owns the object.
1346                         success = TRUE;
1347                         break;
1348                     }
1349                 } else {
1350                     if (line.startsWith(ICAL_BEGIN_VTIMEZONE, -1)) {
1351                         LocalPointer<UnicodeString> element(new UnicodeString(line), status);
1352                         if (U_FAILURE(status)) {
1353                             goto cleanupVtzlines;
1354                         }
1355                         vtzlines->addElement(element.getAlias(), status);
1356                         if (U_FAILURE(status)) {
1357                             goto cleanupVtzlines;
1358                         }
1359                         element.orphan(); // on success, vtzlines owns the object.
1360                         line.remove();
1361                         start = TRUE;
1362                         eol = FALSE;
1363                     }
1364                 }
1365             } else {
1366                 line.append(ch);
1367             }
1368         }
1369     }
1370     if (!success) {
1371         if (U_SUCCESS(status)) {
1372             status = U_INVALID_STATE_ERROR;
1373         }
1374         goto cleanupVtzlines;
1375     }
1376     parse(status);
1377     return;
1378 
1379 cleanupVtzlines:
1380     delete vtzlines;
1381     vtzlines = nullptr;
1382 }
1383 
1384 // parser state
1385 #define INI 0   // Initial state
1386 #define VTZ 1   // In VTIMEZONE
1387 #define TZI 2   // In STANDARD or DAYLIGHT
1388 
1389 #define DEF_DSTSAVINGS (60*60*1000)
1390 #define DEF_TZSTARTTIME (0.0)
1391 
1392 void
parse(UErrorCode & status)1393 VTimeZone::parse(UErrorCode& status) {
1394     if (U_FAILURE(status)) {
1395         return;
1396     }
1397     if (vtzlines == nullptr || vtzlines->size() == 0) {
1398         status = U_INVALID_STATE_ERROR;
1399         return;
1400     }
1401     InitialTimeZoneRule *initialRule = nullptr;
1402     RuleBasedTimeZone *rbtz = nullptr;
1403 
1404     // timezone ID
1405     UnicodeString tzid;
1406 
1407     int32_t state = INI;
1408     int32_t n = 0;
1409     UBool dst = FALSE;      // current zone type
1410     UnicodeString from;     // current zone from offset
1411     UnicodeString to;       // current zone offset
1412     UnicodeString zonename;   // current zone name
1413     UnicodeString dtstart;  // current zone starts
1414     UBool isRRULE = FALSE;  // true if the rule is described by RRULE
1415     int32_t initialRawOffset = 0;   // initial offset
1416     int32_t initialDSTSavings = 0;  // initial offset
1417     UDate firstStart = MAX_MILLIS;  // the earliest rule start time
1418     UnicodeString name;     // RFC2445 prop name
1419     UnicodeString value;    // RFC2445 prop value
1420 
1421     UVector *dates = nullptr;  // list of RDATE or RRULE strings
1422     UVector *rules = nullptr;  // list of TimeZoneRule instances
1423 
1424     int32_t finalRuleIdx = -1;
1425     int32_t finalRuleCount = 0;
1426 
1427     rules = new UVector(status);
1428     if (rules == nullptr) {
1429         status = U_MEMORY_ALLOCATION_ERROR;
1430     }
1431     if (U_FAILURE(status)) {
1432         goto cleanupParse;
1433     }
1434      // Set the deleter to remove TimeZoneRule vectors to avoid memory leaks due to unowned TimeZoneRules.
1435     rules->setDeleter(deleteTimeZoneRule);
1436 
1437     dates = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, status);
1438     if (dates == nullptr) {
1439         status = U_MEMORY_ALLOCATION_ERROR;
1440     }
1441     if (U_FAILURE(status)) {
1442         goto cleanupParse;
1443     }
1444 
1445     for (n = 0; n < vtzlines->size(); n++) {
1446         UnicodeString *line = (UnicodeString*)vtzlines->elementAt(n);
1447         int32_t valueSep = line->indexOf(COLON);
1448         if (valueSep < 0) {
1449             continue;
1450         }
1451         name.setTo(*line, 0, valueSep);
1452         value.setTo(*line, valueSep + 1);
1453 
1454         switch (state) {
1455         case INI:
1456             if (name.compare(ICAL_BEGIN, -1) == 0
1457                 && value.compare(ICAL_VTIMEZONE, -1) == 0) {
1458                 state = VTZ;
1459             }
1460             break;
1461 
1462         case VTZ:
1463             if (name.compare(ICAL_TZID, -1) == 0) {
1464                 tzid = value;
1465             } else if (name.compare(ICAL_TZURL, -1) == 0) {
1466                 tzurl = value;
1467             } else if (name.compare(ICAL_LASTMOD, -1) == 0) {
1468                 // Always in 'Z' format, so the offset argument for the parse method
1469                 // can be any value.
1470                 lastmod = parseDateTimeString(value, 0, status);
1471                 if (U_FAILURE(status)) {
1472                     goto cleanupParse;
1473                 }
1474             } else if (name.compare(ICAL_BEGIN, -1) == 0) {
1475                 UBool isDST = (value.compare(ICAL_DAYLIGHT, -1) == 0);
1476                 if (value.compare(ICAL_STANDARD, -1) == 0 || isDST) {
1477                     // tzid must be ready at this point
1478                     if (tzid.length() == 0) {
1479                         goto cleanupParse;
1480                     }
1481                     // initialize current zone properties
1482                     if (dates->size() != 0) {
1483                         dates->removeAllElements();
1484                     }
1485                     isRRULE = FALSE;
1486                     from.remove();
1487                     to.remove();
1488                     zonename.remove();
1489                     dst = isDST;
1490                     state = TZI;
1491                 } else {
1492                     // BEGIN property other than STANDARD/DAYLIGHT
1493                     // must not be there.
1494                     goto cleanupParse;
1495                 }
1496             } else if (name.compare(ICAL_END, -1) == 0) {
1497                 break;
1498             }
1499             break;
1500         case TZI:
1501             if (name.compare(ICAL_DTSTART, -1) == 0) {
1502                 dtstart = value;
1503             } else if (name.compare(ICAL_TZNAME, -1) == 0) {
1504                 zonename = value;
1505             } else if (name.compare(ICAL_TZOFFSETFROM, -1) == 0) {
1506                 from = value;
1507             } else if (name.compare(ICAL_TZOFFSETTO, -1) == 0) {
1508                 to = value;
1509             } else if (name.compare(ICAL_RDATE, -1) == 0) {
1510                 // RDATE mixed with RRULE is not supported
1511                 if (isRRULE) {
1512                     goto cleanupParse;
1513                 }
1514                 // RDATE value may contain multiple date delimited
1515                 // by comma
1516                 UBool nextDate = TRUE;
1517                 int32_t dstart = 0;
1518                 UnicodeString *dstr = nullptr;
1519                 while (nextDate) {
1520                     int32_t dend = value.indexOf(COMMA, dstart);
1521                     if (dend == -1) {
1522                         dstr = new UnicodeString(value, dstart);
1523                         nextDate = FALSE;
1524                     } else {
1525                         dstr = new UnicodeString(value, dstart, dend - dstart);
1526                     }
1527                     if (dstr == nullptr) {
1528                         status = U_MEMORY_ALLOCATION_ERROR;
1529                     } else {
1530                         dates->addElement(dstr, status);
1531                     }
1532                     if (U_FAILURE(status)) {
1533                         goto cleanupParse;
1534                     }
1535                     dstart = dend + 1;
1536                 }
1537             } else if (name.compare(ICAL_RRULE, -1) == 0) {
1538                 // RRULE mixed with RDATE is not supported
1539                 if (!isRRULE && dates->size() != 0) {
1540                     goto cleanupParse;
1541                 }
1542                 isRRULE = true;
1543                 LocalPointer<UnicodeString> element(new UnicodeString(value), status);
1544                 if (U_FAILURE(status)) {
1545                     goto cleanupParse;
1546                 }
1547                 dates->addElement(element.getAlias(), status);
1548                 if (U_FAILURE(status)) {
1549                     goto cleanupParse;
1550                 }
1551                 element.orphan(); // on success, dates owns the object.
1552             } else if (name.compare(ICAL_END, -1) == 0) {
1553                 // Mandatory properties
1554                 if (dtstart.length() == 0 || from.length() == 0 || to.length() == 0) {
1555                     goto cleanupParse;
1556                 }
1557                 // if zonename is not available, create one from tzid
1558                 if (zonename.length() == 0) {
1559                     getDefaultTZName(tzid, dst, zonename);
1560                 }
1561 
1562                 // create a time zone rule
1563                 TimeZoneRule *rule = nullptr;
1564                 int32_t fromOffset = 0;
1565                 int32_t toOffset = 0;
1566                 int32_t rawOffset = 0;
1567                 int32_t dstSavings = 0;
1568                 UDate start = 0;
1569 
1570                 // Parse TZOFFSETFROM/TZOFFSETTO
1571                 fromOffset = offsetStrToMillis(from, status);
1572                 toOffset = offsetStrToMillis(to, status);
1573                 if (U_FAILURE(status)) {
1574                     goto cleanupParse;
1575                 }
1576 
1577                 if (dst) {
1578                     // If daylight, use the previous offset as rawoffset if positive
1579                     if (toOffset - fromOffset > 0) {
1580                         rawOffset = fromOffset;
1581                         dstSavings = toOffset - fromOffset;
1582                     } else {
1583                         // This is rare case..  just use 1 hour DST savings
1584                         rawOffset = toOffset - DEF_DSTSAVINGS;
1585                         dstSavings = DEF_DSTSAVINGS;
1586                     }
1587                 } else {
1588                     rawOffset = toOffset;
1589                     dstSavings = 0;
1590                 }
1591 
1592                 // start time
1593                 start = parseDateTimeString(dtstart, fromOffset, status);
1594                 if (U_FAILURE(status)) {
1595                     goto cleanupParse;
1596                 }
1597 
1598                 // Create the rule
1599                 UDate actualStart = MAX_MILLIS;
1600                 if (isRRULE) {
1601                     rule = createRuleByRRULE(zonename, rawOffset, dstSavings, start, dates, fromOffset, status);
1602                 } else {
1603                     rule = createRuleByRDATE(zonename, rawOffset, dstSavings, start, dates, fromOffset, status);
1604                 }
1605                 if (U_FAILURE(status) || rule == nullptr) {
1606                     goto cleanupParse;
1607                 } else {
1608                     UBool startAvail = rule->getFirstStart(fromOffset, 0, actualStart);
1609                     if (startAvail && actualStart < firstStart) {
1610                         // save from offset information for the earliest rule
1611                         firstStart = actualStart;
1612                         // If this is STD, assume the time before this transtion
1613                         // is DST when the difference is 1 hour.  This might not be
1614                         // accurate, but VTIMEZONE data does not have such info.
1615                         if (dstSavings > 0) {
1616                             initialRawOffset = fromOffset;
1617                             initialDSTSavings = 0;
1618                         } else {
1619                             if (fromOffset - toOffset == DEF_DSTSAVINGS) {
1620                                 initialRawOffset = fromOffset - DEF_DSTSAVINGS;
1621                                 initialDSTSavings = DEF_DSTSAVINGS;
1622                             } else {
1623                                 initialRawOffset = fromOffset;
1624                                 initialDSTSavings = 0;
1625                             }
1626                         }
1627                     }
1628                 }
1629                 rules->addElement(rule, status);
1630                 if (U_FAILURE(status)) {
1631                     goto cleanupParse;
1632                 }
1633                 state = VTZ;
1634             }
1635             break;
1636         }
1637     }
1638     // Must have at least one rule
1639     if (rules->size() == 0) {
1640         goto cleanupParse;
1641     }
1642 
1643     // Create a initial rule
1644     getDefaultTZName(tzid, FALSE, zonename);
1645     initialRule = new InitialTimeZoneRule(zonename, initialRawOffset, initialDSTSavings);
1646     if (initialRule == nullptr) {
1647         status = U_MEMORY_ALLOCATION_ERROR;
1648         goto cleanupParse;
1649     }
1650 
1651     // Finally, create the RuleBasedTimeZone
1652     rbtz = new RuleBasedTimeZone(tzid, initialRule);
1653     if (rbtz == nullptr) {
1654         status = U_MEMORY_ALLOCATION_ERROR;
1655         goto cleanupParse;
1656     }
1657     initialRule = nullptr; // already adopted by RBTZ, no need to delete
1658 
1659     for (n = 0; n < rules->size(); n++) {
1660         TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n);
1661         AnnualTimeZoneRule *atzrule = dynamic_cast<AnnualTimeZoneRule *>(r);
1662         if (atzrule != nullptr) {
1663             if (atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR) {
1664                 finalRuleCount++;
1665                 finalRuleIdx = n;
1666             }
1667         }
1668     }
1669     if (finalRuleCount > 2) {
1670         // Too many final rules
1671         status = U_ILLEGAL_ARGUMENT_ERROR;
1672         goto cleanupParse;
1673     }
1674 
1675     if (finalRuleCount == 1) {
1676         if (rules->size() == 1) {
1677             // Only one final rule, only governs the initial rule,
1678             // which is already initialized, thus, we do not need to
1679             // add this transition rule
1680             rules->removeAllElements();
1681         } else {
1682             // Normalize the final rule
1683             AnnualTimeZoneRule *finalRule = (AnnualTimeZoneRule*)rules->elementAt(finalRuleIdx);
1684             int32_t tmpRaw = finalRule->getRawOffset();
1685             int32_t tmpDST = finalRule->getDSTSavings();
1686 
1687             // Find the last non-final rule
1688             UDate finalStart, start;
1689             finalRule->getFirstStart(initialRawOffset, initialDSTSavings, finalStart);
1690             start = finalStart;
1691             for (n = 0; n < rules->size(); n++) {
1692                 if (finalRuleIdx == n) {
1693                     continue;
1694                 }
1695                 TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n);
1696                 UDate lastStart;
1697                 r->getFinalStart(tmpRaw, tmpDST, lastStart);
1698                 if (lastStart > start) {
1699                     finalRule->getNextStart(lastStart,
1700                         r->getRawOffset(),
1701                         r->getDSTSavings(),
1702                         FALSE,
1703                         start);
1704                 }
1705             }
1706 
1707             TimeZoneRule *newRule = nullptr;
1708             UnicodeString tznam;
1709             if (start == finalStart) {
1710                 // Transform this into a single transition
1711                 newRule = new TimeArrayTimeZoneRule(
1712                         finalRule->getName(tznam),
1713                         finalRule->getRawOffset(),
1714                         finalRule->getDSTSavings(),
1715                         &finalStart,
1716                         1,
1717                         DateTimeRule::UTC_TIME);
1718             } else {
1719                 // Update the end year
1720                 int32_t y, m, d, dow, doy, mid;
1721                 Grego::timeToFields(start, y, m, d, dow, doy, mid);
1722                 newRule = new AnnualTimeZoneRule(
1723                         finalRule->getName(tznam),
1724                         finalRule->getRawOffset(),
1725                         finalRule->getDSTSavings(),
1726                         *(finalRule->getRule()),
1727                         finalRule->getStartYear(),
1728                         y);
1729             }
1730             if (newRule == nullptr) {
1731                 status = U_MEMORY_ALLOCATION_ERROR;
1732                 goto cleanupParse;
1733             }
1734             rules->removeElementAt(finalRuleIdx);
1735             rules->addElement(newRule, status);
1736             if (U_FAILURE(status)) {
1737                 delete newRule;
1738                 goto cleanupParse;
1739             }
1740         }
1741     }
1742 
1743     while (!rules->isEmpty()) {
1744         TimeZoneRule *tzr = (TimeZoneRule*)rules->orphanElementAt(0);
1745         rbtz->addTransitionRule(tzr, status);
1746         if (U_FAILURE(status)) {
1747             goto cleanupParse;
1748         }
1749     }
1750     rbtz->complete(status);
1751     if (U_FAILURE(status)) {
1752         goto cleanupParse;
1753     }
1754     delete rules;
1755     delete dates;
1756 
1757     tz = rbtz;
1758     setID(tzid);
1759     return;
1760 
1761 cleanupParse:
1762     if (rules != nullptr) {
1763         while (!rules->isEmpty()) {
1764             TimeZoneRule *r = (TimeZoneRule*)rules->orphanElementAt(0);
1765             delete r;
1766         }
1767         delete rules;
1768     }
1769     if (dates != nullptr) {
1770         delete dates;
1771     }
1772     if (initialRule != nullptr) {
1773         delete initialRule;
1774     }
1775     if (rbtz != nullptr) {
1776         delete rbtz;
1777     }
1778     return;
1779 }
1780 
1781 void
write(VTZWriter & writer,UErrorCode & status) const1782 VTimeZone::write(VTZWriter& writer, UErrorCode& status) const {
1783     if (vtzlines != nullptr) {
1784         for (int32_t i = 0; i < vtzlines->size(); i++) {
1785             UnicodeString *line = (UnicodeString*)vtzlines->elementAt(i);
1786             if (line->startsWith(ICAL_TZURL, -1)
1787                 && line->charAt(u_strlen(ICAL_TZURL)) == COLON) {
1788                 writer.write(ICAL_TZURL);
1789                 writer.write(COLON);
1790                 writer.write(tzurl);
1791                 writer.write(ICAL_NEWLINE);
1792             } else if (line->startsWith(ICAL_LASTMOD, -1)
1793                 && line->charAt(u_strlen(ICAL_LASTMOD)) == COLON) {
1794                 UnicodeString utcString;
1795                 writer.write(ICAL_LASTMOD);
1796                 writer.write(COLON);
1797                 writer.write(getUTCDateTimeString(lastmod, utcString));
1798                 writer.write(ICAL_NEWLINE);
1799             } else {
1800                 writer.write(*line);
1801                 writer.write(ICAL_NEWLINE);
1802             }
1803         }
1804     } else {
1805         UnicodeString icutzprop;
1806         UVector customProps(nullptr, uhash_compareUnicodeString, status);
1807         if (olsonzid.length() > 0 && icutzver.length() > 0) {
1808             icutzprop.append(olsonzid);
1809             icutzprop.append(u'[');
1810             icutzprop.append(icutzver);
1811             icutzprop.append(u']');
1812             customProps.addElement(&icutzprop, status);
1813         }
1814         writeZone(writer, *tz, &customProps, status);
1815     }
1816 }
1817 
1818 void
write(UDate start,VTZWriter & writer,UErrorCode & status) const1819 VTimeZone::write(UDate start, VTZWriter& writer, UErrorCode& status) const {
1820     if (U_FAILURE(status)) {
1821         return;
1822     }
1823     InitialTimeZoneRule *initial = nullptr;
1824     UVector *transitionRules = nullptr;
1825     UVector customProps(uprv_deleteUObject, uhash_compareUnicodeString, status);
1826     UnicodeString tzid;
1827 
1828     // Extract rules applicable to dates after the start time
1829     getTimeZoneRulesAfter(start, initial, transitionRules, status);
1830     if (U_FAILURE(status)) {
1831         return;
1832     }
1833 
1834     // Create a RuleBasedTimeZone with the subset rule
1835     getID(tzid);
1836     RuleBasedTimeZone rbtz(tzid, initial);
1837     if (transitionRules != nullptr) {
1838         while (!transitionRules->isEmpty()) {
1839             TimeZoneRule *tr = (TimeZoneRule*)transitionRules->orphanElementAt(0);
1840             rbtz.addTransitionRule(tr, status);
1841             if (U_FAILURE(status)) {
1842                 goto cleanupWritePartial;
1843             }
1844         }
1845         delete transitionRules;
1846         transitionRules = nullptr;
1847     }
1848     rbtz.complete(status);
1849     if (U_FAILURE(status)) {
1850         goto cleanupWritePartial;
1851     }
1852 
1853     if (olsonzid.length() > 0 && icutzver.length() > 0) {
1854         UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP);
1855         if (icutzprop == nullptr) {
1856             status = U_MEMORY_ALLOCATION_ERROR;
1857             goto cleanupWritePartial;
1858         }
1859         icutzprop->append(olsonzid);
1860         icutzprop->append((UChar)0x005B/*'['*/);
1861         icutzprop->append(icutzver);
1862         icutzprop->append(ICU_TZINFO_PARTIAL, -1);
1863         appendMillis(start, *icutzprop);
1864         icutzprop->append((UChar)0x005D/*']'*/);
1865         customProps.addElement(icutzprop, status);
1866         if (U_FAILURE(status)) {
1867             delete icutzprop;
1868             goto cleanupWritePartial;
1869         }
1870     }
1871     writeZone(writer, rbtz, &customProps, status);
1872     return;
1873 
1874 cleanupWritePartial:
1875     if (initial != nullptr) {
1876         delete initial;
1877     }
1878     if (transitionRules != nullptr) {
1879         while (!transitionRules->isEmpty()) {
1880             TimeZoneRule *tr = (TimeZoneRule*)transitionRules->orphanElementAt(0);
1881             delete tr;
1882         }
1883         delete transitionRules;
1884     }
1885 }
1886 
1887 void
writeSimple(UDate time,VTZWriter & writer,UErrorCode & status) const1888 VTimeZone::writeSimple(UDate time, VTZWriter& writer, UErrorCode& status) const {
1889     if (U_FAILURE(status)) {
1890         return;
1891     }
1892 
1893     UVector customProps(uprv_deleteUObject, uhash_compareUnicodeString, status);
1894     UnicodeString tzid;
1895 
1896     // Extract simple rules
1897     InitialTimeZoneRule *initial = nullptr;
1898     AnnualTimeZoneRule *std = nullptr, *dst = nullptr;
1899     getSimpleRulesNear(time, initial, std, dst, status);
1900     if (U_SUCCESS(status)) {
1901         // Create a RuleBasedTimeZone with the subset rule
1902         getID(tzid);
1903         RuleBasedTimeZone rbtz(tzid, initial);
1904         if (std != nullptr && dst != nullptr) {
1905             rbtz.addTransitionRule(std, status);
1906             rbtz.addTransitionRule(dst, status);
1907         }
1908         if (U_FAILURE(status)) {
1909             goto cleanupWriteSimple;
1910         }
1911 
1912         if (olsonzid.length() > 0 && icutzver.length() > 0) {
1913             UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP);
1914             if (icutzprop == nullptr) {
1915                status = U_MEMORY_ALLOCATION_ERROR;
1916                goto cleanupWriteSimple;
1917             }
1918             icutzprop->append(olsonzid);
1919             icutzprop->append((UChar)0x005B/*'['*/);
1920             icutzprop->append(icutzver);
1921             icutzprop->append(ICU_TZINFO_SIMPLE, -1);
1922             appendMillis(time, *icutzprop);
1923             icutzprop->append((UChar)0x005D/*']'*/);
1924             customProps.addElement(icutzprop, status);
1925             if (U_FAILURE(status)) {
1926                 delete icutzprop;
1927                 goto cleanupWriteSimple;
1928             }
1929         }
1930         writeZone(writer, rbtz, &customProps, status);
1931     }
1932     return;
1933 
1934 cleanupWriteSimple:
1935     if (initial != nullptr) {
1936         delete initial;
1937     }
1938     if (std != nullptr) {
1939         delete std;
1940     }
1941     if (dst != nullptr) {
1942         delete dst;
1943     }
1944 }
1945 
1946 void
writeZone(VTZWriter & w,BasicTimeZone & basictz,UVector * customProps,UErrorCode & status) const1947 VTimeZone::writeZone(VTZWriter& w, BasicTimeZone& basictz,
1948                      UVector* customProps, UErrorCode& status) const {
1949     if (U_FAILURE(status)) {
1950         return;
1951     }
1952     writeHeaders(w, status);
1953     if (U_FAILURE(status)) {
1954         return;
1955     }
1956 
1957     if (customProps != nullptr) {
1958         for (int32_t i = 0; i < customProps->size(); i++) {
1959             UnicodeString *custprop = (UnicodeString*)customProps->elementAt(i);
1960             w.write(*custprop);
1961             w.write(ICAL_NEWLINE);
1962         }
1963     }
1964 
1965     UDate t = MIN_MILLIS;
1966     UnicodeString dstName;
1967     int32_t dstFromOffset = 0;
1968     int32_t dstFromDSTSavings = 0;
1969     int32_t dstToOffset = 0;
1970     int32_t dstStartYear = 0;
1971     int32_t dstMonth = 0;
1972     int32_t dstDayOfWeek = 0;
1973     int32_t dstWeekInMonth = 0;
1974     int32_t dstMillisInDay = 0;
1975     UDate dstStartTime = 0.0;
1976     UDate dstUntilTime = 0.0;
1977     int32_t dstCount = 0;
1978     AnnualTimeZoneRule *finalDstRule = nullptr;
1979 
1980     UnicodeString stdName;
1981     int32_t stdFromOffset = 0;
1982     int32_t stdFromDSTSavings = 0;
1983     int32_t stdToOffset = 0;
1984     int32_t stdStartYear = 0;
1985     int32_t stdMonth = 0;
1986     int32_t stdDayOfWeek = 0;
1987     int32_t stdWeekInMonth = 0;
1988     int32_t stdMillisInDay = 0;
1989     UDate stdStartTime = 0.0;
1990     UDate stdUntilTime = 0.0;
1991     int32_t stdCount = 0;
1992     AnnualTimeZoneRule *finalStdRule = nullptr;
1993 
1994     int32_t year, month, dom, dow, doy, mid;
1995     UBool hasTransitions = FALSE;
1996     TimeZoneTransition tzt;
1997     UBool tztAvail;
1998     UnicodeString name;
1999     UBool isDst;
2000 
2001     // Going through all transitions
2002     while (TRUE) {
2003         tztAvail = basictz.getNextTransition(t, FALSE, tzt);
2004         if (!tztAvail) {
2005             break;
2006         }
2007         hasTransitions = TRUE;
2008         t = tzt.getTime();
2009         tzt.getTo()->getName(name);
2010         isDst = (tzt.getTo()->getDSTSavings() != 0);
2011         int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings();
2012         int32_t fromDSTSavings = tzt.getFrom()->getDSTSavings();
2013         int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings();
2014         Grego::timeToFields(tzt.getTime() + fromOffset, year, month, dom, dow, doy, mid);
2015         int32_t weekInMonth = Grego::dayOfWeekInMonth(year, month, dom);
2016         UBool sameRule = FALSE;
2017         const AnnualTimeZoneRule *atzrule;
2018         if (isDst) {
2019             if (finalDstRule == nullptr
2020                 && (atzrule = dynamic_cast<const AnnualTimeZoneRule *>(tzt.getTo())) != nullptr
2021                 && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
2022             ) {
2023                 finalDstRule = atzrule->clone();
2024             }
2025             if (dstCount > 0) {
2026                 if (year == dstStartYear + dstCount
2027                         && name.compare(dstName) == 0
2028                         && dstFromOffset == fromOffset
2029                         && dstToOffset == toOffset
2030                         && dstMonth == month
2031                         && dstDayOfWeek == dow
2032                         && dstWeekInMonth == weekInMonth
2033                         && dstMillisInDay == mid) {
2034                     // Update until time
2035                     dstUntilTime = t;
2036                     dstCount++;
2037                     sameRule = TRUE;
2038                 }
2039                 if (!sameRule) {
2040                     if (dstCount == 1) {
2041                         writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime,
2042                                 TRUE, status);
2043                     } else {
2044                         writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
2045                                 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
2046                     }
2047                     if (U_FAILURE(status)) {
2048                         goto cleanupWriteZone;
2049                     }
2050                 }
2051             }
2052             if (!sameRule) {
2053                 // Reset this DST information
2054                 dstName = name;
2055                 dstFromOffset = fromOffset;
2056                 dstFromDSTSavings = fromDSTSavings;
2057                 dstToOffset = toOffset;
2058                 dstStartYear = year;
2059                 dstMonth = month;
2060                 dstDayOfWeek = dow;
2061                 dstWeekInMonth = weekInMonth;
2062                 dstMillisInDay = mid;
2063                 dstStartTime = dstUntilTime = t;
2064                 dstCount = 1;
2065             }
2066             if (finalStdRule != nullptr && finalDstRule != nullptr) {
2067                 break;
2068             }
2069         } else {
2070             if (finalStdRule == nullptr
2071                 && (atzrule = dynamic_cast<const AnnualTimeZoneRule *>(tzt.getTo())) != nullptr
2072                 && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
2073             ) {
2074                 finalStdRule = atzrule->clone();
2075             }
2076             if (stdCount > 0) {
2077                 if (year == stdStartYear + stdCount
2078                         && name.compare(stdName) == 0
2079                         && stdFromOffset == fromOffset
2080                         && stdToOffset == toOffset
2081                         && stdMonth == month
2082                         && stdDayOfWeek == dow
2083                         && stdWeekInMonth == weekInMonth
2084                         && stdMillisInDay == mid) {
2085                     // Update until time
2086                     stdUntilTime = t;
2087                     stdCount++;
2088                     sameRule = TRUE;
2089                 }
2090                 if (!sameRule) {
2091                     if (stdCount == 1) {
2092                         writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime,
2093                                 TRUE, status);
2094                     } else {
2095                         writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2096                                 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2097                     }
2098                     if (U_FAILURE(status)) {
2099                         goto cleanupWriteZone;
2100                     }
2101                 }
2102             }
2103             if (!sameRule) {
2104                 // Reset this STD information
2105                 stdName = name;
2106                 stdFromOffset = fromOffset;
2107                 stdFromDSTSavings = fromDSTSavings;
2108                 stdToOffset = toOffset;
2109                 stdStartYear = year;
2110                 stdMonth = month;
2111                 stdDayOfWeek = dow;
2112                 stdWeekInMonth = weekInMonth;
2113                 stdMillisInDay = mid;
2114                 stdStartTime = stdUntilTime = t;
2115                 stdCount = 1;
2116             }
2117             if (finalStdRule != nullptr && finalDstRule != nullptr) {
2118                 break;
2119             }
2120         }
2121     }
2122     if (!hasTransitions) {
2123         // No transition - put a single non transition RDATE
2124         int32_t raw, dst, offset;
2125         basictz.getOffset(0.0/*any time*/, FALSE, raw, dst, status);
2126         if (U_FAILURE(status)) {
2127             goto cleanupWriteZone;
2128         }
2129         offset = raw + dst;
2130         isDst = (dst != 0);
2131         UnicodeString tzid;
2132         basictz.getID(tzid);
2133         getDefaultTZName(tzid, isDst, name);
2134         writeZonePropsByTime(w, isDst, name,
2135                 offset, offset, DEF_TZSTARTTIME - offset, FALSE, status);
2136         if (U_FAILURE(status)) {
2137             goto cleanupWriteZone;
2138         }
2139     } else {
2140         if (dstCount > 0) {
2141             if (finalDstRule == nullptr) {
2142                 if (dstCount == 1) {
2143                     writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime,
2144                             TRUE, status);
2145                 } else {
2146                     writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
2147                             dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
2148                 }
2149                 if (U_FAILURE(status)) {
2150                     goto cleanupWriteZone;
2151                 }
2152             } else {
2153                 if (dstCount == 1) {
2154                     writeFinalRule(w, TRUE, finalDstRule,
2155                             dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, dstStartTime, status);
2156                 } else {
2157                     // Use a single rule if possible
2158                     if (isEquivalentDateRule(dstMonth, dstWeekInMonth, dstDayOfWeek, finalDstRule->getRule())) {
2159                         writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
2160                                 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, MAX_MILLIS, status);
2161                     } else {
2162                         // Not equivalent rule - write out two different rules
2163                         writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
2164                                 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
2165                         if (U_FAILURE(status)) {
2166                             goto cleanupWriteZone;
2167                         }
2168                         UDate nextStart;
2169                         UBool nextStartAvail = finalDstRule->getNextStart(dstUntilTime, dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, false, nextStart);
2170                         U_ASSERT(nextStartAvail);
2171                         if (nextStartAvail) {
2172                             writeFinalRule(w, TRUE, finalDstRule,
2173                                     dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, nextStart, status);
2174                         }
2175                     }
2176                 }
2177                 if (U_FAILURE(status)) {
2178                     goto cleanupWriteZone;
2179                 }
2180             }
2181         }
2182         if (stdCount > 0) {
2183             if (finalStdRule == nullptr) {
2184                 if (stdCount == 1) {
2185                     writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime,
2186                             TRUE, status);
2187                 } else {
2188                     writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2189                             stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2190                 }
2191                 if (U_FAILURE(status)) {
2192                     goto cleanupWriteZone;
2193                 }
2194             } else {
2195                 if (stdCount == 1) {
2196                     writeFinalRule(w, FALSE, finalStdRule,
2197                             stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, stdStartTime, status);
2198                 } else {
2199                     // Use a single rule if possible
2200                     if (isEquivalentDateRule(stdMonth, stdWeekInMonth, stdDayOfWeek, finalStdRule->getRule())) {
2201                         writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2202                                 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, MAX_MILLIS, status);
2203                     } else {
2204                         // Not equivalent rule - write out two different rules
2205                         writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2206                                 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2207                         if (U_FAILURE(status)) {
2208                             goto cleanupWriteZone;
2209                         }
2210                         UDate nextStart;
2211                         UBool nextStartAvail = finalStdRule->getNextStart(stdUntilTime, stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, false, nextStart);
2212                         U_ASSERT(nextStartAvail);
2213                         if (nextStartAvail) {
2214                             writeFinalRule(w, FALSE, finalStdRule,
2215                                     stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, nextStart, status);
2216                         }
2217                     }
2218                 }
2219                 if (U_FAILURE(status)) {
2220                     goto cleanupWriteZone;
2221                 }
2222             }
2223         }
2224     }
2225     writeFooter(w, status);
2226 
2227 cleanupWriteZone:
2228 
2229     if (finalStdRule != nullptr) {
2230         delete finalStdRule;
2231     }
2232     if (finalDstRule != nullptr) {
2233         delete finalDstRule;
2234     }
2235 }
2236 
2237 void
writeHeaders(VTZWriter & writer,UErrorCode & status) const2238 VTimeZone::writeHeaders(VTZWriter& writer, UErrorCode& status) const {
2239     if (U_FAILURE(status)) {
2240         return;
2241     }
2242     UnicodeString tzid;
2243     tz->getID(tzid);
2244 
2245     writer.write(ICAL_BEGIN);
2246     writer.write(COLON);
2247     writer.write(ICAL_VTIMEZONE);
2248     writer.write(ICAL_NEWLINE);
2249     writer.write(ICAL_TZID);
2250     writer.write(COLON);
2251     writer.write(tzid);
2252     writer.write(ICAL_NEWLINE);
2253     if (tzurl.length() != 0) {
2254         writer.write(ICAL_TZURL);
2255         writer.write(COLON);
2256         writer.write(tzurl);
2257         writer.write(ICAL_NEWLINE);
2258     }
2259     if (lastmod != MAX_MILLIS) {
2260         UnicodeString lastmodStr;
2261         writer.write(ICAL_LASTMOD);
2262         writer.write(COLON);
2263         writer.write(getUTCDateTimeString(lastmod, lastmodStr));
2264         writer.write(ICAL_NEWLINE);
2265     }
2266 }
2267 
2268 /*
2269  * Write the closing section of the VTIMEZONE definition block
2270  */
2271 void
writeFooter(VTZWriter & writer,UErrorCode & status) const2272 VTimeZone::writeFooter(VTZWriter& writer, UErrorCode& status) const {
2273     if (U_FAILURE(status)) {
2274         return;
2275     }
2276     writer.write(ICAL_END);
2277     writer.write(COLON);
2278     writer.write(ICAL_VTIMEZONE);
2279     writer.write(ICAL_NEWLINE);
2280 }
2281 
2282 /*
2283  * Write a single start time
2284  */
2285 void
writeZonePropsByTime(VTZWriter & writer,UBool isDst,const UnicodeString & zonename,int32_t fromOffset,int32_t toOffset,UDate time,UBool withRDATE,UErrorCode & status) const2286 VTimeZone::writeZonePropsByTime(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2287                                 int32_t fromOffset, int32_t toOffset, UDate time, UBool withRDATE,
2288                                 UErrorCode& status) const {
2289     if (U_FAILURE(status)) {
2290         return;
2291     }
2292     beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, time, status);
2293     if (U_FAILURE(status)) {
2294         return;
2295     }
2296     if (withRDATE) {
2297         writer.write(ICAL_RDATE);
2298         writer.write(COLON);
2299         UnicodeString timestr;
2300         writer.write(getDateTimeString(time + fromOffset, timestr));
2301         writer.write(ICAL_NEWLINE);
2302     }
2303     endZoneProps(writer, isDst, status);
2304     if (U_FAILURE(status)) {
2305         return;
2306     }
2307 }
2308 
2309 /*
2310  * Write start times defined by a DOM rule using VTIMEZONE RRULE
2311  */
2312 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) const2313 VTimeZone::writeZonePropsByDOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2314                                int32_t fromOffset, int32_t toOffset,
2315                                int32_t month, int32_t dayOfMonth, UDate startTime, UDate untilTime,
2316                                UErrorCode& status) const {
2317     if (U_FAILURE(status)) {
2318         return;
2319     }
2320     beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
2321     if (U_FAILURE(status)) {
2322         return;
2323     }
2324     beginRRULE(writer, month, status);
2325     if (U_FAILURE(status)) {
2326         return;
2327     }
2328     writer.write(ICAL_BYMONTHDAY);
2329     writer.write(EQUALS_SIGN);
2330     UnicodeString dstr;
2331     appendAsciiDigits(dayOfMonth, 0, dstr);
2332     writer.write(dstr);
2333     if (untilTime != MAX_MILLIS) {
2334         appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2335         if (U_FAILURE(status)) {
2336             return;
2337         }
2338     }
2339     writer.write(ICAL_NEWLINE);
2340     endZoneProps(writer, isDst, status);
2341 }
2342 
2343 /*
2344  * Write start times defined by a DOW rule using VTIMEZONE RRULE
2345  */
2346 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) const2347 VTimeZone::writeZonePropsByDOW(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2348                                int32_t fromOffset, int32_t toOffset,
2349                                int32_t month, int32_t weekInMonth, int32_t dayOfWeek,
2350                                UDate startTime, UDate untilTime, UErrorCode& status) const {
2351     if (U_FAILURE(status)) {
2352         return;
2353     }
2354     beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
2355     if (U_FAILURE(status)) {
2356         return;
2357     }
2358     beginRRULE(writer, month, status);
2359     if (U_FAILURE(status)) {
2360         return;
2361     }
2362     writer.write(ICAL_BYDAY);
2363     writer.write(EQUALS_SIGN);
2364     UnicodeString dstr;
2365     appendAsciiDigits(weekInMonth, 0, dstr);
2366     writer.write(dstr);    // -4, -3, -2, -1, 1, 2, 3, 4
2367     writer.write(ICAL_DOW_NAMES[dayOfWeek - 1]);    // SU, MO, TU...
2368 
2369     if (untilTime != MAX_MILLIS) {
2370         appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2371         if (U_FAILURE(status)) {
2372             return;
2373         }
2374     }
2375     writer.write(ICAL_NEWLINE);
2376     endZoneProps(writer, isDst, status);
2377 }
2378 
2379 /*
2380  * Write start times defined by a DOW_GEQ_DOM rule using VTIMEZONE RRULE
2381  */
2382 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) const2383 VTimeZone::writeZonePropsByDOW_GEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2384                                        int32_t fromOffset, int32_t toOffset,
2385                                        int32_t month, int32_t dayOfMonth, int32_t dayOfWeek,
2386                                        UDate startTime, UDate untilTime, UErrorCode& status) const {
2387     if (U_FAILURE(status)) {
2388         return;
2389     }
2390     // Check if this rule can be converted to DOW rule
2391     if (dayOfMonth%7 == 1) {
2392         // Can be represented by DOW rule
2393         writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2394                 month, (dayOfMonth + 6)/7, dayOfWeek, startTime, untilTime, status);
2395         if (U_FAILURE(status)) {
2396             return;
2397         }
2398     } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 6) {
2399         // Can be represented by DOW rule with negative week number
2400         writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2401                 month, -1*((MONTHLENGTH[month] - dayOfMonth + 1)/7), dayOfWeek, startTime, untilTime, status);
2402         if (U_FAILURE(status)) {
2403             return;
2404         }
2405     } else {
2406         // Otherwise, use BYMONTHDAY to include all possible dates
2407         beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
2408         if (U_FAILURE(status)) {
2409             return;
2410         }
2411         // Check if all days are in the same month
2412         int32_t startDay = dayOfMonth;
2413         int32_t currentMonthDays = 7;
2414 
2415         if (dayOfMonth <= 0) {
2416             // The start day is in previous month
2417             int32_t prevMonthDays = 1 - dayOfMonth;
2418             currentMonthDays -= prevMonthDays;
2419 
2420             int32_t prevMonth = (month - 1) < 0 ? 11 : month - 1;
2421 
2422             // Note: When a rule is separated into two, UNTIL attribute needs to be
2423             // calculated for each of them.  For now, we skip this, because we basically use this method
2424             // only for final rules, which does not have the UNTIL attribute
2425             writeZonePropsByDOW_GEQ_DOM_sub(writer, prevMonth, -prevMonthDays, dayOfWeek, prevMonthDays,
2426                 MAX_MILLIS /* Do not use UNTIL */, fromOffset, status);
2427             if (U_FAILURE(status)) {
2428                 return;
2429             }
2430 
2431             // Start from 1 for the rest
2432             startDay = 1;
2433         } else if (dayOfMonth + 6 > MONTHLENGTH[month]) {
2434             // Note: This code does not actually work well in February.  For now, days in month in
2435             // non-leap year.
2436             int32_t nextMonthDays = dayOfMonth + 6 - MONTHLENGTH[month];
2437             currentMonthDays -= nextMonthDays;
2438 
2439             int32_t nextMonth = (month + 1) > 11 ? 0 : month + 1;
2440 
2441             writeZonePropsByDOW_GEQ_DOM_sub(writer, nextMonth, 1, dayOfWeek, nextMonthDays,
2442                 MAX_MILLIS /* Do not use UNTIL */, fromOffset, status);
2443             if (U_FAILURE(status)) {
2444                 return;
2445             }
2446         }
2447         writeZonePropsByDOW_GEQ_DOM_sub(writer, month, startDay, dayOfWeek, currentMonthDays,
2448             untilTime, fromOffset, status);
2449         if (U_FAILURE(status)) {
2450             return;
2451         }
2452         endZoneProps(writer, isDst, status);
2453     }
2454 }
2455 
2456 /*
2457  * Called from writeZonePropsByDOW_GEQ_DOM
2458  */
2459 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) const2460 VTimeZone::writeZonePropsByDOW_GEQ_DOM_sub(VTZWriter& writer, int32_t month, int32_t dayOfMonth,
2461                                            int32_t dayOfWeek, int32_t numDays,
2462                                            UDate untilTime, int32_t fromOffset, UErrorCode& status) const {
2463 
2464     if (U_FAILURE(status)) {
2465         return;
2466     }
2467     int32_t startDayNum = dayOfMonth;
2468     UBool isFeb = (month == UCAL_FEBRUARY);
2469     if (dayOfMonth < 0 && !isFeb) {
2470         // Use positive number if possible
2471         startDayNum = MONTHLENGTH[month] + dayOfMonth + 1;
2472     }
2473     beginRRULE(writer, month, status);
2474     if (U_FAILURE(status)) {
2475         return;
2476     }
2477     writer.write(ICAL_BYDAY);
2478     writer.write(EQUALS_SIGN);
2479     writer.write(ICAL_DOW_NAMES[dayOfWeek - 1]);    // SU, MO, TU...
2480     writer.write(SEMICOLON);
2481     writer.write(ICAL_BYMONTHDAY);
2482     writer.write(EQUALS_SIGN);
2483 
2484     UnicodeString dstr;
2485     appendAsciiDigits(startDayNum, 0, dstr);
2486     writer.write(dstr);
2487     for (int32_t i = 1; i < numDays; i++) {
2488         writer.write(COMMA);
2489         dstr.remove();
2490         appendAsciiDigits(startDayNum + i, 0, dstr);
2491         writer.write(dstr);
2492     }
2493 
2494     if (untilTime != MAX_MILLIS) {
2495         appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2496         if (U_FAILURE(status)) {
2497             return;
2498         }
2499     }
2500     writer.write(ICAL_NEWLINE);
2501 }
2502 
2503 /*
2504  * Write start times defined by a DOW_LEQ_DOM rule using VTIMEZONE RRULE
2505  */
2506 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) const2507 VTimeZone::writeZonePropsByDOW_LEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2508                                        int32_t fromOffset, int32_t toOffset,
2509                                        int32_t month, int32_t dayOfMonth, int32_t dayOfWeek,
2510                                        UDate startTime, UDate untilTime, UErrorCode& status) const {
2511     if (U_FAILURE(status)) {
2512         return;
2513     }
2514     // Check if this rule can be converted to DOW rule
2515     if (dayOfMonth%7 == 0) {
2516         // Can be represented by DOW rule
2517         writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2518                 month, dayOfMonth/7, dayOfWeek, startTime, untilTime, status);
2519     } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 0){
2520         // Can be represented by DOW rule with negative week number
2521         writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2522                 month, -1*((MONTHLENGTH[month] - dayOfMonth)/7 + 1), dayOfWeek, startTime, untilTime, status);
2523     } else if (month == UCAL_FEBRUARY && dayOfMonth == 29) {
2524         // Specical case for February
2525         writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2526                 UCAL_FEBRUARY, -1, dayOfWeek, startTime, untilTime, status);
2527     } else {
2528         // Otherwise, convert this to DOW_GEQ_DOM rule
2529         writeZonePropsByDOW_GEQ_DOM(writer, isDst, zonename, fromOffset, toOffset,
2530                 month, dayOfMonth - 6, dayOfWeek, startTime, untilTime, status);
2531     }
2532 }
2533 
2534 /*
2535  * Write the final time zone rule using RRULE, with no UNTIL attribute
2536  */
2537 void
writeFinalRule(VTZWriter & writer,UBool isDst,const AnnualTimeZoneRule * rule,int32_t fromRawOffset,int32_t fromDSTSavings,UDate startTime,UErrorCode & status) const2538 VTimeZone::writeFinalRule(VTZWriter& writer, UBool isDst, const AnnualTimeZoneRule* rule,
2539                           int32_t fromRawOffset, int32_t fromDSTSavings,
2540                           UDate startTime, UErrorCode& status) const {
2541     if (U_FAILURE(status)) {
2542         return;
2543     }
2544     UBool modifiedRule = TRUE;
2545     const DateTimeRule *dtrule = toWallTimeRule(rule->getRule(), fromRawOffset, fromDSTSavings, status);
2546     if (U_FAILURE(status)) {
2547         return;
2548     }
2549     if (dtrule == nullptr) {
2550         modifiedRule = FALSE;
2551         dtrule = rule->getRule();
2552     }
2553 
2554     // If the rule's mills in a day is out of range, adjust start time.
2555     // Olson tzdata supports 24:00 of a day, but VTIMEZONE does not.
2556     // See ticket#7008/#7518
2557 
2558     int32_t timeInDay = dtrule->getRuleMillisInDay();
2559     if (timeInDay < 0) {
2560         startTime = startTime + (0 - timeInDay);
2561     } else if (timeInDay >= U_MILLIS_PER_DAY) {
2562         startTime = startTime - (timeInDay - (U_MILLIS_PER_DAY - 1));
2563     }
2564 
2565     int32_t toOffset = rule->getRawOffset() + rule->getDSTSavings();
2566     UnicodeString name;
2567     rule->getName(name);
2568     switch (dtrule->getDateRuleType()) {
2569     case DateTimeRule::DOM:
2570         writeZonePropsByDOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2571                 dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), startTime, MAX_MILLIS, status);
2572         break;
2573     case DateTimeRule::DOW:
2574         writeZonePropsByDOW(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2575                 dtrule->getRuleMonth(), dtrule->getRuleWeekInMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
2576         break;
2577     case DateTimeRule::DOW_GEQ_DOM:
2578         writeZonePropsByDOW_GEQ_DOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2579                 dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
2580         break;
2581     case DateTimeRule::DOW_LEQ_DOM:
2582         writeZonePropsByDOW_LEQ_DOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2583                 dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
2584         break;
2585     }
2586     if (modifiedRule) {
2587         delete dtrule;
2588     }
2589 }
2590 
2591 /*
2592  * Write the opening section of zone properties
2593  */
2594 void
beginZoneProps(VTZWriter & writer,UBool isDst,const UnicodeString & zonename,int32_t fromOffset,int32_t toOffset,UDate startTime,UErrorCode & status) const2595 VTimeZone::beginZoneProps(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2596                           int32_t fromOffset, int32_t toOffset, UDate startTime, UErrorCode& status) const {
2597     if (U_FAILURE(status)) {
2598         return;
2599     }
2600     writer.write(ICAL_BEGIN);
2601     writer.write(COLON);
2602     if (isDst) {
2603         writer.write(ICAL_DAYLIGHT);
2604     } else {
2605         writer.write(ICAL_STANDARD);
2606     }
2607     writer.write(ICAL_NEWLINE);
2608 
2609     UnicodeString dstr;
2610 
2611     // TZOFFSETTO
2612     writer.write(ICAL_TZOFFSETTO);
2613     writer.write(COLON);
2614     millisToOffset(toOffset, dstr);
2615     writer.write(dstr);
2616     writer.write(ICAL_NEWLINE);
2617 
2618     // TZOFFSETFROM
2619     writer.write(ICAL_TZOFFSETFROM);
2620     writer.write(COLON);
2621     millisToOffset(fromOffset, dstr);
2622     writer.write(dstr);
2623     writer.write(ICAL_NEWLINE);
2624 
2625     // TZNAME
2626     writer.write(ICAL_TZNAME);
2627     writer.write(COLON);
2628     writer.write(zonename);
2629     writer.write(ICAL_NEWLINE);
2630 
2631     // DTSTART
2632     writer.write(ICAL_DTSTART);
2633     writer.write(COLON);
2634     writer.write(getDateTimeString(startTime + fromOffset, dstr));
2635     writer.write(ICAL_NEWLINE);
2636 }
2637 
2638 /*
2639  * Writes the closing section of zone properties
2640  */
2641 void
endZoneProps(VTZWriter & writer,UBool isDst,UErrorCode & status) const2642 VTimeZone::endZoneProps(VTZWriter& writer, UBool isDst, UErrorCode& status) const {
2643     if (U_FAILURE(status)) {
2644         return;
2645     }
2646     // END:STANDARD or END:DAYLIGHT
2647     writer.write(ICAL_END);
2648     writer.write(COLON);
2649     if (isDst) {
2650         writer.write(ICAL_DAYLIGHT);
2651     } else {
2652         writer.write(ICAL_STANDARD);
2653     }
2654     writer.write(ICAL_NEWLINE);
2655 }
2656 
2657 /*
2658  * Write the beginning part of RRULE line
2659  */
2660 void
beginRRULE(VTZWriter & writer,int32_t month,UErrorCode & status) const2661 VTimeZone::beginRRULE(VTZWriter& writer, int32_t month, UErrorCode& status) const {
2662     if (U_FAILURE(status)) {
2663         return;
2664     }
2665     UnicodeString dstr;
2666     writer.write(ICAL_RRULE);
2667     writer.write(COLON);
2668     writer.write(ICAL_FREQ);
2669     writer.write(EQUALS_SIGN);
2670     writer.write(ICAL_YEARLY);
2671     writer.write(SEMICOLON);
2672     writer.write(ICAL_BYMONTH);
2673     writer.write(EQUALS_SIGN);
2674     appendAsciiDigits(month + 1, 0, dstr);
2675     writer.write(dstr);
2676     writer.write(SEMICOLON);
2677 }
2678 
2679 /*
2680  * Append the UNTIL attribute after RRULE line
2681  */
2682 void
appendUNTIL(VTZWriter & writer,const UnicodeString & until,UErrorCode & status) const2683 VTimeZone::appendUNTIL(VTZWriter& writer, const UnicodeString& until,  UErrorCode& status) const {
2684     if (U_FAILURE(status)) {
2685         return;
2686     }
2687     if (until.length() > 0) {
2688         writer.write(SEMICOLON);
2689         writer.write(ICAL_UNTIL);
2690         writer.write(EQUALS_SIGN);
2691         writer.write(until);
2692     }
2693 }
2694 
2695 U_NAMESPACE_END
2696 
2697 #endif /* #if !UCONFIG_NO_FORMATTING */
2698 
2699 //eof
2700