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