2 *******************************************************************************
3 * Copyright (C) 2007-2013, 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"
24 // This is the deleter that will be use to remove TimeZoneRule
26 static void U_CALLCONV
27 deleteTimeZoneRule(void* obj
) {
28 delete (TimeZoneRule
*) obj
;
32 // Smybol characters used by RFC2445 VTIMEZONE
33 static const UChar COLON
= 0x3A; /* : */
34 static const UChar SEMICOLON
= 0x3B; /* ; */
35 static const UChar EQUALS_SIGN
= 0x3D; /* = */
36 static const UChar COMMA
= 0x2C; /* , */
37 static const UChar PLUS
= 0x2B; /* + */
38 static const UChar MINUS
= 0x2D; /* - */
40 // RFC2445 VTIMEZONE tokens
41 static const UChar ICAL_BEGIN_VTIMEZONE
[] = {0x42, 0x45, 0x47, 0x49, 0x4E, 0x3A, 0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "BEGIN:VTIMEZONE" */
42 static const UChar ICAL_END_VTIMEZONE
[] = {0x45, 0x4E, 0x44, 0x3A, 0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "END:VTIMEZONE" */
43 static const UChar ICAL_BEGIN
[] = {0x42, 0x45, 0x47, 0x49, 0x4E, 0}; /* "BEGIN" */
44 static const UChar ICAL_END
[] = {0x45, 0x4E, 0x44, 0}; /* "END" */
45 static const UChar ICAL_VTIMEZONE
[] = {0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "VTIMEZONE" */
46 static const UChar ICAL_TZID
[] = {0x54, 0x5A, 0x49, 0x44, 0}; /* "TZID" */
47 static const UChar ICAL_STANDARD
[] = {0x53, 0x54, 0x41, 0x4E, 0x44, 0x41, 0x52, 0x44, 0}; /* "STANDARD" */
48 static const UChar ICAL_DAYLIGHT
[] = {0x44, 0x41, 0x59, 0x4C, 0x49, 0x47, 0x48, 0x54, 0}; /* "DAYLIGHT" */
49 static const UChar ICAL_DTSTART
[] = {0x44, 0x54, 0x53, 0x54, 0x41, 0x52, 0x54, 0}; /* "DTSTART" */
50 static const UChar ICAL_TZOFFSETFROM
[] = {0x54, 0x5A, 0x4F, 0x46, 0x46, 0x53, 0x45, 0x54, 0x46, 0x52, 0x4F, 0x4D, 0}; /* "TZOFFSETFROM" */
51 static const UChar ICAL_TZOFFSETTO
[] = {0x54, 0x5A, 0x4F, 0x46, 0x46, 0x53, 0x45, 0x54, 0x54, 0x4F, 0}; /* "TZOFFSETTO" */
52 static const UChar ICAL_RDATE
[] = {0x52, 0x44, 0x41, 0x54, 0x45, 0}; /* "RDATE" */
53 static const UChar ICAL_RRULE
[] = {0x52, 0x52, 0x55, 0x4C, 0x45, 0}; /* "RRULE" */
54 static const UChar ICAL_TZNAME
[] = {0x54, 0x5A, 0x4E, 0x41, 0x4D, 0x45, 0}; /* "TZNAME" */
55 static const UChar ICAL_TZURL
[] = {0x54, 0x5A, 0x55, 0x52, 0x4C, 0}; /* "TZURL" */
56 static const UChar ICAL_LASTMOD
[] = {0x4C, 0x41, 0x53, 0x54, 0x2D, 0x4D, 0x4F, 0x44, 0x49, 0x46, 0x49, 0x45, 0x44, 0}; /* "LAST-MODIFIED" */
58 static const UChar ICAL_FREQ
[] = {0x46, 0x52, 0x45, 0x51, 0}; /* "FREQ" */
59 static const UChar ICAL_UNTIL
[] = {0x55, 0x4E, 0x54, 0x49, 0x4C, 0}; /* "UNTIL" */
60 static const UChar ICAL_YEARLY
[] = {0x59, 0x45, 0x41, 0x52, 0x4C, 0x59, 0}; /* "YEARLY" */
61 static const UChar ICAL_BYMONTH
[] = {0x42, 0x59, 0x4D, 0x4F, 0x4E, 0x54, 0x48, 0}; /* "BYMONTH" */
62 static const UChar ICAL_BYDAY
[] = {0x42, 0x59, 0x44, 0x41, 0x59, 0}; /* "BYDAY" */
63 static const UChar ICAL_BYMONTHDAY
[] = {0x42, 0x59, 0x4D, 0x4F, 0x4E, 0x54, 0x48, 0x44, 0x41, 0x59, 0}; /* "BYMONTHDAY" */
65 static const UChar ICAL_NEWLINE
[] = {0x0D, 0x0A, 0}; /* CRLF */
67 static const UChar ICAL_DOW_NAMES
[7][3] = {
68 {0x53, 0x55, 0}, /* "SU" */
69 {0x4D, 0x4F, 0}, /* "MO" */
70 {0x54, 0x55, 0}, /* "TU" */
71 {0x57, 0x45, 0}, /* "WE" */
72 {0x54, 0x48, 0}, /* "TH" */
73 {0x46, 0x52, 0}, /* "FR" */
74 {0x53, 0x41, 0} /* "SA" */};
76 // Month length for non-leap year
77 static const int32_t MONTHLENGTH
[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
79 // ICU custom property
80 static const UChar ICU_TZINFO_PROP
[] = {0x58, 0x2D, 0x54, 0x5A, 0x49, 0x4E, 0x46, 0x4F, 0x3A, 0}; /* "X-TZINFO:" */
81 static const UChar ICU_TZINFO_PARTIAL
[] = {0x2F, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6C, 0x40, 0}; /* "/Partial@" */
82 static const UChar ICU_TZINFO_SIMPLE
[] = {0x2F, 0x53, 0x69, 0x6D, 0x70, 0x6C, 0x65, 0x40, 0}; /* "/Simple@" */
86 * Simple fixed digit ASCII number to integer converter
88 static int32_t parseAsciiDigits(const UnicodeString
& str
, int32_t start
, int32_t length
, UErrorCode
& status
) {
89 if (U_FAILURE(status
)) {
92 if (length
<= 0 || str
.length() < start
|| (start
+ length
) > str
.length()) {
93 status
= U_INVALID_FORMAT_ERROR
;
97 if (str
.charAt(start
) == PLUS
) {
100 } else if (str
.charAt(start
) == MINUS
) {
106 for (int32_t i
= 0; i
< length
; i
++) {
107 int32_t digit
= str
.charAt(start
+ i
) - 0x0030;
108 if (digit
< 0 || digit
> 9) {
109 status
= U_INVALID_FORMAT_ERROR
;
112 num
= 10 * num
+ digit
;
117 static UnicodeString
& appendAsciiDigits(int32_t number
, uint8_t length
, UnicodeString
& str
) {
118 UBool negative
= FALSE
;
119 int32_t digits
[10]; // max int32_t is 10 decimal digits
127 length
= length
> 10 ? 10 : length
;
132 digits
[i
++] = number
% 10;
134 } while (number
!= 0);
138 for (i
= 0; i
< length
; i
++) {
139 digits
[i
] = number
% 10;
146 for (i
= length
- 1; i
>= 0; i
--) {
147 str
.append((UChar
)(digits
[i
] + 0x0030));
152 static UnicodeString
& appendMillis(UDate date
, UnicodeString
& str
) {
153 UBool negative
= FALSE
;
154 int32_t digits
[20]; // max int64_t is 20 decimal digits
158 if (date
< MIN_MILLIS
) {
159 number
= (int64_t)MIN_MILLIS
;
160 } else if (date
> MAX_MILLIS
) {
161 number
= (int64_t)MAX_MILLIS
;
163 number
= (int64_t)date
;
171 digits
[i
++] = (int32_t)(number
% 10);
173 } while (number
!= 0);
180 str
.append((UChar
)(digits
[i
--] + 0x0030));
186 * Convert date/time to RFC2445 Date-Time form #1 DATE WITH LOCAL TIME
188 static UnicodeString
& getDateTimeString(UDate time
, UnicodeString
& str
) {
189 int32_t year
, month
, dom
, dow
, doy
, mid
;
190 Grego::timeToFields(time
, year
, month
, dom
, dow
, doy
, mid
);
193 appendAsciiDigits(year
, 4, str
);
194 appendAsciiDigits(month
+ 1, 2, str
);
195 appendAsciiDigits(dom
, 2, str
);
196 str
.append((UChar
)0x0054 /*'T'*/);
199 int32_t hour
= t
/ U_MILLIS_PER_HOUR
;
200 t
%= U_MILLIS_PER_HOUR
;
201 int32_t min
= t
/ U_MILLIS_PER_MINUTE
;
202 t
%= U_MILLIS_PER_MINUTE
;
203 int32_t sec
= t
/ U_MILLIS_PER_SECOND
;
205 appendAsciiDigits(hour
, 2, str
);
206 appendAsciiDigits(min
, 2, str
);
207 appendAsciiDigits(sec
, 2, str
);
212 * Convert date/time to RFC2445 Date-Time form #2 DATE WITH UTC TIME
214 static UnicodeString
& getUTCDateTimeString(UDate time
, UnicodeString
& str
) {
215 getDateTimeString(time
, str
);
216 str
.append((UChar
)0x005A /*'Z'*/);
221 * Parse RFC2445 Date-Time form #1 DATE WITH LOCAL TIME and
222 * #2 DATE WITH UTC TIME
224 static UDate
parseDateTimeString(const UnicodeString
& str
, int32_t offset
, UErrorCode
& status
) {
225 if (U_FAILURE(status
)) {
229 int32_t year
= 0, month
= 0, day
= 0, hour
= 0, min
= 0, sec
= 0;
231 UBool isValid
= FALSE
;
233 int length
= str
.length();
234 if (length
!= 15 && length
!= 16) {
235 // FORM#1 15 characters, such as "20060317T142115"
236 // FORM#2 16 characters, such as "20060317T142115Z"
239 if (str
.charAt(8) != 0x0054) {
240 // charcter "T" must be used for separating date and time
244 if (str
.charAt(15) != 0x005A) {
251 year
= parseAsciiDigits(str
, 0, 4, status
);
252 month
= parseAsciiDigits(str
, 4, 2, status
) - 1; // 0-based
253 day
= parseAsciiDigits(str
, 6, 2, status
);
254 hour
= parseAsciiDigits(str
, 9, 2, status
);
255 min
= parseAsciiDigits(str
, 11, 2, status
);
256 sec
= parseAsciiDigits(str
, 13, 2, status
);
258 if (U_FAILURE(status
)) {
263 int32_t maxDayOfMonth
= Grego::monthLength(year
, month
);
264 if (year
< 0 || month
< 0 || month
> 11 || day
< 1 || day
> maxDayOfMonth
||
265 hour
< 0 || hour
>= 24 || min
< 0 || min
>= 60 || sec
< 0 || sec
>= 60) {
273 status
= U_INVALID_FORMAT_ERROR
;
276 // Calculate the time
277 UDate time
= Grego::fieldsToDay(year
, month
, day
) * U_MILLIS_PER_DAY
;
278 time
+= (hour
* U_MILLIS_PER_HOUR
+ min
* U_MILLIS_PER_MINUTE
+ sec
* U_MILLIS_PER_SECOND
);
286 * Convert RFC2445 utc-offset string to milliseconds
288 static int32_t offsetStrToMillis(const UnicodeString
& str
, UErrorCode
& status
) {
289 if (U_FAILURE(status
)) {
293 UBool isValid
= FALSE
;
294 int32_t sign
= 0, hour
= 0, min
= 0, sec
= 0;
297 int length
= str
.length();
298 if (length
!= 5 && length
!= 7) {
299 // utf-offset must be 5 or 7 characters
303 UChar s
= str
.charAt(0);
306 } else if (s
== MINUS
) {
309 // utf-offset must start with "+" or "-"
312 hour
= parseAsciiDigits(str
, 1, 2, status
);
313 min
= parseAsciiDigits(str
, 3, 2, status
);
315 sec
= parseAsciiDigits(str
, 5, 2, status
);
317 if (U_FAILURE(status
)) {
324 status
= U_INVALID_FORMAT_ERROR
;
327 int32_t millis
= sign
* ((hour
* 60 + min
) * 60 + sec
) * 1000;
332 * Convert milliseconds to RFC2445 utc-offset string
334 static void millisToOffset(int32_t millis
, UnicodeString
& str
) {
342 int32_t hour
, min
, sec
;
343 int32_t t
= millis
/ 1000;
350 appendAsciiDigits(hour
, 2, str
);
351 appendAsciiDigits(min
, 2, str
);
352 appendAsciiDigits(sec
, 2, str
);
356 * Create a default TZNAME from TZID
358 static void getDefaultTZName(const UnicodeString tzid
, UBool isDST
, UnicodeString
& zonename
) {
361 zonename
+= UNICODE_STRING_SIMPLE("(DST)");
363 zonename
+= UNICODE_STRING_SIMPLE("(STD)");
368 * Parse individual RRULE
372 * month calculated by BYMONTH-1, or -1 when not found
373 * dow day of week in BYDAY, or 0 when not found
374 * wim day of week ordinal number in BYDAY, or 0 when not found
375 * dom an array of day of month
376 * domCount number of availble days in dom (domCount is specifying the size of dom on input)
377 * until time defined by UNTIL attribute or MIN_MILLIS if not available
379 static void parseRRULE(const UnicodeString
& rrule
, int32_t& month
, int32_t& dow
, int32_t& wim
,
380 int32_t* dom
, int32_t& domCount
, UDate
& until
, UErrorCode
& status
) {
381 if (U_FAILURE(status
)) {
391 UBool yearly
= FALSE
;
392 //UBool parseError = FALSE;
394 int32_t prop_start
= 0;
396 UnicodeString prop
, attr
, value
;
397 UBool nextProp
= TRUE
;
400 prop_end
= rrule
.indexOf(SEMICOLON
, prop_start
);
401 if (prop_end
== -1) {
402 prop
.setTo(rrule
, prop_start
);
405 prop
.setTo(rrule
, prop_start
, prop_end
- prop_start
);
406 prop_start
= prop_end
+ 1;
408 int32_t eql
= prop
.indexOf(EQUALS_SIGN
);
410 attr
.setTo(prop
, 0, eql
);
411 value
.setTo(prop
, eql
+ 1);
413 goto rruleParseError
;
416 if (attr
.compare(ICAL_FREQ
, -1) == 0) {
417 // only support YEARLY frequency type
418 if (value
.compare(ICAL_YEARLY
, -1) == 0) {
421 goto rruleParseError
;
423 } else if (attr
.compare(ICAL_UNTIL
, -1) == 0) {
424 // ISO8601 UTC format, for example, "20060315T020000Z"
425 until
= parseDateTimeString(value
, 0, status
);
426 if (U_FAILURE(status
)) {
427 goto rruleParseError
;
429 } else if (attr
.compare(ICAL_BYMONTH
, -1) == 0) {
430 // Note: BYMONTH may contain multiple months, but only single month make sense for
431 // VTIMEZONE property.
432 if (value
.length() > 2) {
433 goto rruleParseError
;
435 month
= parseAsciiDigits(value
, 0, value
.length(), status
) - 1;
436 if (U_FAILURE(status
) || month
< 0 || month
>= 12) {
437 goto rruleParseError
;
439 } else if (attr
.compare(ICAL_BYDAY
, -1) == 0) {
440 // Note: BYDAY may contain multiple day of week separated by comma. It is unlikely used for
441 // VTIMEZONE property. We do not support the case.
443 // 2-letter format is used just for representing a day of week, for example, "SU" for Sunday
444 // 3 or 4-letter format is used for represeinging Nth day of week, for example, "-1SA" for last Saturday
445 int32_t length
= value
.length();
446 if (length
< 2 || length
> 4) {
447 goto rruleParseError
;
452 if (value
.charAt(0) == PLUS
) {
454 } else if (value
.charAt(0) == MINUS
) {
456 } else if (length
== 4) {
457 goto rruleParseError
;
459 int32_t n
= parseAsciiDigits(value
, length
- 3, 1, status
);
460 if (U_FAILURE(status
) || n
== 0 || n
> 4) {
461 goto rruleParseError
;
464 value
.remove(0, length
- 2);
467 for (wday
= 0; wday
< 7; wday
++) {
468 if (value
.compare(ICAL_DOW_NAMES
[wday
], 2) == 0) {
473 // Sunday(1) - Saturday(7)
476 goto rruleParseError
;
478 } else if (attr
.compare(ICAL_BYMONTHDAY
, -1) == 0) {
479 // Note: BYMONTHDAY may contain multiple days delimitted by comma
481 // A value of BYMONTHDAY could be negative, for example, -1 means
482 // the last day in a month
484 int32_t dom_start
= 0;
486 UBool nextDOM
= TRUE
;
488 dom_end
= value
.indexOf(COMMA
, dom_start
);
490 dom_end
= value
.length();
493 if (dom_idx
< domCount
) {
494 dom
[dom_idx
] = parseAsciiDigits(value
, dom_start
, dom_end
- dom_start
, status
);
495 if (U_FAILURE(status
)) {
496 goto rruleParseError
;
500 status
= U_BUFFER_OVERFLOW_ERROR
;
501 goto rruleParseError
;
503 dom_start
= dom_end
+ 1;
509 // FREQ=YEARLY must be set
510 goto rruleParseError
;
512 // Set actual number of parsed DOM (ICAL_BYMONTHDAY)
517 if (U_SUCCESS(status
)) {
519 status
= U_INVALID_FORMAT_ERROR
;
523 static TimeZoneRule
* createRuleByRRULE(const UnicodeString
& zonename
, int rawOffset
, int dstSavings
, UDate start
,
524 UVector
* dates
, int fromOffset
, UErrorCode
& status
) {
525 if (U_FAILURE(status
)) {
528 if (dates
== NULL
|| dates
->size() == 0) {
529 status
= U_ILLEGAL_ARGUMENT_ERROR
;
534 DateTimeRule
*adtr
= NULL
;
536 // Parse the first rule
537 UnicodeString rrule
= *((UnicodeString
*)dates
->elementAt(0));
538 int32_t month
, dayOfWeek
, nthDayOfWeek
, dayOfMonth
= 0;
540 int32_t daysCount
= sizeof(days
)/sizeof(days
[0]);
543 parseRRULE(rrule
, month
, dayOfWeek
, nthDayOfWeek
, days
, daysCount
, until
, status
);
544 if (U_FAILURE(status
)) {
548 if (dates
->size() == 1) {
551 // Multiple BYMONTHDAY values
552 if (daysCount
!= 7 || month
== -1 || dayOfWeek
== 0) {
553 // Only support the rule using 7 continuous days
554 // BYMONTH and BYDAY must be set at the same time
555 goto unsupportedRRule
;
557 int32_t firstDay
= 31; // max possible number of dates in a month
558 for (i
= 0; i
< 7; i
++) {
559 // Resolve negative day numbers. A negative day number should
560 // not be used in February, but if we see such case, we use 28
563 days
[i
] = MONTHLENGTH
[month
] + days
[i
] + 1;
565 if (days
[i
] < firstDay
) {
569 // Make sure days are continuous
570 for (i
= 1; i
< 7; i
++) {
572 for (j
= 0; j
< 7; j
++) {
573 if (days
[j
] == firstDay
+ i
) {
579 // days are not continuous
580 goto unsupportedRRule
;
583 // Use DOW_GEQ_DOM rule with firstDay as the start date
584 dayOfMonth
= firstDay
;
587 // Check if BYMONTH + BYMONTHDAY + BYDAY rule with multiple RRULE lines.
588 // Otherwise, not supported.
589 if (month
== -1 || dayOfWeek
== 0 || daysCount
== 0) {
590 // This is not the case
591 goto unsupportedRRule
;
593 // Parse the rest of rules if number of rules is not exceeding 7.
594 // We can only support 7 continuous days starting from a day of month.
595 if (dates
->size() > 7) {
596 goto unsupportedRRule
;
599 // Note: To check valid date range across multiple rule is a little
600 // bit complicated. For now, this code is not doing strict range
601 // checking across month boundary
603 int32_t earliestMonth
= month
;
604 int32_t earliestDay
= 31;
605 for (i
= 0; i
< daysCount
; i
++) {
606 int32_t dom
= days
[i
];
607 dom
= dom
> 0 ? dom
: MONTHLENGTH
[month
] + dom
+ 1;
608 earliestDay
= dom
< earliestDay
? dom
: earliestDay
;
611 int32_t anotherMonth
= -1;
612 for (i
= 1; i
< dates
->size(); i
++) {
613 rrule
= *((UnicodeString
*)dates
->elementAt(i
));
615 int32_t tmp_month
, tmp_dayOfWeek
, tmp_nthDayOfWeek
;
617 int32_t tmp_daysCount
= sizeof(tmp_days
)/sizeof(tmp_days
[0]);
618 parseRRULE(rrule
, tmp_month
, tmp_dayOfWeek
, tmp_nthDayOfWeek
, tmp_days
, tmp_daysCount
, tmp_until
, status
);
619 if (U_FAILURE(status
)) {
622 // If UNTIL is newer than previous one, use the one
623 if (tmp_until
> until
) {
627 // Check if BYMONTH + BYMONTHDAY + BYDAY rule
628 if (tmp_month
== -1 || tmp_dayOfWeek
== 0 || tmp_daysCount
== 0) {
629 goto unsupportedRRule
;
631 // Count number of BYMONTHDAY
632 if (daysCount
+ tmp_daysCount
> 7) {
633 // We cannot support BYMONTHDAY more than 7
634 goto unsupportedRRule
;
636 // Check if the same BYDAY is used. Otherwise, we cannot
638 if (tmp_dayOfWeek
!= dayOfWeek
) {
639 goto unsupportedRRule
;
641 // Check if the month is same or right next to the primary month
642 if (tmp_month
!= month
) {
643 if (anotherMonth
== -1) {
644 int32_t diff
= tmp_month
- month
;
645 if (diff
== -11 || diff
== -1) {
647 anotherMonth
= tmp_month
;
648 earliestMonth
= anotherMonth
;
649 // Reset earliest day
651 } else if (diff
== 11 || diff
== 1) {
653 anotherMonth
= tmp_month
;
655 // The day range cannot exceed more than 2 months
656 goto unsupportedRRule
;
658 } else if (tmp_month
!= month
&& tmp_month
!= anotherMonth
) {
659 // The day range cannot exceed more than 2 months
660 goto unsupportedRRule
;
663 // If ealier month, go through days to find the earliest day
664 if (tmp_month
== earliestMonth
) {
665 for (j
= 0; j
< tmp_daysCount
; j
++) {
666 tmp_days
[j
] = tmp_days
[j
] > 0 ? tmp_days
[j
] : MONTHLENGTH
[tmp_month
] + tmp_days
[j
] + 1;
667 earliestDay
= tmp_days
[j
] < earliestDay
? tmp_days
[j
] : earliestDay
;
670 daysCount
+= tmp_daysCount
;
672 if (daysCount
!= 7) {
673 // Number of BYMONTHDAY entries must be 7
674 goto unsupportedRRule
;
676 month
= earliestMonth
;
677 dayOfMonth
= earliestDay
;
680 // Calculate start/end year and missing fields
681 int32_t startYear
, startMonth
, startDOM
, startDOW
, startDOY
, startMID
;
682 Grego::timeToFields(start
+ fromOffset
, startYear
, startMonth
, startDOM
,
683 startDOW
, startDOY
, startMID
);
685 // If BYMONTH is not set, use the month of DTSTART
688 if (dayOfWeek
== 0 && nthDayOfWeek
== 0 && dayOfMonth
== 0) {
689 // If only YEARLY is set, use the day of DTSTART as BYMONTHDAY
690 dayOfMonth
= startDOM
;
694 if (until
!= MIN_MILLIS
) {
695 int32_t endMonth
, endDOM
, endDOW
, endDOY
, endMID
;
696 Grego::timeToFields(until
, endYear
, endMonth
, endDOM
, endDOW
, endDOY
, endMID
);
698 endYear
= AnnualTimeZoneRule::MAX_YEAR
;
701 // Create the AnnualDateTimeRule
702 if (dayOfWeek
== 0 && nthDayOfWeek
== 0 && dayOfMonth
!= 0) {
703 // Day in month rule, for example, 15th day in the month
704 adtr
= new DateTimeRule(month
, dayOfMonth
, startMID
, DateTimeRule::WALL_TIME
);
705 } else if (dayOfWeek
!= 0 && nthDayOfWeek
!= 0 && dayOfMonth
== 0) {
706 // Nth day of week rule, for example, last Sunday
707 adtr
= new DateTimeRule(month
, nthDayOfWeek
, dayOfWeek
, startMID
, DateTimeRule::WALL_TIME
);
708 } else if (dayOfWeek
!= 0 && nthDayOfWeek
== 0 && dayOfMonth
!= 0) {
709 // First day of week after day of month rule, for example,
710 // first Sunday after 15th day in the month
711 adtr
= new DateTimeRule(month
, dayOfMonth
, dayOfWeek
, TRUE
, startMID
, DateTimeRule::WALL_TIME
);
714 goto unsupportedRRule
;
716 return new AnnualTimeZoneRule(zonename
, rawOffset
, dstSavings
, adtr
, startYear
, endYear
);
719 status
= U_INVALID_STATE_ERROR
;
724 * Create a TimeZoneRule by the RDATE definition
726 static TimeZoneRule
* createRuleByRDATE(const UnicodeString
& zonename
, int32_t rawOffset
, int32_t dstSavings
,
727 UDate start
, UVector
* dates
, int32_t fromOffset
, UErrorCode
& status
) {
728 if (U_FAILURE(status
)) {
731 TimeArrayTimeZoneRule
*retVal
= NULL
;
732 if (dates
== NULL
|| dates
->size() == 0) {
733 // When no RDATE line is provided, use start (DTSTART)
734 // as the transition time
735 retVal
= new TimeArrayTimeZoneRule(zonename
, rawOffset
, dstSavings
,
736 &start
, 1, DateTimeRule::UTC_TIME
);
738 // Create an array of transition times
739 int32_t size
= dates
->size();
740 UDate
* times
= (UDate
*)uprv_malloc(sizeof(UDate
) * size
);
742 status
= U_MEMORY_ALLOCATION_ERROR
;
745 for (int32_t i
= 0; i
< size
; i
++) {
746 UnicodeString
*datestr
= (UnicodeString
*)dates
->elementAt(i
);
747 times
[i
] = parseDateTimeString(*datestr
, fromOffset
, status
);
748 if (U_FAILURE(status
)) {
753 retVal
= new TimeArrayTimeZoneRule(zonename
, rawOffset
, dstSavings
,
754 times
, size
, DateTimeRule::UTC_TIME
);
761 * Check if the DOW rule specified by month, weekInMonth and dayOfWeek is equivalent
762 * to the DateTimerule.
764 static UBool
isEquivalentDateRule(int32_t month
, int32_t weekInMonth
, int32_t dayOfWeek
, const DateTimeRule
*dtrule
) {
765 if (month
!= dtrule
->getRuleMonth() || dayOfWeek
!= dtrule
->getRuleDayOfWeek()) {
768 if (dtrule
->getTimeRuleType() != DateTimeRule::WALL_TIME
) {
769 // Do not try to do more intelligent comparison for now.
772 if (dtrule
->getDateRuleType() == DateTimeRule::DOW
773 && dtrule
->getRuleWeekInMonth() == weekInMonth
) {
776 int32_t ruleDOM
= dtrule
->getRuleDayOfMonth();
777 if (dtrule
->getDateRuleType() == DateTimeRule::DOW_GEQ_DOM
) {
778 if (ruleDOM%7
== 1 && (ruleDOM
+ 6)/7 == weekInMonth
) {
781 if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - ruleDOM
)%7
== 6
782 && weekInMonth
== -1*((MONTHLENGTH
[month
]-ruleDOM
+1)/7)) {
786 if (dtrule
->getDateRuleType() == DateTimeRule::DOW_LEQ_DOM
) {
787 if (ruleDOM%7
== 0 && ruleDOM
/7 == weekInMonth
) {
790 if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - ruleDOM
)%7
== 0
791 && weekInMonth
== -1*((MONTHLENGTH
[month
] - ruleDOM
)/7 + 1)) {
799 * Convert the rule to its equivalent rule using WALL_TIME mode.
800 * This function returns NULL when the specified DateTimeRule is already
801 * using WALL_TIME mode.
803 static DateTimeRule
* toWallTimeRule(const DateTimeRule
* rule
, int32_t rawOffset
, int32_t dstSavings
) {
804 if (rule
->getTimeRuleType() == DateTimeRule::WALL_TIME
) {
807 int32_t wallt
= rule
->getRuleMillisInDay();
808 if (rule
->getTimeRuleType() == DateTimeRule::UTC_TIME
) {
809 wallt
+= (rawOffset
+ dstSavings
);
810 } else if (rule
->getTimeRuleType() == DateTimeRule::STANDARD_TIME
) {
814 int32_t month
= -1, dom
= 0, dow
= 0;
815 DateTimeRule::DateRuleType dtype
;
819 wallt
+= U_MILLIS_PER_DAY
;
820 } else if (wallt
>= U_MILLIS_PER_DAY
) {
822 wallt
-= U_MILLIS_PER_DAY
;
825 month
= rule
->getRuleMonth();
826 dom
= rule
->getRuleDayOfMonth();
827 dow
= rule
->getRuleDayOfWeek();
828 dtype
= rule
->getDateRuleType();
831 if (dtype
== DateTimeRule::DOW
) {
832 // Convert to DOW_GEW_DOM or DOW_LEQ_DOM rule first
833 int32_t wim
= rule
->getRuleWeekInMonth();
835 dtype
= DateTimeRule::DOW_GEQ_DOM
;
836 dom
= 7 * (wim
- 1) + 1;
838 dtype
= DateTimeRule::DOW_LEQ_DOM
;
839 dom
= MONTHLENGTH
[month
] + 7 * (wim
+ 1);
842 // Shift one day before or after
846 month
= month
< UCAL_JANUARY
? UCAL_DECEMBER
: month
;
847 dom
= MONTHLENGTH
[month
];
848 } else if (dom
> MONTHLENGTH
[month
]) {
850 month
= month
> UCAL_DECEMBER
? UCAL_JANUARY
: month
;
853 if (dtype
!= DateTimeRule::DOM
) {
854 // Adjust day of week
856 if (dow
< UCAL_SUNDAY
) {
858 } else if (dow
> UCAL_SATURDAY
) {
864 DateTimeRule
*modifiedRule
;
865 if (dtype
== DateTimeRule::DOM
) {
866 modifiedRule
= new DateTimeRule(month
, dom
, wallt
, DateTimeRule::WALL_TIME
);
868 modifiedRule
= new DateTimeRule(month
, dom
, dow
,
869 (dtype
== DateTimeRule::DOW_GEQ_DOM
), wallt
, DateTimeRule::WALL_TIME
);
875 * Minumum implementations of stream writer/reader, writing/reading
876 * UnicodeString. For now, we do not want to introduce the dependency
877 * on the ICU I/O stream in this module. But we want to keep the code
878 * equivalent to the ICU4J implementation, which utilizes java.io.Writer/
883 VTZWriter(UnicodeString
& out
);
886 void write(const UnicodeString
& str
);
887 void write(UChar ch
);
888 void write(const UChar
* str
);
889 //void write(const UChar* str, int32_t length);
894 VTZWriter::VTZWriter(UnicodeString
& output
) {
898 VTZWriter::~VTZWriter() {
902 VTZWriter::write(const UnicodeString
& str
) {
907 VTZWriter::write(UChar ch
) {
912 VTZWriter::write(const UChar
* str
) {
913 out
->append(str
, -1);
918 VTZWriter::write(const UChar* str, int32_t length) {
919 out->append(str, length);
925 VTZReader(const UnicodeString
& input
);
930 const UnicodeString
* in
;
934 VTZReader::VTZReader(const UnicodeString
& input
) {
939 VTZReader::~VTZReader() {
943 VTZReader::read(void) {
945 if (index
< in
->length()) {
946 ch
= in
->charAt(index
);
953 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(VTimeZone
)
955 VTimeZone::VTimeZone()
956 : BasicTimeZone(), tz(NULL
), vtzlines(NULL
),
957 lastmod(MAX_MILLIS
) {
960 VTimeZone::VTimeZone(const VTimeZone
& source
)
961 : BasicTimeZone(source
), tz(NULL
), vtzlines(NULL
),
962 tzurl(source
.tzurl
), lastmod(source
.lastmod
),
963 olsonzid(source
.olsonzid
), icutzver(source
.icutzver
) {
964 if (source
.tz
!= NULL
) {
965 tz
= (BasicTimeZone
*)source
.tz
->clone();
967 if (source
.vtzlines
!= NULL
) {
968 UErrorCode status
= U_ZERO_ERROR
;
969 int32_t size
= source
.vtzlines
->size();
970 vtzlines
= new UVector(uprv_deleteUObject
, uhash_compareUnicodeString
, size
, status
);
971 if (U_SUCCESS(status
)) {
972 for (int32_t i
= 0; i
< size
; i
++) {
973 UnicodeString
*line
= (UnicodeString
*)source
.vtzlines
->elementAt(i
);
974 vtzlines
->addElement(line
->clone(), status
);
975 if (U_FAILURE(status
)) {
980 if (U_FAILURE(status
) && vtzlines
!= NULL
) {
986 VTimeZone::~VTimeZone() {
990 if (vtzlines
!= NULL
) {
996 VTimeZone::operator=(const VTimeZone
& right
) {
997 if (this == &right
) {
1000 if (*this != right
) {
1001 BasicTimeZone::operator=(right
);
1006 if (right
.tz
!= NULL
) {
1007 tz
= (BasicTimeZone
*)right
.tz
->clone();
1009 if (vtzlines
!= NULL
) {
1012 if (right
.vtzlines
!= NULL
) {
1013 UErrorCode status
= U_ZERO_ERROR
;
1014 int32_t size
= right
.vtzlines
->size();
1015 vtzlines
= new UVector(uprv_deleteUObject
, uhash_compareUnicodeString
, size
, status
);
1016 if (U_SUCCESS(status
)) {
1017 for (int32_t i
= 0; i
< size
; i
++) {
1018 UnicodeString
*line
= (UnicodeString
*)right
.vtzlines
->elementAt(i
);
1019 vtzlines
->addElement(line
->clone(), status
);
1020 if (U_FAILURE(status
)) {
1025 if (U_FAILURE(status
) && vtzlines
!= NULL
) {
1030 tzurl
= right
.tzurl
;
1031 lastmod
= right
.lastmod
;
1032 olsonzid
= right
.olsonzid
;
1033 icutzver
= right
.icutzver
;
1039 VTimeZone::operator==(const TimeZone
& that
) const {
1040 if (this == &that
) {
1043 if (typeid(*this) != typeid(that
) || !BasicTimeZone::operator==(that
)) {
1046 VTimeZone
*vtz
= (VTimeZone
*)&that
;
1047 if (*tz
== *(vtz
->tz
)
1048 && tzurl
== vtz
->tzurl
1049 && lastmod
== vtz
->lastmod
1050 /* && olsonzid = that.olsonzid */
1051 /* && icutzver = that.icutzver */) {
1058 VTimeZone::operator!=(const TimeZone
& that
) const {
1059 return !operator==(that
);
1063 VTimeZone::createVTimeZoneByID(const UnicodeString
& ID
) {
1064 VTimeZone
*vtz
= new VTimeZone();
1065 vtz
->tz
= (BasicTimeZone
*)TimeZone::createTimeZone(ID
);
1066 vtz
->tz
->getID(vtz
->olsonzid
);
1068 // Set ICU tzdata version
1069 UErrorCode status
= U_ZERO_ERROR
;
1070 UResourceBundle
*bundle
= NULL
;
1071 const UChar
* versionStr
= NULL
;
1073 bundle
= ures_openDirect(NULL
, "zoneinfo64", &status
);
1074 versionStr
= ures_getStringByKey(bundle
, "TZVersion", &len
, &status
);
1075 if (U_SUCCESS(status
)) {
1076 vtz
->icutzver
.setTo(versionStr
, len
);
1083 VTimeZone::createVTimeZoneFromBasicTimeZone(const BasicTimeZone
& basic_time_zone
, UErrorCode
&status
) {
1084 if (U_FAILURE(status
)) {
1087 VTimeZone
*vtz
= new VTimeZone();
1089 status
= U_MEMORY_ALLOCATION_ERROR
;
1092 vtz
->tz
= (BasicTimeZone
*)basic_time_zone
.clone();
1093 if (vtz
->tz
== NULL
) {
1094 status
= U_MEMORY_ALLOCATION_ERROR
;
1098 vtz
->tz
->getID(vtz
->olsonzid
);
1100 // Set ICU tzdata version
1101 UResourceBundle
*bundle
= NULL
;
1102 const UChar
* versionStr
= NULL
;
1104 bundle
= ures_openDirect(NULL
, "zoneinfo64", &status
);
1105 versionStr
= ures_getStringByKey(bundle
, "TZVersion", &len
, &status
);
1106 if (U_SUCCESS(status
)) {
1107 vtz
->icutzver
.setTo(versionStr
, len
);
1114 VTimeZone::createVTimeZone(const UnicodeString
& vtzdata
, UErrorCode
& status
) {
1115 if (U_FAILURE(status
)) {
1118 VTZReader
reader(vtzdata
);
1119 VTimeZone
*vtz
= new VTimeZone();
1120 vtz
->load(reader
, status
);
1121 if (U_FAILURE(status
)) {
1129 VTimeZone::getTZURL(UnicodeString
& url
) const {
1130 if (tzurl
.length() > 0) {
1138 VTimeZone::setTZURL(const UnicodeString
& url
) {
1143 VTimeZone::getLastModified(UDate
& lastModified
) const {
1144 if (lastmod
!= MAX_MILLIS
) {
1145 lastModified
= lastmod
;
1152 VTimeZone::setLastModified(UDate lastModified
) {
1153 lastmod
= lastModified
;
1157 VTimeZone::write(UnicodeString
& result
, UErrorCode
& status
) const {
1159 VTZWriter
writer(result
);
1160 write(writer
, status
);
1164 VTimeZone::write(UDate start
, UnicodeString
& result
, UErrorCode
& status
) const {
1166 VTZWriter
writer(result
);
1167 write(start
, writer
, status
);
1171 VTimeZone::writeSimple(UDate time
, UnicodeString
& result
, UErrorCode
& status
) const {
1173 VTZWriter
writer(result
);
1174 writeSimple(time
, writer
, status
);
1178 VTimeZone::clone(void) const {
1179 return new VTimeZone(*this);
1183 VTimeZone::getOffset(uint8_t era
, int32_t year
, int32_t month
, int32_t day
,
1184 uint8_t dayOfWeek
, int32_t millis
, UErrorCode
& status
) const {
1185 return tz
->getOffset(era
, year
, month
, day
, dayOfWeek
, millis
, status
);
1189 VTimeZone::getOffset(uint8_t era
, int32_t year
, int32_t month
, int32_t day
,
1190 uint8_t dayOfWeek
, int32_t millis
,
1191 int32_t monthLength
, UErrorCode
& status
) const {
1192 return tz
->getOffset(era
, year
, month
, day
, dayOfWeek
, millis
, monthLength
, status
);
1196 VTimeZone::getOffset(UDate date
, UBool local
, int32_t& rawOffset
,
1197 int32_t& dstOffset
, UErrorCode
& status
) const {
1198 return tz
->getOffset(date
, local
, rawOffset
, dstOffset
, status
);
1202 VTimeZone::setRawOffset(int32_t offsetMillis
) {
1203 tz
->setRawOffset(offsetMillis
);
1207 VTimeZone::getRawOffset(void) const {
1208 return tz
->getRawOffset();
1212 VTimeZone::useDaylightTime(void) const {
1213 return tz
->useDaylightTime();
1217 VTimeZone::inDaylightTime(UDate date
, UErrorCode
& status
) const {
1218 return tz
->inDaylightTime(date
, status
);
1222 VTimeZone::hasSameRules(const TimeZone
& other
) const {
1223 return tz
->hasSameRules(other
);
1227 VTimeZone::getNextTransition(UDate base
, UBool inclusive
, TimeZoneTransition
& result
) const {
1228 return tz
->getNextTransition(base
, inclusive
, result
);
1232 VTimeZone::getPreviousTransition(UDate base
, UBool inclusive
, TimeZoneTransition
& result
) const {
1233 return tz
->getPreviousTransition(base
, inclusive
, result
);
1237 VTimeZone::countTransitionRules(UErrorCode
& status
) const {
1238 return tz
->countTransitionRules(status
);
1242 VTimeZone::getTimeZoneRules(const InitialTimeZoneRule
*& initial
,
1243 const TimeZoneRule
* trsrules
[], int32_t& trscount
,
1244 UErrorCode
& status
) const {
1245 tz
->getTimeZoneRules(initial
, trsrules
, trscount
, status
);
1249 VTimeZone::load(VTZReader
& reader
, UErrorCode
& status
) {
1250 vtzlines
= new UVector(uprv_deleteUObject
, uhash_compareUnicodeString
, DEFAULT_VTIMEZONE_LINES
, status
);
1251 if (U_FAILURE(status
)) {
1255 UBool start
= FALSE
;
1256 UBool success
= FALSE
;
1260 UChar ch
= reader
.read();
1263 if (start
&& line
.startsWith(ICAL_END_VTIMEZONE
, -1)) {
1264 vtzlines
->addElement(new UnicodeString(line
), status
);
1265 if (U_FAILURE(status
)) {
1266 goto cleanupVtzlines
;
1273 // CR, must be followed by LF according to the definition in RFC2445
1277 if (ch
!= 0x0009 && ch
!= 0x0020) {
1278 // NOT followed by TAB/SP -> new line
1280 if (line
.length() > 0) {
1281 vtzlines
->addElement(new UnicodeString(line
), status
);
1282 if (U_FAILURE(status
)) {
1283 goto cleanupVtzlines
;
1298 if (line
.startsWith(ICAL_END_VTIMEZONE
, -1)) {
1299 vtzlines
->addElement(new UnicodeString(line
), status
);
1300 if (U_FAILURE(status
)) {
1301 goto cleanupVtzlines
;
1307 if (line
.startsWith(ICAL_BEGIN_VTIMEZONE
, -1)) {
1308 vtzlines
->addElement(new UnicodeString(line
), status
);
1309 if (U_FAILURE(status
)) {
1310 goto cleanupVtzlines
;
1323 if (U_SUCCESS(status
)) {
1324 status
= U_INVALID_STATE_ERROR
;
1326 goto cleanupVtzlines
;
1337 #define INI 0 // Initial state
1338 #define VTZ 1 // In VTIMEZONE
1339 #define TZI 2 // In STANDARD or DAYLIGHT
1341 #define DEF_DSTSAVINGS (60*60*1000)
1342 #define DEF_TZSTARTTIME (0.0)
1345 VTimeZone::parse(UErrorCode
& status
) {
1346 if (U_FAILURE(status
)) {
1349 if (vtzlines
== NULL
|| vtzlines
->size() == 0) {
1350 status
= U_INVALID_STATE_ERROR
;
1353 InitialTimeZoneRule
*initialRule
= NULL
;
1354 RuleBasedTimeZone
*rbtz
= NULL
;
1359 int32_t state
= INI
;
1361 UBool dst
= FALSE
; // current zone type
1362 UnicodeString from
; // current zone from offset
1363 UnicodeString to
; // current zone offset
1364 UnicodeString zonename
; // current zone name
1365 UnicodeString dtstart
; // current zone starts
1366 UBool isRRULE
= FALSE
; // true if the rule is described by RRULE
1367 int32_t initialRawOffset
= 0; // initial offset
1368 int32_t initialDSTSavings
= 0; // initial offset
1369 UDate firstStart
= MAX_MILLIS
; // the earliest rule start time
1370 UnicodeString name
; // RFC2445 prop name
1371 UnicodeString value
; // RFC2445 prop value
1373 UVector
*dates
= NULL
; // list of RDATE or RRULE strings
1374 UVector
*rules
= NULL
; // list of TimeZoneRule instances
1376 int32_t finalRuleIdx
= -1;
1377 int32_t finalRuleCount
= 0;
1379 rules
= new UVector(status
);
1380 if (U_FAILURE(status
)) {
1383 // Set the deleter to remove TimeZoneRule vectors to avoid memory leaks due to unowned TimeZoneRules.
1384 rules
->setDeleter(deleteTimeZoneRule
);
1386 dates
= new UVector(uprv_deleteUObject
, uhash_compareUnicodeString
, status
);
1387 if (U_FAILURE(status
)) {
1390 if (rules
== NULL
|| dates
== NULL
) {
1391 status
= U_MEMORY_ALLOCATION_ERROR
;
1395 for (n
= 0; n
< vtzlines
->size(); n
++) {
1396 UnicodeString
*line
= (UnicodeString
*)vtzlines
->elementAt(n
);
1397 int32_t valueSep
= line
->indexOf(COLON
);
1401 name
.setTo(*line
, 0, valueSep
);
1402 value
.setTo(*line
, valueSep
+ 1);
1406 if (name
.compare(ICAL_BEGIN
, -1) == 0
1407 && value
.compare(ICAL_VTIMEZONE
, -1) == 0) {
1413 if (name
.compare(ICAL_TZID
, -1) == 0) {
1415 } else if (name
.compare(ICAL_TZURL
, -1) == 0) {
1417 } else if (name
.compare(ICAL_LASTMOD
, -1) == 0) {
1418 // Always in 'Z' format, so the offset argument for the parse method
1419 // can be any value.
1420 lastmod
= parseDateTimeString(value
, 0, status
);
1421 if (U_FAILURE(status
)) {
1424 } else if (name
.compare(ICAL_BEGIN
, -1) == 0) {
1425 UBool isDST
= (value
.compare(ICAL_DAYLIGHT
, -1) == 0);
1426 if (value
.compare(ICAL_STANDARD
, -1) == 0 || isDST
) {
1427 // tzid must be ready at this point
1428 if (tzid
.length() == 0) {
1431 // initialize current zone properties
1432 if (dates
->size() != 0) {
1433 dates
->removeAllElements();
1442 // BEGIN property other than STANDARD/DAYLIGHT
1443 // must not be there.
1446 } else if (name
.compare(ICAL_END
, -1) == 0) {
1451 if (name
.compare(ICAL_DTSTART
, -1) == 0) {
1453 } else if (name
.compare(ICAL_TZNAME
, -1) == 0) {
1455 } else if (name
.compare(ICAL_TZOFFSETFROM
, -1) == 0) {
1457 } else if (name
.compare(ICAL_TZOFFSETTO
, -1) == 0) {
1459 } else if (name
.compare(ICAL_RDATE
, -1) == 0) {
1460 // RDATE mixed with RRULE is not supported
1464 // RDATE value may contain multiple date delimited
1466 UBool nextDate
= TRUE
;
1468 UnicodeString
*dstr
;
1470 int32_t dend
= value
.indexOf(COMMA
, dstart
);
1472 dstr
= new UnicodeString(value
, dstart
);
1475 dstr
= new UnicodeString(value
, dstart
, dend
- dstart
);
1477 dates
->addElement(dstr
, status
);
1478 if (U_FAILURE(status
)) {
1483 } else if (name
.compare(ICAL_RRULE
, -1) == 0) {
1484 // RRULE mixed with RDATE is not supported
1485 if (!isRRULE
&& dates
->size() != 0) {
1489 dates
->addElement(new UnicodeString(value
), status
);
1490 if (U_FAILURE(status
)) {
1493 } else if (name
.compare(ICAL_END
, -1) == 0) {
1494 // Mandatory properties
1495 if (dtstart
.length() == 0 || from
.length() == 0 || to
.length() == 0) {
1498 // if zonename is not available, create one from tzid
1499 if (zonename
.length() == 0) {
1500 getDefaultTZName(tzid
, dst
, zonename
);
1503 // create a time zone rule
1504 TimeZoneRule
*rule
= NULL
;
1505 int32_t fromOffset
= 0;
1506 int32_t toOffset
= 0;
1507 int32_t rawOffset
= 0;
1508 int32_t dstSavings
= 0;
1511 // Parse TZOFFSETFROM/TZOFFSETTO
1512 fromOffset
= offsetStrToMillis(from
, status
);
1513 toOffset
= offsetStrToMillis(to
, status
);
1514 if (U_FAILURE(status
)) {
1519 // If daylight, use the previous offset as rawoffset if positive
1520 if (toOffset
- fromOffset
> 0) {
1521 rawOffset
= fromOffset
;
1522 dstSavings
= toOffset
- fromOffset
;
1524 // This is rare case.. just use 1 hour DST savings
1525 rawOffset
= toOffset
- DEF_DSTSAVINGS
;
1526 dstSavings
= DEF_DSTSAVINGS
;
1529 rawOffset
= toOffset
;
1534 start
= parseDateTimeString(dtstart
, fromOffset
, status
);
1535 if (U_FAILURE(status
)) {
1540 UDate actualStart
= MAX_MILLIS
;
1542 rule
= createRuleByRRULE(zonename
, rawOffset
, dstSavings
, start
, dates
, fromOffset
, status
);
1544 rule
= createRuleByRDATE(zonename
, rawOffset
, dstSavings
, start
, dates
, fromOffset
, status
);
1546 if (U_FAILURE(status
) || rule
== NULL
) {
1549 UBool startAvail
= rule
->getFirstStart(fromOffset
, 0, actualStart
);
1550 if (startAvail
&& actualStart
< firstStart
) {
1551 // save from offset information for the earliest rule
1552 firstStart
= actualStart
;
1553 // If this is STD, assume the time before this transtion
1554 // is DST when the difference is 1 hour. This might not be
1555 // accurate, but VTIMEZONE data does not have such info.
1556 if (dstSavings
> 0) {
1557 initialRawOffset
= fromOffset
;
1558 initialDSTSavings
= 0;
1560 if (fromOffset
- toOffset
== DEF_DSTSAVINGS
) {
1561 initialRawOffset
= fromOffset
- DEF_DSTSAVINGS
;
1562 initialDSTSavings
= DEF_DSTSAVINGS
;
1564 initialRawOffset
= fromOffset
;
1565 initialDSTSavings
= 0;
1570 rules
->addElement(rule
, status
);
1571 if (U_FAILURE(status
)) {
1579 // Must have at least one rule
1580 if (rules
->size() == 0) {
1584 // Create a initial rule
1585 getDefaultTZName(tzid
, FALSE
, zonename
);
1586 initialRule
= new InitialTimeZoneRule(zonename
,
1587 initialRawOffset
, initialDSTSavings
);
1588 if (initialRule
== NULL
) {
1589 status
= U_MEMORY_ALLOCATION_ERROR
;
1593 // Finally, create the RuleBasedTimeZone
1594 rbtz
= new RuleBasedTimeZone(tzid
, initialRule
);
1596 status
= U_MEMORY_ALLOCATION_ERROR
;
1599 initialRule
= NULL
; // already adopted by RBTZ, no need to delete
1601 for (n
= 0; n
< rules
->size(); n
++) {
1602 TimeZoneRule
*r
= (TimeZoneRule
*)rules
->elementAt(n
);
1603 AnnualTimeZoneRule
*atzrule
= dynamic_cast<AnnualTimeZoneRule
*>(r
);
1604 if (atzrule
!= NULL
) {
1605 if (atzrule
->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
) {
1611 if (finalRuleCount
> 2) {
1612 // Too many final rules
1613 status
= U_ILLEGAL_ARGUMENT_ERROR
;
1617 if (finalRuleCount
== 1) {
1618 if (rules
->size() == 1) {
1619 // Only one final rule, only governs the initial rule,
1620 // which is already initialized, thus, we do not need to
1621 // add this transition rule
1622 rules
->removeAllElements();
1624 // Normalize the final rule
1625 AnnualTimeZoneRule
*finalRule
= (AnnualTimeZoneRule
*)rules
->elementAt(finalRuleIdx
);
1626 int32_t tmpRaw
= finalRule
->getRawOffset();
1627 int32_t tmpDST
= finalRule
->getDSTSavings();
1629 // Find the last non-final rule
1630 UDate finalStart
, start
;
1631 finalRule
->getFirstStart(initialRawOffset
, initialDSTSavings
, finalStart
);
1633 for (n
= 0; n
< rules
->size(); n
++) {
1634 if (finalRuleIdx
== n
) {
1637 TimeZoneRule
*r
= (TimeZoneRule
*)rules
->elementAt(n
);
1639 r
->getFinalStart(tmpRaw
, tmpDST
, lastStart
);
1640 if (lastStart
> start
) {
1641 finalRule
->getNextStart(lastStart
,
1649 TimeZoneRule
*newRule
;
1650 UnicodeString tznam
;
1651 if (start
== finalStart
) {
1652 // Transform this into a single transition
1653 newRule
= new TimeArrayTimeZoneRule(
1654 finalRule
->getName(tznam
),
1655 finalRule
->getRawOffset(),
1656 finalRule
->getDSTSavings(),
1659 DateTimeRule::UTC_TIME
);
1661 // Update the end year
1662 int32_t y
, m
, d
, dow
, doy
, mid
;
1663 Grego::timeToFields(start
, y
, m
, d
, dow
, doy
, mid
);
1664 newRule
= new AnnualTimeZoneRule(
1665 finalRule
->getName(tznam
),
1666 finalRule
->getRawOffset(),
1667 finalRule
->getDSTSavings(),
1668 *(finalRule
->getRule()),
1669 finalRule
->getStartYear(),
1672 if (newRule
== NULL
) {
1673 status
= U_MEMORY_ALLOCATION_ERROR
;
1676 rules
->removeElementAt(finalRuleIdx
);
1677 rules
->addElement(newRule
, status
);
1678 if (U_FAILURE(status
)) {
1685 while (!rules
->isEmpty()) {
1686 TimeZoneRule
*tzr
= (TimeZoneRule
*)rules
->orphanElementAt(0);
1687 rbtz
->addTransitionRule(tzr
, status
);
1688 if (U_FAILURE(status
)) {
1692 rbtz
->complete(status
);
1693 if (U_FAILURE(status
)) {
1704 if (rules
!= NULL
) {
1705 while (!rules
->isEmpty()) {
1706 TimeZoneRule
*r
= (TimeZoneRule
*)rules
->orphanElementAt(0);
1711 if (dates
!= NULL
) {
1714 if (initialRule
!= NULL
) {
1724 VTimeZone::write(VTZWriter
& writer
, UErrorCode
& status
) const {
1725 if (vtzlines
!= NULL
) {
1726 for (int32_t i
= 0; i
< vtzlines
->size(); i
++) {
1727 UnicodeString
*line
= (UnicodeString
*)vtzlines
->elementAt(i
);
1728 if (line
->startsWith(ICAL_TZURL
, -1)
1729 && line
->charAt(u_strlen(ICAL_TZURL
)) == COLON
) {
1730 writer
.write(ICAL_TZURL
);
1731 writer
.write(COLON
);
1732 writer
.write(tzurl
);
1733 writer
.write(ICAL_NEWLINE
);
1734 } else if (line
->startsWith(ICAL_LASTMOD
, -1)
1735 && line
->charAt(u_strlen(ICAL_LASTMOD
)) == COLON
) {
1736 UnicodeString utcString
;
1737 writer
.write(ICAL_LASTMOD
);
1738 writer
.write(COLON
);
1739 writer
.write(getUTCDateTimeString(lastmod
, utcString
));
1740 writer
.write(ICAL_NEWLINE
);
1742 writer
.write(*line
);
1743 writer
.write(ICAL_NEWLINE
);
1747 UVector
*customProps
= NULL
;
1748 if (olsonzid
.length() > 0 && icutzver
.length() > 0) {
1749 customProps
= new UVector(uprv_deleteUObject
, uhash_compareUnicodeString
, status
);
1750 if (U_FAILURE(status
)) {
1753 UnicodeString
*icutzprop
= new UnicodeString(ICU_TZINFO_PROP
);
1754 icutzprop
->append(olsonzid
);
1755 icutzprop
->append((UChar
)0x005B/*'['*/);
1756 icutzprop
->append(icutzver
);
1757 icutzprop
->append((UChar
)0x005D/*']'*/);
1758 customProps
->addElement(icutzprop
, status
);
1759 if (U_FAILURE(status
)) {
1765 writeZone(writer
, *tz
, customProps
, status
);
1771 VTimeZone::write(UDate start
, VTZWriter
& writer
, UErrorCode
& status
) const {
1772 if (U_FAILURE(status
)) {
1775 InitialTimeZoneRule
*initial
= NULL
;
1776 UVector
*transitionRules
= NULL
;
1777 UVector
customProps(uprv_deleteUObject
, uhash_compareUnicodeString
, status
);
1780 // Extract rules applicable to dates after the start time
1781 getTimeZoneRulesAfter(start
, initial
, transitionRules
, status
);
1782 if (U_FAILURE(status
)) {
1786 // Create a RuleBasedTimeZone with the subset rule
1788 RuleBasedTimeZone
rbtz(tzid
, initial
);
1789 if (transitionRules
!= NULL
) {
1790 while (!transitionRules
->isEmpty()) {
1791 TimeZoneRule
*tr
= (TimeZoneRule
*)transitionRules
->orphanElementAt(0);
1792 rbtz
.addTransitionRule(tr
, status
);
1793 if (U_FAILURE(status
)) {
1794 goto cleanupWritePartial
;
1797 delete transitionRules
;
1798 transitionRules
= NULL
;
1800 rbtz
.complete(status
);
1801 if (U_FAILURE(status
)) {
1802 goto cleanupWritePartial
;
1805 if (olsonzid
.length() > 0 && icutzver
.length() > 0) {
1806 UnicodeString
*icutzprop
= new UnicodeString(ICU_TZINFO_PROP
);
1807 icutzprop
->append(olsonzid
);
1808 icutzprop
->append((UChar
)0x005B/*'['*/);
1809 icutzprop
->append(icutzver
);
1810 icutzprop
->append(ICU_TZINFO_PARTIAL
, -1);
1811 appendMillis(start
, *icutzprop
);
1812 icutzprop
->append((UChar
)0x005D/*']'*/);
1813 customProps
.addElement(icutzprop
, status
);
1814 if (U_FAILURE(status
)) {
1816 goto cleanupWritePartial
;
1819 writeZone(writer
, rbtz
, &customProps
, status
);
1822 cleanupWritePartial
:
1823 if (initial
!= NULL
) {
1826 if (transitionRules
!= NULL
) {
1827 while (!transitionRules
->isEmpty()) {
1828 TimeZoneRule
*tr
= (TimeZoneRule
*)transitionRules
->orphanElementAt(0);
1831 delete transitionRules
;
1836 VTimeZone::writeSimple(UDate time
, VTZWriter
& writer
, UErrorCode
& status
) const {
1837 if (U_FAILURE(status
)) {
1841 UVector
customProps(uprv_deleteUObject
, uhash_compareUnicodeString
, status
);
1844 // Extract simple rules
1845 InitialTimeZoneRule
*initial
= NULL
;
1846 AnnualTimeZoneRule
*std
= NULL
, *dst
= NULL
;
1847 getSimpleRulesNear(time
, initial
, std
, dst
, status
);
1848 if (U_SUCCESS(status
)) {
1849 // Create a RuleBasedTimeZone with the subset rule
1851 RuleBasedTimeZone
rbtz(tzid
, initial
);
1852 if (std
!= NULL
&& dst
!= NULL
) {
1853 rbtz
.addTransitionRule(std
, status
);
1854 rbtz
.addTransitionRule(dst
, status
);
1856 if (U_FAILURE(status
)) {
1857 goto cleanupWriteSimple
;
1860 if (olsonzid
.length() > 0 && icutzver
.length() > 0) {
1861 UnicodeString
*icutzprop
= new UnicodeString(ICU_TZINFO_PROP
);
1862 icutzprop
->append(olsonzid
);
1863 icutzprop
->append((UChar
)0x005B/*'['*/);
1864 icutzprop
->append(icutzver
);
1865 icutzprop
->append(ICU_TZINFO_SIMPLE
, -1);
1866 appendMillis(time
, *icutzprop
);
1867 icutzprop
->append((UChar
)0x005D/*']'*/);
1868 customProps
.addElement(icutzprop
, status
);
1869 if (U_FAILURE(status
)) {
1871 goto cleanupWriteSimple
;
1874 writeZone(writer
, rbtz
, &customProps
, status
);
1879 if (initial
!= NULL
) {
1891 VTimeZone::writeZone(VTZWriter
& w
, BasicTimeZone
& basictz
,
1892 UVector
* customProps
, UErrorCode
& status
) const {
1893 if (U_FAILURE(status
)) {
1896 writeHeaders(w
, status
);
1897 if (U_FAILURE(status
)) {
1901 if (customProps
!= NULL
) {
1902 for (int32_t i
= 0; i
< customProps
->size(); i
++) {
1903 UnicodeString
*custprop
= (UnicodeString
*)customProps
->elementAt(i
);
1905 w
.write(ICAL_NEWLINE
);
1909 UDate t
= MIN_MILLIS
;
1910 UnicodeString dstName
;
1911 int32_t dstFromOffset
= 0;
1912 int32_t dstFromDSTSavings
= 0;
1913 int32_t dstToOffset
= 0;
1914 int32_t dstStartYear
= 0;
1915 int32_t dstMonth
= 0;
1916 int32_t dstDayOfWeek
= 0;
1917 int32_t dstWeekInMonth
= 0;
1918 int32_t dstMillisInDay
= 0;
1919 UDate dstStartTime
= 0.0;
1920 UDate dstUntilTime
= 0.0;
1921 int32_t dstCount
= 0;
1922 AnnualTimeZoneRule
*finalDstRule
= NULL
;
1924 UnicodeString stdName
;
1925 int32_t stdFromOffset
= 0;
1926 int32_t stdFromDSTSavings
= 0;
1927 int32_t stdToOffset
= 0;
1928 int32_t stdStartYear
= 0;
1929 int32_t stdMonth
= 0;
1930 int32_t stdDayOfWeek
= 0;
1931 int32_t stdWeekInMonth
= 0;
1932 int32_t stdMillisInDay
= 0;
1933 UDate stdStartTime
= 0.0;
1934 UDate stdUntilTime
= 0.0;
1935 int32_t stdCount
= 0;
1936 AnnualTimeZoneRule
*finalStdRule
= NULL
;
1938 int32_t year
, month
, dom
, dow
, doy
, mid
;
1939 UBool hasTransitions
= FALSE
;
1940 TimeZoneTransition tzt
;
1945 // Going through all transitions
1947 tztAvail
= basictz
.getNextTransition(t
, FALSE
, tzt
);
1951 hasTransitions
= TRUE
;
1953 tzt
.getTo()->getName(name
);
1954 isDst
= (tzt
.getTo()->getDSTSavings() != 0);
1955 int32_t fromOffset
= tzt
.getFrom()->getRawOffset() + tzt
.getFrom()->getDSTSavings();
1956 int32_t fromDSTSavings
= tzt
.getFrom()->getDSTSavings();
1957 int32_t toOffset
= tzt
.getTo()->getRawOffset() + tzt
.getTo()->getDSTSavings();
1958 Grego::timeToFields(tzt
.getTime() + fromOffset
, year
, month
, dom
, dow
, doy
, mid
);
1959 int32_t weekInMonth
= Grego::dayOfWeekInMonth(year
, month
, dom
);
1960 UBool sameRule
= FALSE
;
1961 const AnnualTimeZoneRule
*atzrule
;
1963 if (finalDstRule
== NULL
1964 && (atzrule
= dynamic_cast<const AnnualTimeZoneRule
*>(tzt
.getTo())) != NULL
1965 && atzrule
->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
1967 finalDstRule
= (AnnualTimeZoneRule
*)tzt
.getTo()->clone();
1970 if (year
== dstStartYear
+ dstCount
1971 && name
.compare(dstName
) == 0
1972 && dstFromOffset
== fromOffset
1973 && dstToOffset
== toOffset
1974 && dstMonth
== month
1975 && dstDayOfWeek
== dow
1976 && dstWeekInMonth
== weekInMonth
1977 && dstMillisInDay
== mid
) {
1978 // Update until time
1984 if (dstCount
== 1) {
1985 writeZonePropsByTime(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
, dstStartTime
,
1988 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
1989 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, dstUntilTime
, status
);
1991 if (U_FAILURE(status
)) {
1992 goto cleanupWriteZone
;
1997 // Reset this DST information
1999 dstFromOffset
= fromOffset
;
2000 dstFromDSTSavings
= fromDSTSavings
;
2001 dstToOffset
= toOffset
;
2002 dstStartYear
= year
;
2005 dstWeekInMonth
= weekInMonth
;
2006 dstMillisInDay
= mid
;
2007 dstStartTime
= dstUntilTime
= t
;
2010 if (finalStdRule
!= NULL
&& finalDstRule
!= NULL
) {
2014 if (finalStdRule
== NULL
2015 && (atzrule
= dynamic_cast<const AnnualTimeZoneRule
*>(tzt
.getTo())) != NULL
2016 && atzrule
->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
2018 finalStdRule
= (AnnualTimeZoneRule
*)tzt
.getTo()->clone();
2021 if (year
== stdStartYear
+ stdCount
2022 && name
.compare(stdName
) == 0
2023 && stdFromOffset
== fromOffset
2024 && stdToOffset
== toOffset
2025 && stdMonth
== month
2026 && stdDayOfWeek
== dow
2027 && stdWeekInMonth
== weekInMonth
2028 && stdMillisInDay
== mid
) {
2029 // Update until time
2035 if (stdCount
== 1) {
2036 writeZonePropsByTime(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
, stdStartTime
,
2039 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2040 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, stdUntilTime
, status
);
2042 if (U_FAILURE(status
)) {
2043 goto cleanupWriteZone
;
2048 // Reset this STD information
2050 stdFromOffset
= fromOffset
;
2051 stdFromDSTSavings
= fromDSTSavings
;
2052 stdToOffset
= toOffset
;
2053 stdStartYear
= year
;
2056 stdWeekInMonth
= weekInMonth
;
2057 stdMillisInDay
= mid
;
2058 stdStartTime
= stdUntilTime
= t
;
2061 if (finalStdRule
!= NULL
&& finalDstRule
!= NULL
) {
2066 if (!hasTransitions
) {
2067 // No transition - put a single non transition RDATE
2068 int32_t raw
, dst
, offset
;
2069 basictz
.getOffset(0.0/*any time*/, FALSE
, raw
, dst
, status
);
2070 if (U_FAILURE(status
)) {
2071 goto cleanupWriteZone
;
2076 basictz
.getID(tzid
);
2077 getDefaultTZName(tzid
, isDst
, name
);
2078 writeZonePropsByTime(w
, isDst
, name
,
2079 offset
, offset
, DEF_TZSTARTTIME
- offset
, FALSE
, status
);
2080 if (U_FAILURE(status
)) {
2081 goto cleanupWriteZone
;
2085 if (finalDstRule
== NULL
) {
2086 if (dstCount
== 1) {
2087 writeZonePropsByTime(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
, dstStartTime
,
2090 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
2091 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, dstUntilTime
, status
);
2093 if (U_FAILURE(status
)) {
2094 goto cleanupWriteZone
;
2097 if (dstCount
== 1) {
2098 writeFinalRule(w
, TRUE
, finalDstRule
,
2099 dstFromOffset
- dstFromDSTSavings
, dstFromDSTSavings
, dstStartTime
, status
);
2101 // Use a single rule if possible
2102 if (isEquivalentDateRule(dstMonth
, dstWeekInMonth
, dstDayOfWeek
, finalDstRule
->getRule())) {
2103 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
2104 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, MAX_MILLIS
, status
);
2106 // Not equivalent rule - write out two different rules
2107 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
2108 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, dstUntilTime
, status
);
2109 if (U_FAILURE(status
)) {
2110 goto cleanupWriteZone
;
2112 writeFinalRule(w
, TRUE
, finalDstRule
,
2113 dstFromOffset
- dstFromDSTSavings
, dstFromDSTSavings
, dstStartTime
, status
);
2116 if (U_FAILURE(status
)) {
2117 goto cleanupWriteZone
;
2122 if (finalStdRule
== NULL
) {
2123 if (stdCount
== 1) {
2124 writeZonePropsByTime(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
, stdStartTime
,
2127 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2128 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, stdUntilTime
, status
);
2130 if (U_FAILURE(status
)) {
2131 goto cleanupWriteZone
;
2134 if (stdCount
== 1) {
2135 writeFinalRule(w
, FALSE
, finalStdRule
,
2136 stdFromOffset
- stdFromDSTSavings
, stdFromDSTSavings
, stdStartTime
, status
);
2138 // Use a single rule if possible
2139 if (isEquivalentDateRule(stdMonth
, stdWeekInMonth
, stdDayOfWeek
, finalStdRule
->getRule())) {
2140 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2141 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, MAX_MILLIS
, status
);
2143 // Not equivalent rule - write out two different rules
2144 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2145 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, stdUntilTime
, status
);
2146 if (U_FAILURE(status
)) {
2147 goto cleanupWriteZone
;
2149 writeFinalRule(w
, FALSE
, finalStdRule
,
2150 stdFromOffset
- stdFromDSTSavings
, stdFromDSTSavings
, stdStartTime
, status
);
2153 if (U_FAILURE(status
)) {
2154 goto cleanupWriteZone
;
2159 writeFooter(w
, status
);
2163 if (finalStdRule
!= NULL
) {
2164 delete finalStdRule
;
2166 if (finalDstRule
!= NULL
) {
2167 delete finalDstRule
;
2172 VTimeZone::writeHeaders(VTZWriter
& writer
, UErrorCode
& status
) const {
2173 if (U_FAILURE(status
)) {
2179 writer
.write(ICAL_BEGIN
);
2180 writer
.write(COLON
);
2181 writer
.write(ICAL_VTIMEZONE
);
2182 writer
.write(ICAL_NEWLINE
);
2183 writer
.write(ICAL_TZID
);
2184 writer
.write(COLON
);
2186 writer
.write(ICAL_NEWLINE
);
2187 if (tzurl
.length() != 0) {
2188 writer
.write(ICAL_TZURL
);
2189 writer
.write(COLON
);
2190 writer
.write(tzurl
);
2191 writer
.write(ICAL_NEWLINE
);
2193 if (lastmod
!= MAX_MILLIS
) {
2194 UnicodeString lastmodStr
;
2195 writer
.write(ICAL_LASTMOD
);
2196 writer
.write(COLON
);
2197 writer
.write(getUTCDateTimeString(lastmod
, lastmodStr
));
2198 writer
.write(ICAL_NEWLINE
);
2203 * Write the closing section of the VTIMEZONE definition block
2206 VTimeZone::writeFooter(VTZWriter
& writer
, UErrorCode
& status
) const {
2207 if (U_FAILURE(status
)) {
2210 writer
.write(ICAL_END
);
2211 writer
.write(COLON
);
2212 writer
.write(ICAL_VTIMEZONE
);
2213 writer
.write(ICAL_NEWLINE
);
2217 * Write a single start time
2220 VTimeZone::writeZonePropsByTime(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2221 int32_t fromOffset
, int32_t toOffset
, UDate time
, UBool withRDATE
,
2222 UErrorCode
& status
) const {
2223 if (U_FAILURE(status
)) {
2226 beginZoneProps(writer
, isDst
, zonename
, fromOffset
, toOffset
, time
, status
);
2227 if (U_FAILURE(status
)) {
2231 writer
.write(ICAL_RDATE
);
2232 writer
.write(COLON
);
2233 UnicodeString timestr
;
2234 writer
.write(getDateTimeString(time
+ fromOffset
, timestr
));
2235 writer
.write(ICAL_NEWLINE
);
2237 endZoneProps(writer
, isDst
, status
);
2238 if (U_FAILURE(status
)) {
2244 * Write start times defined by a DOM rule using VTIMEZONE RRULE
2247 VTimeZone::writeZonePropsByDOM(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2248 int32_t fromOffset
, int32_t toOffset
,
2249 int32_t month
, int32_t dayOfMonth
, UDate startTime
, UDate untilTime
,
2250 UErrorCode
& status
) const {
2251 if (U_FAILURE(status
)) {
2254 beginZoneProps(writer
, isDst
, zonename
, fromOffset
, toOffset
, startTime
, status
);
2255 if (U_FAILURE(status
)) {
2258 beginRRULE(writer
, month
, status
);
2259 if (U_FAILURE(status
)) {
2262 writer
.write(ICAL_BYMONTHDAY
);
2263 writer
.write(EQUALS_SIGN
);
2265 appendAsciiDigits(dayOfMonth
, 0, dstr
);
2267 if (untilTime
!= MAX_MILLIS
) {
2268 appendUNTIL(writer
, getDateTimeString(untilTime
+ fromOffset
, dstr
), status
);
2269 if (U_FAILURE(status
)) {
2273 writer
.write(ICAL_NEWLINE
);
2274 endZoneProps(writer
, isDst
, status
);
2278 * Write start times defined by a DOW rule using VTIMEZONE RRULE
2281 VTimeZone::writeZonePropsByDOW(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2282 int32_t fromOffset
, int32_t toOffset
,
2283 int32_t month
, int32_t weekInMonth
, int32_t dayOfWeek
,
2284 UDate startTime
, UDate untilTime
, UErrorCode
& status
) const {
2285 if (U_FAILURE(status
)) {
2288 beginZoneProps(writer
, isDst
, zonename
, fromOffset
, toOffset
, startTime
, status
);
2289 if (U_FAILURE(status
)) {
2292 beginRRULE(writer
, month
, status
);
2293 if (U_FAILURE(status
)) {
2296 writer
.write(ICAL_BYDAY
);
2297 writer
.write(EQUALS_SIGN
);
2299 appendAsciiDigits(weekInMonth
, 0, dstr
);
2300 writer
.write(dstr
); // -4, -3, -2, -1, 1, 2, 3, 4
2301 writer
.write(ICAL_DOW_NAMES
[dayOfWeek
- 1]); // SU, MO, TU...
2303 if (untilTime
!= MAX_MILLIS
) {
2304 appendUNTIL(writer
, getDateTimeString(untilTime
+ fromOffset
, dstr
), status
);
2305 if (U_FAILURE(status
)) {
2309 writer
.write(ICAL_NEWLINE
);
2310 endZoneProps(writer
, isDst
, status
);
2314 * Write start times defined by a DOW_GEQ_DOM rule using VTIMEZONE RRULE
2317 VTimeZone::writeZonePropsByDOW_GEQ_DOM(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2318 int32_t fromOffset
, int32_t toOffset
,
2319 int32_t month
, int32_t dayOfMonth
, int32_t dayOfWeek
,
2320 UDate startTime
, UDate untilTime
, UErrorCode
& status
) const {
2321 if (U_FAILURE(status
)) {
2324 // Check if this rule can be converted to DOW rule
2325 if (dayOfMonth%7
== 1) {
2326 // Can be represented by DOW rule
2327 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2328 month
, (dayOfMonth
+ 6)/7, dayOfWeek
, startTime
, untilTime
, status
);
2329 if (U_FAILURE(status
)) {
2332 } else if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - dayOfMonth
)%7
== 6) {
2333 // Can be represented by DOW rule with negative week number
2334 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2335 month
, -1*((MONTHLENGTH
[month
] - dayOfMonth
+ 1)/7), dayOfWeek
, startTime
, untilTime
, status
);
2336 if (U_FAILURE(status
)) {
2340 // Otherwise, use BYMONTHDAY to include all possible dates
2341 beginZoneProps(writer
, isDst
, zonename
, fromOffset
, toOffset
, startTime
, status
);
2342 if (U_FAILURE(status
)) {
2345 // Check if all days are in the same month
2346 int32_t startDay
= dayOfMonth
;
2347 int32_t currentMonthDays
= 7;
2349 if (dayOfMonth
<= 0) {
2350 // The start day is in previous month
2351 int32_t prevMonthDays
= 1 - dayOfMonth
;
2352 currentMonthDays
-= prevMonthDays
;
2354 int32_t prevMonth
= (month
- 1) < 0 ? 11 : month
- 1;
2356 // Note: When a rule is separated into two, UNTIL attribute needs to be
2357 // calculated for each of them. For now, we skip this, because we basically use this method
2358 // only for final rules, which does not have the UNTIL attribute
2359 writeZonePropsByDOW_GEQ_DOM_sub(writer
, prevMonth
, -prevMonthDays
, dayOfWeek
, prevMonthDays
,
2360 MAX_MILLIS
/* Do not use UNTIL */, fromOffset
, status
);
2361 if (U_FAILURE(status
)) {
2365 // Start from 1 for the rest
2367 } else if (dayOfMonth
+ 6 > MONTHLENGTH
[month
]) {
2368 // Note: This code does not actually work well in February. For now, days in month in
2370 int32_t nextMonthDays
= dayOfMonth
+ 6 - MONTHLENGTH
[month
];
2371 currentMonthDays
-= nextMonthDays
;
2373 int32_t nextMonth
= (month
+ 1) > 11 ? 0 : month
+ 1;
2375 writeZonePropsByDOW_GEQ_DOM_sub(writer
, nextMonth
, 1, dayOfWeek
, nextMonthDays
,
2376 MAX_MILLIS
/* Do not use UNTIL */, fromOffset
, status
);
2377 if (U_FAILURE(status
)) {
2381 writeZonePropsByDOW_GEQ_DOM_sub(writer
, month
, startDay
, dayOfWeek
, currentMonthDays
,
2382 untilTime
, fromOffset
, status
);
2383 if (U_FAILURE(status
)) {
2386 endZoneProps(writer
, isDst
, status
);
2391 * Called from writeZonePropsByDOW_GEQ_DOM
2394 VTimeZone::writeZonePropsByDOW_GEQ_DOM_sub(VTZWriter
& writer
, int32_t month
, int32_t dayOfMonth
,
2395 int32_t dayOfWeek
, int32_t numDays
,
2396 UDate untilTime
, int32_t fromOffset
, UErrorCode
& status
) const {
2398 if (U_FAILURE(status
)) {
2401 int32_t startDayNum
= dayOfMonth
;
2402 UBool isFeb
= (month
== UCAL_FEBRUARY
);
2403 if (dayOfMonth
< 0 && !isFeb
) {
2404 // Use positive number if possible
2405 startDayNum
= MONTHLENGTH
[month
] + dayOfMonth
+ 1;
2407 beginRRULE(writer
, month
, status
);
2408 if (U_FAILURE(status
)) {
2411 writer
.write(ICAL_BYDAY
);
2412 writer
.write(EQUALS_SIGN
);
2413 writer
.write(ICAL_DOW_NAMES
[dayOfWeek
- 1]); // SU, MO, TU...
2414 writer
.write(SEMICOLON
);
2415 writer
.write(ICAL_BYMONTHDAY
);
2416 writer
.write(EQUALS_SIGN
);
2419 appendAsciiDigits(startDayNum
, 0, dstr
);
2421 for (int32_t i
= 1; i
< numDays
; i
++) {
2422 writer
.write(COMMA
);
2424 appendAsciiDigits(startDayNum
+ i
, 0, dstr
);
2428 if (untilTime
!= MAX_MILLIS
) {
2429 appendUNTIL(writer
, getDateTimeString(untilTime
+ fromOffset
, dstr
), status
);
2430 if (U_FAILURE(status
)) {
2434 writer
.write(ICAL_NEWLINE
);
2438 * Write start times defined by a DOW_LEQ_DOM rule using VTIMEZONE RRULE
2441 VTimeZone::writeZonePropsByDOW_LEQ_DOM(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2442 int32_t fromOffset
, int32_t toOffset
,
2443 int32_t month
, int32_t dayOfMonth
, int32_t dayOfWeek
,
2444 UDate startTime
, UDate untilTime
, UErrorCode
& status
) const {
2445 if (U_FAILURE(status
)) {
2448 // Check if this rule can be converted to DOW rule
2449 if (dayOfMonth%7
== 0) {
2450 // Can be represented by DOW rule
2451 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2452 month
, dayOfMonth
/7, dayOfWeek
, startTime
, untilTime
, status
);
2453 } else if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - dayOfMonth
)%7
== 0){
2454 // Can be represented by DOW rule with negative week number
2455 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2456 month
, -1*((MONTHLENGTH
[month
] - dayOfMonth
)/7 + 1), dayOfWeek
, startTime
, untilTime
, status
);
2457 } else if (month
== UCAL_FEBRUARY
&& dayOfMonth
== 29) {
2458 // Specical case for February
2459 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2460 UCAL_FEBRUARY
, -1, dayOfWeek
, startTime
, untilTime
, status
);
2462 // Otherwise, convert this to DOW_GEQ_DOM rule
2463 writeZonePropsByDOW_GEQ_DOM(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2464 month
, dayOfMonth
- 6, dayOfWeek
, startTime
, untilTime
, status
);
2469 * Write the final time zone rule using RRULE, with no UNTIL attribute
2472 VTimeZone::writeFinalRule(VTZWriter
& writer
, UBool isDst
, const AnnualTimeZoneRule
* rule
,
2473 int32_t fromRawOffset
, int32_t fromDSTSavings
,
2474 UDate startTime
, UErrorCode
& status
) const {
2475 if (U_FAILURE(status
)) {
2478 UBool modifiedRule
= TRUE
;
2479 const DateTimeRule
*dtrule
= toWallTimeRule(rule
->getRule(), fromRawOffset
, fromDSTSavings
);
2480 if (dtrule
== NULL
) {
2481 modifiedRule
= FALSE
;
2482 dtrule
= rule
->getRule();
2485 // If the rule's mills in a day is out of range, adjust start time.
2486 // Olson tzdata supports 24:00 of a day, but VTIMEZONE does not.
2487 // See ticket#7008/#7518
2489 int32_t timeInDay
= dtrule
->getRuleMillisInDay();
2490 if (timeInDay
< 0) {
2491 startTime
= startTime
+ (0 - timeInDay
);
2492 } else if (timeInDay
>= U_MILLIS_PER_DAY
) {
2493 startTime
= startTime
- (timeInDay
- (U_MILLIS_PER_DAY
- 1));
2496 int32_t toOffset
= rule
->getRawOffset() + rule
->getDSTSavings();
2498 rule
->getName(name
);
2499 switch (dtrule
->getDateRuleType()) {
2500 case DateTimeRule::DOM
:
2501 writeZonePropsByDOM(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2502 dtrule
->getRuleMonth(), dtrule
->getRuleDayOfMonth(), startTime
, MAX_MILLIS
, status
);
2504 case DateTimeRule::DOW
:
2505 writeZonePropsByDOW(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2506 dtrule
->getRuleMonth(), dtrule
->getRuleWeekInMonth(), dtrule
->getRuleDayOfWeek(), startTime
, MAX_MILLIS
, status
);
2508 case DateTimeRule::DOW_GEQ_DOM
:
2509 writeZonePropsByDOW_GEQ_DOM(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2510 dtrule
->getRuleMonth(), dtrule
->getRuleDayOfMonth(), dtrule
->getRuleDayOfWeek(), startTime
, MAX_MILLIS
, status
);
2512 case DateTimeRule::DOW_LEQ_DOM
:
2513 writeZonePropsByDOW_LEQ_DOM(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2514 dtrule
->getRuleMonth(), dtrule
->getRuleDayOfMonth(), dtrule
->getRuleDayOfWeek(), startTime
, MAX_MILLIS
, status
);
2523 * Write the opening section of zone properties
2526 VTimeZone::beginZoneProps(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2527 int32_t fromOffset
, int32_t toOffset
, UDate startTime
, UErrorCode
& status
) const {
2528 if (U_FAILURE(status
)) {
2531 writer
.write(ICAL_BEGIN
);
2532 writer
.write(COLON
);
2534 writer
.write(ICAL_DAYLIGHT
);
2536 writer
.write(ICAL_STANDARD
);
2538 writer
.write(ICAL_NEWLINE
);
2543 writer
.write(ICAL_TZOFFSETTO
);
2544 writer
.write(COLON
);
2545 millisToOffset(toOffset
, dstr
);
2547 writer
.write(ICAL_NEWLINE
);
2550 writer
.write(ICAL_TZOFFSETFROM
);
2551 writer
.write(COLON
);
2552 millisToOffset(fromOffset
, dstr
);
2554 writer
.write(ICAL_NEWLINE
);
2557 writer
.write(ICAL_TZNAME
);
2558 writer
.write(COLON
);
2559 writer
.write(zonename
);
2560 writer
.write(ICAL_NEWLINE
);
2563 writer
.write(ICAL_DTSTART
);
2564 writer
.write(COLON
);
2565 writer
.write(getDateTimeString(startTime
+ fromOffset
, dstr
));
2566 writer
.write(ICAL_NEWLINE
);
2570 * Writes the closing section of zone properties
2573 VTimeZone::endZoneProps(VTZWriter
& writer
, UBool isDst
, UErrorCode
& status
) const {
2574 if (U_FAILURE(status
)) {
2577 // END:STANDARD or END:DAYLIGHT
2578 writer
.write(ICAL_END
);
2579 writer
.write(COLON
);
2581 writer
.write(ICAL_DAYLIGHT
);
2583 writer
.write(ICAL_STANDARD
);
2585 writer
.write(ICAL_NEWLINE
);
2589 * Write the beggining part of RRULE line
2592 VTimeZone::beginRRULE(VTZWriter
& writer
, int32_t month
, UErrorCode
& status
) const {
2593 if (U_FAILURE(status
)) {
2597 writer
.write(ICAL_RRULE
);
2598 writer
.write(COLON
);
2599 writer
.write(ICAL_FREQ
);
2600 writer
.write(EQUALS_SIGN
);
2601 writer
.write(ICAL_YEARLY
);
2602 writer
.write(SEMICOLON
);
2603 writer
.write(ICAL_BYMONTH
);
2604 writer
.write(EQUALS_SIGN
);
2605 appendAsciiDigits(month
+ 1, 0, dstr
);
2607 writer
.write(SEMICOLON
);
2611 * Append the UNTIL attribute after RRULE line
2614 VTimeZone::appendUNTIL(VTZWriter
& writer
, const UnicodeString
& until
, UErrorCode
& status
) const {
2615 if (U_FAILURE(status
)) {
2618 if (until
.length() > 0) {
2619 writer
.write(SEMICOLON
);
2620 writer
.write(ICAL_UNTIL
);
2621 writer
.write(EQUALS_SIGN
);
2622 writer
.write(until
);
2628 #endif /* #if !UCONFIG_NO_FORMATTING */