2 *******************************************************************************
3 * Copyright (C) 2007-2016, International Business Machines Corporation and
4 * others. All Rights Reserved.
5 *******************************************************************************
8 #include "utypeinfo.h" // for 'typeid' to work
10 #include "unicode/utypes.h"
12 #if !UCONFIG_NO_FORMATTING
14 #include "unicode/vtzone.h"
15 #include "unicode/rbtz.h"
16 #include "unicode/ucal.h"
17 #include "unicode/ures.h"
25 // This is the deleter that will be use to remove TimeZoneRule
27 static void U_CALLCONV
28 deleteTimeZoneRule(void* obj
) {
29 delete (TimeZoneRule
*) obj
;
33 // Smybol characters used by RFC2445 VTIMEZONE
34 static const UChar COLON
= 0x3A; /* : */
35 static const UChar SEMICOLON
= 0x3B; /* ; */
36 static const UChar EQUALS_SIGN
= 0x3D; /* = */
37 static const UChar COMMA
= 0x2C; /* , */
38 static const UChar PLUS
= 0x2B; /* + */
39 static const UChar MINUS
= 0x2D; /* - */
41 // RFC2445 VTIMEZONE tokens
42 static const UChar ICAL_BEGIN_VTIMEZONE
[] = {0x42, 0x45, 0x47, 0x49, 0x4E, 0x3A, 0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "BEGIN:VTIMEZONE" */
43 static const UChar ICAL_END_VTIMEZONE
[] = {0x45, 0x4E, 0x44, 0x3A, 0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "END:VTIMEZONE" */
44 static const UChar ICAL_BEGIN
[] = {0x42, 0x45, 0x47, 0x49, 0x4E, 0}; /* "BEGIN" */
45 static const UChar ICAL_END
[] = {0x45, 0x4E, 0x44, 0}; /* "END" */
46 static const UChar ICAL_VTIMEZONE
[] = {0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "VTIMEZONE" */
47 static const UChar ICAL_TZID
[] = {0x54, 0x5A, 0x49, 0x44, 0}; /* "TZID" */
48 static const UChar ICAL_STANDARD
[] = {0x53, 0x54, 0x41, 0x4E, 0x44, 0x41, 0x52, 0x44, 0}; /* "STANDARD" */
49 static const UChar ICAL_DAYLIGHT
[] = {0x44, 0x41, 0x59, 0x4C, 0x49, 0x47, 0x48, 0x54, 0}; /* "DAYLIGHT" */
50 static const UChar ICAL_DTSTART
[] = {0x44, 0x54, 0x53, 0x54, 0x41, 0x52, 0x54, 0}; /* "DTSTART" */
51 static const UChar ICAL_TZOFFSETFROM
[] = {0x54, 0x5A, 0x4F, 0x46, 0x46, 0x53, 0x45, 0x54, 0x46, 0x52, 0x4F, 0x4D, 0}; /* "TZOFFSETFROM" */
52 static const UChar ICAL_TZOFFSETTO
[] = {0x54, 0x5A, 0x4F, 0x46, 0x46, 0x53, 0x45, 0x54, 0x54, 0x4F, 0}; /* "TZOFFSETTO" */
53 static const UChar ICAL_RDATE
[] = {0x52, 0x44, 0x41, 0x54, 0x45, 0}; /* "RDATE" */
54 static const UChar ICAL_RRULE
[] = {0x52, 0x52, 0x55, 0x4C, 0x45, 0}; /* "RRULE" */
55 static const UChar ICAL_TZNAME
[] = {0x54, 0x5A, 0x4E, 0x41, 0x4D, 0x45, 0}; /* "TZNAME" */
56 static const UChar ICAL_TZURL
[] = {0x54, 0x5A, 0x55, 0x52, 0x4C, 0}; /* "TZURL" */
57 static const UChar ICAL_LASTMOD
[] = {0x4C, 0x41, 0x53, 0x54, 0x2D, 0x4D, 0x4F, 0x44, 0x49, 0x46, 0x49, 0x45, 0x44, 0}; /* "LAST-MODIFIED" */
59 static const UChar ICAL_FREQ
[] = {0x46, 0x52, 0x45, 0x51, 0}; /* "FREQ" */
60 static const UChar ICAL_UNTIL
[] = {0x55, 0x4E, 0x54, 0x49, 0x4C, 0}; /* "UNTIL" */
61 static const UChar ICAL_YEARLY
[] = {0x59, 0x45, 0x41, 0x52, 0x4C, 0x59, 0}; /* "YEARLY" */
62 static const UChar ICAL_BYMONTH
[] = {0x42, 0x59, 0x4D, 0x4F, 0x4E, 0x54, 0x48, 0}; /* "BYMONTH" */
63 static const UChar ICAL_BYDAY
[] = {0x42, 0x59, 0x44, 0x41, 0x59, 0}; /* "BYDAY" */
64 static const UChar ICAL_BYMONTHDAY
[] = {0x42, 0x59, 0x4D, 0x4F, 0x4E, 0x54, 0x48, 0x44, 0x41, 0x59, 0}; /* "BYMONTHDAY" */
66 static const UChar ICAL_NEWLINE
[] = {0x0D, 0x0A, 0}; /* CRLF */
68 static const UChar ICAL_DOW_NAMES
[7][3] = {
69 {0x53, 0x55, 0}, /* "SU" */
70 {0x4D, 0x4F, 0}, /* "MO" */
71 {0x54, 0x55, 0}, /* "TU" */
72 {0x57, 0x45, 0}, /* "WE" */
73 {0x54, 0x48, 0}, /* "TH" */
74 {0x46, 0x52, 0}, /* "FR" */
75 {0x53, 0x41, 0} /* "SA" */};
77 // Month length for non-leap year
78 static const int32_t MONTHLENGTH
[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
80 // ICU custom property
81 static const UChar ICU_TZINFO_PROP
[] = {0x58, 0x2D, 0x54, 0x5A, 0x49, 0x4E, 0x46, 0x4F, 0x3A, 0}; /* "X-TZINFO:" */
82 static const UChar ICU_TZINFO_PARTIAL
[] = {0x2F, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6C, 0x40, 0}; /* "/Partial@" */
83 static const UChar ICU_TZINFO_SIMPLE
[] = {0x2F, 0x53, 0x69, 0x6D, 0x70, 0x6C, 0x65, 0x40, 0}; /* "/Simple@" */
87 * Simple fixed digit ASCII number to integer converter
89 static int32_t parseAsciiDigits(const UnicodeString
& str
, int32_t start
, int32_t length
, UErrorCode
& status
) {
90 if (U_FAILURE(status
)) {
93 if (length
<= 0 || str
.length() < start
|| (start
+ length
) > str
.length()) {
94 status
= U_INVALID_FORMAT_ERROR
;
98 if (str
.charAt(start
) == PLUS
) {
101 } else if (str
.charAt(start
) == MINUS
) {
107 for (int32_t i
= 0; i
< length
; i
++) {
108 int32_t digit
= str
.charAt(start
+ i
) - 0x0030;
109 if (digit
< 0 || digit
> 9) {
110 status
= U_INVALID_FORMAT_ERROR
;
113 num
= 10 * num
+ digit
;
118 static UnicodeString
& appendAsciiDigits(int32_t number
, uint8_t length
, UnicodeString
& str
) {
119 UBool negative
= FALSE
;
120 int32_t digits
[10]; // max int32_t is 10 decimal digits
128 length
= length
> 10 ? 10 : length
;
133 digits
[i
++] = number
% 10;
135 } while (number
!= 0);
139 for (i
= 0; i
< length
; i
++) {
140 digits
[i
] = number
% 10;
147 for (i
= length
- 1; i
>= 0; i
--) {
148 str
.append((UChar
)(digits
[i
] + 0x0030));
153 static UnicodeString
& appendMillis(UDate date
, UnicodeString
& str
) {
154 UBool negative
= FALSE
;
155 int32_t digits
[20]; // max int64_t is 20 decimal digits
159 if (date
< MIN_MILLIS
) {
160 number
= (int64_t)MIN_MILLIS
;
161 } else if (date
> MAX_MILLIS
) {
162 number
= (int64_t)MAX_MILLIS
;
164 number
= (int64_t)date
;
172 digits
[i
++] = (int32_t)(number
% 10);
174 } while (number
!= 0);
181 str
.append((UChar
)(digits
[i
--] + 0x0030));
187 * Convert date/time to RFC2445 Date-Time form #1 DATE WITH LOCAL TIME
189 static UnicodeString
& getDateTimeString(UDate time
, UnicodeString
& str
) {
190 int32_t year
, month
, dom
, dow
, doy
, mid
;
191 Grego::timeToFields(time
, year
, month
, dom
, dow
, doy
, mid
);
194 appendAsciiDigits(year
, 4, str
);
195 appendAsciiDigits(month
+ 1, 2, str
);
196 appendAsciiDigits(dom
, 2, str
);
197 str
.append((UChar
)0x0054 /*'T'*/);
200 int32_t hour
= t
/ U_MILLIS_PER_HOUR
;
201 t
%= U_MILLIS_PER_HOUR
;
202 int32_t min
= t
/ U_MILLIS_PER_MINUTE
;
203 t
%= U_MILLIS_PER_MINUTE
;
204 int32_t sec
= t
/ U_MILLIS_PER_SECOND
;
206 appendAsciiDigits(hour
, 2, str
);
207 appendAsciiDigits(min
, 2, str
);
208 appendAsciiDigits(sec
, 2, str
);
213 * Convert date/time to RFC2445 Date-Time form #2 DATE WITH UTC TIME
215 static UnicodeString
& getUTCDateTimeString(UDate time
, UnicodeString
& str
) {
216 getDateTimeString(time
, str
);
217 str
.append((UChar
)0x005A /*'Z'*/);
222 * Parse RFC2445 Date-Time form #1 DATE WITH LOCAL TIME and
223 * #2 DATE WITH UTC TIME
225 static UDate
parseDateTimeString(const UnicodeString
& str
, int32_t offset
, UErrorCode
& status
) {
226 if (U_FAILURE(status
)) {
230 int32_t year
= 0, month
= 0, day
= 0, hour
= 0, min
= 0, sec
= 0;
232 UBool isValid
= FALSE
;
234 int length
= str
.length();
235 if (length
!= 15 && length
!= 16) {
236 // FORM#1 15 characters, such as "20060317T142115"
237 // FORM#2 16 characters, such as "20060317T142115Z"
240 if (str
.charAt(8) != 0x0054) {
241 // charcter "T" must be used for separating date and time
245 if (str
.charAt(15) != 0x005A) {
252 year
= parseAsciiDigits(str
, 0, 4, status
);
253 month
= parseAsciiDigits(str
, 4, 2, status
) - 1; // 0-based
254 day
= parseAsciiDigits(str
, 6, 2, status
);
255 hour
= parseAsciiDigits(str
, 9, 2, status
);
256 min
= parseAsciiDigits(str
, 11, 2, status
);
257 sec
= parseAsciiDigits(str
, 13, 2, status
);
259 if (U_FAILURE(status
)) {
264 int32_t maxDayOfMonth
= Grego::monthLength(year
, month
);
265 if (year
< 0 || month
< 0 || month
> 11 || day
< 1 || day
> maxDayOfMonth
||
266 hour
< 0 || hour
>= 24 || min
< 0 || min
>= 60 || sec
< 0 || sec
>= 60) {
274 status
= U_INVALID_FORMAT_ERROR
;
277 // Calculate the time
278 UDate time
= Grego::fieldsToDay(year
, month
, day
) * U_MILLIS_PER_DAY
;
279 time
+= (hour
* U_MILLIS_PER_HOUR
+ min
* U_MILLIS_PER_MINUTE
+ sec
* U_MILLIS_PER_SECOND
);
287 * Convert RFC2445 utc-offset string to milliseconds
289 static int32_t offsetStrToMillis(const UnicodeString
& str
, UErrorCode
& status
) {
290 if (U_FAILURE(status
)) {
294 UBool isValid
= FALSE
;
295 int32_t sign
= 0, hour
= 0, min
= 0, sec
= 0;
298 int length
= str
.length();
299 if (length
!= 5 && length
!= 7) {
300 // utf-offset must be 5 or 7 characters
304 UChar s
= str
.charAt(0);
307 } else if (s
== MINUS
) {
310 // utf-offset must start with "+" or "-"
313 hour
= parseAsciiDigits(str
, 1, 2, status
);
314 min
= parseAsciiDigits(str
, 3, 2, status
);
316 sec
= parseAsciiDigits(str
, 5, 2, status
);
318 if (U_FAILURE(status
)) {
325 status
= U_INVALID_FORMAT_ERROR
;
328 int32_t millis
= sign
* ((hour
* 60 + min
) * 60 + sec
) * 1000;
333 * Convert milliseconds to RFC2445 utc-offset string
335 static void millisToOffset(int32_t millis
, UnicodeString
& str
) {
343 int32_t hour
, min
, sec
;
344 int32_t t
= millis
/ 1000;
351 appendAsciiDigits(hour
, 2, str
);
352 appendAsciiDigits(min
, 2, str
);
353 appendAsciiDigits(sec
, 2, str
);
357 * Create a default TZNAME from TZID
359 static void getDefaultTZName(const UnicodeString tzid
, UBool isDST
, UnicodeString
& zonename
) {
362 zonename
+= UNICODE_STRING_SIMPLE("(DST)");
364 zonename
+= UNICODE_STRING_SIMPLE("(STD)");
369 * Parse individual RRULE
373 * month calculated by BYMONTH-1, or -1 when not found
374 * dow day of week in BYDAY, or 0 when not found
375 * wim day of week ordinal number in BYDAY, or 0 when not found
376 * dom an array of day of month
377 * domCount number of availble days in dom (domCount is specifying the size of dom on input)
378 * until time defined by UNTIL attribute or MIN_MILLIS if not available
380 static void parseRRULE(const UnicodeString
& rrule
, int32_t& month
, int32_t& dow
, int32_t& wim
,
381 int32_t* dom
, int32_t& domCount
, UDate
& until
, UErrorCode
& status
) {
382 if (U_FAILURE(status
)) {
392 UBool yearly
= FALSE
;
393 //UBool parseError = FALSE;
395 int32_t prop_start
= 0;
397 UnicodeString prop
, attr
, value
;
398 UBool nextProp
= TRUE
;
401 prop_end
= rrule
.indexOf(SEMICOLON
, prop_start
);
402 if (prop_end
== -1) {
403 prop
.setTo(rrule
, prop_start
);
406 prop
.setTo(rrule
, prop_start
, prop_end
- prop_start
);
407 prop_start
= prop_end
+ 1;
409 int32_t eql
= prop
.indexOf(EQUALS_SIGN
);
411 attr
.setTo(prop
, 0, eql
);
412 value
.setTo(prop
, eql
+ 1);
414 goto rruleParseError
;
417 if (attr
.compare(ICAL_FREQ
, -1) == 0) {
418 // only support YEARLY frequency type
419 if (value
.compare(ICAL_YEARLY
, -1) == 0) {
422 goto rruleParseError
;
424 } else if (attr
.compare(ICAL_UNTIL
, -1) == 0) {
425 // ISO8601 UTC format, for example, "20060315T020000Z"
426 until
= parseDateTimeString(value
, 0, status
);
427 if (U_FAILURE(status
)) {
428 goto rruleParseError
;
430 } else if (attr
.compare(ICAL_BYMONTH
, -1) == 0) {
431 // Note: BYMONTH may contain multiple months, but only single month make sense for
432 // VTIMEZONE property.
433 if (value
.length() > 2) {
434 goto rruleParseError
;
436 month
= parseAsciiDigits(value
, 0, value
.length(), status
) - 1;
437 if (U_FAILURE(status
) || month
< 0 || month
>= 12) {
438 goto rruleParseError
;
440 } else if (attr
.compare(ICAL_BYDAY
, -1) == 0) {
441 // Note: BYDAY may contain multiple day of week separated by comma. It is unlikely used for
442 // VTIMEZONE property. We do not support the case.
444 // 2-letter format is used just for representing a day of week, for example, "SU" for Sunday
445 // 3 or 4-letter format is used for represeinging Nth day of week, for example, "-1SA" for last Saturday
446 int32_t length
= value
.length();
447 if (length
< 2 || length
> 4) {
448 goto rruleParseError
;
453 if (value
.charAt(0) == PLUS
) {
455 } else if (value
.charAt(0) == MINUS
) {
457 } else if (length
== 4) {
458 goto rruleParseError
;
460 int32_t n
= parseAsciiDigits(value
, length
- 3, 1, status
);
461 if (U_FAILURE(status
) || n
== 0 || n
> 4) {
462 goto rruleParseError
;
465 value
.remove(0, length
- 2);
468 for (wday
= 0; wday
< 7; wday
++) {
469 if (value
.compare(ICAL_DOW_NAMES
[wday
], 2) == 0) {
474 // Sunday(1) - Saturday(7)
477 goto rruleParseError
;
479 } else if (attr
.compare(ICAL_BYMONTHDAY
, -1) == 0) {
480 // Note: BYMONTHDAY may contain multiple days delimitted by comma
482 // A value of BYMONTHDAY could be negative, for example, -1 means
483 // the last day in a month
485 int32_t dom_start
= 0;
487 UBool nextDOM
= TRUE
;
489 dom_end
= value
.indexOf(COMMA
, dom_start
);
491 dom_end
= value
.length();
494 if (dom_idx
< domCount
) {
495 dom
[dom_idx
] = parseAsciiDigits(value
, dom_start
, dom_end
- dom_start
, status
);
496 if (U_FAILURE(status
)) {
497 goto rruleParseError
;
501 status
= U_BUFFER_OVERFLOW_ERROR
;
502 goto rruleParseError
;
504 dom_start
= dom_end
+ 1;
510 // FREQ=YEARLY must be set
511 goto rruleParseError
;
513 // Set actual number of parsed DOM (ICAL_BYMONTHDAY)
518 if (U_SUCCESS(status
)) {
520 status
= U_INVALID_FORMAT_ERROR
;
524 static TimeZoneRule
* createRuleByRRULE(const UnicodeString
& zonename
, int rawOffset
, int dstSavings
, UDate start
,
525 UVector
* dates
, int fromOffset
, UErrorCode
& status
) {
526 if (U_FAILURE(status
)) {
529 if (dates
== NULL
|| dates
->size() == 0) {
530 status
= U_ILLEGAL_ARGUMENT_ERROR
;
535 DateTimeRule
*adtr
= NULL
;
537 // Parse the first rule
538 UnicodeString rrule
= *((UnicodeString
*)dates
->elementAt(0));
539 int32_t month
, dayOfWeek
, nthDayOfWeek
, dayOfMonth
= 0;
541 int32_t daysCount
= UPRV_LENGTHOF(days
);
544 parseRRULE(rrule
, month
, dayOfWeek
, nthDayOfWeek
, days
, daysCount
, until
, status
);
545 if (U_FAILURE(status
)) {
549 if (dates
->size() == 1) {
552 // Multiple BYMONTHDAY values
553 if (daysCount
!= 7 || month
== -1 || dayOfWeek
== 0) {
554 // Only support the rule using 7 continuous days
555 // BYMONTH and BYDAY must be set at the same time
556 goto unsupportedRRule
;
558 int32_t firstDay
= 31; // max possible number of dates in a month
559 for (i
= 0; i
< 7; i
++) {
560 // Resolve negative day numbers. A negative day number should
561 // not be used in February, but if we see such case, we use 28
564 days
[i
] = MONTHLENGTH
[month
] + days
[i
] + 1;
566 if (days
[i
] < firstDay
) {
570 // Make sure days are continuous
571 for (i
= 1; i
< 7; i
++) {
573 for (j
= 0; j
< 7; j
++) {
574 if (days
[j
] == firstDay
+ i
) {
580 // days are not continuous
581 goto unsupportedRRule
;
584 // Use DOW_GEQ_DOM rule with firstDay as the start date
585 dayOfMonth
= firstDay
;
588 // Check if BYMONTH + BYMONTHDAY + BYDAY rule with multiple RRULE lines.
589 // Otherwise, not supported.
590 if (month
== -1 || dayOfWeek
== 0 || daysCount
== 0) {
591 // This is not the case
592 goto unsupportedRRule
;
594 // Parse the rest of rules if number of rules is not exceeding 7.
595 // We can only support 7 continuous days starting from a day of month.
596 if (dates
->size() > 7) {
597 goto unsupportedRRule
;
600 // Note: To check valid date range across multiple rule is a little
601 // bit complicated. For now, this code is not doing strict range
602 // checking across month boundary
604 int32_t earliestMonth
= month
;
605 int32_t earliestDay
= 31;
606 for (i
= 0; i
< daysCount
; i
++) {
607 int32_t dom
= days
[i
];
608 dom
= dom
> 0 ? dom
: MONTHLENGTH
[month
] + dom
+ 1;
609 earliestDay
= dom
< earliestDay
? dom
: earliestDay
;
612 int32_t anotherMonth
= -1;
613 for (i
= 1; i
< dates
->size(); i
++) {
614 rrule
= *((UnicodeString
*)dates
->elementAt(i
));
616 int32_t tmp_month
, tmp_dayOfWeek
, tmp_nthDayOfWeek
;
618 int32_t tmp_daysCount
= UPRV_LENGTHOF(tmp_days
);
619 parseRRULE(rrule
, tmp_month
, tmp_dayOfWeek
, tmp_nthDayOfWeek
, tmp_days
, tmp_daysCount
, tmp_until
, status
);
620 if (U_FAILURE(status
)) {
623 // If UNTIL is newer than previous one, use the one
624 if (tmp_until
> until
) {
628 // Check if BYMONTH + BYMONTHDAY + BYDAY rule
629 if (tmp_month
== -1 || tmp_dayOfWeek
== 0 || tmp_daysCount
== 0) {
630 goto unsupportedRRule
;
632 // Count number of BYMONTHDAY
633 if (daysCount
+ tmp_daysCount
> 7) {
634 // We cannot support BYMONTHDAY more than 7
635 goto unsupportedRRule
;
637 // Check if the same BYDAY is used. Otherwise, we cannot
639 if (tmp_dayOfWeek
!= dayOfWeek
) {
640 goto unsupportedRRule
;
642 // Check if the month is same or right next to the primary month
643 if (tmp_month
!= month
) {
644 if (anotherMonth
== -1) {
645 int32_t diff
= tmp_month
- month
;
646 if (diff
== -11 || diff
== -1) {
648 anotherMonth
= tmp_month
;
649 earliestMonth
= anotherMonth
;
650 // Reset earliest day
652 } else if (diff
== 11 || diff
== 1) {
654 anotherMonth
= tmp_month
;
656 // The day range cannot exceed more than 2 months
657 goto unsupportedRRule
;
659 } else if (tmp_month
!= month
&& tmp_month
!= anotherMonth
) {
660 // The day range cannot exceed more than 2 months
661 goto unsupportedRRule
;
664 // If ealier month, go through days to find the earliest day
665 if (tmp_month
== earliestMonth
) {
666 for (j
= 0; j
< tmp_daysCount
; j
++) {
667 tmp_days
[j
] = tmp_days
[j
] > 0 ? tmp_days
[j
] : MONTHLENGTH
[tmp_month
] + tmp_days
[j
] + 1;
668 earliestDay
= tmp_days
[j
] < earliestDay
? tmp_days
[j
] : earliestDay
;
671 daysCount
+= tmp_daysCount
;
673 if (daysCount
!= 7) {
674 // Number of BYMONTHDAY entries must be 7
675 goto unsupportedRRule
;
677 month
= earliestMonth
;
678 dayOfMonth
= earliestDay
;
681 // Calculate start/end year and missing fields
682 int32_t startYear
, startMonth
, startDOM
, startDOW
, startDOY
, startMID
;
683 Grego::timeToFields(start
+ fromOffset
, startYear
, startMonth
, startDOM
,
684 startDOW
, startDOY
, startMID
);
686 // If BYMONTH is not set, use the month of DTSTART
689 if (dayOfWeek
== 0 && nthDayOfWeek
== 0 && dayOfMonth
== 0) {
690 // If only YEARLY is set, use the day of DTSTART as BYMONTHDAY
691 dayOfMonth
= startDOM
;
695 if (until
!= MIN_MILLIS
) {
696 int32_t endMonth
, endDOM
, endDOW
, endDOY
, endMID
;
697 Grego::timeToFields(until
, endYear
, endMonth
, endDOM
, endDOW
, endDOY
, endMID
);
699 endYear
= AnnualTimeZoneRule::MAX_YEAR
;
702 // Create the AnnualDateTimeRule
703 if (dayOfWeek
== 0 && nthDayOfWeek
== 0 && dayOfMonth
!= 0) {
704 // Day in month rule, for example, 15th day in the month
705 adtr
= new DateTimeRule(month
, dayOfMonth
, startMID
, DateTimeRule::WALL_TIME
);
706 } else if (dayOfWeek
!= 0 && nthDayOfWeek
!= 0 && dayOfMonth
== 0) {
707 // Nth day of week rule, for example, last Sunday
708 adtr
= new DateTimeRule(month
, nthDayOfWeek
, dayOfWeek
, startMID
, DateTimeRule::WALL_TIME
);
709 } else if (dayOfWeek
!= 0 && nthDayOfWeek
== 0 && dayOfMonth
!= 0) {
710 // First day of week after day of month rule, for example,
711 // first Sunday after 15th day in the month
712 adtr
= new DateTimeRule(month
, dayOfMonth
, dayOfWeek
, TRUE
, startMID
, DateTimeRule::WALL_TIME
);
715 goto unsupportedRRule
;
717 return new AnnualTimeZoneRule(zonename
, rawOffset
, dstSavings
, adtr
, startYear
, endYear
);
720 status
= U_INVALID_STATE_ERROR
;
725 * Create a TimeZoneRule by the RDATE definition
727 static TimeZoneRule
* createRuleByRDATE(const UnicodeString
& zonename
, int32_t rawOffset
, int32_t dstSavings
,
728 UDate start
, UVector
* dates
, int32_t fromOffset
, UErrorCode
& status
) {
729 if (U_FAILURE(status
)) {
732 TimeArrayTimeZoneRule
*retVal
= NULL
;
733 if (dates
== NULL
|| dates
->size() == 0) {
734 // When no RDATE line is provided, use start (DTSTART)
735 // as the transition time
736 retVal
= new TimeArrayTimeZoneRule(zonename
, rawOffset
, dstSavings
,
737 &start
, 1, DateTimeRule::UTC_TIME
);
739 // Create an array of transition times
740 int32_t size
= dates
->size();
741 UDate
* times
= (UDate
*)uprv_malloc(sizeof(UDate
) * size
);
743 status
= U_MEMORY_ALLOCATION_ERROR
;
746 for (int32_t i
= 0; i
< size
; i
++) {
747 UnicodeString
*datestr
= (UnicodeString
*)dates
->elementAt(i
);
748 times
[i
] = parseDateTimeString(*datestr
, fromOffset
, status
);
749 if (U_FAILURE(status
)) {
754 retVal
= new TimeArrayTimeZoneRule(zonename
, rawOffset
, dstSavings
,
755 times
, size
, DateTimeRule::UTC_TIME
);
762 * Check if the DOW rule specified by month, weekInMonth and dayOfWeek is equivalent
763 * to the DateTimerule.
765 static UBool
isEquivalentDateRule(int32_t month
, int32_t weekInMonth
, int32_t dayOfWeek
, const DateTimeRule
*dtrule
) {
766 if (month
!= dtrule
->getRuleMonth() || dayOfWeek
!= dtrule
->getRuleDayOfWeek()) {
769 if (dtrule
->getTimeRuleType() != DateTimeRule::WALL_TIME
) {
770 // Do not try to do more intelligent comparison for now.
773 if (dtrule
->getDateRuleType() == DateTimeRule::DOW
774 && dtrule
->getRuleWeekInMonth() == weekInMonth
) {
777 int32_t ruleDOM
= dtrule
->getRuleDayOfMonth();
778 if (dtrule
->getDateRuleType() == DateTimeRule::DOW_GEQ_DOM
) {
779 if (ruleDOM%7
== 1 && (ruleDOM
+ 6)/7 == weekInMonth
) {
782 if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - ruleDOM
)%7
== 6
783 && weekInMonth
== -1*((MONTHLENGTH
[month
]-ruleDOM
+1)/7)) {
787 if (dtrule
->getDateRuleType() == DateTimeRule::DOW_LEQ_DOM
) {
788 if (ruleDOM%7
== 0 && ruleDOM
/7 == weekInMonth
) {
791 if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - ruleDOM
)%7
== 0
792 && weekInMonth
== -1*((MONTHLENGTH
[month
] - ruleDOM
)/7 + 1)) {
800 * Convert the rule to its equivalent rule using WALL_TIME mode.
801 * This function returns NULL when the specified DateTimeRule is already
802 * using WALL_TIME mode.
804 static DateTimeRule
* toWallTimeRule(const DateTimeRule
* rule
, int32_t rawOffset
, int32_t dstSavings
) {
805 if (rule
->getTimeRuleType() == DateTimeRule::WALL_TIME
) {
808 int32_t wallt
= rule
->getRuleMillisInDay();
809 if (rule
->getTimeRuleType() == DateTimeRule::UTC_TIME
) {
810 wallt
+= (rawOffset
+ dstSavings
);
811 } else if (rule
->getTimeRuleType() == DateTimeRule::STANDARD_TIME
) {
815 int32_t month
= -1, dom
= 0, dow
= 0;
816 DateTimeRule::DateRuleType dtype
;
820 wallt
+= U_MILLIS_PER_DAY
;
821 } else if (wallt
>= U_MILLIS_PER_DAY
) {
823 wallt
-= U_MILLIS_PER_DAY
;
826 month
= rule
->getRuleMonth();
827 dom
= rule
->getRuleDayOfMonth();
828 dow
= rule
->getRuleDayOfWeek();
829 dtype
= rule
->getDateRuleType();
832 if (dtype
== DateTimeRule::DOW
) {
833 // Convert to DOW_GEW_DOM or DOW_LEQ_DOM rule first
834 int32_t wim
= rule
->getRuleWeekInMonth();
836 dtype
= DateTimeRule::DOW_GEQ_DOM
;
837 dom
= 7 * (wim
- 1) + 1;
839 dtype
= DateTimeRule::DOW_LEQ_DOM
;
840 dom
= MONTHLENGTH
[month
] + 7 * (wim
+ 1);
843 // Shift one day before or after
847 month
= month
< UCAL_JANUARY
? UCAL_DECEMBER
: month
;
848 dom
= MONTHLENGTH
[month
];
849 } else if (dom
> MONTHLENGTH
[month
]) {
851 month
= month
> UCAL_DECEMBER
? UCAL_JANUARY
: month
;
854 if (dtype
!= DateTimeRule::DOM
) {
855 // Adjust day of week
857 if (dow
< UCAL_SUNDAY
) {
859 } else if (dow
> UCAL_SATURDAY
) {
865 DateTimeRule
*modifiedRule
;
866 if (dtype
== DateTimeRule::DOM
) {
867 modifiedRule
= new DateTimeRule(month
, dom
, wallt
, DateTimeRule::WALL_TIME
);
869 modifiedRule
= new DateTimeRule(month
, dom
, dow
,
870 (dtype
== DateTimeRule::DOW_GEQ_DOM
), wallt
, DateTimeRule::WALL_TIME
);
876 * Minumum 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/
884 VTZWriter(UnicodeString
& out
);
887 void write(const UnicodeString
& str
);
888 void write(UChar ch
);
889 void write(const UChar
* str
);
890 //void write(const UChar* str, int32_t length);
895 VTZWriter::VTZWriter(UnicodeString
& output
) {
899 VTZWriter::~VTZWriter() {
903 VTZWriter::write(const UnicodeString
& str
) {
908 VTZWriter::write(UChar ch
) {
913 VTZWriter::write(const UChar
* str
) {
914 out
->append(str
, -1);
919 VTZWriter::write(const UChar* str, int32_t length) {
920 out->append(str, length);
926 VTZReader(const UnicodeString
& input
);
931 const UnicodeString
* in
;
935 VTZReader::VTZReader(const UnicodeString
& input
) {
940 VTZReader::~VTZReader() {
944 VTZReader::read(void) {
946 if (index
< in
->length()) {
947 ch
= in
->charAt(index
);
954 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(VTimeZone
)
956 VTimeZone::VTimeZone()
957 : BasicTimeZone(), tz(NULL
), vtzlines(NULL
),
958 lastmod(MAX_MILLIS
) {
961 VTimeZone::VTimeZone(const VTimeZone
& source
)
962 : BasicTimeZone(source
), tz(NULL
), vtzlines(NULL
),
963 tzurl(source
.tzurl
), lastmod(source
.lastmod
),
964 olsonzid(source
.olsonzid
), icutzver(source
.icutzver
) {
965 if (source
.tz
!= NULL
) {
966 tz
= (BasicTimeZone
*)source
.tz
->clone();
968 if (source
.vtzlines
!= NULL
) {
969 UErrorCode status
= U_ZERO_ERROR
;
970 int32_t size
= source
.vtzlines
->size();
971 vtzlines
= new UVector(uprv_deleteUObject
, uhash_compareUnicodeString
, size
, status
);
972 if (U_SUCCESS(status
)) {
973 for (int32_t i
= 0; i
< size
; i
++) {
974 UnicodeString
*line
= (UnicodeString
*)source
.vtzlines
->elementAt(i
);
975 vtzlines
->addElement(line
->clone(), status
);
976 if (U_FAILURE(status
)) {
981 if (U_FAILURE(status
) && vtzlines
!= NULL
) {
987 VTimeZone::~VTimeZone() {
991 if (vtzlines
!= NULL
) {
997 VTimeZone::operator=(const VTimeZone
& right
) {
998 if (this == &right
) {
1001 if (*this != right
) {
1002 BasicTimeZone::operator=(right
);
1007 if (right
.tz
!= NULL
) {
1008 tz
= (BasicTimeZone
*)right
.tz
->clone();
1010 if (vtzlines
!= NULL
) {
1013 if (right
.vtzlines
!= NULL
) {
1014 UErrorCode status
= U_ZERO_ERROR
;
1015 int32_t size
= right
.vtzlines
->size();
1016 vtzlines
= new UVector(uprv_deleteUObject
, uhash_compareUnicodeString
, size
, status
);
1017 if (U_SUCCESS(status
)) {
1018 for (int32_t i
= 0; i
< size
; i
++) {
1019 UnicodeString
*line
= (UnicodeString
*)right
.vtzlines
->elementAt(i
);
1020 vtzlines
->addElement(line
->clone(), status
);
1021 if (U_FAILURE(status
)) {
1026 if (U_FAILURE(status
) && vtzlines
!= NULL
) {
1031 tzurl
= right
.tzurl
;
1032 lastmod
= right
.lastmod
;
1033 olsonzid
= right
.olsonzid
;
1034 icutzver
= right
.icutzver
;
1040 VTimeZone::operator==(const TimeZone
& that
) const {
1041 if (this == &that
) {
1044 if (typeid(*this) != typeid(that
) || !BasicTimeZone::operator==(that
)) {
1047 VTimeZone
*vtz
= (VTimeZone
*)&that
;
1048 if (*tz
== *(vtz
->tz
)
1049 && tzurl
== vtz
->tzurl
1050 && lastmod
== vtz
->lastmod
1051 /* && olsonzid = that.olsonzid */
1052 /* && icutzver = that.icutzver */) {
1059 VTimeZone::operator!=(const TimeZone
& that
) const {
1060 return !operator==(that
);
1064 VTimeZone::createVTimeZoneByID(const UnicodeString
& ID
) {
1065 VTimeZone
*vtz
= new VTimeZone();
1066 vtz
->tz
= (BasicTimeZone
*)TimeZone::createTimeZone(ID
);
1067 vtz
->tz
->getID(vtz
->olsonzid
);
1069 // Set ICU tzdata version
1070 UErrorCode status
= U_ZERO_ERROR
;
1071 UResourceBundle
*bundle
= NULL
;
1072 const UChar
* versionStr
= NULL
;
1074 bundle
= ures_openDirect(NULL
, "zoneinfo64", &status
);
1075 versionStr
= ures_getStringByKey(bundle
, "TZVersion", &len
, &status
);
1076 if (U_SUCCESS(status
)) {
1077 vtz
->icutzver
.setTo(versionStr
, len
);
1084 VTimeZone::createVTimeZoneFromBasicTimeZone(const BasicTimeZone
& basic_time_zone
, UErrorCode
&status
) {
1085 if (U_FAILURE(status
)) {
1088 VTimeZone
*vtz
= new VTimeZone();
1090 status
= U_MEMORY_ALLOCATION_ERROR
;
1093 vtz
->tz
= (BasicTimeZone
*)basic_time_zone
.clone();
1094 if (vtz
->tz
== NULL
) {
1095 status
= U_MEMORY_ALLOCATION_ERROR
;
1099 vtz
->tz
->getID(vtz
->olsonzid
);
1101 // Set ICU tzdata version
1102 UResourceBundle
*bundle
= NULL
;
1103 const UChar
* versionStr
= NULL
;
1105 bundle
= ures_openDirect(NULL
, "zoneinfo64", &status
);
1106 versionStr
= ures_getStringByKey(bundle
, "TZVersion", &len
, &status
);
1107 if (U_SUCCESS(status
)) {
1108 vtz
->icutzver
.setTo(versionStr
, len
);
1115 VTimeZone::createVTimeZone(const UnicodeString
& vtzdata
, UErrorCode
& status
) {
1116 if (U_FAILURE(status
)) {
1119 VTZReader
reader(vtzdata
);
1120 VTimeZone
*vtz
= new VTimeZone();
1121 vtz
->load(reader
, status
);
1122 if (U_FAILURE(status
)) {
1130 VTimeZone::getTZURL(UnicodeString
& url
) const {
1131 if (tzurl
.length() > 0) {
1139 VTimeZone::setTZURL(const UnicodeString
& url
) {
1144 VTimeZone::getLastModified(UDate
& lastModified
) const {
1145 if (lastmod
!= MAX_MILLIS
) {
1146 lastModified
= lastmod
;
1153 VTimeZone::setLastModified(UDate lastModified
) {
1154 lastmod
= lastModified
;
1158 VTimeZone::write(UnicodeString
& result
, UErrorCode
& status
) const {
1160 VTZWriter
writer(result
);
1161 write(writer
, status
);
1165 VTimeZone::write(UDate start
, UnicodeString
& result
, UErrorCode
& status
) const {
1167 VTZWriter
writer(result
);
1168 write(start
, writer
, status
);
1172 VTimeZone::writeSimple(UDate time
, UnicodeString
& result
, UErrorCode
& status
) const {
1174 VTZWriter
writer(result
);
1175 writeSimple(time
, writer
, status
);
1179 VTimeZone::clone(void) const {
1180 return new VTimeZone(*this);
1184 VTimeZone::getOffset(uint8_t era
, int32_t year
, int32_t month
, int32_t day
,
1185 uint8_t dayOfWeek
, int32_t millis
, UErrorCode
& status
) const {
1186 return tz
->getOffset(era
, year
, month
, day
, dayOfWeek
, millis
, status
);
1190 VTimeZone::getOffset(uint8_t era
, int32_t year
, int32_t month
, int32_t day
,
1191 uint8_t dayOfWeek
, int32_t millis
,
1192 int32_t monthLength
, UErrorCode
& status
) const {
1193 return tz
->getOffset(era
, year
, month
, day
, dayOfWeek
, millis
, monthLength
, status
);
1197 VTimeZone::getOffset(UDate date
, UBool local
, int32_t& rawOffset
,
1198 int32_t& dstOffset
, UErrorCode
& status
) const {
1199 return tz
->getOffset(date
, local
, rawOffset
, dstOffset
, status
);
1203 VTimeZone::setRawOffset(int32_t offsetMillis
) {
1204 tz
->setRawOffset(offsetMillis
);
1208 VTimeZone::getRawOffset(void) const {
1209 return tz
->getRawOffset();
1213 VTimeZone::useDaylightTime(void) const {
1214 return tz
->useDaylightTime();
1218 VTimeZone::inDaylightTime(UDate date
, UErrorCode
& status
) const {
1219 return tz
->inDaylightTime(date
, status
);
1223 VTimeZone::hasSameRules(const TimeZone
& other
) const {
1224 return tz
->hasSameRules(other
);
1228 VTimeZone::getNextTransition(UDate base
, UBool inclusive
, TimeZoneTransition
& result
) const {
1229 return tz
->getNextTransition(base
, inclusive
, result
);
1233 VTimeZone::getPreviousTransition(UDate base
, UBool inclusive
, TimeZoneTransition
& result
) const {
1234 return tz
->getPreviousTransition(base
, inclusive
, result
);
1238 VTimeZone::countTransitionRules(UErrorCode
& status
) const {
1239 return tz
->countTransitionRules(status
);
1243 VTimeZone::getTimeZoneRules(const InitialTimeZoneRule
*& initial
,
1244 const TimeZoneRule
* trsrules
[], int32_t& trscount
,
1245 UErrorCode
& status
) const {
1246 tz
->getTimeZoneRules(initial
, trsrules
, trscount
, status
);
1250 VTimeZone::load(VTZReader
& reader
, UErrorCode
& status
) {
1251 vtzlines
= new UVector(uprv_deleteUObject
, uhash_compareUnicodeString
, DEFAULT_VTIMEZONE_LINES
, status
);
1252 if (U_FAILURE(status
)) {
1256 UBool start
= FALSE
;
1257 UBool success
= FALSE
;
1261 UChar ch
= reader
.read();
1264 if (start
&& line
.startsWith(ICAL_END_VTIMEZONE
, -1)) {
1265 vtzlines
->addElement(new UnicodeString(line
), status
);
1266 if (U_FAILURE(status
)) {
1267 goto cleanupVtzlines
;
1274 // CR, must be followed by LF according to the definition in RFC2445
1278 if (ch
!= 0x0009 && ch
!= 0x0020) {
1279 // NOT followed by TAB/SP -> new line
1281 if (line
.length() > 0) {
1282 vtzlines
->addElement(new UnicodeString(line
), status
);
1283 if (U_FAILURE(status
)) {
1284 goto cleanupVtzlines
;
1299 if (line
.startsWith(ICAL_END_VTIMEZONE
, -1)) {
1300 vtzlines
->addElement(new UnicodeString(line
), status
);
1301 if (U_FAILURE(status
)) {
1302 goto cleanupVtzlines
;
1308 if (line
.startsWith(ICAL_BEGIN_VTIMEZONE
, -1)) {
1309 vtzlines
->addElement(new UnicodeString(line
), status
);
1310 if (U_FAILURE(status
)) {
1311 goto cleanupVtzlines
;
1324 if (U_SUCCESS(status
)) {
1325 status
= U_INVALID_STATE_ERROR
;
1327 goto cleanupVtzlines
;
1338 #define INI 0 // Initial state
1339 #define VTZ 1 // In VTIMEZONE
1340 #define TZI 2 // In STANDARD or DAYLIGHT
1342 #define DEF_DSTSAVINGS (60*60*1000)
1343 #define DEF_TZSTARTTIME (0.0)
1346 VTimeZone::parse(UErrorCode
& status
) {
1347 if (U_FAILURE(status
)) {
1350 if (vtzlines
== NULL
|| vtzlines
->size() == 0) {
1351 status
= U_INVALID_STATE_ERROR
;
1354 InitialTimeZoneRule
*initialRule
= NULL
;
1355 RuleBasedTimeZone
*rbtz
= NULL
;
1360 int32_t state
= INI
;
1362 UBool dst
= FALSE
; // current zone type
1363 UnicodeString from
; // current zone from offset
1364 UnicodeString to
; // current zone offset
1365 UnicodeString zonename
; // current zone name
1366 UnicodeString dtstart
; // current zone starts
1367 UBool isRRULE
= FALSE
; // true if the rule is described by RRULE
1368 int32_t initialRawOffset
= 0; // initial offset
1369 int32_t initialDSTSavings
= 0; // initial offset
1370 UDate firstStart
= MAX_MILLIS
; // the earliest rule start time
1371 UnicodeString name
; // RFC2445 prop name
1372 UnicodeString value
; // RFC2445 prop value
1374 UVector
*dates
= NULL
; // list of RDATE or RRULE strings
1375 UVector
*rules
= NULL
; // list of TimeZoneRule instances
1377 int32_t finalRuleIdx
= -1;
1378 int32_t finalRuleCount
= 0;
1380 rules
= new UVector(status
);
1381 if (U_FAILURE(status
)) {
1384 // Set the deleter to remove TimeZoneRule vectors to avoid memory leaks due to unowned TimeZoneRules.
1385 rules
->setDeleter(deleteTimeZoneRule
);
1387 dates
= new UVector(uprv_deleteUObject
, uhash_compareUnicodeString
, status
);
1388 if (U_FAILURE(status
)) {
1391 if (rules
== NULL
|| dates
== NULL
) {
1392 status
= U_MEMORY_ALLOCATION_ERROR
;
1396 for (n
= 0; n
< vtzlines
->size(); n
++) {
1397 UnicodeString
*line
= (UnicodeString
*)vtzlines
->elementAt(n
);
1398 int32_t valueSep
= line
->indexOf(COLON
);
1402 name
.setTo(*line
, 0, valueSep
);
1403 value
.setTo(*line
, valueSep
+ 1);
1407 if (name
.compare(ICAL_BEGIN
, -1) == 0
1408 && value
.compare(ICAL_VTIMEZONE
, -1) == 0) {
1414 if (name
.compare(ICAL_TZID
, -1) == 0) {
1416 } else if (name
.compare(ICAL_TZURL
, -1) == 0) {
1418 } else if (name
.compare(ICAL_LASTMOD
, -1) == 0) {
1419 // Always in 'Z' format, so the offset argument for the parse method
1420 // can be any value.
1421 lastmod
= parseDateTimeString(value
, 0, status
);
1422 if (U_FAILURE(status
)) {
1425 } else if (name
.compare(ICAL_BEGIN
, -1) == 0) {
1426 UBool isDST
= (value
.compare(ICAL_DAYLIGHT
, -1) == 0);
1427 if (value
.compare(ICAL_STANDARD
, -1) == 0 || isDST
) {
1428 // tzid must be ready at this point
1429 if (tzid
.length() == 0) {
1432 // initialize current zone properties
1433 if (dates
->size() != 0) {
1434 dates
->removeAllElements();
1443 // BEGIN property other than STANDARD/DAYLIGHT
1444 // must not be there.
1447 } else if (name
.compare(ICAL_END
, -1) == 0) {
1452 if (name
.compare(ICAL_DTSTART
, -1) == 0) {
1454 } else if (name
.compare(ICAL_TZNAME
, -1) == 0) {
1456 } else if (name
.compare(ICAL_TZOFFSETFROM
, -1) == 0) {
1458 } else if (name
.compare(ICAL_TZOFFSETTO
, -1) == 0) {
1460 } else if (name
.compare(ICAL_RDATE
, -1) == 0) {
1461 // RDATE mixed with RRULE is not supported
1465 // RDATE value may contain multiple date delimited
1467 UBool nextDate
= TRUE
;
1469 UnicodeString
*dstr
;
1471 int32_t dend
= value
.indexOf(COMMA
, dstart
);
1473 dstr
= new UnicodeString(value
, dstart
);
1476 dstr
= new UnicodeString(value
, dstart
, dend
- dstart
);
1478 dates
->addElement(dstr
, status
);
1479 if (U_FAILURE(status
)) {
1484 } else if (name
.compare(ICAL_RRULE
, -1) == 0) {
1485 // RRULE mixed with RDATE is not supported
1486 if (!isRRULE
&& dates
->size() != 0) {
1490 dates
->addElement(new UnicodeString(value
), status
);
1491 if (U_FAILURE(status
)) {
1494 } else if (name
.compare(ICAL_END
, -1) == 0) {
1495 // Mandatory properties
1496 if (dtstart
.length() == 0 || from
.length() == 0 || to
.length() == 0) {
1499 // if zonename is not available, create one from tzid
1500 if (zonename
.length() == 0) {
1501 getDefaultTZName(tzid
, dst
, zonename
);
1504 // create a time zone rule
1505 TimeZoneRule
*rule
= NULL
;
1506 int32_t fromOffset
= 0;
1507 int32_t toOffset
= 0;
1508 int32_t rawOffset
= 0;
1509 int32_t dstSavings
= 0;
1512 // Parse TZOFFSETFROM/TZOFFSETTO
1513 fromOffset
= offsetStrToMillis(from
, status
);
1514 toOffset
= offsetStrToMillis(to
, status
);
1515 if (U_FAILURE(status
)) {
1520 // If daylight, use the previous offset as rawoffset if positive
1521 if (toOffset
- fromOffset
> 0) {
1522 rawOffset
= fromOffset
;
1523 dstSavings
= toOffset
- fromOffset
;
1525 // This is rare case.. just use 1 hour DST savings
1526 rawOffset
= toOffset
- DEF_DSTSAVINGS
;
1527 dstSavings
= DEF_DSTSAVINGS
;
1530 rawOffset
= toOffset
;
1535 start
= parseDateTimeString(dtstart
, fromOffset
, status
);
1536 if (U_FAILURE(status
)) {
1541 UDate actualStart
= MAX_MILLIS
;
1543 rule
= createRuleByRRULE(zonename
, rawOffset
, dstSavings
, start
, dates
, fromOffset
, status
);
1545 rule
= createRuleByRDATE(zonename
, rawOffset
, dstSavings
, start
, dates
, fromOffset
, status
);
1547 if (U_FAILURE(status
) || rule
== NULL
) {
1550 UBool startAvail
= rule
->getFirstStart(fromOffset
, 0, actualStart
);
1551 if (startAvail
&& actualStart
< firstStart
) {
1552 // save from offset information for the earliest rule
1553 firstStart
= actualStart
;
1554 // If this is STD, assume the time before this transtion
1555 // is DST when the difference is 1 hour. This might not be
1556 // accurate, but VTIMEZONE data does not have such info.
1557 if (dstSavings
> 0) {
1558 initialRawOffset
= fromOffset
;
1559 initialDSTSavings
= 0;
1561 if (fromOffset
- toOffset
== DEF_DSTSAVINGS
) {
1562 initialRawOffset
= fromOffset
- DEF_DSTSAVINGS
;
1563 initialDSTSavings
= DEF_DSTSAVINGS
;
1565 initialRawOffset
= fromOffset
;
1566 initialDSTSavings
= 0;
1571 rules
->addElement(rule
, status
);
1572 if (U_FAILURE(status
)) {
1580 // Must have at least one rule
1581 if (rules
->size() == 0) {
1585 // Create a initial rule
1586 getDefaultTZName(tzid
, FALSE
, zonename
);
1587 initialRule
= new InitialTimeZoneRule(zonename
,
1588 initialRawOffset
, initialDSTSavings
);
1589 if (initialRule
== NULL
) {
1590 status
= U_MEMORY_ALLOCATION_ERROR
;
1594 // Finally, create the RuleBasedTimeZone
1595 rbtz
= new RuleBasedTimeZone(tzid
, initialRule
);
1597 status
= U_MEMORY_ALLOCATION_ERROR
;
1600 initialRule
= NULL
; // already adopted by RBTZ, no need to delete
1602 for (n
= 0; n
< rules
->size(); n
++) {
1603 TimeZoneRule
*r
= (TimeZoneRule
*)rules
->elementAt(n
);
1604 AnnualTimeZoneRule
*atzrule
= dynamic_cast<AnnualTimeZoneRule
*>(r
);
1605 if (atzrule
!= NULL
) {
1606 if (atzrule
->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
) {
1612 if (finalRuleCount
> 2) {
1613 // Too many final rules
1614 status
= U_ILLEGAL_ARGUMENT_ERROR
;
1618 if (finalRuleCount
== 1) {
1619 if (rules
->size() == 1) {
1620 // Only one final rule, only governs the initial rule,
1621 // which is already initialized, thus, we do not need to
1622 // add this transition rule
1623 rules
->removeAllElements();
1625 // Normalize the final rule
1626 AnnualTimeZoneRule
*finalRule
= (AnnualTimeZoneRule
*)rules
->elementAt(finalRuleIdx
);
1627 int32_t tmpRaw
= finalRule
->getRawOffset();
1628 int32_t tmpDST
= finalRule
->getDSTSavings();
1630 // Find the last non-final rule
1631 UDate finalStart
, start
;
1632 finalRule
->getFirstStart(initialRawOffset
, initialDSTSavings
, finalStart
);
1634 for (n
= 0; n
< rules
->size(); n
++) {
1635 if (finalRuleIdx
== n
) {
1638 TimeZoneRule
*r
= (TimeZoneRule
*)rules
->elementAt(n
);
1640 r
->getFinalStart(tmpRaw
, tmpDST
, lastStart
);
1641 if (lastStart
> start
) {
1642 finalRule
->getNextStart(lastStart
,
1650 TimeZoneRule
*newRule
;
1651 UnicodeString tznam
;
1652 if (start
== finalStart
) {
1653 // Transform this into a single transition
1654 newRule
= new TimeArrayTimeZoneRule(
1655 finalRule
->getName(tznam
),
1656 finalRule
->getRawOffset(),
1657 finalRule
->getDSTSavings(),
1660 DateTimeRule::UTC_TIME
);
1662 // Update the end year
1663 int32_t y
, m
, d
, dow
, doy
, mid
;
1664 Grego::timeToFields(start
, y
, m
, d
, dow
, doy
, mid
);
1665 newRule
= new AnnualTimeZoneRule(
1666 finalRule
->getName(tznam
),
1667 finalRule
->getRawOffset(),
1668 finalRule
->getDSTSavings(),
1669 *(finalRule
->getRule()),
1670 finalRule
->getStartYear(),
1673 if (newRule
== NULL
) {
1674 status
= U_MEMORY_ALLOCATION_ERROR
;
1677 rules
->removeElementAt(finalRuleIdx
);
1678 rules
->addElement(newRule
, status
);
1679 if (U_FAILURE(status
)) {
1686 while (!rules
->isEmpty()) {
1687 TimeZoneRule
*tzr
= (TimeZoneRule
*)rules
->orphanElementAt(0);
1688 rbtz
->addTransitionRule(tzr
, status
);
1689 if (U_FAILURE(status
)) {
1693 rbtz
->complete(status
);
1694 if (U_FAILURE(status
)) {
1705 if (rules
!= NULL
) {
1706 while (!rules
->isEmpty()) {
1707 TimeZoneRule
*r
= (TimeZoneRule
*)rules
->orphanElementAt(0);
1712 if (dates
!= NULL
) {
1715 if (initialRule
!= NULL
) {
1725 VTimeZone::write(VTZWriter
& writer
, UErrorCode
& status
) const {
1726 if (vtzlines
!= NULL
) {
1727 for (int32_t i
= 0; i
< vtzlines
->size(); i
++) {
1728 UnicodeString
*line
= (UnicodeString
*)vtzlines
->elementAt(i
);
1729 if (line
->startsWith(ICAL_TZURL
, -1)
1730 && line
->charAt(u_strlen(ICAL_TZURL
)) == COLON
) {
1731 writer
.write(ICAL_TZURL
);
1732 writer
.write(COLON
);
1733 writer
.write(tzurl
);
1734 writer
.write(ICAL_NEWLINE
);
1735 } else if (line
->startsWith(ICAL_LASTMOD
, -1)
1736 && line
->charAt(u_strlen(ICAL_LASTMOD
)) == COLON
) {
1737 UnicodeString utcString
;
1738 writer
.write(ICAL_LASTMOD
);
1739 writer
.write(COLON
);
1740 writer
.write(getUTCDateTimeString(lastmod
, utcString
));
1741 writer
.write(ICAL_NEWLINE
);
1743 writer
.write(*line
);
1744 writer
.write(ICAL_NEWLINE
);
1748 UVector
*customProps
= NULL
;
1749 if (olsonzid
.length() > 0 && icutzver
.length() > 0) {
1750 customProps
= new UVector(uprv_deleteUObject
, uhash_compareUnicodeString
, status
);
1751 if (U_FAILURE(status
)) {
1754 UnicodeString
*icutzprop
= new UnicodeString(ICU_TZINFO_PROP
);
1755 icutzprop
->append(olsonzid
);
1756 icutzprop
->append((UChar
)0x005B/*'['*/);
1757 icutzprop
->append(icutzver
);
1758 icutzprop
->append((UChar
)0x005D/*']'*/);
1759 customProps
->addElement(icutzprop
, status
);
1760 if (U_FAILURE(status
)) {
1766 writeZone(writer
, *tz
, customProps
, status
);
1772 VTimeZone::write(UDate start
, VTZWriter
& writer
, UErrorCode
& status
) const {
1773 if (U_FAILURE(status
)) {
1776 InitialTimeZoneRule
*initial
= NULL
;
1777 UVector
*transitionRules
= NULL
;
1778 UVector
customProps(uprv_deleteUObject
, uhash_compareUnicodeString
, status
);
1781 // Extract rules applicable to dates after the start time
1782 getTimeZoneRulesAfter(start
, initial
, transitionRules
, status
);
1783 if (U_FAILURE(status
)) {
1787 // Create a RuleBasedTimeZone with the subset rule
1789 RuleBasedTimeZone
rbtz(tzid
, initial
);
1790 if (transitionRules
!= NULL
) {
1791 while (!transitionRules
->isEmpty()) {
1792 TimeZoneRule
*tr
= (TimeZoneRule
*)transitionRules
->orphanElementAt(0);
1793 rbtz
.addTransitionRule(tr
, status
);
1794 if (U_FAILURE(status
)) {
1795 goto cleanupWritePartial
;
1798 delete transitionRules
;
1799 transitionRules
= NULL
;
1801 rbtz
.complete(status
);
1802 if (U_FAILURE(status
)) {
1803 goto cleanupWritePartial
;
1806 if (olsonzid
.length() > 0 && icutzver
.length() > 0) {
1807 UnicodeString
*icutzprop
= new UnicodeString(ICU_TZINFO_PROP
);
1808 icutzprop
->append(olsonzid
);
1809 icutzprop
->append((UChar
)0x005B/*'['*/);
1810 icutzprop
->append(icutzver
);
1811 icutzprop
->append(ICU_TZINFO_PARTIAL
, -1);
1812 appendMillis(start
, *icutzprop
);
1813 icutzprop
->append((UChar
)0x005D/*']'*/);
1814 customProps
.addElement(icutzprop
, status
);
1815 if (U_FAILURE(status
)) {
1817 goto cleanupWritePartial
;
1820 writeZone(writer
, rbtz
, &customProps
, status
);
1823 cleanupWritePartial
:
1824 if (initial
!= NULL
) {
1827 if (transitionRules
!= NULL
) {
1828 while (!transitionRules
->isEmpty()) {
1829 TimeZoneRule
*tr
= (TimeZoneRule
*)transitionRules
->orphanElementAt(0);
1832 delete transitionRules
;
1837 VTimeZone::writeSimple(UDate time
, VTZWriter
& writer
, UErrorCode
& status
) const {
1838 if (U_FAILURE(status
)) {
1842 UVector
customProps(uprv_deleteUObject
, uhash_compareUnicodeString
, status
);
1845 // Extract simple rules
1846 InitialTimeZoneRule
*initial
= NULL
;
1847 AnnualTimeZoneRule
*std
= NULL
, *dst
= NULL
;
1848 getSimpleRulesNear(time
, initial
, std
, dst
, status
);
1849 if (U_SUCCESS(status
)) {
1850 // Create a RuleBasedTimeZone with the subset rule
1852 RuleBasedTimeZone
rbtz(tzid
, initial
);
1853 if (std
!= NULL
&& dst
!= NULL
) {
1854 rbtz
.addTransitionRule(std
, status
);
1855 rbtz
.addTransitionRule(dst
, status
);
1857 if (U_FAILURE(status
)) {
1858 goto cleanupWriteSimple
;
1861 if (olsonzid
.length() > 0 && icutzver
.length() > 0) {
1862 UnicodeString
*icutzprop
= new UnicodeString(ICU_TZINFO_PROP
);
1863 icutzprop
->append(olsonzid
);
1864 icutzprop
->append((UChar
)0x005B/*'['*/);
1865 icutzprop
->append(icutzver
);
1866 icutzprop
->append(ICU_TZINFO_SIMPLE
, -1);
1867 appendMillis(time
, *icutzprop
);
1868 icutzprop
->append((UChar
)0x005D/*']'*/);
1869 customProps
.addElement(icutzprop
, status
);
1870 if (U_FAILURE(status
)) {
1872 goto cleanupWriteSimple
;
1875 writeZone(writer
, rbtz
, &customProps
, status
);
1880 if (initial
!= NULL
) {
1892 VTimeZone::writeZone(VTZWriter
& w
, BasicTimeZone
& basictz
,
1893 UVector
* customProps
, UErrorCode
& status
) const {
1894 if (U_FAILURE(status
)) {
1897 writeHeaders(w
, status
);
1898 if (U_FAILURE(status
)) {
1902 if (customProps
!= NULL
) {
1903 for (int32_t i
= 0; i
< customProps
->size(); i
++) {
1904 UnicodeString
*custprop
= (UnicodeString
*)customProps
->elementAt(i
);
1906 w
.write(ICAL_NEWLINE
);
1910 UDate t
= MIN_MILLIS
;
1911 UnicodeString dstName
;
1912 int32_t dstFromOffset
= 0;
1913 int32_t dstFromDSTSavings
= 0;
1914 int32_t dstToOffset
= 0;
1915 int32_t dstStartYear
= 0;
1916 int32_t dstMonth
= 0;
1917 int32_t dstDayOfWeek
= 0;
1918 int32_t dstWeekInMonth
= 0;
1919 int32_t dstMillisInDay
= 0;
1920 UDate dstStartTime
= 0.0;
1921 UDate dstUntilTime
= 0.0;
1922 int32_t dstCount
= 0;
1923 AnnualTimeZoneRule
*finalDstRule
= NULL
;
1925 UnicodeString stdName
;
1926 int32_t stdFromOffset
= 0;
1927 int32_t stdFromDSTSavings
= 0;
1928 int32_t stdToOffset
= 0;
1929 int32_t stdStartYear
= 0;
1930 int32_t stdMonth
= 0;
1931 int32_t stdDayOfWeek
= 0;
1932 int32_t stdWeekInMonth
= 0;
1933 int32_t stdMillisInDay
= 0;
1934 UDate stdStartTime
= 0.0;
1935 UDate stdUntilTime
= 0.0;
1936 int32_t stdCount
= 0;
1937 AnnualTimeZoneRule
*finalStdRule
= NULL
;
1939 int32_t year
, month
, dom
, dow
, doy
, mid
;
1940 UBool hasTransitions
= FALSE
;
1941 TimeZoneTransition tzt
;
1946 // Going through all transitions
1948 tztAvail
= basictz
.getNextTransition(t
, FALSE
, tzt
);
1952 hasTransitions
= TRUE
;
1954 tzt
.getTo()->getName(name
);
1955 isDst
= (tzt
.getTo()->getDSTSavings() != 0);
1956 int32_t fromOffset
= tzt
.getFrom()->getRawOffset() + tzt
.getFrom()->getDSTSavings();
1957 int32_t fromDSTSavings
= tzt
.getFrom()->getDSTSavings();
1958 int32_t toOffset
= tzt
.getTo()->getRawOffset() + tzt
.getTo()->getDSTSavings();
1959 Grego::timeToFields(tzt
.getTime() + fromOffset
, year
, month
, dom
, dow
, doy
, mid
);
1960 int32_t weekInMonth
= Grego::dayOfWeekInMonth(year
, month
, dom
);
1961 UBool sameRule
= FALSE
;
1962 const AnnualTimeZoneRule
*atzrule
;
1964 if (finalDstRule
== NULL
1965 && (atzrule
= dynamic_cast<const AnnualTimeZoneRule
*>(tzt
.getTo())) != NULL
1966 && atzrule
->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
1968 finalDstRule
= (AnnualTimeZoneRule
*)tzt
.getTo()->clone();
1971 if (year
== dstStartYear
+ dstCount
1972 && name
.compare(dstName
) == 0
1973 && dstFromOffset
== fromOffset
1974 && dstToOffset
== toOffset
1975 && dstMonth
== month
1976 && dstDayOfWeek
== dow
1977 && dstWeekInMonth
== weekInMonth
1978 && dstMillisInDay
== mid
) {
1979 // Update until time
1985 if (dstCount
== 1) {
1986 writeZonePropsByTime(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
, dstStartTime
,
1989 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
1990 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, dstUntilTime
, status
);
1992 if (U_FAILURE(status
)) {
1993 goto cleanupWriteZone
;
1998 // Reset this DST information
2000 dstFromOffset
= fromOffset
;
2001 dstFromDSTSavings
= fromDSTSavings
;
2002 dstToOffset
= toOffset
;
2003 dstStartYear
= year
;
2006 dstWeekInMonth
= weekInMonth
;
2007 dstMillisInDay
= mid
;
2008 dstStartTime
= dstUntilTime
= t
;
2011 if (finalStdRule
!= NULL
&& finalDstRule
!= NULL
) {
2015 if (finalStdRule
== NULL
2016 && (atzrule
= dynamic_cast<const AnnualTimeZoneRule
*>(tzt
.getTo())) != NULL
2017 && atzrule
->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
2019 finalStdRule
= (AnnualTimeZoneRule
*)tzt
.getTo()->clone();
2022 if (year
== stdStartYear
+ stdCount
2023 && name
.compare(stdName
) == 0
2024 && stdFromOffset
== fromOffset
2025 && stdToOffset
== toOffset
2026 && stdMonth
== month
2027 && stdDayOfWeek
== dow
2028 && stdWeekInMonth
== weekInMonth
2029 && stdMillisInDay
== mid
) {
2030 // Update until time
2036 if (stdCount
== 1) {
2037 writeZonePropsByTime(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
, stdStartTime
,
2040 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2041 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, stdUntilTime
, status
);
2043 if (U_FAILURE(status
)) {
2044 goto cleanupWriteZone
;
2049 // Reset this STD information
2051 stdFromOffset
= fromOffset
;
2052 stdFromDSTSavings
= fromDSTSavings
;
2053 stdToOffset
= toOffset
;
2054 stdStartYear
= year
;
2057 stdWeekInMonth
= weekInMonth
;
2058 stdMillisInDay
= mid
;
2059 stdStartTime
= stdUntilTime
= t
;
2062 if (finalStdRule
!= NULL
&& finalDstRule
!= NULL
) {
2067 if (!hasTransitions
) {
2068 // No transition - put a single non transition RDATE
2069 int32_t raw
, dst
, offset
;
2070 basictz
.getOffset(0.0/*any time*/, FALSE
, raw
, dst
, status
);
2071 if (U_FAILURE(status
)) {
2072 goto cleanupWriteZone
;
2077 basictz
.getID(tzid
);
2078 getDefaultTZName(tzid
, isDst
, name
);
2079 writeZonePropsByTime(w
, isDst
, name
,
2080 offset
, offset
, DEF_TZSTARTTIME
- offset
, FALSE
, status
);
2081 if (U_FAILURE(status
)) {
2082 goto cleanupWriteZone
;
2086 if (finalDstRule
== NULL
) {
2087 if (dstCount
== 1) {
2088 writeZonePropsByTime(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
, dstStartTime
,
2091 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
2092 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, dstUntilTime
, status
);
2094 if (U_FAILURE(status
)) {
2095 goto cleanupWriteZone
;
2098 if (dstCount
== 1) {
2099 writeFinalRule(w
, TRUE
, finalDstRule
,
2100 dstFromOffset
- dstFromDSTSavings
, dstFromDSTSavings
, dstStartTime
, status
);
2102 // Use a single rule if possible
2103 if (isEquivalentDateRule(dstMonth
, dstWeekInMonth
, dstDayOfWeek
, finalDstRule
->getRule())) {
2104 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
2105 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, MAX_MILLIS
, status
);
2107 // Not equivalent rule - write out two different rules
2108 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
2109 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, dstUntilTime
, status
);
2110 if (U_FAILURE(status
)) {
2111 goto cleanupWriteZone
;
2114 UBool nextStartAvail
= finalDstRule
->getNextStart(dstUntilTime
, dstFromOffset
- dstFromDSTSavings
, dstFromDSTSavings
, false, nextStart
);
2115 U_ASSERT(nextStartAvail
);
2116 if (nextStartAvail
) {
2117 writeFinalRule(w
, TRUE
, finalDstRule
,
2118 dstFromOffset
- dstFromDSTSavings
, dstFromDSTSavings
, nextStart
, status
);
2122 if (U_FAILURE(status
)) {
2123 goto cleanupWriteZone
;
2128 if (finalStdRule
== NULL
) {
2129 if (stdCount
== 1) {
2130 writeZonePropsByTime(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
, stdStartTime
,
2133 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2134 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, stdUntilTime
, status
);
2136 if (U_FAILURE(status
)) {
2137 goto cleanupWriteZone
;
2140 if (stdCount
== 1) {
2141 writeFinalRule(w
, FALSE
, finalStdRule
,
2142 stdFromOffset
- stdFromDSTSavings
, stdFromDSTSavings
, stdStartTime
, status
);
2144 // Use a single rule if possible
2145 if (isEquivalentDateRule(stdMonth
, stdWeekInMonth
, stdDayOfWeek
, finalStdRule
->getRule())) {
2146 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2147 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, MAX_MILLIS
, status
);
2149 // Not equivalent rule - write out two different rules
2150 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2151 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, stdUntilTime
, status
);
2152 if (U_FAILURE(status
)) {
2153 goto cleanupWriteZone
;
2156 UBool nextStartAvail
= finalStdRule
->getNextStart(stdUntilTime
, stdFromOffset
- stdFromDSTSavings
, stdFromDSTSavings
, false, nextStart
);
2157 U_ASSERT(nextStartAvail
);
2158 if (nextStartAvail
) {
2159 writeFinalRule(w
, FALSE
, finalStdRule
,
2160 stdFromOffset
- stdFromDSTSavings
, stdFromDSTSavings
, nextStart
, status
);
2164 if (U_FAILURE(status
)) {
2165 goto cleanupWriteZone
;
2170 writeFooter(w
, status
);
2174 if (finalStdRule
!= NULL
) {
2175 delete finalStdRule
;
2177 if (finalDstRule
!= NULL
) {
2178 delete finalDstRule
;
2183 VTimeZone::writeHeaders(VTZWriter
& writer
, UErrorCode
& status
) const {
2184 if (U_FAILURE(status
)) {
2190 writer
.write(ICAL_BEGIN
);
2191 writer
.write(COLON
);
2192 writer
.write(ICAL_VTIMEZONE
);
2193 writer
.write(ICAL_NEWLINE
);
2194 writer
.write(ICAL_TZID
);
2195 writer
.write(COLON
);
2197 writer
.write(ICAL_NEWLINE
);
2198 if (tzurl
.length() != 0) {
2199 writer
.write(ICAL_TZURL
);
2200 writer
.write(COLON
);
2201 writer
.write(tzurl
);
2202 writer
.write(ICAL_NEWLINE
);
2204 if (lastmod
!= MAX_MILLIS
) {
2205 UnicodeString lastmodStr
;
2206 writer
.write(ICAL_LASTMOD
);
2207 writer
.write(COLON
);
2208 writer
.write(getUTCDateTimeString(lastmod
, lastmodStr
));
2209 writer
.write(ICAL_NEWLINE
);
2214 * Write the closing section of the VTIMEZONE definition block
2217 VTimeZone::writeFooter(VTZWriter
& writer
, UErrorCode
& status
) const {
2218 if (U_FAILURE(status
)) {
2221 writer
.write(ICAL_END
);
2222 writer
.write(COLON
);
2223 writer
.write(ICAL_VTIMEZONE
);
2224 writer
.write(ICAL_NEWLINE
);
2228 * Write a single start time
2231 VTimeZone::writeZonePropsByTime(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2232 int32_t fromOffset
, int32_t toOffset
, UDate time
, UBool withRDATE
,
2233 UErrorCode
& status
) const {
2234 if (U_FAILURE(status
)) {
2237 beginZoneProps(writer
, isDst
, zonename
, fromOffset
, toOffset
, time
, status
);
2238 if (U_FAILURE(status
)) {
2242 writer
.write(ICAL_RDATE
);
2243 writer
.write(COLON
);
2244 UnicodeString timestr
;
2245 writer
.write(getDateTimeString(time
+ fromOffset
, timestr
));
2246 writer
.write(ICAL_NEWLINE
);
2248 endZoneProps(writer
, isDst
, status
);
2249 if (U_FAILURE(status
)) {
2255 * Write start times defined by a DOM rule using VTIMEZONE RRULE
2258 VTimeZone::writeZonePropsByDOM(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2259 int32_t fromOffset
, int32_t toOffset
,
2260 int32_t month
, int32_t dayOfMonth
, UDate startTime
, UDate untilTime
,
2261 UErrorCode
& status
) const {
2262 if (U_FAILURE(status
)) {
2265 beginZoneProps(writer
, isDst
, zonename
, fromOffset
, toOffset
, startTime
, status
);
2266 if (U_FAILURE(status
)) {
2269 beginRRULE(writer
, month
, status
);
2270 if (U_FAILURE(status
)) {
2273 writer
.write(ICAL_BYMONTHDAY
);
2274 writer
.write(EQUALS_SIGN
);
2276 appendAsciiDigits(dayOfMonth
, 0, dstr
);
2278 if (untilTime
!= MAX_MILLIS
) {
2279 appendUNTIL(writer
, getDateTimeString(untilTime
+ fromOffset
, dstr
), status
);
2280 if (U_FAILURE(status
)) {
2284 writer
.write(ICAL_NEWLINE
);
2285 endZoneProps(writer
, isDst
, status
);
2289 * Write start times defined by a DOW rule using VTIMEZONE RRULE
2292 VTimeZone::writeZonePropsByDOW(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2293 int32_t fromOffset
, int32_t toOffset
,
2294 int32_t month
, int32_t weekInMonth
, int32_t dayOfWeek
,
2295 UDate startTime
, UDate untilTime
, UErrorCode
& status
) const {
2296 if (U_FAILURE(status
)) {
2299 beginZoneProps(writer
, isDst
, zonename
, fromOffset
, toOffset
, startTime
, status
);
2300 if (U_FAILURE(status
)) {
2303 beginRRULE(writer
, month
, status
);
2304 if (U_FAILURE(status
)) {
2307 writer
.write(ICAL_BYDAY
);
2308 writer
.write(EQUALS_SIGN
);
2310 appendAsciiDigits(weekInMonth
, 0, dstr
);
2311 writer
.write(dstr
); // -4, -3, -2, -1, 1, 2, 3, 4
2312 writer
.write(ICAL_DOW_NAMES
[dayOfWeek
- 1]); // SU, MO, TU...
2314 if (untilTime
!= MAX_MILLIS
) {
2315 appendUNTIL(writer
, getDateTimeString(untilTime
+ fromOffset
, dstr
), status
);
2316 if (U_FAILURE(status
)) {
2320 writer
.write(ICAL_NEWLINE
);
2321 endZoneProps(writer
, isDst
, status
);
2325 * Write start times defined by a DOW_GEQ_DOM rule using VTIMEZONE RRULE
2328 VTimeZone::writeZonePropsByDOW_GEQ_DOM(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2329 int32_t fromOffset
, int32_t toOffset
,
2330 int32_t month
, int32_t dayOfMonth
, int32_t dayOfWeek
,
2331 UDate startTime
, UDate untilTime
, UErrorCode
& status
) const {
2332 if (U_FAILURE(status
)) {
2335 // Check if this rule can be converted to DOW rule
2336 if (dayOfMonth%7
== 1) {
2337 // Can be represented by DOW rule
2338 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2339 month
, (dayOfMonth
+ 6)/7, dayOfWeek
, startTime
, untilTime
, status
);
2340 if (U_FAILURE(status
)) {
2343 } else if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - dayOfMonth
)%7
== 6) {
2344 // Can be represented by DOW rule with negative week number
2345 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2346 month
, -1*((MONTHLENGTH
[month
] - dayOfMonth
+ 1)/7), dayOfWeek
, startTime
, untilTime
, status
);
2347 if (U_FAILURE(status
)) {
2351 // Otherwise, use BYMONTHDAY to include all possible dates
2352 beginZoneProps(writer
, isDst
, zonename
, fromOffset
, toOffset
, startTime
, status
);
2353 if (U_FAILURE(status
)) {
2356 // Check if all days are in the same month
2357 int32_t startDay
= dayOfMonth
;
2358 int32_t currentMonthDays
= 7;
2360 if (dayOfMonth
<= 0) {
2361 // The start day is in previous month
2362 int32_t prevMonthDays
= 1 - dayOfMonth
;
2363 currentMonthDays
-= prevMonthDays
;
2365 int32_t prevMonth
= (month
- 1) < 0 ? 11 : month
- 1;
2367 // Note: When a rule is separated into two, UNTIL attribute needs to be
2368 // calculated for each of them. For now, we skip this, because we basically use this method
2369 // only for final rules, which does not have the UNTIL attribute
2370 writeZonePropsByDOW_GEQ_DOM_sub(writer
, prevMonth
, -prevMonthDays
, dayOfWeek
, prevMonthDays
,
2371 MAX_MILLIS
/* Do not use UNTIL */, fromOffset
, status
);
2372 if (U_FAILURE(status
)) {
2376 // Start from 1 for the rest
2378 } else if (dayOfMonth
+ 6 > MONTHLENGTH
[month
]) {
2379 // Note: This code does not actually work well in February. For now, days in month in
2381 int32_t nextMonthDays
= dayOfMonth
+ 6 - MONTHLENGTH
[month
];
2382 currentMonthDays
-= nextMonthDays
;
2384 int32_t nextMonth
= (month
+ 1) > 11 ? 0 : month
+ 1;
2386 writeZonePropsByDOW_GEQ_DOM_sub(writer
, nextMonth
, 1, dayOfWeek
, nextMonthDays
,
2387 MAX_MILLIS
/* Do not use UNTIL */, fromOffset
, status
);
2388 if (U_FAILURE(status
)) {
2392 writeZonePropsByDOW_GEQ_DOM_sub(writer
, month
, startDay
, dayOfWeek
, currentMonthDays
,
2393 untilTime
, fromOffset
, status
);
2394 if (U_FAILURE(status
)) {
2397 endZoneProps(writer
, isDst
, status
);
2402 * Called from writeZonePropsByDOW_GEQ_DOM
2405 VTimeZone::writeZonePropsByDOW_GEQ_DOM_sub(VTZWriter
& writer
, int32_t month
, int32_t dayOfMonth
,
2406 int32_t dayOfWeek
, int32_t numDays
,
2407 UDate untilTime
, int32_t fromOffset
, UErrorCode
& status
) const {
2409 if (U_FAILURE(status
)) {
2412 int32_t startDayNum
= dayOfMonth
;
2413 UBool isFeb
= (month
== UCAL_FEBRUARY
);
2414 if (dayOfMonth
< 0 && !isFeb
) {
2415 // Use positive number if possible
2416 startDayNum
= MONTHLENGTH
[month
] + dayOfMonth
+ 1;
2418 beginRRULE(writer
, month
, status
);
2419 if (U_FAILURE(status
)) {
2422 writer
.write(ICAL_BYDAY
);
2423 writer
.write(EQUALS_SIGN
);
2424 writer
.write(ICAL_DOW_NAMES
[dayOfWeek
- 1]); // SU, MO, TU...
2425 writer
.write(SEMICOLON
);
2426 writer
.write(ICAL_BYMONTHDAY
);
2427 writer
.write(EQUALS_SIGN
);
2430 appendAsciiDigits(startDayNum
, 0, dstr
);
2432 for (int32_t i
= 1; i
< numDays
; i
++) {
2433 writer
.write(COMMA
);
2435 appendAsciiDigits(startDayNum
+ i
, 0, dstr
);
2439 if (untilTime
!= MAX_MILLIS
) {
2440 appendUNTIL(writer
, getDateTimeString(untilTime
+ fromOffset
, dstr
), status
);
2441 if (U_FAILURE(status
)) {
2445 writer
.write(ICAL_NEWLINE
);
2449 * Write start times defined by a DOW_LEQ_DOM rule using VTIMEZONE RRULE
2452 VTimeZone::writeZonePropsByDOW_LEQ_DOM(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2453 int32_t fromOffset
, int32_t toOffset
,
2454 int32_t month
, int32_t dayOfMonth
, int32_t dayOfWeek
,
2455 UDate startTime
, UDate untilTime
, UErrorCode
& status
) const {
2456 if (U_FAILURE(status
)) {
2459 // Check if this rule can be converted to DOW rule
2460 if (dayOfMonth%7
== 0) {
2461 // Can be represented by DOW rule
2462 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2463 month
, dayOfMonth
/7, dayOfWeek
, startTime
, untilTime
, status
);
2464 } else if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - dayOfMonth
)%7
== 0){
2465 // Can be represented by DOW rule with negative week number
2466 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2467 month
, -1*((MONTHLENGTH
[month
] - dayOfMonth
)/7 + 1), dayOfWeek
, startTime
, untilTime
, status
);
2468 } else if (month
== UCAL_FEBRUARY
&& dayOfMonth
== 29) {
2469 // Specical case for February
2470 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2471 UCAL_FEBRUARY
, -1, dayOfWeek
, startTime
, untilTime
, status
);
2473 // Otherwise, convert this to DOW_GEQ_DOM rule
2474 writeZonePropsByDOW_GEQ_DOM(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2475 month
, dayOfMonth
- 6, dayOfWeek
, startTime
, untilTime
, status
);
2480 * Write the final time zone rule using RRULE, with no UNTIL attribute
2483 VTimeZone::writeFinalRule(VTZWriter
& writer
, UBool isDst
, const AnnualTimeZoneRule
* rule
,
2484 int32_t fromRawOffset
, int32_t fromDSTSavings
,
2485 UDate startTime
, UErrorCode
& status
) const {
2486 if (U_FAILURE(status
)) {
2489 UBool modifiedRule
= TRUE
;
2490 const DateTimeRule
*dtrule
= toWallTimeRule(rule
->getRule(), fromRawOffset
, fromDSTSavings
);
2491 if (dtrule
== NULL
) {
2492 modifiedRule
= FALSE
;
2493 dtrule
= rule
->getRule();
2496 // If the rule's mills in a day is out of range, adjust start time.
2497 // Olson tzdata supports 24:00 of a day, but VTIMEZONE does not.
2498 // See ticket#7008/#7518
2500 int32_t timeInDay
= dtrule
->getRuleMillisInDay();
2501 if (timeInDay
< 0) {
2502 startTime
= startTime
+ (0 - timeInDay
);
2503 } else if (timeInDay
>= U_MILLIS_PER_DAY
) {
2504 startTime
= startTime
- (timeInDay
- (U_MILLIS_PER_DAY
- 1));
2507 int32_t toOffset
= rule
->getRawOffset() + rule
->getDSTSavings();
2509 rule
->getName(name
);
2510 switch (dtrule
->getDateRuleType()) {
2511 case DateTimeRule::DOM
:
2512 writeZonePropsByDOM(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2513 dtrule
->getRuleMonth(), dtrule
->getRuleDayOfMonth(), startTime
, MAX_MILLIS
, status
);
2515 case DateTimeRule::DOW
:
2516 writeZonePropsByDOW(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2517 dtrule
->getRuleMonth(), dtrule
->getRuleWeekInMonth(), dtrule
->getRuleDayOfWeek(), startTime
, MAX_MILLIS
, status
);
2519 case DateTimeRule::DOW_GEQ_DOM
:
2520 writeZonePropsByDOW_GEQ_DOM(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2521 dtrule
->getRuleMonth(), dtrule
->getRuleDayOfMonth(), dtrule
->getRuleDayOfWeek(), startTime
, MAX_MILLIS
, status
);
2523 case DateTimeRule::DOW_LEQ_DOM
:
2524 writeZonePropsByDOW_LEQ_DOM(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2525 dtrule
->getRuleMonth(), dtrule
->getRuleDayOfMonth(), dtrule
->getRuleDayOfWeek(), startTime
, MAX_MILLIS
, status
);
2534 * Write the opening section of zone properties
2537 VTimeZone::beginZoneProps(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2538 int32_t fromOffset
, int32_t toOffset
, UDate startTime
, UErrorCode
& status
) const {
2539 if (U_FAILURE(status
)) {
2542 writer
.write(ICAL_BEGIN
);
2543 writer
.write(COLON
);
2545 writer
.write(ICAL_DAYLIGHT
);
2547 writer
.write(ICAL_STANDARD
);
2549 writer
.write(ICAL_NEWLINE
);
2554 writer
.write(ICAL_TZOFFSETTO
);
2555 writer
.write(COLON
);
2556 millisToOffset(toOffset
, dstr
);
2558 writer
.write(ICAL_NEWLINE
);
2561 writer
.write(ICAL_TZOFFSETFROM
);
2562 writer
.write(COLON
);
2563 millisToOffset(fromOffset
, dstr
);
2565 writer
.write(ICAL_NEWLINE
);
2568 writer
.write(ICAL_TZNAME
);
2569 writer
.write(COLON
);
2570 writer
.write(zonename
);
2571 writer
.write(ICAL_NEWLINE
);
2574 writer
.write(ICAL_DTSTART
);
2575 writer
.write(COLON
);
2576 writer
.write(getDateTimeString(startTime
+ fromOffset
, dstr
));
2577 writer
.write(ICAL_NEWLINE
);
2581 * Writes the closing section of zone properties
2584 VTimeZone::endZoneProps(VTZWriter
& writer
, UBool isDst
, UErrorCode
& status
) const {
2585 if (U_FAILURE(status
)) {
2588 // END:STANDARD or END:DAYLIGHT
2589 writer
.write(ICAL_END
);
2590 writer
.write(COLON
);
2592 writer
.write(ICAL_DAYLIGHT
);
2594 writer
.write(ICAL_STANDARD
);
2596 writer
.write(ICAL_NEWLINE
);
2600 * Write the beggining part of RRULE line
2603 VTimeZone::beginRRULE(VTZWriter
& writer
, int32_t month
, UErrorCode
& status
) const {
2604 if (U_FAILURE(status
)) {
2608 writer
.write(ICAL_RRULE
);
2609 writer
.write(COLON
);
2610 writer
.write(ICAL_FREQ
);
2611 writer
.write(EQUALS_SIGN
);
2612 writer
.write(ICAL_YEARLY
);
2613 writer
.write(SEMICOLON
);
2614 writer
.write(ICAL_BYMONTH
);
2615 writer
.write(EQUALS_SIGN
);
2616 appendAsciiDigits(month
+ 1, 0, dstr
);
2618 writer
.write(SEMICOLON
);
2622 * Append the UNTIL attribute after RRULE line
2625 VTimeZone::appendUNTIL(VTZWriter
& writer
, const UnicodeString
& until
, UErrorCode
& status
) const {
2626 if (U_FAILURE(status
)) {
2629 if (until
.length() > 0) {
2630 writer
.write(SEMICOLON
);
2631 writer
.write(ICAL_UNTIL
);
2632 writer
.write(EQUALS_SIGN
);
2633 writer
.write(until
);
2639 #endif /* #if !UCONFIG_NO_FORMATTING */