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