1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 *******************************************************************************
5 * Copyright (C) 2007-2016, International Business Machines Corporation and
6 * others. All Rights Reserved.
7 *******************************************************************************
10 #include "utypeinfo.h" // for 'typeid' to work
12 #include "unicode/utypes.h"
14 #if !UCONFIG_NO_FORMATTING
16 #include "unicode/vtzone.h"
17 #include "unicode/rbtz.h"
18 #include "unicode/ucal.h"
19 #include "unicode/ures.h"
27 // This is the deleter that will be use to remove TimeZoneRule
29 static void U_CALLCONV
30 deleteTimeZoneRule(void* obj
) {
31 delete (TimeZoneRule
*) obj
;
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; /* - */
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" */
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" */
68 static const UChar ICAL_NEWLINE
[] = {0x0D, 0x0A, 0}; /* CRLF */
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" */};
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};
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@" */
89 * Simple fixed digit ASCII number to integer converter
91 static int32_t parseAsciiDigits(const UnicodeString
& str
, int32_t start
, int32_t length
, UErrorCode
& status
) {
92 if (U_FAILURE(status
)) {
95 if (length
<= 0 || str
.length() < start
|| (start
+ length
) > str
.length()) {
96 status
= U_INVALID_FORMAT_ERROR
;
100 if (str
.charAt(start
) == PLUS
) {
103 } else if (str
.charAt(start
) == MINUS
) {
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
;
115 num
= 10 * num
+ digit
;
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
130 length
= length
> 10 ? 10 : length
;
135 digits
[i
++] = number
% 10;
137 } while (number
!= 0);
138 length
= static_cast<uint8_t>(i
);
141 for (i
= 0; i
< length
; i
++) {
142 digits
[i
] = number
% 10;
149 for (i
= length
- 1; i
>= 0; i
--) {
150 str
.append((UChar
)(digits
[i
] + 0x0030));
155 static UnicodeString
& appendMillis(UDate date
, UnicodeString
& str
) {
156 UBool negative
= FALSE
;
157 int32_t digits
[20]; // max int64_t is 20 decimal digits
161 if (date
< MIN_MILLIS
) {
162 number
= (int64_t)MIN_MILLIS
;
163 } else if (date
> MAX_MILLIS
) {
164 number
= (int64_t)MAX_MILLIS
;
166 number
= (int64_t)date
;
174 digits
[i
++] = (int32_t)(number
% 10);
176 } while (number
!= 0);
183 str
.append((UChar
)(digits
[i
--] + 0x0030));
189 * Convert date/time to RFC2445 Date-Time form #1 DATE WITH LOCAL TIME
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
);
196 appendAsciiDigits(year
, 4, str
);
197 appendAsciiDigits(month
+ 1, 2, str
);
198 appendAsciiDigits(dom
, 2, str
);
199 str
.append((UChar
)0x0054 /*'T'*/);
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
;
208 appendAsciiDigits(hour
, 2, str
);
209 appendAsciiDigits(min
, 2, str
);
210 appendAsciiDigits(sec
, 2, str
);
215 * Convert date/time to RFC2445 Date-Time form #2 DATE WITH UTC TIME
217 static UnicodeString
& getUTCDateTimeString(UDate time
, UnicodeString
& str
) {
218 getDateTimeString(time
, str
);
219 str
.append((UChar
)0x005A /*'Z'*/);
224 * Parse RFC2445 Date-Time form #1 DATE WITH LOCAL TIME and
225 * #2 DATE WITH UTC TIME
227 static UDate
parseDateTimeString(const UnicodeString
& str
, int32_t offset
, UErrorCode
& status
) {
228 if (U_FAILURE(status
)) {
232 int32_t year
= 0, month
= 0, day
= 0, hour
= 0, min
= 0, sec
= 0;
234 UBool isValid
= FALSE
;
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"
242 if (str
.charAt(8) != 0x0054) {
243 // charcter "T" must be used for separating date and time
247 if (str
.charAt(15) != 0x005A) {
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
);
261 if (U_FAILURE(status
)) {
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) {
276 status
= U_INVALID_FORMAT_ERROR
;
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
);
289 * Convert RFC2445 utc-offset string to milliseconds
291 static int32_t offsetStrToMillis(const UnicodeString
& str
, UErrorCode
& status
) {
292 if (U_FAILURE(status
)) {
296 UBool isValid
= FALSE
;
297 int32_t sign
= 0, hour
= 0, min
= 0, sec
= 0;
300 int length
= str
.length();
301 if (length
!= 5 && length
!= 7) {
302 // utf-offset must be 5 or 7 characters
306 UChar s
= str
.charAt(0);
309 } else if (s
== MINUS
) {
312 // utf-offset must start with "+" or "-"
315 hour
= parseAsciiDigits(str
, 1, 2, status
);
316 min
= parseAsciiDigits(str
, 3, 2, status
);
318 sec
= parseAsciiDigits(str
, 5, 2, status
);
320 if (U_FAILURE(status
)) {
327 status
= U_INVALID_FORMAT_ERROR
;
330 int32_t millis
= sign
* ((hour
* 60 + min
) * 60 + sec
) * 1000;
335 * Convert milliseconds to RFC2445 utc-offset string
337 static void millisToOffset(int32_t millis
, UnicodeString
& str
) {
345 int32_t hour
, min
, sec
;
346 int32_t t
= millis
/ 1000;
353 appendAsciiDigits(hour
, 2, str
);
354 appendAsciiDigits(min
, 2, str
);
355 appendAsciiDigits(sec
, 2, str
);
359 * Create a default TZNAME from TZID
361 static void getDefaultTZName(const UnicodeString
&tzid
, UBool isDST
, UnicodeString
& zonename
) {
364 zonename
+= UNICODE_STRING_SIMPLE("(DST)");
366 zonename
+= UNICODE_STRING_SIMPLE("(STD)");
371 * Parse individual RRULE
375 * month calculated by BYMONTH-1, or -1 when not found
376 * dow day of week in BYDAY, or 0 when not found
377 * wim day of week ordinal number in BYDAY, or 0 when not found
378 * dom an array of day of month
379 * domCount number of availble days in dom (domCount is specifying the size of dom on input)
380 * until time defined by UNTIL attribute or MIN_MILLIS if not available
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
)) {
394 UBool yearly
= FALSE
;
395 //UBool parseError = FALSE;
397 int32_t prop_start
= 0;
399 UnicodeString prop
, attr
, value
;
400 UBool nextProp
= TRUE
;
403 prop_end
= rrule
.indexOf(SEMICOLON
, prop_start
);
404 if (prop_end
== -1) {
405 prop
.setTo(rrule
, prop_start
);
408 prop
.setTo(rrule
, prop_start
, prop_end
- prop_start
);
409 prop_start
= prop_end
+ 1;
411 int32_t eql
= prop
.indexOf(EQUALS_SIGN
);
413 attr
.setTo(prop
, 0, eql
);
414 value
.setTo(prop
, eql
+ 1);
416 goto rruleParseError
;
419 if (attr
.compare(ICAL_FREQ
, -1) == 0) {
420 // only support YEARLY frequency type
421 if (value
.compare(ICAL_YEARLY
, -1) == 0) {
424 goto rruleParseError
;
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
;
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
;
438 month
= parseAsciiDigits(value
, 0, value
.length(), status
) - 1;
439 if (U_FAILURE(status
) || month
< 0 || month
>= 12) {
440 goto rruleParseError
;
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.
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
;
455 if (value
.charAt(0) == PLUS
) {
457 } else if (value
.charAt(0) == MINUS
) {
459 } else if (length
== 4) {
460 goto rruleParseError
;
462 int32_t n
= parseAsciiDigits(value
, length
- 3, 1, status
);
463 if (U_FAILURE(status
) || n
== 0 || n
> 4) {
464 goto rruleParseError
;
467 value
.remove(0, length
- 2);
470 for (wday
= 0; wday
< 7; wday
++) {
471 if (value
.compare(ICAL_DOW_NAMES
[wday
], 2) == 0) {
476 // Sunday(1) - Saturday(7)
479 goto rruleParseError
;
481 } else if (attr
.compare(ICAL_BYMONTHDAY
, -1) == 0) {
482 // Note: BYMONTHDAY may contain multiple days delimitted by comma
484 // A value of BYMONTHDAY could be negative, for example, -1 means
485 // the last day in a month
487 int32_t dom_start
= 0;
489 UBool nextDOM
= TRUE
;
491 dom_end
= value
.indexOf(COMMA
, dom_start
);
493 dom_end
= value
.length();
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
;
503 status
= U_BUFFER_OVERFLOW_ERROR
;
504 goto rruleParseError
;
506 dom_start
= dom_end
+ 1;
512 // FREQ=YEARLY must be set
513 goto rruleParseError
;
515 // Set actual number of parsed DOM (ICAL_BYMONTHDAY)
520 if (U_SUCCESS(status
)) {
522 status
= U_INVALID_FORMAT_ERROR
;
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
)) {
531 if (dates
== NULL
|| dates
->size() == 0) {
532 status
= U_ILLEGAL_ARGUMENT_ERROR
;
537 DateTimeRule
*adtr
= NULL
;
539 // Parse the first rule
540 UnicodeString rrule
= *((UnicodeString
*)dates
->elementAt(0));
541 int32_t month
, dayOfWeek
, nthDayOfWeek
, dayOfMonth
= 0;
543 int32_t daysCount
= UPRV_LENGTHOF(days
);
546 parseRRULE(rrule
, month
, dayOfWeek
, nthDayOfWeek
, days
, daysCount
, until
, status
);
547 if (U_FAILURE(status
)) {
551 if (dates
->size() == 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
;
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
566 days
[i
] = MONTHLENGTH
[month
] + days
[i
] + 1;
568 if (days
[i
] < firstDay
) {
572 // Make sure days are continuous
573 for (i
= 1; i
< 7; i
++) {
575 for (j
= 0; j
< 7; j
++) {
576 if (days
[j
] == firstDay
+ i
) {
582 // days are not continuous
583 goto unsupportedRRule
;
586 // Use DOW_GEQ_DOM rule with firstDay as the start date
587 dayOfMonth
= firstDay
;
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
;
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
;
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
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
;
614 int32_t anotherMonth
= -1;
615 for (i
= 1; i
< dates
->size(); i
++) {
616 rrule
= *((UnicodeString
*)dates
->elementAt(i
));
618 int32_t tmp_month
, tmp_dayOfWeek
, tmp_nthDayOfWeek
;
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
)) {
625 // If UNTIL is newer than previous one, use the one
626 if (tmp_until
> until
) {
630 // Check if BYMONTH + BYMONTHDAY + BYDAY rule
631 if (tmp_month
== -1 || tmp_dayOfWeek
== 0 || tmp_daysCount
== 0) {
632 goto unsupportedRRule
;
634 // Count number of BYMONTHDAY
635 if (daysCount
+ tmp_daysCount
> 7) {
636 // We cannot support BYMONTHDAY more than 7
637 goto unsupportedRRule
;
639 // Check if the same BYDAY is used. Otherwise, we cannot
641 if (tmp_dayOfWeek
!= dayOfWeek
) {
642 goto unsupportedRRule
;
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) {
650 anotherMonth
= tmp_month
;
651 earliestMonth
= anotherMonth
;
652 // Reset earliest day
654 } else if (diff
== 11 || diff
== 1) {
656 anotherMonth
= tmp_month
;
658 // The day range cannot exceed more than 2 months
659 goto unsupportedRRule
;
661 } else if (tmp_month
!= month
&& tmp_month
!= anotherMonth
) {
662 // The day range cannot exceed more than 2 months
663 goto unsupportedRRule
;
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
;
673 daysCount
+= tmp_daysCount
;
675 if (daysCount
!= 7) {
676 // Number of BYMONTHDAY entries must be 7
677 goto unsupportedRRule
;
679 month
= earliestMonth
;
680 dayOfMonth
= earliestDay
;
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
);
688 // If BYMONTH is not set, use the month of DTSTART
691 if (dayOfWeek
== 0 && nthDayOfWeek
== 0 && dayOfMonth
== 0) {
692 // If only YEARLY is set, use the day of DTSTART as BYMONTHDAY
693 dayOfMonth
= startDOM
;
697 if (until
!= MIN_MILLIS
) {
698 int32_t endMonth
, endDOM
, endDOW
, endDOY
, endMID
;
699 Grego::timeToFields(until
, endYear
, endMonth
, endDOM
, endDOW
, endDOY
, endMID
);
701 endYear
= AnnualTimeZoneRule::MAX_YEAR
;
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
);
717 goto unsupportedRRule
;
719 return new AnnualTimeZoneRule(zonename
, rawOffset
, dstSavings
, adtr
, startYear
, endYear
);
722 status
= U_INVALID_STATE_ERROR
;
727 * Create a TimeZoneRule by the RDATE definition
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
)) {
734 TimeArrayTimeZoneRule
*retVal
= NULL
;
735 if (dates
== NULL
|| 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
,
739 &start
, 1, DateTimeRule::UTC_TIME
);
741 // Create an array of transition times
742 int32_t size
= dates
->size();
743 UDate
* times
= (UDate
*)uprv_malloc(sizeof(UDate
) * size
);
745 status
= U_MEMORY_ALLOCATION_ERROR
;
748 for (int32_t i
= 0; i
< size
; i
++) {
749 UnicodeString
*datestr
= (UnicodeString
*)dates
->elementAt(i
);
750 times
[i
] = parseDateTimeString(*datestr
, fromOffset
, status
);
751 if (U_FAILURE(status
)) {
756 retVal
= new TimeArrayTimeZoneRule(zonename
, rawOffset
, dstSavings
,
757 times
, size
, DateTimeRule::UTC_TIME
);
764 * Check if the DOW rule specified by month, weekInMonth and dayOfWeek is equivalent
765 * to the DateTimerule.
767 static UBool
isEquivalentDateRule(int32_t month
, int32_t weekInMonth
, int32_t dayOfWeek
, const DateTimeRule
*dtrule
) {
768 if (month
!= dtrule
->getRuleMonth() || dayOfWeek
!= dtrule
->getRuleDayOfWeek()) {
771 if (dtrule
->getTimeRuleType() != DateTimeRule::WALL_TIME
) {
772 // Do not try to do more intelligent comparison for now.
775 if (dtrule
->getDateRuleType() == DateTimeRule::DOW
776 && dtrule
->getRuleWeekInMonth() == weekInMonth
) {
779 int32_t ruleDOM
= dtrule
->getRuleDayOfMonth();
780 if (dtrule
->getDateRuleType() == DateTimeRule::DOW_GEQ_DOM
) {
781 if (ruleDOM%7
== 1 && (ruleDOM
+ 6)/7 == weekInMonth
) {
784 if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - ruleDOM
)%7
== 6
785 && weekInMonth
== -1*((MONTHLENGTH
[month
]-ruleDOM
+1)/7)) {
789 if (dtrule
->getDateRuleType() == DateTimeRule::DOW_LEQ_DOM
) {
790 if (ruleDOM%7
== 0 && ruleDOM
/7 == weekInMonth
) {
793 if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - ruleDOM
)%7
== 0
794 && weekInMonth
== -1*((MONTHLENGTH
[month
] - ruleDOM
)/7 + 1)) {
802 * Convert the rule to its equivalent rule using WALL_TIME mode.
803 * This function returns NULL when the specified DateTimeRule is already
804 * using WALL_TIME mode.
806 static DateTimeRule
* toWallTimeRule(const DateTimeRule
* rule
, int32_t rawOffset
, int32_t dstSavings
) {
807 if (rule
->getTimeRuleType() == DateTimeRule::WALL_TIME
) {
810 int32_t wallt
= rule
->getRuleMillisInDay();
811 if (rule
->getTimeRuleType() == DateTimeRule::UTC_TIME
) {
812 wallt
+= (rawOffset
+ dstSavings
);
813 } else if (rule
->getTimeRuleType() == DateTimeRule::STANDARD_TIME
) {
817 int32_t month
= -1, dom
= 0, dow
= 0;
818 DateTimeRule::DateRuleType dtype
;
822 wallt
+= U_MILLIS_PER_DAY
;
823 } else if (wallt
>= U_MILLIS_PER_DAY
) {
825 wallt
-= U_MILLIS_PER_DAY
;
828 month
= rule
->getRuleMonth();
829 dom
= rule
->getRuleDayOfMonth();
830 dow
= rule
->getRuleDayOfWeek();
831 dtype
= rule
->getDateRuleType();
834 if (dtype
== DateTimeRule::DOW
) {
835 // Convert to DOW_GEW_DOM or DOW_LEQ_DOM rule first
836 int32_t wim
= rule
->getRuleWeekInMonth();
838 dtype
= DateTimeRule::DOW_GEQ_DOM
;
839 dom
= 7 * (wim
- 1) + 1;
841 dtype
= DateTimeRule::DOW_LEQ_DOM
;
842 dom
= MONTHLENGTH
[month
] + 7 * (wim
+ 1);
845 // Shift one day before or after
849 month
= month
< UCAL_JANUARY
? UCAL_DECEMBER
: month
;
850 dom
= MONTHLENGTH
[month
];
851 } else if (dom
> MONTHLENGTH
[month
]) {
853 month
= month
> UCAL_DECEMBER
? UCAL_JANUARY
: month
;
856 if (dtype
!= DateTimeRule::DOM
) {
857 // Adjust day of week
859 if (dow
< UCAL_SUNDAY
) {
861 } else if (dow
> UCAL_SATURDAY
) {
867 DateTimeRule
*modifiedRule
;
868 if (dtype
== DateTimeRule::DOM
) {
869 modifiedRule
= new DateTimeRule(month
, dom
, wallt
, DateTimeRule::WALL_TIME
);
871 modifiedRule
= new DateTimeRule(month
, dom
, dow
,
872 (dtype
== DateTimeRule::DOW_GEQ_DOM
), wallt
, DateTimeRule::WALL_TIME
);
878 * Minumum implementations of stream writer/reader, writing/reading
879 * UnicodeString. For now, we do not want to introduce the dependency
880 * on the ICU I/O stream in this module. But we want to keep the code
881 * equivalent to the ICU4J implementation, which utilizes java.io.Writer/
886 VTZWriter(UnicodeString
& out
);
889 void write(const UnicodeString
& str
);
890 void write(UChar ch
);
891 void write(const UChar
* str
);
892 //void write(const UChar* str, int32_t length);
897 VTZWriter::VTZWriter(UnicodeString
& output
) {
901 VTZWriter::~VTZWriter() {
905 VTZWriter::write(const UnicodeString
& str
) {
910 VTZWriter::write(UChar ch
) {
915 VTZWriter::write(const UChar
* str
) {
916 out
->append(str
, -1);
921 VTZWriter::write(const UChar* str, int32_t length) {
922 out->append(str, length);
928 VTZReader(const UnicodeString
& input
);
933 const UnicodeString
* in
;
937 VTZReader::VTZReader(const UnicodeString
& input
) {
942 VTZReader::~VTZReader() {
946 VTZReader::read(void) {
948 if (index
< in
->length()) {
949 ch
= in
->charAt(index
);
956 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(VTimeZone
)
958 VTimeZone::VTimeZone()
959 : BasicTimeZone(), tz(NULL
), vtzlines(NULL
),
960 lastmod(MAX_MILLIS
) {
963 VTimeZone::VTimeZone(const VTimeZone
& source
)
964 : BasicTimeZone(source
), tz(NULL
), vtzlines(NULL
),
965 tzurl(source
.tzurl
), lastmod(source
.lastmod
),
966 olsonzid(source
.olsonzid
), icutzver(source
.icutzver
) {
967 if (source
.tz
!= NULL
) {
968 tz
= (BasicTimeZone
*)source
.tz
->clone();
970 if (source
.vtzlines
!= NULL
) {
971 UErrorCode status
= U_ZERO_ERROR
;
972 int32_t size
= source
.vtzlines
->size();
973 vtzlines
= new UVector(uprv_deleteUObject
, uhash_compareUnicodeString
, size
, status
);
974 if (U_SUCCESS(status
)) {
975 for (int32_t i
= 0; i
< size
; i
++) {
976 UnicodeString
*line
= (UnicodeString
*)source
.vtzlines
->elementAt(i
);
977 vtzlines
->addElement(line
->clone(), status
);
978 if (U_FAILURE(status
)) {
983 if (U_FAILURE(status
) && vtzlines
!= NULL
) {
989 VTimeZone::~VTimeZone() {
993 if (vtzlines
!= NULL
) {
999 VTimeZone::operator=(const VTimeZone
& right
) {
1000 if (this == &right
) {
1003 if (*this != right
) {
1004 BasicTimeZone::operator=(right
);
1009 if (right
.tz
!= NULL
) {
1010 tz
= (BasicTimeZone
*)right
.tz
->clone();
1012 if (vtzlines
!= NULL
) {
1015 if (right
.vtzlines
!= NULL
) {
1016 UErrorCode status
= U_ZERO_ERROR
;
1017 int32_t size
= right
.vtzlines
->size();
1018 vtzlines
= new UVector(uprv_deleteUObject
, uhash_compareUnicodeString
, size
, status
);
1019 if (U_SUCCESS(status
)) {
1020 for (int32_t i
= 0; i
< size
; i
++) {
1021 UnicodeString
*line
= (UnicodeString
*)right
.vtzlines
->elementAt(i
);
1022 vtzlines
->addElement(line
->clone(), status
);
1023 if (U_FAILURE(status
)) {
1028 if (U_FAILURE(status
) && vtzlines
!= NULL
) {
1033 tzurl
= right
.tzurl
;
1034 lastmod
= right
.lastmod
;
1035 olsonzid
= right
.olsonzid
;
1036 icutzver
= right
.icutzver
;
1042 VTimeZone::operator==(const TimeZone
& that
) const {
1043 if (this == &that
) {
1046 if (typeid(*this) != typeid(that
) || !BasicTimeZone::operator==(that
)) {
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 */) {
1061 VTimeZone::operator!=(const TimeZone
& that
) const {
1062 return !operator==(that
);
1066 VTimeZone::createVTimeZoneByID(const UnicodeString
& ID
) {
1067 VTimeZone
*vtz
= new VTimeZone();
1068 vtz
->tz
= (BasicTimeZone
*)TimeZone::createTimeZone(ID
);
1069 vtz
->tz
->getID(vtz
->olsonzid
);
1071 // Set ICU tzdata version
1072 UErrorCode status
= U_ZERO_ERROR
;
1073 UResourceBundle
*bundle
= NULL
;
1074 const UChar
* versionStr
= NULL
;
1076 bundle
= ures_openDirect(NULL
, "zoneinfo64", &status
);
1077 versionStr
= ures_getStringByKey(bundle
, "TZVersion", &len
, &status
);
1078 if (U_SUCCESS(status
)) {
1079 vtz
->icutzver
.setTo(versionStr
, len
);
1086 VTimeZone::createVTimeZoneFromBasicTimeZone(const BasicTimeZone
& basic_time_zone
, UErrorCode
&status
) {
1087 if (U_FAILURE(status
)) {
1090 VTimeZone
*vtz
= new VTimeZone();
1092 status
= U_MEMORY_ALLOCATION_ERROR
;
1095 vtz
->tz
= (BasicTimeZone
*)basic_time_zone
.clone();
1096 if (vtz
->tz
== NULL
) {
1097 status
= U_MEMORY_ALLOCATION_ERROR
;
1101 vtz
->tz
->getID(vtz
->olsonzid
);
1103 // Set ICU tzdata version
1104 UResourceBundle
*bundle
= NULL
;
1105 const UChar
* versionStr
= NULL
;
1107 bundle
= ures_openDirect(NULL
, "zoneinfo64", &status
);
1108 versionStr
= ures_getStringByKey(bundle
, "TZVersion", &len
, &status
);
1109 if (U_SUCCESS(status
)) {
1110 vtz
->icutzver
.setTo(versionStr
, len
);
1117 VTimeZone::createVTimeZone(const UnicodeString
& vtzdata
, UErrorCode
& status
) {
1118 if (U_FAILURE(status
)) {
1121 VTZReader
reader(vtzdata
);
1122 VTimeZone
*vtz
= new VTimeZone();
1123 vtz
->load(reader
, status
);
1124 if (U_FAILURE(status
)) {
1132 VTimeZone::getTZURL(UnicodeString
& url
) const {
1133 if (tzurl
.length() > 0) {
1141 VTimeZone::setTZURL(const UnicodeString
& url
) {
1146 VTimeZone::getLastModified(UDate
& lastModified
) const {
1147 if (lastmod
!= MAX_MILLIS
) {
1148 lastModified
= lastmod
;
1155 VTimeZone::setLastModified(UDate lastModified
) {
1156 lastmod
= lastModified
;
1160 VTimeZone::write(UnicodeString
& result
, UErrorCode
& status
) const {
1162 VTZWriter
writer(result
);
1163 write(writer
, status
);
1167 VTimeZone::write(UDate start
, UnicodeString
& result
, UErrorCode
& status
) const {
1169 VTZWriter
writer(result
);
1170 write(start
, writer
, status
);
1174 VTimeZone::writeSimple(UDate time
, UnicodeString
& result
, UErrorCode
& status
) const {
1176 VTZWriter
writer(result
);
1177 writeSimple(time
, writer
, status
);
1181 VTimeZone::clone(void) const {
1182 return new VTimeZone(*this);
1186 VTimeZone::getOffset(uint8_t era
, int32_t year
, int32_t month
, int32_t day
,
1187 uint8_t dayOfWeek
, int32_t millis
, UErrorCode
& status
) const {
1188 return tz
->getOffset(era
, year
, month
, day
, dayOfWeek
, millis
, status
);
1192 VTimeZone::getOffset(uint8_t era
, int32_t year
, int32_t month
, int32_t day
,
1193 uint8_t dayOfWeek
, int32_t millis
,
1194 int32_t monthLength
, UErrorCode
& status
) const {
1195 return tz
->getOffset(era
, year
, month
, day
, dayOfWeek
, millis
, monthLength
, status
);
1199 VTimeZone::getOffset(UDate date
, UBool local
, int32_t& rawOffset
,
1200 int32_t& dstOffset
, UErrorCode
& status
) const {
1201 return tz
->getOffset(date
, local
, rawOffset
, dstOffset
, status
);
1205 VTimeZone::setRawOffset(int32_t offsetMillis
) {
1206 tz
->setRawOffset(offsetMillis
);
1210 VTimeZone::getRawOffset(void) const {
1211 return tz
->getRawOffset();
1215 VTimeZone::useDaylightTime(void) const {
1216 return tz
->useDaylightTime();
1220 VTimeZone::inDaylightTime(UDate date
, UErrorCode
& status
) const {
1221 return tz
->inDaylightTime(date
, status
);
1225 VTimeZone::hasSameRules(const TimeZone
& other
) const {
1226 return tz
->hasSameRules(other
);
1230 VTimeZone::getNextTransition(UDate base
, UBool inclusive
, TimeZoneTransition
& result
) const {
1231 return tz
->getNextTransition(base
, inclusive
, result
);
1235 VTimeZone::getPreviousTransition(UDate base
, UBool inclusive
, TimeZoneTransition
& result
) const {
1236 return tz
->getPreviousTransition(base
, inclusive
, result
);
1240 VTimeZone::countTransitionRules(UErrorCode
& status
) const {
1241 return tz
->countTransitionRules(status
);
1245 VTimeZone::getTimeZoneRules(const InitialTimeZoneRule
*& initial
,
1246 const TimeZoneRule
* trsrules
[], int32_t& trscount
,
1247 UErrorCode
& status
) const {
1248 tz
->getTimeZoneRules(initial
, trsrules
, trscount
, status
);
1252 VTimeZone::load(VTZReader
& reader
, UErrorCode
& status
) {
1253 vtzlines
= new UVector(uprv_deleteUObject
, uhash_compareUnicodeString
, DEFAULT_VTIMEZONE_LINES
, status
);
1254 if (U_FAILURE(status
)) {
1258 UBool start
= FALSE
;
1259 UBool success
= FALSE
;
1263 UChar ch
= reader
.read();
1266 if (start
&& line
.startsWith(ICAL_END_VTIMEZONE
, -1)) {
1267 vtzlines
->addElement(new UnicodeString(line
), status
);
1268 if (U_FAILURE(status
)) {
1269 goto cleanupVtzlines
;
1276 // CR, must be followed by LF according to the definition in RFC2445
1280 if (ch
!= 0x0009 && ch
!= 0x0020) {
1281 // NOT followed by TAB/SP -> new line
1283 if (line
.length() > 0) {
1284 vtzlines
->addElement(new UnicodeString(line
), status
);
1285 if (U_FAILURE(status
)) {
1286 goto cleanupVtzlines
;
1301 if (line
.startsWith(ICAL_END_VTIMEZONE
, -1)) {
1302 vtzlines
->addElement(new UnicodeString(line
), status
);
1303 if (U_FAILURE(status
)) {
1304 goto cleanupVtzlines
;
1310 if (line
.startsWith(ICAL_BEGIN_VTIMEZONE
, -1)) {
1311 vtzlines
->addElement(new UnicodeString(line
), status
);
1312 if (U_FAILURE(status
)) {
1313 goto cleanupVtzlines
;
1326 if (U_SUCCESS(status
)) {
1327 status
= U_INVALID_STATE_ERROR
;
1329 goto cleanupVtzlines
;
1340 #define INI 0 // Initial state
1341 #define VTZ 1 // In VTIMEZONE
1342 #define TZI 2 // In STANDARD or DAYLIGHT
1344 #define DEF_DSTSAVINGS (60*60*1000)
1345 #define DEF_TZSTARTTIME (0.0)
1348 VTimeZone::parse(UErrorCode
& status
) {
1349 if (U_FAILURE(status
)) {
1352 if (vtzlines
== NULL
|| vtzlines
->size() == 0) {
1353 status
= U_INVALID_STATE_ERROR
;
1356 InitialTimeZoneRule
*initialRule
= NULL
;
1357 RuleBasedTimeZone
*rbtz
= NULL
;
1362 int32_t state
= INI
;
1364 UBool dst
= FALSE
; // current zone type
1365 UnicodeString from
; // current zone from offset
1366 UnicodeString to
; // current zone offset
1367 UnicodeString zonename
; // current zone name
1368 UnicodeString dtstart
; // current zone starts
1369 UBool isRRULE
= FALSE
; // true if the rule is described by RRULE
1370 int32_t initialRawOffset
= 0; // initial offset
1371 int32_t initialDSTSavings
= 0; // initial offset
1372 UDate firstStart
= MAX_MILLIS
; // the earliest rule start time
1373 UnicodeString name
; // RFC2445 prop name
1374 UnicodeString value
; // RFC2445 prop value
1376 UVector
*dates
= NULL
; // list of RDATE or RRULE strings
1377 UVector
*rules
= NULL
; // list of TimeZoneRule instances
1379 int32_t finalRuleIdx
= -1;
1380 int32_t finalRuleCount
= 0;
1382 rules
= new UVector(status
);
1383 if (U_FAILURE(status
)) {
1386 // Set the deleter to remove TimeZoneRule vectors to avoid memory leaks due to unowned TimeZoneRules.
1387 rules
->setDeleter(deleteTimeZoneRule
);
1389 dates
= new UVector(uprv_deleteUObject
, uhash_compareUnicodeString
, status
);
1390 if (U_FAILURE(status
)) {
1393 if (rules
== NULL
|| dates
== NULL
) {
1394 status
= U_MEMORY_ALLOCATION_ERROR
;
1398 for (n
= 0; n
< vtzlines
->size(); n
++) {
1399 UnicodeString
*line
= (UnicodeString
*)vtzlines
->elementAt(n
);
1400 int32_t valueSep
= line
->indexOf(COLON
);
1404 name
.setTo(*line
, 0, valueSep
);
1405 value
.setTo(*line
, valueSep
+ 1);
1409 if (name
.compare(ICAL_BEGIN
, -1) == 0
1410 && value
.compare(ICAL_VTIMEZONE
, -1) == 0) {
1416 if (name
.compare(ICAL_TZID
, -1) == 0) {
1418 } else if (name
.compare(ICAL_TZURL
, -1) == 0) {
1420 } else if (name
.compare(ICAL_LASTMOD
, -1) == 0) {
1421 // Always in 'Z' format, so the offset argument for the parse method
1422 // can be any value.
1423 lastmod
= parseDateTimeString(value
, 0, status
);
1424 if (U_FAILURE(status
)) {
1427 } else if (name
.compare(ICAL_BEGIN
, -1) == 0) {
1428 UBool isDST
= (value
.compare(ICAL_DAYLIGHT
, -1) == 0);
1429 if (value
.compare(ICAL_STANDARD
, -1) == 0 || isDST
) {
1430 // tzid must be ready at this point
1431 if (tzid
.length() == 0) {
1434 // initialize current zone properties
1435 if (dates
->size() != 0) {
1436 dates
->removeAllElements();
1445 // BEGIN property other than STANDARD/DAYLIGHT
1446 // must not be there.
1449 } else if (name
.compare(ICAL_END
, -1) == 0) {
1454 if (name
.compare(ICAL_DTSTART
, -1) == 0) {
1456 } else if (name
.compare(ICAL_TZNAME
, -1) == 0) {
1458 } else if (name
.compare(ICAL_TZOFFSETFROM
, -1) == 0) {
1460 } else if (name
.compare(ICAL_TZOFFSETTO
, -1) == 0) {
1462 } else if (name
.compare(ICAL_RDATE
, -1) == 0) {
1463 // RDATE mixed with RRULE is not supported
1467 // RDATE value may contain multiple date delimited
1469 UBool nextDate
= TRUE
;
1471 UnicodeString
*dstr
;
1473 int32_t dend
= value
.indexOf(COMMA
, dstart
);
1475 dstr
= new UnicodeString(value
, dstart
);
1478 dstr
= new UnicodeString(value
, dstart
, dend
- dstart
);
1480 dates
->addElement(dstr
, status
);
1481 if (U_FAILURE(status
)) {
1486 } else if (name
.compare(ICAL_RRULE
, -1) == 0) {
1487 // RRULE mixed with RDATE is not supported
1488 if (!isRRULE
&& dates
->size() != 0) {
1492 dates
->addElement(new UnicodeString(value
), status
);
1493 if (U_FAILURE(status
)) {
1496 } else if (name
.compare(ICAL_END
, -1) == 0) {
1497 // Mandatory properties
1498 if (dtstart
.length() == 0 || from
.length() == 0 || to
.length() == 0) {
1501 // if zonename is not available, create one from tzid
1502 if (zonename
.length() == 0) {
1503 getDefaultTZName(tzid
, dst
, zonename
);
1506 // create a time zone rule
1507 TimeZoneRule
*rule
= NULL
;
1508 int32_t fromOffset
= 0;
1509 int32_t toOffset
= 0;
1510 int32_t rawOffset
= 0;
1511 int32_t dstSavings
= 0;
1514 // Parse TZOFFSETFROM/TZOFFSETTO
1515 fromOffset
= offsetStrToMillis(from
, status
);
1516 toOffset
= offsetStrToMillis(to
, status
);
1517 if (U_FAILURE(status
)) {
1522 // If daylight, use the previous offset as rawoffset if positive
1523 if (toOffset
- fromOffset
> 0) {
1524 rawOffset
= fromOffset
;
1525 dstSavings
= toOffset
- fromOffset
;
1527 // This is rare case.. just use 1 hour DST savings
1528 rawOffset
= toOffset
- DEF_DSTSAVINGS
;
1529 dstSavings
= DEF_DSTSAVINGS
;
1532 rawOffset
= toOffset
;
1537 start
= parseDateTimeString(dtstart
, fromOffset
, status
);
1538 if (U_FAILURE(status
)) {
1543 UDate actualStart
= MAX_MILLIS
;
1545 rule
= createRuleByRRULE(zonename
, rawOffset
, dstSavings
, start
, dates
, fromOffset
, status
);
1547 rule
= createRuleByRDATE(zonename
, rawOffset
, dstSavings
, start
, dates
, fromOffset
, status
);
1549 if (U_FAILURE(status
) || rule
== NULL
) {
1552 UBool startAvail
= rule
->getFirstStart(fromOffset
, 0, actualStart
);
1553 if (startAvail
&& actualStart
< firstStart
) {
1554 // save from offset information for the earliest rule
1555 firstStart
= actualStart
;
1556 // If this is STD, assume the time before this transtion
1557 // is DST when the difference is 1 hour. This might not be
1558 // accurate, but VTIMEZONE data does not have such info.
1559 if (dstSavings
> 0) {
1560 initialRawOffset
= fromOffset
;
1561 initialDSTSavings
= 0;
1563 if (fromOffset
- toOffset
== DEF_DSTSAVINGS
) {
1564 initialRawOffset
= fromOffset
- DEF_DSTSAVINGS
;
1565 initialDSTSavings
= DEF_DSTSAVINGS
;
1567 initialRawOffset
= fromOffset
;
1568 initialDSTSavings
= 0;
1573 rules
->addElement(rule
, status
);
1574 if (U_FAILURE(status
)) {
1582 // Must have at least one rule
1583 if (rules
->size() == 0) {
1587 // Create a initial rule
1588 getDefaultTZName(tzid
, FALSE
, zonename
);
1589 initialRule
= new InitialTimeZoneRule(zonename
,
1590 initialRawOffset
, initialDSTSavings
);
1591 if (initialRule
== NULL
) {
1592 status
= U_MEMORY_ALLOCATION_ERROR
;
1596 // Finally, create the RuleBasedTimeZone
1597 rbtz
= new RuleBasedTimeZone(tzid
, initialRule
);
1599 status
= U_MEMORY_ALLOCATION_ERROR
;
1602 initialRule
= NULL
; // already adopted by RBTZ, no need to delete
1604 for (n
= 0; n
< rules
->size(); n
++) {
1605 TimeZoneRule
*r
= (TimeZoneRule
*)rules
->elementAt(n
);
1606 AnnualTimeZoneRule
*atzrule
= dynamic_cast<AnnualTimeZoneRule
*>(r
);
1607 if (atzrule
!= NULL
) {
1608 if (atzrule
->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
) {
1614 if (finalRuleCount
> 2) {
1615 // Too many final rules
1616 status
= U_ILLEGAL_ARGUMENT_ERROR
;
1620 if (finalRuleCount
== 1) {
1621 if (rules
->size() == 1) {
1622 // Only one final rule, only governs the initial rule,
1623 // which is already initialized, thus, we do not need to
1624 // add this transition rule
1625 rules
->removeAllElements();
1627 // Normalize the final rule
1628 AnnualTimeZoneRule
*finalRule
= (AnnualTimeZoneRule
*)rules
->elementAt(finalRuleIdx
);
1629 int32_t tmpRaw
= finalRule
->getRawOffset();
1630 int32_t tmpDST
= finalRule
->getDSTSavings();
1632 // Find the last non-final rule
1633 UDate finalStart
, start
;
1634 finalRule
->getFirstStart(initialRawOffset
, initialDSTSavings
, finalStart
);
1636 for (n
= 0; n
< rules
->size(); n
++) {
1637 if (finalRuleIdx
== n
) {
1640 TimeZoneRule
*r
= (TimeZoneRule
*)rules
->elementAt(n
);
1642 r
->getFinalStart(tmpRaw
, tmpDST
, lastStart
);
1643 if (lastStart
> start
) {
1644 finalRule
->getNextStart(lastStart
,
1652 TimeZoneRule
*newRule
;
1653 UnicodeString tznam
;
1654 if (start
== finalStart
) {
1655 // Transform this into a single transition
1656 newRule
= new TimeArrayTimeZoneRule(
1657 finalRule
->getName(tznam
),
1658 finalRule
->getRawOffset(),
1659 finalRule
->getDSTSavings(),
1662 DateTimeRule::UTC_TIME
);
1664 // Update the end year
1665 int32_t y
, m
, d
, dow
, doy
, mid
;
1666 Grego::timeToFields(start
, y
, m
, d
, dow
, doy
, mid
);
1667 newRule
= new AnnualTimeZoneRule(
1668 finalRule
->getName(tznam
),
1669 finalRule
->getRawOffset(),
1670 finalRule
->getDSTSavings(),
1671 *(finalRule
->getRule()),
1672 finalRule
->getStartYear(),
1675 if (newRule
== NULL
) {
1676 status
= U_MEMORY_ALLOCATION_ERROR
;
1679 rules
->removeElementAt(finalRuleIdx
);
1680 rules
->addElement(newRule
, status
);
1681 if (U_FAILURE(status
)) {
1688 while (!rules
->isEmpty()) {
1689 TimeZoneRule
*tzr
= (TimeZoneRule
*)rules
->orphanElementAt(0);
1690 rbtz
->addTransitionRule(tzr
, status
);
1691 if (U_FAILURE(status
)) {
1695 rbtz
->complete(status
);
1696 if (U_FAILURE(status
)) {
1707 if (rules
!= NULL
) {
1708 while (!rules
->isEmpty()) {
1709 TimeZoneRule
*r
= (TimeZoneRule
*)rules
->orphanElementAt(0);
1714 if (dates
!= NULL
) {
1717 if (initialRule
!= NULL
) {
1727 VTimeZone::write(VTZWriter
& writer
, UErrorCode
& status
) const {
1728 if (vtzlines
!= NULL
) {
1729 for (int32_t i
= 0; i
< vtzlines
->size(); i
++) {
1730 UnicodeString
*line
= (UnicodeString
*)vtzlines
->elementAt(i
);
1731 if (line
->startsWith(ICAL_TZURL
, -1)
1732 && line
->charAt(u_strlen(ICAL_TZURL
)) == COLON
) {
1733 writer
.write(ICAL_TZURL
);
1734 writer
.write(COLON
);
1735 writer
.write(tzurl
);
1736 writer
.write(ICAL_NEWLINE
);
1737 } else if (line
->startsWith(ICAL_LASTMOD
, -1)
1738 && line
->charAt(u_strlen(ICAL_LASTMOD
)) == COLON
) {
1739 UnicodeString utcString
;
1740 writer
.write(ICAL_LASTMOD
);
1741 writer
.write(COLON
);
1742 writer
.write(getUTCDateTimeString(lastmod
, utcString
));
1743 writer
.write(ICAL_NEWLINE
);
1745 writer
.write(*line
);
1746 writer
.write(ICAL_NEWLINE
);
1750 UnicodeString icutzprop
;
1751 UVector
customProps(nullptr, uhash_compareUnicodeString
, status
);
1752 if (olsonzid
.length() > 0 && icutzver
.length() > 0) {
1753 icutzprop
.append(olsonzid
);
1754 icutzprop
.append(u
'[');
1755 icutzprop
.append(icutzver
);
1756 icutzprop
.append(u
']');
1757 customProps
.addElement(&icutzprop
, status
);
1759 writeZone(writer
, *tz
, &customProps
, status
);
1764 VTimeZone::write(UDate start
, VTZWriter
& writer
, UErrorCode
& status
) const {
1765 if (U_FAILURE(status
)) {
1768 InitialTimeZoneRule
*initial
= NULL
;
1769 UVector
*transitionRules
= NULL
;
1770 UVector
customProps(uprv_deleteUObject
, uhash_compareUnicodeString
, status
);
1773 // Extract rules applicable to dates after the start time
1774 getTimeZoneRulesAfter(start
, initial
, transitionRules
, status
);
1775 if (U_FAILURE(status
)) {
1779 // Create a RuleBasedTimeZone with the subset rule
1781 RuleBasedTimeZone
rbtz(tzid
, initial
);
1782 if (transitionRules
!= NULL
) {
1783 while (!transitionRules
->isEmpty()) {
1784 TimeZoneRule
*tr
= (TimeZoneRule
*)transitionRules
->orphanElementAt(0);
1785 rbtz
.addTransitionRule(tr
, status
);
1786 if (U_FAILURE(status
)) {
1787 goto cleanupWritePartial
;
1790 delete transitionRules
;
1791 transitionRules
= NULL
;
1793 rbtz
.complete(status
);
1794 if (U_FAILURE(status
)) {
1795 goto cleanupWritePartial
;
1798 if (olsonzid
.length() > 0 && icutzver
.length() > 0) {
1799 UnicodeString
*icutzprop
= new UnicodeString(ICU_TZINFO_PROP
);
1800 icutzprop
->append(olsonzid
);
1801 icutzprop
->append((UChar
)0x005B/*'['*/);
1802 icutzprop
->append(icutzver
);
1803 icutzprop
->append(ICU_TZINFO_PARTIAL
, -1);
1804 appendMillis(start
, *icutzprop
);
1805 icutzprop
->append((UChar
)0x005D/*']'*/);
1806 customProps
.addElement(icutzprop
, status
);
1807 if (U_FAILURE(status
)) {
1809 goto cleanupWritePartial
;
1812 writeZone(writer
, rbtz
, &customProps
, status
);
1815 cleanupWritePartial
:
1816 if (initial
!= NULL
) {
1819 if (transitionRules
!= NULL
) {
1820 while (!transitionRules
->isEmpty()) {
1821 TimeZoneRule
*tr
= (TimeZoneRule
*)transitionRules
->orphanElementAt(0);
1824 delete transitionRules
;
1829 VTimeZone::writeSimple(UDate time
, VTZWriter
& writer
, UErrorCode
& status
) const {
1830 if (U_FAILURE(status
)) {
1834 UVector
customProps(uprv_deleteUObject
, uhash_compareUnicodeString
, status
);
1837 // Extract simple rules
1838 InitialTimeZoneRule
*initial
= NULL
;
1839 AnnualTimeZoneRule
*std
= NULL
, *dst
= NULL
;
1840 getSimpleRulesNear(time
, initial
, std
, dst
, status
);
1841 if (U_SUCCESS(status
)) {
1842 // Create a RuleBasedTimeZone with the subset rule
1844 RuleBasedTimeZone
rbtz(tzid
, initial
);
1845 if (std
!= NULL
&& dst
!= NULL
) {
1846 rbtz
.addTransitionRule(std
, status
);
1847 rbtz
.addTransitionRule(dst
, status
);
1849 if (U_FAILURE(status
)) {
1850 goto cleanupWriteSimple
;
1853 if (olsonzid
.length() > 0 && icutzver
.length() > 0) {
1854 UnicodeString
*icutzprop
= new UnicodeString(ICU_TZINFO_PROP
);
1855 icutzprop
->append(olsonzid
);
1856 icutzprop
->append((UChar
)0x005B/*'['*/);
1857 icutzprop
->append(icutzver
);
1858 icutzprop
->append(ICU_TZINFO_SIMPLE
, -1);
1859 appendMillis(time
, *icutzprop
);
1860 icutzprop
->append((UChar
)0x005D/*']'*/);
1861 customProps
.addElement(icutzprop
, status
);
1862 if (U_FAILURE(status
)) {
1864 goto cleanupWriteSimple
;
1867 writeZone(writer
, rbtz
, &customProps
, status
);
1872 if (initial
!= NULL
) {
1884 VTimeZone::writeZone(VTZWriter
& w
, BasicTimeZone
& basictz
,
1885 UVector
* customProps
, UErrorCode
& status
) const {
1886 if (U_FAILURE(status
)) {
1889 writeHeaders(w
, status
);
1890 if (U_FAILURE(status
)) {
1894 if (customProps
!= NULL
) {
1895 for (int32_t i
= 0; i
< customProps
->size(); i
++) {
1896 UnicodeString
*custprop
= (UnicodeString
*)customProps
->elementAt(i
);
1898 w
.write(ICAL_NEWLINE
);
1902 UDate t
= MIN_MILLIS
;
1903 UnicodeString dstName
;
1904 int32_t dstFromOffset
= 0;
1905 int32_t dstFromDSTSavings
= 0;
1906 int32_t dstToOffset
= 0;
1907 int32_t dstStartYear
= 0;
1908 int32_t dstMonth
= 0;
1909 int32_t dstDayOfWeek
= 0;
1910 int32_t dstWeekInMonth
= 0;
1911 int32_t dstMillisInDay
= 0;
1912 UDate dstStartTime
= 0.0;
1913 UDate dstUntilTime
= 0.0;
1914 int32_t dstCount
= 0;
1915 AnnualTimeZoneRule
*finalDstRule
= NULL
;
1917 UnicodeString stdName
;
1918 int32_t stdFromOffset
= 0;
1919 int32_t stdFromDSTSavings
= 0;
1920 int32_t stdToOffset
= 0;
1921 int32_t stdStartYear
= 0;
1922 int32_t stdMonth
= 0;
1923 int32_t stdDayOfWeek
= 0;
1924 int32_t stdWeekInMonth
= 0;
1925 int32_t stdMillisInDay
= 0;
1926 UDate stdStartTime
= 0.0;
1927 UDate stdUntilTime
= 0.0;
1928 int32_t stdCount
= 0;
1929 AnnualTimeZoneRule
*finalStdRule
= NULL
;
1931 int32_t year
, month
, dom
, dow
, doy
, mid
;
1932 UBool hasTransitions
= FALSE
;
1933 TimeZoneTransition tzt
;
1938 // Going through all transitions
1940 tztAvail
= basictz
.getNextTransition(t
, FALSE
, tzt
);
1944 hasTransitions
= TRUE
;
1946 tzt
.getTo()->getName(name
);
1947 isDst
= (tzt
.getTo()->getDSTSavings() != 0);
1948 int32_t fromOffset
= tzt
.getFrom()->getRawOffset() + tzt
.getFrom()->getDSTSavings();
1949 int32_t fromDSTSavings
= tzt
.getFrom()->getDSTSavings();
1950 int32_t toOffset
= tzt
.getTo()->getRawOffset() + tzt
.getTo()->getDSTSavings();
1951 Grego::timeToFields(tzt
.getTime() + fromOffset
, year
, month
, dom
, dow
, doy
, mid
);
1952 int32_t weekInMonth
= Grego::dayOfWeekInMonth(year
, month
, dom
);
1953 UBool sameRule
= FALSE
;
1954 const AnnualTimeZoneRule
*atzrule
;
1956 if (finalDstRule
== NULL
1957 && (atzrule
= dynamic_cast<const AnnualTimeZoneRule
*>(tzt
.getTo())) != NULL
1958 && atzrule
->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
1960 finalDstRule
= (AnnualTimeZoneRule
*)tzt
.getTo()->clone();
1963 if (year
== dstStartYear
+ dstCount
1964 && name
.compare(dstName
) == 0
1965 && dstFromOffset
== fromOffset
1966 && dstToOffset
== toOffset
1967 && dstMonth
== month
1968 && dstDayOfWeek
== dow
1969 && dstWeekInMonth
== weekInMonth
1970 && dstMillisInDay
== mid
) {
1971 // Update until time
1977 if (dstCount
== 1) {
1978 writeZonePropsByTime(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
, dstStartTime
,
1981 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
1982 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, dstUntilTime
, status
);
1984 if (U_FAILURE(status
)) {
1985 goto cleanupWriteZone
;
1990 // Reset this DST information
1992 dstFromOffset
= fromOffset
;
1993 dstFromDSTSavings
= fromDSTSavings
;
1994 dstToOffset
= toOffset
;
1995 dstStartYear
= year
;
1998 dstWeekInMonth
= weekInMonth
;
1999 dstMillisInDay
= mid
;
2000 dstStartTime
= dstUntilTime
= t
;
2003 if (finalStdRule
!= NULL
&& finalDstRule
!= NULL
) {
2007 if (finalStdRule
== NULL
2008 && (atzrule
= dynamic_cast<const AnnualTimeZoneRule
*>(tzt
.getTo())) != NULL
2009 && atzrule
->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
2011 finalStdRule
= (AnnualTimeZoneRule
*)tzt
.getTo()->clone();
2014 if (year
== stdStartYear
+ stdCount
2015 && name
.compare(stdName
) == 0
2016 && stdFromOffset
== fromOffset
2017 && stdToOffset
== toOffset
2018 && stdMonth
== month
2019 && stdDayOfWeek
== dow
2020 && stdWeekInMonth
== weekInMonth
2021 && stdMillisInDay
== mid
) {
2022 // Update until time
2028 if (stdCount
== 1) {
2029 writeZonePropsByTime(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
, stdStartTime
,
2032 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2033 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, stdUntilTime
, status
);
2035 if (U_FAILURE(status
)) {
2036 goto cleanupWriteZone
;
2041 // Reset this STD information
2043 stdFromOffset
= fromOffset
;
2044 stdFromDSTSavings
= fromDSTSavings
;
2045 stdToOffset
= toOffset
;
2046 stdStartYear
= year
;
2049 stdWeekInMonth
= weekInMonth
;
2050 stdMillisInDay
= mid
;
2051 stdStartTime
= stdUntilTime
= t
;
2054 if (finalStdRule
!= NULL
&& finalDstRule
!= NULL
) {
2059 if (!hasTransitions
) {
2060 // No transition - put a single non transition RDATE
2061 int32_t raw
, dst
, offset
;
2062 basictz
.getOffset(0.0/*any time*/, FALSE
, raw
, dst
, status
);
2063 if (U_FAILURE(status
)) {
2064 goto cleanupWriteZone
;
2069 basictz
.getID(tzid
);
2070 getDefaultTZName(tzid
, isDst
, name
);
2071 writeZonePropsByTime(w
, isDst
, name
,
2072 offset
, offset
, DEF_TZSTARTTIME
- offset
, FALSE
, status
);
2073 if (U_FAILURE(status
)) {
2074 goto cleanupWriteZone
;
2078 if (finalDstRule
== NULL
) {
2079 if (dstCount
== 1) {
2080 writeZonePropsByTime(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
, dstStartTime
,
2083 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
2084 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, dstUntilTime
, status
);
2086 if (U_FAILURE(status
)) {
2087 goto cleanupWriteZone
;
2090 if (dstCount
== 1) {
2091 writeFinalRule(w
, TRUE
, finalDstRule
,
2092 dstFromOffset
- dstFromDSTSavings
, dstFromDSTSavings
, dstStartTime
, status
);
2094 // Use a single rule if possible
2095 if (isEquivalentDateRule(dstMonth
, dstWeekInMonth
, dstDayOfWeek
, finalDstRule
->getRule())) {
2096 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
2097 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, MAX_MILLIS
, status
);
2099 // Not equivalent rule - write out two different rules
2100 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
2101 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, dstUntilTime
, status
);
2102 if (U_FAILURE(status
)) {
2103 goto cleanupWriteZone
;
2106 UBool nextStartAvail
= finalDstRule
->getNextStart(dstUntilTime
, dstFromOffset
- dstFromDSTSavings
, dstFromDSTSavings
, false, nextStart
);
2107 U_ASSERT(nextStartAvail
);
2108 if (nextStartAvail
) {
2109 writeFinalRule(w
, TRUE
, finalDstRule
,
2110 dstFromOffset
- dstFromDSTSavings
, dstFromDSTSavings
, nextStart
, status
);
2114 if (U_FAILURE(status
)) {
2115 goto cleanupWriteZone
;
2120 if (finalStdRule
== NULL
) {
2121 if (stdCount
== 1) {
2122 writeZonePropsByTime(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
, stdStartTime
,
2125 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2126 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, stdUntilTime
, status
);
2128 if (U_FAILURE(status
)) {
2129 goto cleanupWriteZone
;
2132 if (stdCount
== 1) {
2133 writeFinalRule(w
, FALSE
, finalStdRule
,
2134 stdFromOffset
- stdFromDSTSavings
, stdFromDSTSavings
, stdStartTime
, status
);
2136 // Use a single rule if possible
2137 if (isEquivalentDateRule(stdMonth
, stdWeekInMonth
, stdDayOfWeek
, finalStdRule
->getRule())) {
2138 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2139 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, MAX_MILLIS
, status
);
2141 // Not equivalent rule - write out two different rules
2142 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2143 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, stdUntilTime
, status
);
2144 if (U_FAILURE(status
)) {
2145 goto cleanupWriteZone
;
2148 UBool nextStartAvail
= finalStdRule
->getNextStart(stdUntilTime
, stdFromOffset
- stdFromDSTSavings
, stdFromDSTSavings
, false, nextStart
);
2149 U_ASSERT(nextStartAvail
);
2150 if (nextStartAvail
) {
2151 writeFinalRule(w
, FALSE
, finalStdRule
,
2152 stdFromOffset
- stdFromDSTSavings
, stdFromDSTSavings
, nextStart
, status
);
2156 if (U_FAILURE(status
)) {
2157 goto cleanupWriteZone
;
2162 writeFooter(w
, status
);
2166 if (finalStdRule
!= NULL
) {
2167 delete finalStdRule
;
2169 if (finalDstRule
!= NULL
) {
2170 delete finalDstRule
;
2175 VTimeZone::writeHeaders(VTZWriter
& writer
, UErrorCode
& status
) const {
2176 if (U_FAILURE(status
)) {
2182 writer
.write(ICAL_BEGIN
);
2183 writer
.write(COLON
);
2184 writer
.write(ICAL_VTIMEZONE
);
2185 writer
.write(ICAL_NEWLINE
);
2186 writer
.write(ICAL_TZID
);
2187 writer
.write(COLON
);
2189 writer
.write(ICAL_NEWLINE
);
2190 if (tzurl
.length() != 0) {
2191 writer
.write(ICAL_TZURL
);
2192 writer
.write(COLON
);
2193 writer
.write(tzurl
);
2194 writer
.write(ICAL_NEWLINE
);
2196 if (lastmod
!= MAX_MILLIS
) {
2197 UnicodeString lastmodStr
;
2198 writer
.write(ICAL_LASTMOD
);
2199 writer
.write(COLON
);
2200 writer
.write(getUTCDateTimeString(lastmod
, lastmodStr
));
2201 writer
.write(ICAL_NEWLINE
);
2206 * Write the closing section of the VTIMEZONE definition block
2209 VTimeZone::writeFooter(VTZWriter
& writer
, UErrorCode
& status
) const {
2210 if (U_FAILURE(status
)) {
2213 writer
.write(ICAL_END
);
2214 writer
.write(COLON
);
2215 writer
.write(ICAL_VTIMEZONE
);
2216 writer
.write(ICAL_NEWLINE
);
2220 * Write a single start time
2223 VTimeZone::writeZonePropsByTime(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2224 int32_t fromOffset
, int32_t toOffset
, UDate time
, UBool withRDATE
,
2225 UErrorCode
& status
) const {
2226 if (U_FAILURE(status
)) {
2229 beginZoneProps(writer
, isDst
, zonename
, fromOffset
, toOffset
, time
, status
);
2230 if (U_FAILURE(status
)) {
2234 writer
.write(ICAL_RDATE
);
2235 writer
.write(COLON
);
2236 UnicodeString timestr
;
2237 writer
.write(getDateTimeString(time
+ fromOffset
, timestr
));
2238 writer
.write(ICAL_NEWLINE
);
2240 endZoneProps(writer
, isDst
, status
);
2241 if (U_FAILURE(status
)) {
2247 * Write start times defined by a DOM rule using VTIMEZONE RRULE
2250 VTimeZone::writeZonePropsByDOM(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2251 int32_t fromOffset
, int32_t toOffset
,
2252 int32_t month
, int32_t dayOfMonth
, UDate startTime
, UDate untilTime
,
2253 UErrorCode
& status
) const {
2254 if (U_FAILURE(status
)) {
2257 beginZoneProps(writer
, isDst
, zonename
, fromOffset
, toOffset
, startTime
, status
);
2258 if (U_FAILURE(status
)) {
2261 beginRRULE(writer
, month
, status
);
2262 if (U_FAILURE(status
)) {
2265 writer
.write(ICAL_BYMONTHDAY
);
2266 writer
.write(EQUALS_SIGN
);
2268 appendAsciiDigits(dayOfMonth
, 0, dstr
);
2270 if (untilTime
!= MAX_MILLIS
) {
2271 appendUNTIL(writer
, getDateTimeString(untilTime
+ fromOffset
, dstr
), status
);
2272 if (U_FAILURE(status
)) {
2276 writer
.write(ICAL_NEWLINE
);
2277 endZoneProps(writer
, isDst
, status
);
2281 * Write start times defined by a DOW rule using VTIMEZONE RRULE
2284 VTimeZone::writeZonePropsByDOW(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2285 int32_t fromOffset
, int32_t toOffset
,
2286 int32_t month
, int32_t weekInMonth
, int32_t dayOfWeek
,
2287 UDate startTime
, UDate untilTime
, UErrorCode
& status
) const {
2288 if (U_FAILURE(status
)) {
2291 beginZoneProps(writer
, isDst
, zonename
, fromOffset
, toOffset
, startTime
, status
);
2292 if (U_FAILURE(status
)) {
2295 beginRRULE(writer
, month
, status
);
2296 if (U_FAILURE(status
)) {
2299 writer
.write(ICAL_BYDAY
);
2300 writer
.write(EQUALS_SIGN
);
2302 appendAsciiDigits(weekInMonth
, 0, dstr
);
2303 writer
.write(dstr
); // -4, -3, -2, -1, 1, 2, 3, 4
2304 writer
.write(ICAL_DOW_NAMES
[dayOfWeek
- 1]); // SU, MO, TU...
2306 if (untilTime
!= MAX_MILLIS
) {
2307 appendUNTIL(writer
, getDateTimeString(untilTime
+ fromOffset
, dstr
), status
);
2308 if (U_FAILURE(status
)) {
2312 writer
.write(ICAL_NEWLINE
);
2313 endZoneProps(writer
, isDst
, status
);
2317 * Write start times defined by a DOW_GEQ_DOM rule using VTIMEZONE RRULE
2320 VTimeZone::writeZonePropsByDOW_GEQ_DOM(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2321 int32_t fromOffset
, int32_t toOffset
,
2322 int32_t month
, int32_t dayOfMonth
, int32_t dayOfWeek
,
2323 UDate startTime
, UDate untilTime
, UErrorCode
& status
) const {
2324 if (U_FAILURE(status
)) {
2327 // Check if this rule can be converted to DOW rule
2328 if (dayOfMonth%7
== 1) {
2329 // Can be represented by DOW rule
2330 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2331 month
, (dayOfMonth
+ 6)/7, dayOfWeek
, startTime
, untilTime
, status
);
2332 if (U_FAILURE(status
)) {
2335 } else if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - dayOfMonth
)%7
== 6) {
2336 // Can be represented by DOW rule with negative week number
2337 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2338 month
, -1*((MONTHLENGTH
[month
] - dayOfMonth
+ 1)/7), dayOfWeek
, startTime
, untilTime
, status
);
2339 if (U_FAILURE(status
)) {
2343 // Otherwise, use BYMONTHDAY to include all possible dates
2344 beginZoneProps(writer
, isDst
, zonename
, fromOffset
, toOffset
, startTime
, status
);
2345 if (U_FAILURE(status
)) {
2348 // Check if all days are in the same month
2349 int32_t startDay
= dayOfMonth
;
2350 int32_t currentMonthDays
= 7;
2352 if (dayOfMonth
<= 0) {
2353 // The start day is in previous month
2354 int32_t prevMonthDays
= 1 - dayOfMonth
;
2355 currentMonthDays
-= prevMonthDays
;
2357 int32_t prevMonth
= (month
- 1) < 0 ? 11 : month
- 1;
2359 // Note: When a rule is separated into two, UNTIL attribute needs to be
2360 // calculated for each of them. For now, we skip this, because we basically use this method
2361 // only for final rules, which does not have the UNTIL attribute
2362 writeZonePropsByDOW_GEQ_DOM_sub(writer
, prevMonth
, -prevMonthDays
, dayOfWeek
, prevMonthDays
,
2363 MAX_MILLIS
/* Do not use UNTIL */, fromOffset
, status
);
2364 if (U_FAILURE(status
)) {
2368 // Start from 1 for the rest
2370 } else if (dayOfMonth
+ 6 > MONTHLENGTH
[month
]) {
2371 // Note: This code does not actually work well in February. For now, days in month in
2373 int32_t nextMonthDays
= dayOfMonth
+ 6 - MONTHLENGTH
[month
];
2374 currentMonthDays
-= nextMonthDays
;
2376 int32_t nextMonth
= (month
+ 1) > 11 ? 0 : month
+ 1;
2378 writeZonePropsByDOW_GEQ_DOM_sub(writer
, nextMonth
, 1, dayOfWeek
, nextMonthDays
,
2379 MAX_MILLIS
/* Do not use UNTIL */, fromOffset
, status
);
2380 if (U_FAILURE(status
)) {
2384 writeZonePropsByDOW_GEQ_DOM_sub(writer
, month
, startDay
, dayOfWeek
, currentMonthDays
,
2385 untilTime
, fromOffset
, status
);
2386 if (U_FAILURE(status
)) {
2389 endZoneProps(writer
, isDst
, status
);
2394 * Called from writeZonePropsByDOW_GEQ_DOM
2397 VTimeZone::writeZonePropsByDOW_GEQ_DOM_sub(VTZWriter
& writer
, int32_t month
, int32_t dayOfMonth
,
2398 int32_t dayOfWeek
, int32_t numDays
,
2399 UDate untilTime
, int32_t fromOffset
, UErrorCode
& status
) const {
2401 if (U_FAILURE(status
)) {
2404 int32_t startDayNum
= dayOfMonth
;
2405 UBool isFeb
= (month
== UCAL_FEBRUARY
);
2406 if (dayOfMonth
< 0 && !isFeb
) {
2407 // Use positive number if possible
2408 startDayNum
= MONTHLENGTH
[month
] + dayOfMonth
+ 1;
2410 beginRRULE(writer
, month
, status
);
2411 if (U_FAILURE(status
)) {
2414 writer
.write(ICAL_BYDAY
);
2415 writer
.write(EQUALS_SIGN
);
2416 writer
.write(ICAL_DOW_NAMES
[dayOfWeek
- 1]); // SU, MO, TU...
2417 writer
.write(SEMICOLON
);
2418 writer
.write(ICAL_BYMONTHDAY
);
2419 writer
.write(EQUALS_SIGN
);
2422 appendAsciiDigits(startDayNum
, 0, dstr
);
2424 for (int32_t i
= 1; i
< numDays
; i
++) {
2425 writer
.write(COMMA
);
2427 appendAsciiDigits(startDayNum
+ i
, 0, dstr
);
2431 if (untilTime
!= MAX_MILLIS
) {
2432 appendUNTIL(writer
, getDateTimeString(untilTime
+ fromOffset
, dstr
), status
);
2433 if (U_FAILURE(status
)) {
2437 writer
.write(ICAL_NEWLINE
);
2441 * Write start times defined by a DOW_LEQ_DOM rule using VTIMEZONE RRULE
2444 VTimeZone::writeZonePropsByDOW_LEQ_DOM(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2445 int32_t fromOffset
, int32_t toOffset
,
2446 int32_t month
, int32_t dayOfMonth
, int32_t dayOfWeek
,
2447 UDate startTime
, UDate untilTime
, UErrorCode
& status
) const {
2448 if (U_FAILURE(status
)) {
2451 // Check if this rule can be converted to DOW rule
2452 if (dayOfMonth%7
== 0) {
2453 // Can be represented by DOW rule
2454 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2455 month
, dayOfMonth
/7, dayOfWeek
, startTime
, untilTime
, status
);
2456 } else if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - dayOfMonth
)%7
== 0){
2457 // Can be represented by DOW rule with negative week number
2458 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2459 month
, -1*((MONTHLENGTH
[month
] - dayOfMonth
)/7 + 1), dayOfWeek
, startTime
, untilTime
, status
);
2460 } else if (month
== UCAL_FEBRUARY
&& dayOfMonth
== 29) {
2461 // Specical case for February
2462 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2463 UCAL_FEBRUARY
, -1, dayOfWeek
, startTime
, untilTime
, status
);
2465 // Otherwise, convert this to DOW_GEQ_DOM rule
2466 writeZonePropsByDOW_GEQ_DOM(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2467 month
, dayOfMonth
- 6, dayOfWeek
, startTime
, untilTime
, status
);
2472 * Write the final time zone rule using RRULE, with no UNTIL attribute
2475 VTimeZone::writeFinalRule(VTZWriter
& writer
, UBool isDst
, const AnnualTimeZoneRule
* rule
,
2476 int32_t fromRawOffset
, int32_t fromDSTSavings
,
2477 UDate startTime
, UErrorCode
& status
) const {
2478 if (U_FAILURE(status
)) {
2481 UBool modifiedRule
= TRUE
;
2482 const DateTimeRule
*dtrule
= toWallTimeRule(rule
->getRule(), fromRawOffset
, fromDSTSavings
);
2483 if (dtrule
== NULL
) {
2484 modifiedRule
= FALSE
;
2485 dtrule
= rule
->getRule();
2488 // If the rule's mills in a day is out of range, adjust start time.
2489 // Olson tzdata supports 24:00 of a day, but VTIMEZONE does not.
2490 // See ticket#7008/#7518
2492 int32_t timeInDay
= dtrule
->getRuleMillisInDay();
2493 if (timeInDay
< 0) {
2494 startTime
= startTime
+ (0 - timeInDay
);
2495 } else if (timeInDay
>= U_MILLIS_PER_DAY
) {
2496 startTime
= startTime
- (timeInDay
- (U_MILLIS_PER_DAY
- 1));
2499 int32_t toOffset
= rule
->getRawOffset() + rule
->getDSTSavings();
2501 rule
->getName(name
);
2502 switch (dtrule
->getDateRuleType()) {
2503 case DateTimeRule::DOM
:
2504 writeZonePropsByDOM(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2505 dtrule
->getRuleMonth(), dtrule
->getRuleDayOfMonth(), startTime
, MAX_MILLIS
, status
);
2507 case DateTimeRule::DOW
:
2508 writeZonePropsByDOW(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2509 dtrule
->getRuleMonth(), dtrule
->getRuleWeekInMonth(), dtrule
->getRuleDayOfWeek(), startTime
, MAX_MILLIS
, status
);
2511 case DateTimeRule::DOW_GEQ_DOM
:
2512 writeZonePropsByDOW_GEQ_DOM(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2513 dtrule
->getRuleMonth(), dtrule
->getRuleDayOfMonth(), dtrule
->getRuleDayOfWeek(), startTime
, MAX_MILLIS
, status
);
2515 case DateTimeRule::DOW_LEQ_DOM
:
2516 writeZonePropsByDOW_LEQ_DOM(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2517 dtrule
->getRuleMonth(), dtrule
->getRuleDayOfMonth(), dtrule
->getRuleDayOfWeek(), startTime
, MAX_MILLIS
, status
);
2526 * Write the opening section of zone properties
2529 VTimeZone::beginZoneProps(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2530 int32_t fromOffset
, int32_t toOffset
, UDate startTime
, UErrorCode
& status
) const {
2531 if (U_FAILURE(status
)) {
2534 writer
.write(ICAL_BEGIN
);
2535 writer
.write(COLON
);
2537 writer
.write(ICAL_DAYLIGHT
);
2539 writer
.write(ICAL_STANDARD
);
2541 writer
.write(ICAL_NEWLINE
);
2546 writer
.write(ICAL_TZOFFSETTO
);
2547 writer
.write(COLON
);
2548 millisToOffset(toOffset
, dstr
);
2550 writer
.write(ICAL_NEWLINE
);
2553 writer
.write(ICAL_TZOFFSETFROM
);
2554 writer
.write(COLON
);
2555 millisToOffset(fromOffset
, dstr
);
2557 writer
.write(ICAL_NEWLINE
);
2560 writer
.write(ICAL_TZNAME
);
2561 writer
.write(COLON
);
2562 writer
.write(zonename
);
2563 writer
.write(ICAL_NEWLINE
);
2566 writer
.write(ICAL_DTSTART
);
2567 writer
.write(COLON
);
2568 writer
.write(getDateTimeString(startTime
+ fromOffset
, dstr
));
2569 writer
.write(ICAL_NEWLINE
);
2573 * Writes the closing section of zone properties
2576 VTimeZone::endZoneProps(VTZWriter
& writer
, UBool isDst
, UErrorCode
& status
) const {
2577 if (U_FAILURE(status
)) {
2580 // END:STANDARD or END:DAYLIGHT
2581 writer
.write(ICAL_END
);
2582 writer
.write(COLON
);
2584 writer
.write(ICAL_DAYLIGHT
);
2586 writer
.write(ICAL_STANDARD
);
2588 writer
.write(ICAL_NEWLINE
);
2592 * Write the beggining part of RRULE line
2595 VTimeZone::beginRRULE(VTZWriter
& writer
, int32_t month
, UErrorCode
& status
) const {
2596 if (U_FAILURE(status
)) {
2600 writer
.write(ICAL_RRULE
);
2601 writer
.write(COLON
);
2602 writer
.write(ICAL_FREQ
);
2603 writer
.write(EQUALS_SIGN
);
2604 writer
.write(ICAL_YEARLY
);
2605 writer
.write(SEMICOLON
);
2606 writer
.write(ICAL_BYMONTH
);
2607 writer
.write(EQUALS_SIGN
);
2608 appendAsciiDigits(month
+ 1, 0, dstr
);
2610 writer
.write(SEMICOLON
);
2614 * Append the UNTIL attribute after RRULE line
2617 VTimeZone::appendUNTIL(VTZWriter
& writer
, const UnicodeString
& until
, UErrorCode
& status
) const {
2618 if (U_FAILURE(status
)) {
2621 if (until
.length() > 0) {
2622 writer
.write(SEMICOLON
);
2623 writer
.write(ICAL_UNTIL
);
2624 writer
.write(EQUALS_SIGN
);
2625 writer
.write(until
);
2631 #endif /* #if !UCONFIG_NO_FORMATTING */