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 if (tz != nullptr) {
989 delete tz;
990 }
991 if (vtzlines != nullptr) {
992 delete vtzlines;
993 }
994 }
995
996 VTimeZone&
operator =(const VTimeZone & right)997 VTimeZone::operator=(const VTimeZone& right) {
998 if (this == &right) {
999 return *this;
1000 }
1001 if (*this != right) {
1002 BasicTimeZone::operator=(right);
1003 if (tz != nullptr) {
1004 delete tz;
1005 tz = nullptr;
1006 }
1007 if (right.tz != nullptr) {
1008 tz = right.tz->clone();
1009 }
1010 if (vtzlines != nullptr) {
1011 delete vtzlines;
1012 vtzlines = nullptr;
1013 }
1014 if (right.vtzlines != nullptr) {
1015 UErrorCode status = U_ZERO_ERROR;
1016 int32_t size = right.vtzlines->size();
1017 LocalPointer<UVector> lpVtzLines(
1018 new UVector(uprv_deleteUObject, uhash_compareUnicodeString, size, status), status);
1019 if (U_SUCCESS(status)) {
1020 for (int32_t i = 0; i < size; i++) {
1021 LocalPointer<UnicodeString> line(
1022 ((UnicodeString*)right.vtzlines->elementAt(i))->clone(), status);
1023 lpVtzLines->adoptElement(line.orphan(), status);
1024 if (U_FAILURE(status)) {
1025 break;
1026 }
1027 }
1028 if (U_SUCCESS(status)) {
1029 vtzlines = lpVtzLines.orphan();
1030 }
1031 }
1032 }
1033 tzurl = right.tzurl;
1034 lastmod = right.lastmod;
1035 olsonzid = right.olsonzid;
1036 icutzver = right.icutzver;
1037 }
1038 return *this;
1039 }
1040
1041 bool
operator ==(const TimeZone & that) const1042 VTimeZone::operator==(const TimeZone& that) const {
1043 if (this == &that) {
1044 return true;
1045 }
1046 if (typeid(*this) != typeid(that) || !BasicTimeZone::operator==(that)) {
1047 return false;
1048 }
1049 VTimeZone *vtz = (VTimeZone*)&that;
1050 if (*tz == *(vtz->tz)
1051 && tzurl == vtz->tzurl
1052 && lastmod == vtz->lastmod
1053 /* && olsonzid = that.olsonzid */
1054 /* && icutzver = that.icutzver */) {
1055 return true;
1056 }
1057 return false;
1058 }
1059
1060 bool
operator !=(const TimeZone & that) const1061 VTimeZone::operator!=(const TimeZone& that) const {
1062 return !operator==(that);
1063 }
1064
1065 VTimeZone*
createVTimeZoneByID(const UnicodeString & ID)1066 VTimeZone::createVTimeZoneByID(const UnicodeString& ID) {
1067 VTimeZone *vtz = new VTimeZone();
1068 if (vtz == nullptr) {
1069 return nullptr;
1070 }
1071 vtz->tz = (BasicTimeZone*)TimeZone::createTimeZone(ID);
1072 vtz->tz->getID(vtz->olsonzid);
1073
1074 // Set ICU tzdata version
1075 UErrorCode status = U_ZERO_ERROR;
1076 UResourceBundle *bundle = nullptr;
1077 const char16_t* versionStr = nullptr;
1078 int32_t len = 0;
1079 bundle = ures_openDirect(nullptr, "zoneinfo64", &status);
1080 versionStr = ures_getStringByKey(bundle, "TZVersion", &len, &status);
1081 if (U_SUCCESS(status)) {
1082 vtz->icutzver.setTo(versionStr, len);
1083 }
1084 ures_close(bundle);
1085 return vtz;
1086 }
1087
1088 VTimeZone*
createVTimeZoneFromBasicTimeZone(const BasicTimeZone & basic_time_zone,UErrorCode & status)1089 VTimeZone::createVTimeZoneFromBasicTimeZone(const BasicTimeZone& basic_time_zone, UErrorCode &status) {
1090 if (U_FAILURE(status)) {
1091 return nullptr;
1092 }
1093 VTimeZone *vtz = new VTimeZone();
1094 if (vtz == nullptr) {
1095 status = U_MEMORY_ALLOCATION_ERROR;
1096 return nullptr;
1097 }
1098 vtz->tz = basic_time_zone.clone();
1099 if (vtz->tz == nullptr) {
1100 status = U_MEMORY_ALLOCATION_ERROR;
1101 delete vtz;
1102 return nullptr;
1103 }
1104 vtz->tz->getID(vtz->olsonzid);
1105
1106 // Set ICU tzdata version
1107 UResourceBundle *bundle = nullptr;
1108 const char16_t* versionStr = nullptr;
1109 int32_t len = 0;
1110 bundle = ures_openDirect(nullptr, "zoneinfo64", &status);
1111 versionStr = ures_getStringByKey(bundle, "TZVersion", &len, &status);
1112 if (U_SUCCESS(status)) {
1113 vtz->icutzver.setTo(versionStr, len);
1114 }
1115 ures_close(bundle);
1116 return vtz;
1117 }
1118
1119 VTimeZone*
createVTimeZone(const UnicodeString & vtzdata,UErrorCode & status)1120 VTimeZone::createVTimeZone(const UnicodeString& vtzdata, UErrorCode& status) {
1121 if (U_FAILURE(status)) {
1122 return nullptr;
1123 }
1124 VTZReader reader(vtzdata);
1125 VTimeZone *vtz = new VTimeZone();
1126 if (vtz == nullptr) {
1127 status = U_MEMORY_ALLOCATION_ERROR;
1128 return nullptr;
1129 }
1130 vtz->load(reader, status);
1131 if (U_FAILURE(status)) {
1132 delete vtz;
1133 return nullptr;
1134 }
1135 return vtz;
1136 }
1137
1138 UBool
getTZURL(UnicodeString & url) const1139 VTimeZone::getTZURL(UnicodeString& url) const {
1140 if (tzurl.length() > 0) {
1141 url = tzurl;
1142 return true;
1143 }
1144 return false;
1145 }
1146
1147 void
setTZURL(const UnicodeString & url)1148 VTimeZone::setTZURL(const UnicodeString& url) {
1149 tzurl = url;
1150 }
1151
1152 UBool
getLastModified(UDate & lastModified) const1153 VTimeZone::getLastModified(UDate& lastModified) const {
1154 if (lastmod != MAX_MILLIS) {
1155 lastModified = lastmod;
1156 return true;
1157 }
1158 return false;
1159 }
1160
1161 void
setLastModified(UDate lastModified)1162 VTimeZone::setLastModified(UDate lastModified) {
1163 lastmod = lastModified;
1164 }
1165
1166 void
write(UnicodeString & result,UErrorCode & status) const1167 VTimeZone::write(UnicodeString& result, UErrorCode& status) const {
1168 result.remove();
1169 VTZWriter writer(result);
1170 write(writer, status);
1171 }
1172
1173 void
write(UDate start,UnicodeString & result,UErrorCode & status) const1174 VTimeZone::write(UDate start, UnicodeString& result, UErrorCode& status) const {
1175 result.remove();
1176 VTZWriter writer(result);
1177 write(start, writer, status);
1178 }
1179
1180 void
writeSimple(UDate time,UnicodeString & result,UErrorCode & status) const1181 VTimeZone::writeSimple(UDate time, UnicodeString& result, UErrorCode& status) const {
1182 result.remove();
1183 VTZWriter writer(result);
1184 writeSimple(time, writer, status);
1185 }
1186
1187 VTimeZone*
clone() const1188 VTimeZone::clone() const {
1189 return new VTimeZone(*this);
1190 }
1191
1192 int32_t
getOffset(uint8_t era,int32_t year,int32_t month,int32_t day,uint8_t dayOfWeek,int32_t millis,UErrorCode & status) const1193 VTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day,
1194 uint8_t dayOfWeek, int32_t millis, UErrorCode& status) const {
1195 return tz->getOffset(era, year, month, day, dayOfWeek, millis, status);
1196 }
1197
1198 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) const1199 VTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day,
1200 uint8_t dayOfWeek, int32_t millis,
1201 int32_t monthLength, UErrorCode& status) const {
1202 return tz->getOffset(era, year, month, day, dayOfWeek, millis, monthLength, status);
1203 }
1204
1205 void
getOffset(UDate date,UBool local,int32_t & rawOffset,int32_t & dstOffset,UErrorCode & status) const1206 VTimeZone::getOffset(UDate date, UBool local, int32_t& rawOffset,
1207 int32_t& dstOffset, UErrorCode& status) const {
1208 return tz->getOffset(date, local, rawOffset, dstOffset, status);
1209 }
1210
getOffsetFromLocal(UDate date,UTimeZoneLocalOption nonExistingTimeOpt,UTimeZoneLocalOption duplicatedTimeOpt,int32_t & rawOffset,int32_t & dstOffset,UErrorCode & status) const1211 void VTimeZone::getOffsetFromLocal(UDate date, UTimeZoneLocalOption nonExistingTimeOpt,
1212 UTimeZoneLocalOption duplicatedTimeOpt,
1213 int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) const {
1214 tz->getOffsetFromLocal(date, nonExistingTimeOpt, duplicatedTimeOpt, rawOffset, dstOffset, status);
1215 }
1216
1217 void
setRawOffset(int32_t offsetMillis)1218 VTimeZone::setRawOffset(int32_t offsetMillis) {
1219 tz->setRawOffset(offsetMillis);
1220 }
1221
1222 int32_t
getRawOffset() const1223 VTimeZone::getRawOffset() const {
1224 return tz->getRawOffset();
1225 }
1226
1227 UBool
useDaylightTime() const1228 VTimeZone::useDaylightTime() const {
1229 return tz->useDaylightTime();
1230 }
1231
1232 UBool
inDaylightTime(UDate date,UErrorCode & status) const1233 VTimeZone::inDaylightTime(UDate date, UErrorCode& status) const {
1234 return tz->inDaylightTime(date, status);
1235 }
1236
1237 UBool
hasSameRules(const TimeZone & other) const1238 VTimeZone::hasSameRules(const TimeZone& other) const {
1239 return tz->hasSameRules(other);
1240 }
1241
1242 UBool
getNextTransition(UDate base,UBool inclusive,TimeZoneTransition & result) const1243 VTimeZone::getNextTransition(UDate base, UBool inclusive, TimeZoneTransition& result) const {
1244 return tz->getNextTransition(base, inclusive, result);
1245 }
1246
1247 UBool
getPreviousTransition(UDate base,UBool inclusive,TimeZoneTransition & result) const1248 VTimeZone::getPreviousTransition(UDate base, UBool inclusive, TimeZoneTransition& result) const {
1249 return tz->getPreviousTransition(base, inclusive, result);
1250 }
1251
1252 int32_t
countTransitionRules(UErrorCode & status) const1253 VTimeZone::countTransitionRules(UErrorCode& status) const {
1254 return tz->countTransitionRules(status);
1255 }
1256
1257 void
getTimeZoneRules(const InitialTimeZoneRule * & initial,const TimeZoneRule * trsrules[],int32_t & trscount,UErrorCode & status) const1258 VTimeZone::getTimeZoneRules(const InitialTimeZoneRule*& initial,
1259 const TimeZoneRule* trsrules[], int32_t& trscount,
1260 UErrorCode& status) const {
1261 tz->getTimeZoneRules(initial, trsrules, trscount, status);
1262 }
1263
1264 void
load(VTZReader & reader,UErrorCode & status)1265 VTimeZone::load(VTZReader& reader, UErrorCode& status) {
1266 U_ASSERT(vtzlines == nullptr);
1267 LocalPointer<UVector> lpVtzLines(
1268 new UVector(uprv_deleteUObject, uhash_compareUnicodeString, DEFAULT_VTIMEZONE_LINES, status), status);
1269 if (U_FAILURE(status)) {
1270 return;
1271 }
1272 UBool eol = false;
1273 UBool start = false;
1274 UBool success = false;
1275 UnicodeString line;
1276
1277 while (true) {
1278 char16_t ch = reader.read();
1279 if (ch == 0xFFFF) {
1280 // end of file
1281 if (start && line.startsWith(ICAL_END_VTIMEZONE, -1)) {
1282 LocalPointer<UnicodeString> element(new UnicodeString(line), status);
1283 lpVtzLines->adoptElement(element.orphan(), status);
1284 if (U_FAILURE(status)) {
1285 return;
1286 }
1287 success = true;
1288 }
1289 break;
1290 }
1291 if (ch == 0x000D) {
1292 // CR, must be followed by LF according to the definition in RFC2445
1293 continue;
1294 }
1295 if (eol) {
1296 if (ch != 0x0009 && ch != 0x0020) {
1297 // NOT followed by TAB/SP -> new line
1298 if (start) {
1299 if (line.length() > 0) {
1300 LocalPointer<UnicodeString> element(new UnicodeString(line), status);
1301 lpVtzLines->adoptElement(element.orphan(), status);
1302 if (U_FAILURE(status)) {
1303 return;
1304 }
1305 }
1306 }
1307 line.remove();
1308 if (ch != 0x000A) {
1309 line.append(ch);
1310 }
1311 }
1312 eol = false;
1313 } else {
1314 if (ch == 0x000A) {
1315 // LF
1316 eol = true;
1317 if (start) {
1318 if (line.startsWith(ICAL_END_VTIMEZONE, -1)) {
1319 LocalPointer<UnicodeString> element(new UnicodeString(line), status);
1320 lpVtzLines->adoptElement(element.orphan(), status);
1321 if (U_FAILURE(status)) {
1322 return;
1323 }
1324 success = true;
1325 break;
1326 }
1327 } else {
1328 if (line.startsWith(ICAL_BEGIN_VTIMEZONE, -1)) {
1329 LocalPointer<UnicodeString> element(new UnicodeString(line), status);
1330 lpVtzLines->adoptElement(element.orphan(), status);
1331 if (U_FAILURE(status)) {
1332 return;
1333 }
1334 line.remove();
1335 start = true;
1336 eol = false;
1337 }
1338 }
1339 } else {
1340 line.append(ch);
1341 }
1342 }
1343 }
1344 if (!success) {
1345 if (U_SUCCESS(status)) {
1346 status = U_INVALID_STATE_ERROR;
1347 }
1348 return;
1349 }
1350 vtzlines = lpVtzLines.orphan();
1351 parse(status);
1352 }
1353
1354 // parser state
1355 #define INI 0 // Initial state
1356 #define VTZ 1 // In VTIMEZONE
1357 #define TZI 2 // In STANDARD or DAYLIGHT
1358
1359 #define DEF_DSTSAVINGS (60*60*1000)
1360 #define DEF_TZSTARTTIME (0.0)
1361
1362 void
parse(UErrorCode & status)1363 VTimeZone::parse(UErrorCode& status) {
1364 if (U_FAILURE(status)) {
1365 return;
1366 }
1367 if (vtzlines == nullptr || vtzlines->size() == 0) {
1368 status = U_INVALID_STATE_ERROR;
1369 return;
1370 }
1371
1372 // timezone ID
1373 UnicodeString tzid;
1374
1375 int32_t state = INI;
1376 int32_t n = 0;
1377 UBool dst = false; // current zone type
1378 UnicodeString from; // current zone from offset
1379 UnicodeString to; // current zone offset
1380 UnicodeString zonename; // current zone name
1381 UnicodeString dtstart; // current zone starts
1382 UBool isRRULE = false; // true if the rule is described by RRULE
1383 int32_t initialRawOffset = 0; // initial offset
1384 int32_t initialDSTSavings = 0; // initial offset
1385 UDate firstStart = MAX_MILLIS; // the earliest rule start time
1386 UnicodeString name; // RFC2445 prop name
1387 UnicodeString value; // RFC2445 prop value
1388
1389 int32_t finalRuleIdx = -1;
1390 int32_t finalRuleCount = 0;
1391
1392 // Set the deleter on rules to remove TimeZoneRule vectors to avoid memory leaks due to unowned TimeZoneRules.
1393 UVector rules(uprv_deleteUObject, nullptr, status);
1394
1395 // list of RDATE or RRULE strings
1396 UVector dates(uprv_deleteUObject, uhash_compareUnicodeString, status);
1397 if (U_FAILURE(status)) {
1398 return;
1399 }
1400
1401 for (n = 0; n < vtzlines->size(); n++) {
1402 UnicodeString *line = (UnicodeString*)vtzlines->elementAt(n);
1403 int32_t valueSep = line->indexOf(COLON);
1404 if (valueSep < 0) {
1405 continue;
1406 }
1407 name.setTo(*line, 0, valueSep);
1408 value.setTo(*line, valueSep + 1);
1409
1410 switch (state) {
1411 case INI:
1412 if (name.compare(ICAL_BEGIN, -1) == 0
1413 && value.compare(ICAL_VTIMEZONE, -1) == 0) {
1414 state = VTZ;
1415 }
1416 break;
1417
1418 case VTZ:
1419 if (name.compare(ICAL_TZID, -1) == 0) {
1420 tzid = value;
1421 } else if (name.compare(ICAL_TZURL, -1) == 0) {
1422 tzurl = value;
1423 } else if (name.compare(ICAL_LASTMOD, -1) == 0) {
1424 // Always in 'Z' format, so the offset argument for the parse method
1425 // can be any value.
1426 lastmod = parseDateTimeString(value, 0, status);
1427 if (U_FAILURE(status)) {
1428 return;
1429 }
1430 } else if (name.compare(ICAL_BEGIN, -1) == 0) {
1431 UBool isDST = (value.compare(ICAL_DAYLIGHT, -1) == 0);
1432 if (value.compare(ICAL_STANDARD, -1) == 0 || isDST) {
1433 // tzid must be ready at this point
1434 if (tzid.length() == 0) {
1435 return;
1436 }
1437 // initialize current zone properties
1438 if (dates.size() != 0) {
1439 dates.removeAllElements();
1440 }
1441 isRRULE = false;
1442 from.remove();
1443 to.remove();
1444 zonename.remove();
1445 dst = isDST;
1446 state = TZI;
1447 } else {
1448 // BEGIN property other than STANDARD/DAYLIGHT
1449 // must not be there.
1450 return;
1451 }
1452 } else if (name.compare(ICAL_END, -1) == 0) {
1453 break;
1454 }
1455 break;
1456 case TZI:
1457 if (name.compare(ICAL_DTSTART, -1) == 0) {
1458 dtstart = value;
1459 } else if (name.compare(ICAL_TZNAME, -1) == 0) {
1460 zonename = value;
1461 } else if (name.compare(ICAL_TZOFFSETFROM, -1) == 0) {
1462 from = value;
1463 } else if (name.compare(ICAL_TZOFFSETTO, -1) == 0) {
1464 to = value;
1465 } else if (name.compare(ICAL_RDATE, -1) == 0) {
1466 // RDATE mixed with RRULE is not supported
1467 if (isRRULE) {
1468 return;
1469 }
1470 // RDATE value may contain multiple date delimited
1471 // by comma
1472 UBool nextDate = true;
1473 int32_t dstart = 0;
1474 LocalPointer<UnicodeString> dstr;
1475 while (nextDate) {
1476 int32_t dend = value.indexOf(COMMA, dstart);
1477 if (dend == -1) {
1478 dstr.adoptInsteadAndCheckErrorCode(new UnicodeString(value, dstart), status);
1479 nextDate = false;
1480 } else {
1481 dstr.adoptInsteadAndCheckErrorCode(new UnicodeString(value, dstart, dend - dstart), status);
1482 }
1483 dates.adoptElement(dstr.orphan(), status);
1484 if (U_FAILURE(status)) {
1485 return;
1486 }
1487 dstart = dend + 1;
1488 }
1489 } else if (name.compare(ICAL_RRULE, -1) == 0) {
1490 // RRULE mixed with RDATE is not supported
1491 if (!isRRULE && dates.size() != 0) {
1492 return;
1493 }
1494 isRRULE = true;
1495 LocalPointer<UnicodeString> element(new UnicodeString(value), status);
1496 dates.adoptElement(element.orphan(), status);
1497 if (U_FAILURE(status)) {
1498 return;
1499 }
1500 } else if (name.compare(ICAL_END, -1) == 0) {
1501 // Mandatory properties
1502 if (dtstart.length() == 0 || from.length() == 0 || to.length() == 0) {
1503 return;
1504 }
1505 // if zonename is not available, create one from tzid
1506 if (zonename.length() == 0) {
1507 getDefaultTZName(tzid, dst, zonename);
1508 }
1509
1510 // create a time zone rule
1511 LocalPointer<TimeZoneRule> rule;
1512 int32_t fromOffset = 0;
1513 int32_t toOffset = 0;
1514 int32_t rawOffset = 0;
1515 int32_t dstSavings = 0;
1516 UDate start = 0;
1517
1518 // Parse TZOFFSETFROM/TZOFFSETTO
1519 fromOffset = offsetStrToMillis(from, status);
1520 toOffset = offsetStrToMillis(to, status);
1521 if (U_FAILURE(status)) {
1522 return;
1523 }
1524
1525 if (dst) {
1526 // If daylight, use the previous offset as rawoffset if positive
1527 if (toOffset - fromOffset > 0) {
1528 rawOffset = fromOffset;
1529 dstSavings = toOffset - fromOffset;
1530 } else {
1531 // This is rare case.. just use 1 hour DST savings
1532 rawOffset = toOffset - DEF_DSTSAVINGS;
1533 dstSavings = DEF_DSTSAVINGS;
1534 }
1535 } else {
1536 rawOffset = toOffset;
1537 dstSavings = 0;
1538 }
1539
1540 // start time
1541 start = parseDateTimeString(dtstart, fromOffset, status);
1542 if (U_FAILURE(status)) {
1543 return;
1544 }
1545
1546 // Create the rule
1547 UDate actualStart = MAX_MILLIS;
1548 if (isRRULE) {
1549 rule.adoptInsteadAndCheckErrorCode(
1550 createRuleByRRULE(zonename, rawOffset, dstSavings, start, &dates, fromOffset, status), status);
1551 } else {
1552 rule.adoptInsteadAndCheckErrorCode(
1553 createRuleByRDATE(zonename, rawOffset, dstSavings, start, &dates, fromOffset, status), status);
1554 }
1555 if (U_FAILURE(status)) {
1556 return;
1557 } else {
1558 UBool startAvail = rule->getFirstStart(fromOffset, 0, actualStart);
1559 if (startAvail && actualStart < firstStart) {
1560 // save from offset information for the earliest rule
1561 firstStart = actualStart;
1562 // If this is STD, assume the time before this transition
1563 // is DST when the difference is 1 hour. This might not be
1564 // accurate, but VTIMEZONE data does not have such info.
1565 if (dstSavings > 0) {
1566 initialRawOffset = fromOffset;
1567 initialDSTSavings = 0;
1568 } else {
1569 if (fromOffset - toOffset == DEF_DSTSAVINGS) {
1570 initialRawOffset = fromOffset - DEF_DSTSAVINGS;
1571 initialDSTSavings = DEF_DSTSAVINGS;
1572 } else {
1573 initialRawOffset = fromOffset;
1574 initialDSTSavings = 0;
1575 }
1576 }
1577 }
1578 }
1579 rules.adoptElement(rule.orphan(), status);
1580 if (U_FAILURE(status)) {
1581 return;
1582 }
1583 state = VTZ;
1584 }
1585 break;
1586 }
1587 }
1588 // Must have at least one rule
1589 if (rules.size() == 0) {
1590 return;
1591 }
1592
1593 // Create a initial rule
1594 getDefaultTZName(tzid, false, zonename);
1595 LocalPointer<InitialTimeZoneRule> initialRule(
1596 new InitialTimeZoneRule(zonename, initialRawOffset, initialDSTSavings), status);
1597 if (U_FAILURE(status)) {
1598 return;
1599 }
1600
1601 // Finally, create the RuleBasedTimeZone
1602 // C++ awkwardness on memory allocation failure: the constructor wont be run, meaning
1603 // that initialRule wont be adopted/deleted, as it normally would be.
1604 LocalPointer<RuleBasedTimeZone> rbtz(
1605 new RuleBasedTimeZone(tzid, initialRule.getAlias()), status);
1606 if (U_SUCCESS(status)) {
1607 initialRule.orphan();
1608 } else {
1609 return;
1610 }
1611
1612 for (n = 0; n < rules.size(); n++) {
1613 TimeZoneRule *r = (TimeZoneRule*)rules.elementAt(n);
1614 AnnualTimeZoneRule *atzrule = dynamic_cast<AnnualTimeZoneRule *>(r);
1615 if (atzrule != nullptr) {
1616 if (atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR) {
1617 finalRuleCount++;
1618 finalRuleIdx = n;
1619 }
1620 }
1621 }
1622 if (finalRuleCount > 2) {
1623 // Too many final rules
1624 status = U_ILLEGAL_ARGUMENT_ERROR;
1625 return;
1626 }
1627
1628 if (finalRuleCount == 1) {
1629 if (rules.size() == 1) {
1630 // Only one final rule, only governs the initial rule,
1631 // which is already initialized, thus, we do not need to
1632 // add this transition rule
1633 rules.removeAllElements();
1634 } else {
1635 // Normalize the final rule
1636 AnnualTimeZoneRule *finalRule = (AnnualTimeZoneRule*)rules.elementAt(finalRuleIdx);
1637 int32_t tmpRaw = finalRule->getRawOffset();
1638 int32_t tmpDST = finalRule->getDSTSavings();
1639
1640 // Find the last non-final rule
1641 UDate finalStart, start;
1642 finalRule->getFirstStart(initialRawOffset, initialDSTSavings, finalStart);
1643 start = finalStart;
1644 for (n = 0; n < rules.size(); n++) {
1645 if (finalRuleIdx == n) {
1646 continue;
1647 }
1648 TimeZoneRule *r = (TimeZoneRule*)rules.elementAt(n);
1649 UDate lastStart;
1650 r->getFinalStart(tmpRaw, tmpDST, lastStart);
1651 if (lastStart > start) {
1652 finalRule->getNextStart(lastStart,
1653 r->getRawOffset(),
1654 r->getDSTSavings(),
1655 false,
1656 start);
1657 }
1658 }
1659
1660 LocalPointer<TimeZoneRule> newRule;
1661 UnicodeString tznam;
1662 if (start == finalStart) {
1663 // Transform this into a single transition
1664 newRule.adoptInsteadAndCheckErrorCode(
1665 new TimeArrayTimeZoneRule(
1666 finalRule->getName(tznam),
1667 finalRule->getRawOffset(),
1668 finalRule->getDSTSavings(),
1669 &finalStart,
1670 1,
1671 DateTimeRule::UTC_TIME),
1672 status);
1673 } else {
1674 // Update the end year
1675 int32_t y, m, d, dow, doy, mid;
1676 Grego::timeToFields(start, y, m, d, dow, doy, mid);
1677 newRule.adoptInsteadAndCheckErrorCode(
1678 new AnnualTimeZoneRule(
1679 finalRule->getName(tznam),
1680 finalRule->getRawOffset(),
1681 finalRule->getDSTSavings(),
1682 *(finalRule->getRule()),
1683 finalRule->getStartYear(),
1684 y),
1685 status);
1686 }
1687 if (U_FAILURE(status)) {
1688 return;
1689 }
1690 rules.removeElementAt(finalRuleIdx);
1691 rules.adoptElement(newRule.orphan(), status);
1692 if (U_FAILURE(status)) {
1693 return;
1694 }
1695 }
1696 }
1697
1698 while (!rules.isEmpty()) {
1699 TimeZoneRule *tzr = (TimeZoneRule*)rules.orphanElementAt(0);
1700 rbtz->addTransitionRule(tzr, status);
1701 if (U_FAILURE(status)) {
1702 return;
1703 }
1704 }
1705 rbtz->complete(status);
1706 if (U_FAILURE(status)) {
1707 return;
1708 }
1709
1710 tz = rbtz.orphan();
1711 setID(tzid);
1712 }
1713
1714 void
write(VTZWriter & writer,UErrorCode & status) const1715 VTimeZone::write(VTZWriter& writer, UErrorCode& status) const {
1716 if (vtzlines != nullptr) {
1717 for (int32_t i = 0; i < vtzlines->size(); i++) {
1718 UnicodeString *line = (UnicodeString*)vtzlines->elementAt(i);
1719 if (line->startsWith(ICAL_TZURL, -1)
1720 && line->charAt(u_strlen(ICAL_TZURL)) == COLON) {
1721 writer.write(ICAL_TZURL);
1722 writer.write(COLON);
1723 writer.write(tzurl);
1724 writer.write(ICAL_NEWLINE);
1725 } else if (line->startsWith(ICAL_LASTMOD, -1)
1726 && line->charAt(u_strlen(ICAL_LASTMOD)) == COLON) {
1727 UnicodeString utcString;
1728 writer.write(ICAL_LASTMOD);
1729 writer.write(COLON);
1730 writer.write(getUTCDateTimeString(lastmod, utcString));
1731 writer.write(ICAL_NEWLINE);
1732 } else {
1733 writer.write(*line);
1734 writer.write(ICAL_NEWLINE);
1735 }
1736 }
1737 } else {
1738 UnicodeString icutzprop;
1739 UVector customProps(nullptr, uhash_compareUnicodeString, status);
1740 if (olsonzid.length() > 0 && icutzver.length() > 0) {
1741 icutzprop.append(olsonzid);
1742 icutzprop.append(u'[');
1743 icutzprop.append(icutzver);
1744 icutzprop.append(u']');
1745 customProps.addElement(&icutzprop, status);
1746 }
1747 writeZone(writer, *tz, &customProps, status);
1748 }
1749 }
1750
1751 void
write(UDate start,VTZWriter & writer,UErrorCode & status) const1752 VTimeZone::write(UDate start, VTZWriter& writer, UErrorCode& status) const {
1753 if (U_FAILURE(status)) {
1754 return;
1755 }
1756 InitialTimeZoneRule *initial = nullptr;
1757 UVector *transitionRules = nullptr;
1758 UVector customProps(uprv_deleteUObject, uhash_compareUnicodeString, status);
1759 UnicodeString tzid;
1760
1761 // Extract rules applicable to dates after the start time
1762 getTimeZoneRulesAfter(start, initial, transitionRules, status);
1763 LocalPointer<InitialTimeZoneRule> lpInitial(initial);
1764 LocalPointer<UVector> lpTransitionRules(transitionRules);
1765 if (U_FAILURE(status)) {
1766 return;
1767 }
1768
1769 // Create a RuleBasedTimeZone with the subset rule
1770 getID(tzid);
1771 RuleBasedTimeZone rbtz(tzid, lpInitial.orphan());
1772 if (lpTransitionRules.isValid()) {
1773 U_ASSERT(transitionRules->hasDeleter()); // Assumed for U_FAILURE early return, below.
1774 while (!lpTransitionRules->isEmpty()) {
1775 TimeZoneRule *tr = (TimeZoneRule*)lpTransitionRules->orphanElementAt(0);
1776 rbtz.addTransitionRule(tr, status);
1777 if (U_FAILURE(status)) {
1778 return;
1779 }
1780 }
1781 }
1782 rbtz.complete(status);
1783 if (U_FAILURE(status)) {
1784 return;
1785 }
1786
1787 if (olsonzid.length() > 0 && icutzver.length() > 0) {
1788 UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP);
1789 if (icutzprop == nullptr) {
1790 status = U_MEMORY_ALLOCATION_ERROR;
1791 return;
1792 }
1793 icutzprop->append(olsonzid);
1794 icutzprop->append((char16_t)0x005B/*'['*/);
1795 icutzprop->append(icutzver);
1796 icutzprop->append(ICU_TZINFO_PARTIAL, -1);
1797 appendMillis(start, *icutzprop);
1798 icutzprop->append((char16_t)0x005D/*']'*/);
1799 customProps.adoptElement(icutzprop, status);
1800 if (U_FAILURE(status)) {
1801 return;
1802 }
1803 }
1804 writeZone(writer, rbtz, &customProps, status);
1805 }
1806
1807 void
writeSimple(UDate time,VTZWriter & writer,UErrorCode & status) const1808 VTimeZone::writeSimple(UDate time, VTZWriter& writer, UErrorCode& status) const {
1809 if (U_FAILURE(status)) {
1810 return;
1811 }
1812
1813 UVector customProps(uprv_deleteUObject, uhash_compareUnicodeString, status);
1814 UnicodeString tzid;
1815
1816 // Extract simple rules
1817 InitialTimeZoneRule *initial = nullptr;
1818 AnnualTimeZoneRule *std = nullptr, *dst = nullptr;
1819 getSimpleRulesNear(time, initial, std, dst, status);
1820 LocalPointer<InitialTimeZoneRule> lpInitial(initial);
1821 LocalPointer<AnnualTimeZoneRule> lpStd(std);
1822 LocalPointer<AnnualTimeZoneRule> lpDst(dst);
1823 if (U_SUCCESS(status)) {
1824 // Create a RuleBasedTimeZone with the subset rule
1825 getID(tzid);
1826 RuleBasedTimeZone rbtz(tzid, lpInitial.orphan());
1827 if (lpStd.isValid() && lpDst.isValid()) {
1828 rbtz.addTransitionRule(lpStd.orphan(), status);
1829 rbtz.addTransitionRule(lpDst.orphan(), status);
1830 }
1831 if (U_FAILURE(status)) {
1832 return;
1833 }
1834
1835 if (olsonzid.length() > 0 && icutzver.length() > 0) {
1836 LocalPointer<UnicodeString> icutzprop(new UnicodeString(ICU_TZINFO_PROP), status);
1837 if (U_FAILURE(status)) {
1838 return;
1839 }
1840 icutzprop->append(olsonzid);
1841 icutzprop->append((char16_t)0x005B/*'['*/);
1842 icutzprop->append(icutzver);
1843 icutzprop->append(ICU_TZINFO_SIMPLE, -1);
1844 appendMillis(time, *icutzprop);
1845 icutzprop->append((char16_t)0x005D/*']'*/);
1846 customProps.adoptElement(icutzprop.orphan(), status);
1847 }
1848 writeZone(writer, rbtz, &customProps, status);
1849 }
1850 }
1851
1852 void
writeZone(VTZWriter & w,BasicTimeZone & basictz,UVector * customProps,UErrorCode & status) const1853 VTimeZone::writeZone(VTZWriter& w, BasicTimeZone& basictz,
1854 UVector* customProps, UErrorCode& status) const {
1855 if (U_FAILURE(status)) {
1856 return;
1857 }
1858 writeHeaders(w, status);
1859 if (U_FAILURE(status)) {
1860 return;
1861 }
1862
1863 if (customProps != nullptr) {
1864 for (int32_t i = 0; i < customProps->size(); i++) {
1865 UnicodeString *custprop = (UnicodeString*)customProps->elementAt(i);
1866 w.write(*custprop);
1867 w.write(ICAL_NEWLINE);
1868 }
1869 }
1870
1871 UDate t = MIN_MILLIS;
1872 UnicodeString dstName;
1873 int32_t dstFromOffset = 0;
1874 int32_t dstFromDSTSavings = 0;
1875 int32_t dstToOffset = 0;
1876 int32_t dstStartYear = 0;
1877 int32_t dstMonth = 0;
1878 int32_t dstDayOfWeek = 0;
1879 int32_t dstWeekInMonth = 0;
1880 int32_t dstMillisInDay = 0;
1881 UDate dstStartTime = 0.0;
1882 UDate dstUntilTime = 0.0;
1883 int32_t dstCount = 0;
1884 AnnualTimeZoneRule *finalDstRule = nullptr;
1885
1886 UnicodeString stdName;
1887 int32_t stdFromOffset = 0;
1888 int32_t stdFromDSTSavings = 0;
1889 int32_t stdToOffset = 0;
1890 int32_t stdStartYear = 0;
1891 int32_t stdMonth = 0;
1892 int32_t stdDayOfWeek = 0;
1893 int32_t stdWeekInMonth = 0;
1894 int32_t stdMillisInDay = 0;
1895 UDate stdStartTime = 0.0;
1896 UDate stdUntilTime = 0.0;
1897 int32_t stdCount = 0;
1898 AnnualTimeZoneRule *finalStdRule = nullptr;
1899
1900 int32_t year, month, dom, dow, doy, mid;
1901 UBool hasTransitions = false;
1902 TimeZoneTransition tzt;
1903 UBool tztAvail;
1904 UnicodeString name;
1905 UBool isDst;
1906
1907 // Going through all transitions
1908 while (true) {
1909 tztAvail = basictz.getNextTransition(t, false, tzt);
1910 if (!tztAvail) {
1911 break;
1912 }
1913 hasTransitions = true;
1914 t = tzt.getTime();
1915 tzt.getTo()->getName(name);
1916 isDst = (tzt.getTo()->getDSTSavings() != 0);
1917 int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings();
1918 int32_t fromDSTSavings = tzt.getFrom()->getDSTSavings();
1919 int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings();
1920 Grego::timeToFields(tzt.getTime() + fromOffset, year, month, dom, dow, doy, mid);
1921 int32_t weekInMonth = Grego::dayOfWeekInMonth(year, month, dom);
1922 UBool sameRule = false;
1923 const AnnualTimeZoneRule *atzrule;
1924 if (isDst) {
1925 if (finalDstRule == nullptr
1926 && (atzrule = dynamic_cast<const AnnualTimeZoneRule *>(tzt.getTo())) != nullptr
1927 && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
1928 ) {
1929 finalDstRule = atzrule->clone();
1930 }
1931 if (dstCount > 0) {
1932 if (year == dstStartYear + dstCount
1933 && name.compare(dstName) == 0
1934 && dstFromOffset == fromOffset
1935 && dstToOffset == toOffset
1936 && dstMonth == month
1937 && dstDayOfWeek == dow
1938 && dstWeekInMonth == weekInMonth
1939 && dstMillisInDay == mid) {
1940 // Update until time
1941 dstUntilTime = t;
1942 dstCount++;
1943 sameRule = true;
1944 }
1945 if (!sameRule) {
1946 if (dstCount == 1) {
1947 writeZonePropsByTime(w, true, dstName, dstFromOffset, dstToOffset, dstStartTime,
1948 true, status);
1949 } else {
1950 writeZonePropsByDOW(w, true, dstName, dstFromOffset, dstToOffset,
1951 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
1952 }
1953 if (U_FAILURE(status)) {
1954 goto cleanupWriteZone;
1955 }
1956 }
1957 }
1958 if (!sameRule) {
1959 // Reset this DST information
1960 dstName = name;
1961 dstFromOffset = fromOffset;
1962 dstFromDSTSavings = fromDSTSavings;
1963 dstToOffset = toOffset;
1964 dstStartYear = year;
1965 dstMonth = month;
1966 dstDayOfWeek = dow;
1967 dstWeekInMonth = weekInMonth;
1968 dstMillisInDay = mid;
1969 dstStartTime = dstUntilTime = t;
1970 dstCount = 1;
1971 }
1972 if (finalStdRule != nullptr && finalDstRule != nullptr) {
1973 break;
1974 }
1975 } else {
1976 if (finalStdRule == nullptr
1977 && (atzrule = dynamic_cast<const AnnualTimeZoneRule *>(tzt.getTo())) != nullptr
1978 && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
1979 ) {
1980 finalStdRule = atzrule->clone();
1981 }
1982 if (stdCount > 0) {
1983 if (year == stdStartYear + stdCount
1984 && name.compare(stdName) == 0
1985 && stdFromOffset == fromOffset
1986 && stdToOffset == toOffset
1987 && stdMonth == month
1988 && stdDayOfWeek == dow
1989 && stdWeekInMonth == weekInMonth
1990 && stdMillisInDay == mid) {
1991 // Update until time
1992 stdUntilTime = t;
1993 stdCount++;
1994 sameRule = true;
1995 }
1996 if (!sameRule) {
1997 if (stdCount == 1) {
1998 writeZonePropsByTime(w, false, stdName, stdFromOffset, stdToOffset, stdStartTime,
1999 true, status);
2000 } else {
2001 writeZonePropsByDOW(w, false, stdName, stdFromOffset, stdToOffset,
2002 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2003 }
2004 if (U_FAILURE(status)) {
2005 goto cleanupWriteZone;
2006 }
2007 }
2008 }
2009 if (!sameRule) {
2010 // Reset this STD information
2011 stdName = name;
2012 stdFromOffset = fromOffset;
2013 stdFromDSTSavings = fromDSTSavings;
2014 stdToOffset = toOffset;
2015 stdStartYear = year;
2016 stdMonth = month;
2017 stdDayOfWeek = dow;
2018 stdWeekInMonth = weekInMonth;
2019 stdMillisInDay = mid;
2020 stdStartTime = stdUntilTime = t;
2021 stdCount = 1;
2022 }
2023 if (finalStdRule != nullptr && finalDstRule != nullptr) {
2024 break;
2025 }
2026 }
2027 }
2028 if (!hasTransitions) {
2029 // No transition - put a single non transition RDATE
2030 int32_t raw, dst, offset;
2031 basictz.getOffset(0.0/*any time*/, false, raw, dst, status);
2032 if (U_FAILURE(status)) {
2033 goto cleanupWriteZone;
2034 }
2035 offset = raw + dst;
2036 isDst = (dst != 0);
2037 UnicodeString tzid;
2038 basictz.getID(tzid);
2039 getDefaultTZName(tzid, isDst, name);
2040 writeZonePropsByTime(w, isDst, name,
2041 offset, offset, DEF_TZSTARTTIME - offset, false, status);
2042 if (U_FAILURE(status)) {
2043 goto cleanupWriteZone;
2044 }
2045 } else {
2046 if (dstCount > 0) {
2047 if (finalDstRule == nullptr) {
2048 if (dstCount == 1) {
2049 writeZonePropsByTime(w, true, dstName, dstFromOffset, dstToOffset, dstStartTime,
2050 true, status);
2051 } else {
2052 writeZonePropsByDOW(w, true, dstName, dstFromOffset, dstToOffset,
2053 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
2054 }
2055 if (U_FAILURE(status)) {
2056 goto cleanupWriteZone;
2057 }
2058 } else {
2059 if (dstCount == 1) {
2060 writeFinalRule(w, true, finalDstRule,
2061 dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, dstStartTime, status);
2062 } else {
2063 // Use a single rule if possible
2064 if (isEquivalentDateRule(dstMonth, dstWeekInMonth, dstDayOfWeek, finalDstRule->getRule())) {
2065 writeZonePropsByDOW(w, true, dstName, dstFromOffset, dstToOffset,
2066 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, MAX_MILLIS, status);
2067 } else {
2068 // Not equivalent rule - write out two different rules
2069 writeZonePropsByDOW(w, true, dstName, dstFromOffset, dstToOffset,
2070 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
2071 if (U_FAILURE(status)) {
2072 goto cleanupWriteZone;
2073 }
2074 UDate nextStart;
2075 UBool nextStartAvail = finalDstRule->getNextStart(dstUntilTime, dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, false, nextStart);
2076 U_ASSERT(nextStartAvail);
2077 if (nextStartAvail) {
2078 writeFinalRule(w, true, finalDstRule,
2079 dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, nextStart, status);
2080 }
2081 }
2082 }
2083 if (U_FAILURE(status)) {
2084 goto cleanupWriteZone;
2085 }
2086 }
2087 }
2088 if (stdCount > 0) {
2089 if (finalStdRule == nullptr) {
2090 if (stdCount == 1) {
2091 writeZonePropsByTime(w, false, stdName, stdFromOffset, stdToOffset, stdStartTime,
2092 true, status);
2093 } else {
2094 writeZonePropsByDOW(w, false, stdName, stdFromOffset, stdToOffset,
2095 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2096 }
2097 if (U_FAILURE(status)) {
2098 goto cleanupWriteZone;
2099 }
2100 } else {
2101 if (stdCount == 1) {
2102 writeFinalRule(w, false, finalStdRule,
2103 stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, stdStartTime, status);
2104 } else {
2105 // Use a single rule if possible
2106 if (isEquivalentDateRule(stdMonth, stdWeekInMonth, stdDayOfWeek, finalStdRule->getRule())) {
2107 writeZonePropsByDOW(w, false, stdName, stdFromOffset, stdToOffset,
2108 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, MAX_MILLIS, status);
2109 } else {
2110 // Not equivalent rule - write out two different rules
2111 writeZonePropsByDOW(w, false, stdName, stdFromOffset, stdToOffset,
2112 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2113 if (U_FAILURE(status)) {
2114 goto cleanupWriteZone;
2115 }
2116 UDate nextStart;
2117 UBool nextStartAvail = finalStdRule->getNextStart(stdUntilTime, stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, false, nextStart);
2118 U_ASSERT(nextStartAvail);
2119 if (nextStartAvail) {
2120 writeFinalRule(w, false, finalStdRule,
2121 stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, nextStart, status);
2122 }
2123 }
2124 }
2125 if (U_FAILURE(status)) {
2126 goto cleanupWriteZone;
2127 }
2128 }
2129 }
2130 }
2131 writeFooter(w, status);
2132
2133 cleanupWriteZone:
2134
2135 if (finalStdRule != nullptr) {
2136 delete finalStdRule;
2137 }
2138 if (finalDstRule != nullptr) {
2139 delete finalDstRule;
2140 }
2141 }
2142
2143 void
writeHeaders(VTZWriter & writer,UErrorCode & status) const2144 VTimeZone::writeHeaders(VTZWriter& writer, UErrorCode& status) const {
2145 if (U_FAILURE(status)) {
2146 return;
2147 }
2148 UnicodeString tzid;
2149 tz->getID(tzid);
2150
2151 writer.write(ICAL_BEGIN);
2152 writer.write(COLON);
2153 writer.write(ICAL_VTIMEZONE);
2154 writer.write(ICAL_NEWLINE);
2155 writer.write(ICAL_TZID);
2156 writer.write(COLON);
2157 writer.write(tzid);
2158 writer.write(ICAL_NEWLINE);
2159 if (tzurl.length() != 0) {
2160 writer.write(ICAL_TZURL);
2161 writer.write(COLON);
2162 writer.write(tzurl);
2163 writer.write(ICAL_NEWLINE);
2164 }
2165 if (lastmod != MAX_MILLIS) {
2166 UnicodeString lastmodStr;
2167 writer.write(ICAL_LASTMOD);
2168 writer.write(COLON);
2169 writer.write(getUTCDateTimeString(lastmod, lastmodStr));
2170 writer.write(ICAL_NEWLINE);
2171 }
2172 }
2173
2174 /*
2175 * Write the closing section of the VTIMEZONE definition block
2176 */
2177 void
writeFooter(VTZWriter & writer,UErrorCode & status) const2178 VTimeZone::writeFooter(VTZWriter& writer, UErrorCode& status) const {
2179 if (U_FAILURE(status)) {
2180 return;
2181 }
2182 writer.write(ICAL_END);
2183 writer.write(COLON);
2184 writer.write(ICAL_VTIMEZONE);
2185 writer.write(ICAL_NEWLINE);
2186 }
2187
2188 /*
2189 * Write a single start time
2190 */
2191 void
writeZonePropsByTime(VTZWriter & writer,UBool isDst,const UnicodeString & zonename,int32_t fromOffset,int32_t toOffset,UDate time,UBool withRDATE,UErrorCode & status) const2192 VTimeZone::writeZonePropsByTime(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2193 int32_t fromOffset, int32_t toOffset, UDate time, UBool withRDATE,
2194 UErrorCode& status) const {
2195 if (U_FAILURE(status)) {
2196 return;
2197 }
2198 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, time, status);
2199 if (U_FAILURE(status)) {
2200 return;
2201 }
2202 if (withRDATE) {
2203 writer.write(ICAL_RDATE);
2204 writer.write(COLON);
2205 UnicodeString timestr;
2206 writer.write(getDateTimeString(time + fromOffset, timestr));
2207 writer.write(ICAL_NEWLINE);
2208 }
2209 endZoneProps(writer, isDst, status);
2210 if (U_FAILURE(status)) {
2211 return;
2212 }
2213 }
2214
2215 /*
2216 * Write start times defined by a DOM rule using VTIMEZONE RRULE
2217 */
2218 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) const2219 VTimeZone::writeZonePropsByDOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2220 int32_t fromOffset, int32_t toOffset,
2221 int32_t month, int32_t dayOfMonth, UDate startTime, UDate untilTime,
2222 UErrorCode& status) const {
2223 if (U_FAILURE(status)) {
2224 return;
2225 }
2226 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
2227 if (U_FAILURE(status)) {
2228 return;
2229 }
2230 beginRRULE(writer, month, status);
2231 if (U_FAILURE(status)) {
2232 return;
2233 }
2234 writer.write(ICAL_BYMONTHDAY);
2235 writer.write(EQUALS_SIGN);
2236 UnicodeString dstr;
2237 appendAsciiDigits(dayOfMonth, 0, dstr);
2238 writer.write(dstr);
2239 if (untilTime != MAX_MILLIS) {
2240 appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2241 if (U_FAILURE(status)) {
2242 return;
2243 }
2244 }
2245 writer.write(ICAL_NEWLINE);
2246 endZoneProps(writer, isDst, status);
2247 }
2248
2249 /*
2250 * Write start times defined by a DOW rule using VTIMEZONE RRULE
2251 */
2252 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) const2253 VTimeZone::writeZonePropsByDOW(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2254 int32_t fromOffset, int32_t toOffset,
2255 int32_t month, int32_t weekInMonth, int32_t dayOfWeek,
2256 UDate startTime, UDate untilTime, UErrorCode& status) const {
2257 if (U_FAILURE(status)) {
2258 return;
2259 }
2260 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
2261 if (U_FAILURE(status)) {
2262 return;
2263 }
2264 beginRRULE(writer, month, status);
2265 if (U_FAILURE(status)) {
2266 return;
2267 }
2268 writer.write(ICAL_BYDAY);
2269 writer.write(EQUALS_SIGN);
2270 UnicodeString dstr;
2271 appendAsciiDigits(weekInMonth, 0, dstr);
2272 writer.write(dstr); // -4, -3, -2, -1, 1, 2, 3, 4
2273 writer.write(ICAL_DOW_NAMES[dayOfWeek - 1]); // SU, MO, TU...
2274
2275 if (untilTime != MAX_MILLIS) {
2276 appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2277 if (U_FAILURE(status)) {
2278 return;
2279 }
2280 }
2281 writer.write(ICAL_NEWLINE);
2282 endZoneProps(writer, isDst, status);
2283 }
2284
2285 /*
2286 * Write start times defined by a DOW_GEQ_DOM rule using VTIMEZONE RRULE
2287 */
2288 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) const2289 VTimeZone::writeZonePropsByDOW_GEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2290 int32_t fromOffset, int32_t toOffset,
2291 int32_t month, int32_t dayOfMonth, int32_t dayOfWeek,
2292 UDate startTime, UDate untilTime, UErrorCode& status) const {
2293 if (U_FAILURE(status)) {
2294 return;
2295 }
2296 // Check if this rule can be converted to DOW rule
2297 if (dayOfMonth%7 == 1) {
2298 // Can be represented by DOW rule
2299 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2300 month, (dayOfMonth + 6)/7, dayOfWeek, startTime, untilTime, status);
2301 if (U_FAILURE(status)) {
2302 return;
2303 }
2304 } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 6) {
2305 // Can be represented by DOW rule with negative week number
2306 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2307 month, -1*((MONTHLENGTH[month] - dayOfMonth + 1)/7), dayOfWeek, startTime, untilTime, status);
2308 if (U_FAILURE(status)) {
2309 return;
2310 }
2311 } else {
2312 // Otherwise, use BYMONTHDAY to include all possible dates
2313 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
2314 if (U_FAILURE(status)) {
2315 return;
2316 }
2317 // Check if all days are in the same month
2318 int32_t startDay = dayOfMonth;
2319 int32_t currentMonthDays = 7;
2320
2321 if (dayOfMonth <= 0) {
2322 // The start day is in previous month
2323 int32_t prevMonthDays = 1 - dayOfMonth;
2324 currentMonthDays -= prevMonthDays;
2325
2326 int32_t prevMonth = (month - 1) < 0 ? 11 : month - 1;
2327
2328 // Note: When a rule is separated into two, UNTIL attribute needs to be
2329 // calculated for each of them. For now, we skip this, because we basically use this method
2330 // only for final rules, which does not have the UNTIL attribute
2331 writeZonePropsByDOW_GEQ_DOM_sub(writer, prevMonth, -prevMonthDays, dayOfWeek, prevMonthDays,
2332 MAX_MILLIS /* Do not use UNTIL */, fromOffset, status);
2333 if (U_FAILURE(status)) {
2334 return;
2335 }
2336
2337 // Start from 1 for the rest
2338 startDay = 1;
2339 } else if (dayOfMonth + 6 > MONTHLENGTH[month]) {
2340 // Note: This code does not actually work well in February. For now, days in month in
2341 // non-leap year.
2342 int32_t nextMonthDays = dayOfMonth + 6 - MONTHLENGTH[month];
2343 currentMonthDays -= nextMonthDays;
2344
2345 int32_t nextMonth = (month + 1) > 11 ? 0 : month + 1;
2346
2347 writeZonePropsByDOW_GEQ_DOM_sub(writer, nextMonth, 1, dayOfWeek, nextMonthDays,
2348 MAX_MILLIS /* Do not use UNTIL */, fromOffset, status);
2349 if (U_FAILURE(status)) {
2350 return;
2351 }
2352 }
2353 writeZonePropsByDOW_GEQ_DOM_sub(writer, month, startDay, dayOfWeek, currentMonthDays,
2354 untilTime, fromOffset, status);
2355 if (U_FAILURE(status)) {
2356 return;
2357 }
2358 endZoneProps(writer, isDst, status);
2359 }
2360 }
2361
2362 /*
2363 * Called from writeZonePropsByDOW_GEQ_DOM
2364 */
2365 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) const2366 VTimeZone::writeZonePropsByDOW_GEQ_DOM_sub(VTZWriter& writer, int32_t month, int32_t dayOfMonth,
2367 int32_t dayOfWeek, int32_t numDays,
2368 UDate untilTime, int32_t fromOffset, UErrorCode& status) const {
2369
2370 if (U_FAILURE(status)) {
2371 return;
2372 }
2373 int32_t startDayNum = dayOfMonth;
2374 UBool isFeb = (month == UCAL_FEBRUARY);
2375 if (dayOfMonth < 0 && !isFeb) {
2376 // Use positive number if possible
2377 startDayNum = MONTHLENGTH[month] + dayOfMonth + 1;
2378 }
2379 beginRRULE(writer, month, status);
2380 if (U_FAILURE(status)) {
2381 return;
2382 }
2383 writer.write(ICAL_BYDAY);
2384 writer.write(EQUALS_SIGN);
2385 writer.write(ICAL_DOW_NAMES[dayOfWeek - 1]); // SU, MO, TU...
2386 writer.write(SEMICOLON);
2387 writer.write(ICAL_BYMONTHDAY);
2388 writer.write(EQUALS_SIGN);
2389
2390 UnicodeString dstr;
2391 appendAsciiDigits(startDayNum, 0, dstr);
2392 writer.write(dstr);
2393 for (int32_t i = 1; i < numDays; i++) {
2394 writer.write(COMMA);
2395 dstr.remove();
2396 appendAsciiDigits(startDayNum + i, 0, dstr);
2397 writer.write(dstr);
2398 }
2399
2400 if (untilTime != MAX_MILLIS) {
2401 appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2402 if (U_FAILURE(status)) {
2403 return;
2404 }
2405 }
2406 writer.write(ICAL_NEWLINE);
2407 }
2408
2409 /*
2410 * Write start times defined by a DOW_LEQ_DOM rule using VTIMEZONE RRULE
2411 */
2412 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) const2413 VTimeZone::writeZonePropsByDOW_LEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2414 int32_t fromOffset, int32_t toOffset,
2415 int32_t month, int32_t dayOfMonth, int32_t dayOfWeek,
2416 UDate startTime, UDate untilTime, UErrorCode& status) const {
2417 if (U_FAILURE(status)) {
2418 return;
2419 }
2420 // Check if this rule can be converted to DOW rule
2421 if (dayOfMonth%7 == 0) {
2422 // Can be represented by DOW rule
2423 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2424 month, dayOfMonth/7, dayOfWeek, startTime, untilTime, status);
2425 } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 0){
2426 // Can be represented by DOW rule with negative week number
2427 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2428 month, -1*((MONTHLENGTH[month] - dayOfMonth)/7 + 1), dayOfWeek, startTime, untilTime, status);
2429 } else if (month == UCAL_FEBRUARY && dayOfMonth == 29) {
2430 // Special case for February
2431 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2432 UCAL_FEBRUARY, -1, dayOfWeek, startTime, untilTime, status);
2433 } else {
2434 // Otherwise, convert this to DOW_GEQ_DOM rule
2435 writeZonePropsByDOW_GEQ_DOM(writer, isDst, zonename, fromOffset, toOffset,
2436 month, dayOfMonth - 6, dayOfWeek, startTime, untilTime, status);
2437 }
2438 }
2439
2440 /*
2441 * Write the final time zone rule using RRULE, with no UNTIL attribute
2442 */
2443 void
writeFinalRule(VTZWriter & writer,UBool isDst,const AnnualTimeZoneRule * rule,int32_t fromRawOffset,int32_t fromDSTSavings,UDate startTime,UErrorCode & status) const2444 VTimeZone::writeFinalRule(VTZWriter& writer, UBool isDst, const AnnualTimeZoneRule* rule,
2445 int32_t fromRawOffset, int32_t fromDSTSavings,
2446 UDate startTime, UErrorCode& status) const {
2447 if (U_FAILURE(status)) {
2448 return;
2449 }
2450 UBool modifiedRule = true;
2451 const DateTimeRule *dtrule = toWallTimeRule(rule->getRule(), fromRawOffset, fromDSTSavings, status);
2452 if (U_FAILURE(status)) {
2453 return;
2454 }
2455 if (dtrule == nullptr) {
2456 modifiedRule = false;
2457 dtrule = rule->getRule();
2458 }
2459
2460 // If the rule's mills in a day is out of range, adjust start time.
2461 // Olson tzdata supports 24:00 of a day, but VTIMEZONE does not.
2462 // See ticket#7008/#7518
2463
2464 int32_t timeInDay = dtrule->getRuleMillisInDay();
2465 if (timeInDay < 0) {
2466 startTime = startTime + (0 - timeInDay);
2467 } else if (timeInDay >= U_MILLIS_PER_DAY) {
2468 startTime = startTime - (timeInDay - (U_MILLIS_PER_DAY - 1));
2469 }
2470
2471 int32_t toOffset = rule->getRawOffset() + rule->getDSTSavings();
2472 UnicodeString name;
2473 rule->getName(name);
2474 switch (dtrule->getDateRuleType()) {
2475 case DateTimeRule::DOM:
2476 writeZonePropsByDOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2477 dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), startTime, MAX_MILLIS, status);
2478 break;
2479 case DateTimeRule::DOW:
2480 writeZonePropsByDOW(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2481 dtrule->getRuleMonth(), dtrule->getRuleWeekInMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
2482 break;
2483 case DateTimeRule::DOW_GEQ_DOM:
2484 writeZonePropsByDOW_GEQ_DOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2485 dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
2486 break;
2487 case DateTimeRule::DOW_LEQ_DOM:
2488 writeZonePropsByDOW_LEQ_DOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2489 dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
2490 break;
2491 }
2492 if (modifiedRule) {
2493 delete dtrule;
2494 }
2495 }
2496
2497 /*
2498 * Write the opening section of zone properties
2499 */
2500 void
beginZoneProps(VTZWriter & writer,UBool isDst,const UnicodeString & zonename,int32_t fromOffset,int32_t toOffset,UDate startTime,UErrorCode & status) const2501 VTimeZone::beginZoneProps(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2502 int32_t fromOffset, int32_t toOffset, UDate startTime, UErrorCode& status) const {
2503 if (U_FAILURE(status)) {
2504 return;
2505 }
2506 writer.write(ICAL_BEGIN);
2507 writer.write(COLON);
2508 if (isDst) {
2509 writer.write(ICAL_DAYLIGHT);
2510 } else {
2511 writer.write(ICAL_STANDARD);
2512 }
2513 writer.write(ICAL_NEWLINE);
2514
2515 UnicodeString dstr;
2516
2517 // TZOFFSETTO
2518 writer.write(ICAL_TZOFFSETTO);
2519 writer.write(COLON);
2520 millisToOffset(toOffset, dstr);
2521 writer.write(dstr);
2522 writer.write(ICAL_NEWLINE);
2523
2524 // TZOFFSETFROM
2525 writer.write(ICAL_TZOFFSETFROM);
2526 writer.write(COLON);
2527 millisToOffset(fromOffset, dstr);
2528 writer.write(dstr);
2529 writer.write(ICAL_NEWLINE);
2530
2531 // TZNAME
2532 writer.write(ICAL_TZNAME);
2533 writer.write(COLON);
2534 writer.write(zonename);
2535 writer.write(ICAL_NEWLINE);
2536
2537 // DTSTART
2538 writer.write(ICAL_DTSTART);
2539 writer.write(COLON);
2540 writer.write(getDateTimeString(startTime + fromOffset, dstr));
2541 writer.write(ICAL_NEWLINE);
2542 }
2543
2544 /*
2545 * Writes the closing section of zone properties
2546 */
2547 void
endZoneProps(VTZWriter & writer,UBool isDst,UErrorCode & status) const2548 VTimeZone::endZoneProps(VTZWriter& writer, UBool isDst, UErrorCode& status) const {
2549 if (U_FAILURE(status)) {
2550 return;
2551 }
2552 // END:STANDARD or END:DAYLIGHT
2553 writer.write(ICAL_END);
2554 writer.write(COLON);
2555 if (isDst) {
2556 writer.write(ICAL_DAYLIGHT);
2557 } else {
2558 writer.write(ICAL_STANDARD);
2559 }
2560 writer.write(ICAL_NEWLINE);
2561 }
2562
2563 /*
2564 * Write the beginning part of RRULE line
2565 */
2566 void
beginRRULE(VTZWriter & writer,int32_t month,UErrorCode & status) const2567 VTimeZone::beginRRULE(VTZWriter& writer, int32_t month, UErrorCode& status) const {
2568 if (U_FAILURE(status)) {
2569 return;
2570 }
2571 UnicodeString dstr;
2572 writer.write(ICAL_RRULE);
2573 writer.write(COLON);
2574 writer.write(ICAL_FREQ);
2575 writer.write(EQUALS_SIGN);
2576 writer.write(ICAL_YEARLY);
2577 writer.write(SEMICOLON);
2578 writer.write(ICAL_BYMONTH);
2579 writer.write(EQUALS_SIGN);
2580 appendAsciiDigits(month + 1, 0, dstr);
2581 writer.write(dstr);
2582 writer.write(SEMICOLON);
2583 }
2584
2585 /*
2586 * Append the UNTIL attribute after RRULE line
2587 */
2588 void
appendUNTIL(VTZWriter & writer,const UnicodeString & until,UErrorCode & status) const2589 VTimeZone::appendUNTIL(VTZWriter& writer, const UnicodeString& until, UErrorCode& status) const {
2590 if (U_FAILURE(status)) {
2591 return;
2592 }
2593 if (until.length() > 0) {
2594 writer.write(SEMICOLON);
2595 writer.write(ICAL_UNTIL);
2596 writer.write(EQUALS_SIGN);
2597 writer.write(until);
2598 }
2599 }
2600
2601 U_NAMESPACE_END
2602
2603 #endif /* #if !UCONFIG_NO_FORMATTING */
2604
2605 //eof
2606