2 *******************************************************************************
3 * Copyright (C) 2007-2008, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
8 #include "unicode/utypes.h"
10 #if !UCONFIG_NO_FORMATTING
12 #include "unicode/vtzone.h"
13 #include "unicode/rbtz.h"
14 #include "unicode/ucal.h"
15 #include "unicode/ures.h"
23 // This is the deleter that will be use to remove TimeZoneRule
25 static void U_CALLCONV
26 deleteTimeZoneRule(void* obj
) {
27 delete (TimeZoneRule
*) obj
;
31 // Smybol characters used by RFC2445 VTIMEZONE
32 static const UChar COLON
= 0x3A; /* : */
33 static const UChar SEMICOLON
= 0x3B; /* ; */
34 static const UChar EQUALS_SIGN
= 0x3D; /* = */
35 static const UChar COMMA
= 0x2C; /* , */
36 static const UChar PLUS
= 0x2B; /* + */
37 static const UChar MINUS
= 0x2D; /* - */
39 // RFC2445 VTIMEZONE tokens
40 static const UChar ICAL_BEGIN_VTIMEZONE
[] = {0x42, 0x45, 0x47, 0x49, 0x4E, 0x3A, 0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "BEGIN:VTIMEZONE" */
41 static const UChar ICAL_END_VTIMEZONE
[] = {0x45, 0x4E, 0x44, 0x3A, 0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "END:VTIMEZONE" */
42 static const UChar ICAL_BEGIN
[] = {0x42, 0x45, 0x47, 0x49, 0x4E, 0}; /* "BEGIN" */
43 static const UChar ICAL_END
[] = {0x45, 0x4E, 0x44, 0}; /* "END" */
44 static const UChar ICAL_VTIMEZONE
[] = {0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "VTIMEZONE" */
45 static const UChar ICAL_TZID
[] = {0x54, 0x5A, 0x49, 0x44, 0}; /* "TZID" */
46 static const UChar ICAL_STANDARD
[] = {0x53, 0x54, 0x41, 0x4E, 0x44, 0x41, 0x52, 0x44, 0}; /* "STANDARD" */
47 static const UChar ICAL_DAYLIGHT
[] = {0x44, 0x41, 0x59, 0x4C, 0x49, 0x47, 0x48, 0x54, 0}; /* "DAYLIGHT" */
48 static const UChar ICAL_DTSTART
[] = {0x44, 0x54, 0x53, 0x54, 0x41, 0x52, 0x54, 0}; /* "DTSTART" */
49 static const UChar ICAL_TZOFFSETFROM
[] = {0x54, 0x5A, 0x4F, 0x46, 0x46, 0x53, 0x45, 0x54, 0x46, 0x52, 0x4F, 0x4D, 0}; /* "TZOFFSETFROM" */
50 static const UChar ICAL_TZOFFSETTO
[] = {0x54, 0x5A, 0x4F, 0x46, 0x46, 0x53, 0x45, 0x54, 0x54, 0x4F, 0}; /* "TZOFFSETTO" */
51 static const UChar ICAL_RDATE
[] = {0x52, 0x44, 0x41, 0x54, 0x45, 0}; /* "RDATE" */
52 static const UChar ICAL_RRULE
[] = {0x52, 0x52, 0x55, 0x4C, 0x45, 0}; /* "RRULE" */
53 static const UChar ICAL_TZNAME
[] = {0x54, 0x5A, 0x4E, 0x41, 0x4D, 0x45, 0}; /* "TZNAME" */
54 static const UChar ICAL_TZURL
[] = {0x54, 0x5A, 0x55, 0x52, 0x4C, 0}; /* "TZURL" */
55 static const UChar ICAL_LASTMOD
[] = {0x4C, 0x41, 0x53, 0x54, 0x2D, 0x4D, 0x4F, 0x44, 0x49, 0x46, 0x49, 0x45, 0x44, 0}; /* "LAST-MODIFIED" */
57 static const UChar ICAL_FREQ
[] = {0x46, 0x52, 0x45, 0x51, 0}; /* "FREQ" */
58 static const UChar ICAL_UNTIL
[] = {0x55, 0x4E, 0x54, 0x49, 0x4C, 0}; /* "UNTIL" */
59 static const UChar ICAL_YEARLY
[] = {0x59, 0x45, 0x41, 0x52, 0x4C, 0x59, 0}; /* "YEARLY" */
60 static const UChar ICAL_BYMONTH
[] = {0x42, 0x59, 0x4D, 0x4F, 0x4E, 0x54, 0x48, 0}; /* "BYMONTH" */
61 static const UChar ICAL_BYDAY
[] = {0x42, 0x59, 0x44, 0x41, 0x59, 0}; /* "BYDAY" */
62 static const UChar ICAL_BYMONTHDAY
[] = {0x42, 0x59, 0x4D, 0x4F, 0x4E, 0x54, 0x48, 0x44, 0x41, 0x59, 0}; /* "BYMONTHDAY" */
64 static const UChar ICAL_NEWLINE
[] = {0x0D, 0x0A, 0}; /* CRLF */
66 static const UChar ICAL_DOW_NAMES
[7][3] = {
67 {0x53, 0x55, 0}, /* "SU" */
68 {0x4D, 0x4F, 0}, /* "MO" */
69 {0x54, 0x55, 0}, /* "TU" */
70 {0x57, 0x45, 0}, /* "WE" */
71 {0x54, 0x48, 0}, /* "TH" */
72 {0x46, 0x52, 0}, /* "FR" */
73 {0x53, 0x41, 0} /* "SA" */};
75 // Month length for non-leap year
76 static const int32_t MONTHLENGTH
[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
78 // ICU custom property
79 static const UChar ICU_TZINFO_PROP
[] = {0x58, 0x2D, 0x54, 0x5A, 0x49, 0x4E, 0x46, 0x4F, 0x3A, 0}; /* "X-TZINFO:" */
80 static const UChar ICU_TZINFO_PARTIAL
[] = {0x2F, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6C, 0x40, 0}; /* "/Partial@" */
81 static const UChar ICU_TZINFO_SIMPLE
[] = {0x2F, 0x53, 0x69, 0x6D, 0x70, 0x6C, 0x65, 0x40, 0}; /* "/Simple@" */
85 * Simple fixed digit ASCII number to integer converter
87 static int32_t parseAsciiDigits(const UnicodeString
& str
, int32_t start
, int32_t length
, UErrorCode
& status
) {
88 if (U_FAILURE(status
)) {
91 if (length
<= 0 || str
.length() < start
|| (start
+ length
) > str
.length()) {
92 status
= U_INVALID_FORMAT_ERROR
;
96 if (str
.charAt(start
) == PLUS
) {
99 } else if (str
.charAt(start
) == MINUS
) {
105 for (int32_t i
= 0; i
< length
; i
++) {
106 int32_t digit
= str
.charAt(start
+ i
) - 0x0030;
107 if (digit
< 0 || digit
> 9) {
108 status
= U_INVALID_FORMAT_ERROR
;
111 num
= 10 * num
+ digit
;
116 static UnicodeString
& appendAsciiDigits(int32_t number
, uint8_t length
, UnicodeString
& str
) {
117 UBool negative
= FALSE
;
118 int32_t digits
[10]; // max int32_t is 10 decimal digits
126 length
= length
> 10 ? 10 : length
;
131 digits
[i
++] = number
% 10;
133 } while (number
!= 0);
137 for (i
= 0; i
< length
; i
++) {
138 digits
[i
] = number
% 10;
145 for (i
= length
- 1; i
>= 0; i
--) {
146 str
.append((UChar
)(digits
[i
] + 0x0030));
151 static UnicodeString
& appendMillis(UDate date
, UnicodeString
& str
) {
152 UBool negative
= FALSE
;
153 int32_t digits
[20]; // max int64_t is 20 decimal digits
157 if (date
< MIN_MILLIS
) {
158 number
= (int64_t)MIN_MILLIS
;
159 } else if (date
> MAX_MILLIS
) {
160 number
= (int64_t)MAX_MILLIS
;
162 number
= (int64_t)date
;
170 digits
[i
++] = (int32_t)(number
% 10);
172 } while (number
!= 0);
179 str
.append((UChar
)(digits
[i
--] + 0x0030));
185 * Convert date/time to RFC2445 Date-Time form #1 DATE WITH LOCAL TIME
187 static UnicodeString
& getDateTimeString(UDate time
, UnicodeString
& str
) {
188 int32_t year
, month
, dom
, dow
, doy
, mid
;
189 Grego::timeToFields(time
, year
, month
, dom
, dow
, doy
, mid
);
192 appendAsciiDigits(year
, 4, str
);
193 appendAsciiDigits(month
+ 1, 2, str
);
194 appendAsciiDigits(dom
, 2, str
);
195 str
.append((UChar
)0x0054 /*'T'*/);
198 int32_t hour
= t
/ U_MILLIS_PER_HOUR
;
199 t
%= U_MILLIS_PER_HOUR
;
200 int32_t min
= t
/ U_MILLIS_PER_MINUTE
;
201 t
%= U_MILLIS_PER_MINUTE
;
202 int32_t sec
= t
/ U_MILLIS_PER_SECOND
;
204 appendAsciiDigits(hour
, 2, str
);
205 appendAsciiDigits(min
, 2, str
);
206 appendAsciiDigits(sec
, 2, str
);
211 * Convert date/time to RFC2445 Date-Time form #2 DATE WITH UTC TIME
213 static UnicodeString
& getUTCDateTimeString(UDate time
, UnicodeString
& str
) {
214 getDateTimeString(time
, str
);
215 str
.append((UChar
)0x005A /*'Z'*/);
220 * Parse RFC2445 Date-Time form #1 DATE WITH LOCAL TIME and
221 * #2 DATE WITH UTC TIME
223 static UDate
parseDateTimeString(const UnicodeString
& str
, int32_t offset
, UErrorCode
& status
) {
224 if (U_FAILURE(status
)) {
228 int32_t year
= 0, month
= 0, day
= 0, hour
= 0, min
= 0, sec
= 0;
230 UBool isValid
= FALSE
;
232 int length
= str
.length();
233 if (length
!= 15 && length
!= 16) {
234 // FORM#1 15 characters, such as "20060317T142115"
235 // FORM#2 16 characters, such as "20060317T142115Z"
238 if (str
.charAt(8) != 0x0054) {
239 // charcter "T" must be used for separating date and time
243 if (str
.charAt(15) != 0x005A) {
250 year
= parseAsciiDigits(str
, 0, 4, status
);
251 month
= parseAsciiDigits(str
, 4, 2, status
) - 1; // 0-based
252 day
= parseAsciiDigits(str
, 6, 2, status
);
253 hour
= parseAsciiDigits(str
, 9, 2, status
);
254 min
= parseAsciiDigits(str
, 11, 2, status
);
255 sec
= parseAsciiDigits(str
, 13, 2, status
);
257 if (U_FAILURE(status
)) {
262 int32_t maxDayOfMonth
= Grego::monthLength(year
, month
);
263 if (year
< 0 || month
< 0 || month
> 11 || day
< 1 || day
> maxDayOfMonth
||
264 hour
< 0 || hour
>= 24 || min
< 0 || min
>= 60 || sec
< 0 || sec
>= 60) {
272 status
= U_INVALID_FORMAT_ERROR
;
275 // Calculate the time
276 UDate time
= Grego::fieldsToDay(year
, month
, day
) * U_MILLIS_PER_DAY
;
277 time
+= (hour
* U_MILLIS_PER_HOUR
+ min
* U_MILLIS_PER_MINUTE
+ sec
* U_MILLIS_PER_SECOND
);
285 * Convert RFC2445 utc-offset string to milliseconds
287 static int32_t offsetStrToMillis(const UnicodeString
& str
, UErrorCode
& status
) {
288 if (U_FAILURE(status
)) {
292 UBool isValid
= FALSE
;
293 int32_t sign
= 0, hour
= 0, min
= 0, sec
= 0;
296 int length
= str
.length();
297 if (length
!= 5 && length
!= 7) {
298 // utf-offset must be 5 or 7 characters
302 UChar s
= str
.charAt(0);
305 } else if (s
== MINUS
) {
308 // utf-offset must start with "+" or "-"
311 hour
= parseAsciiDigits(str
, 1, 2, status
);
312 min
= parseAsciiDigits(str
, 3, 2, status
);
314 sec
= parseAsciiDigits(str
, 5, 2, status
);
316 if (U_FAILURE(status
)) {
323 status
= U_INVALID_FORMAT_ERROR
;
326 int32_t millis
= sign
* ((hour
* 60 + min
) * 60 + sec
) * 1000;
331 * Convert milliseconds to RFC2445 utc-offset string
333 static void millisToOffset(int32_t millis
, UnicodeString
& str
) {
341 int32_t hour
, min
, sec
;
342 int32_t t
= millis
/ 1000;
349 appendAsciiDigits(hour
, 2, str
);
350 appendAsciiDigits(min
, 2, str
);
351 appendAsciiDigits(sec
, 2, str
);
355 * Create a default TZNAME from TZID
357 static void getDefaultTZName(const UnicodeString tzid
, UBool isDST
, UnicodeString
& tzname
) {
360 tzname
+= UNICODE_STRING_SIMPLE("(DST)");
362 tzname
+= UNICODE_STRING_SIMPLE("(STD)");
367 * Parse individual RRULE
371 * month calculated by BYMONTH-1, or -1 when not found
372 * dow day of week in BYDAY, or 0 when not found
373 * wim day of week ordinal number in BYDAY, or 0 when not found
374 * dom an array of day of month
375 * domCount number of availble days in dom (domCount is specifying the size of dom on input)
376 * until time defined by UNTIL attribute or MIN_MILLIS if not available
378 static void parseRRULE(const UnicodeString
& rrule
, int32_t& month
, int32_t& dow
, int32_t& wim
,
379 int32_t* dom
, int32_t& domCount
, UDate
& until
, UErrorCode
& status
) {
380 if (U_FAILURE(status
)) {
390 UBool yearly
= FALSE
;
391 //UBool parseError = FALSE;
393 int32_t prop_start
= 0;
395 UnicodeString prop
, attr
, value
;
396 UBool nextProp
= TRUE
;
399 prop_end
= rrule
.indexOf(SEMICOLON
, prop_start
);
400 if (prop_end
== -1) {
401 prop
.setTo(rrule
, prop_start
);
404 prop
.setTo(rrule
, prop_start
, prop_end
- prop_start
);
405 prop_start
= prop_end
+ 1;
407 int32_t eql
= prop
.indexOf(EQUALS_SIGN
);
409 attr
.setTo(prop
, 0, eql
);
410 value
.setTo(prop
, eql
+ 1);
412 goto rruleParseError
;
415 if (attr
.compare(ICAL_FREQ
) == 0) {
416 // only support YEARLY frequency type
417 if (value
.compare(ICAL_YEARLY
) == 0) {
420 goto rruleParseError
;
422 } else if (attr
.compare(ICAL_UNTIL
) == 0) {
423 // ISO8601 UTC format, for example, "20060315T020000Z"
424 until
= parseDateTimeString(value
, 0, status
);
425 if (U_FAILURE(status
)) {
426 goto rruleParseError
;
428 } else if (attr
.compare(ICAL_BYMONTH
) == 0) {
429 // Note: BYMONTH may contain multiple months, but only single month make sense for
430 // VTIMEZONE property.
431 if (value
.length() > 2) {
432 goto rruleParseError
;
434 month
= parseAsciiDigits(value
, 0, value
.length(), status
) - 1;
435 if (U_FAILURE(status
) || month
< 0 || month
>= 12) {
436 goto rruleParseError
;
438 } else if (attr
.compare(ICAL_BYDAY
) == 0) {
439 // Note: BYDAY may contain multiple day of week separated by comma. It is unlikely used for
440 // VTIMEZONE property. We do not support the case.
442 // 2-letter format is used just for representing a day of week, for example, "SU" for Sunday
443 // 3 or 4-letter format is used for represeinging Nth day of week, for example, "-1SA" for last Saturday
444 int32_t length
= value
.length();
445 if (length
< 2 || length
> 4) {
446 goto rruleParseError
;
451 if (value
.charAt(0) == PLUS
) {
453 } else if (value
.charAt(0) == MINUS
) {
455 } else if (length
== 4) {
456 goto rruleParseError
;
458 int32_t n
= parseAsciiDigits(value
, length
- 3, 1, status
);
459 if (U_FAILURE(status
) || n
== 0 || n
> 4) {
460 goto rruleParseError
;
463 value
.remove(0, length
- 2);
466 for (wday
= 0; wday
< 7; wday
++) {
467 if (value
.compare(ICAL_DOW_NAMES
[wday
], 2) == 0) {
472 // Sunday(1) - Saturday(7)
475 goto rruleParseError
;
477 } else if (attr
.compare(ICAL_BYMONTHDAY
) == 0) {
478 // Note: BYMONTHDAY may contain multiple days delimitted by comma
480 // A value of BYMONTHDAY could be negative, for example, -1 means
481 // the last day in a month
483 int32_t dom_start
= 0;
485 UBool nextDOM
= TRUE
;
487 dom_end
= value
.indexOf(COMMA
, dom_start
);
489 dom_end
= value
.length();
492 if (dom_idx
< domCount
) {
493 dom
[dom_idx
] = parseAsciiDigits(value
, dom_start
, dom_end
- dom_start
, status
);
494 if (U_FAILURE(status
)) {
495 goto rruleParseError
;
499 status
= U_BUFFER_OVERFLOW_ERROR
;
500 goto rruleParseError
;
502 dom_start
= dom_end
+ 1;
508 // FREQ=YEARLY must be set
509 goto rruleParseError
;
511 // Set actual number of parsed DOM (ICAL_BYMONTHDAY)
516 if (U_SUCCESS(status
)) {
518 status
= U_INVALID_FORMAT_ERROR
;
522 static TimeZoneRule
* createRuleByRRULE(const UnicodeString
& tzname
, int rawOffset
, int dstSavings
, UDate start
,
523 UVector
* dates
, int fromOffset
, UErrorCode
& status
) {
524 if (U_FAILURE(status
)) {
527 if (dates
== NULL
|| dates
->size() == 0) {
528 status
= U_ILLEGAL_ARGUMENT_ERROR
;
533 DateTimeRule
*adtr
= NULL
;
535 // Parse the first rule
536 UnicodeString rrule
= *((UnicodeString
*)dates
->elementAt(0));
537 int32_t month
, dayOfWeek
, nthDayOfWeek
, dayOfMonth
= 0;
539 int32_t daysCount
= sizeof(days
)/sizeof(days
[0]);
542 parseRRULE(rrule
, month
, dayOfWeek
, nthDayOfWeek
, days
, daysCount
, until
, status
);
543 if (U_FAILURE(status
)) {
547 if (dates
->size() == 1) {
550 // Multiple BYMONTHDAY values
551 if (daysCount
!= 7 || month
== -1 || dayOfWeek
== 0) {
552 // Only support the rule using 7 continuous days
553 // BYMONTH and BYDAY must be set at the same time
554 goto unsupportedRRule
;
556 int32_t firstDay
= 31; // max possible number of dates in a month
557 for (i
= 0; i
< 7; i
++) {
558 // Resolve negative day numbers. A negative day number should
559 // not be used in February, but if we see such case, we use 28
562 days
[i
] = MONTHLENGTH
[month
] + days
[i
] + 1;
564 if (days
[i
] < firstDay
) {
568 // Make sure days are continuous
569 for (i
= 1; i
< 7; i
++) {
571 for (j
= 0; j
< 7; j
++) {
572 if (days
[j
] == firstDay
+ i
) {
578 // days are not continuous
579 goto unsupportedRRule
;
582 // Use DOW_GEQ_DOM rule with firstDay as the start date
583 dayOfMonth
= firstDay
;
586 // Check if BYMONTH + BYMONTHDAY + BYDAY rule with multiple RRULE lines.
587 // Otherwise, not supported.
588 if (month
== -1 || dayOfWeek
== 0 || daysCount
== 0) {
589 // This is not the case
590 goto unsupportedRRule
;
592 // Parse the rest of rules if number of rules is not exceeding 7.
593 // We can only support 7 continuous days starting from a day of month.
594 if (dates
->size() > 7) {
595 goto unsupportedRRule
;
598 // Note: To check valid date range across multiple rule is a little
599 // bit complicated. For now, this code is not doing strict range
600 // checking across month boundary
602 int32_t earliestMonth
= month
;
603 int32_t earliestDay
= 31;
604 for (i
= 0; i
< daysCount
; i
++) {
605 int32_t dom
= days
[i
];
606 dom
= dom
> 0 ? dom
: MONTHLENGTH
[month
] + dom
+ 1;
607 earliestDay
= dom
< earliestDay
? dom
: earliestDay
;
610 int32_t anotherMonth
= -1;
611 for (i
= 1; i
< dates
->size(); i
++) {
612 rrule
= *((UnicodeString
*)dates
->elementAt(i
));
614 int32_t tmp_month
, tmp_dayOfWeek
, tmp_nthDayOfWeek
;
616 int32_t tmp_daysCount
= sizeof(tmp_days
)/sizeof(tmp_days
[0]);
617 parseRRULE(rrule
, tmp_month
, tmp_dayOfWeek
, tmp_nthDayOfWeek
, tmp_days
, tmp_daysCount
, tmp_until
, status
);
618 if (U_FAILURE(status
)) {
621 // If UNTIL is newer than previous one, use the one
622 if (tmp_until
> until
) {
626 // Check if BYMONTH + BYMONTHDAY + BYDAY rule
627 if (tmp_month
== -1 || tmp_dayOfWeek
== 0 || tmp_daysCount
== 0) {
628 goto unsupportedRRule
;
630 // Count number of BYMONTHDAY
631 if (daysCount
+ tmp_daysCount
> 7) {
632 // We cannot support BYMONTHDAY more than 7
633 goto unsupportedRRule
;
635 // Check if the same BYDAY is used. Otherwise, we cannot
637 if (tmp_dayOfWeek
!= dayOfWeek
) {
638 goto unsupportedRRule
;
640 // Check if the month is same or right next to the primary month
641 if (tmp_month
!= month
) {
642 if (anotherMonth
== -1) {
643 int32_t diff
= tmp_month
- month
;
644 if (diff
== -11 || diff
== -1) {
646 anotherMonth
= tmp_month
;
647 earliestMonth
= anotherMonth
;
648 // Reset earliest day
650 } else if (diff
== 11 || diff
== 1) {
652 anotherMonth
= tmp_month
;
654 // The day range cannot exceed more than 2 months
655 goto unsupportedRRule
;
657 } else if (tmp_month
!= month
&& tmp_month
!= anotherMonth
) {
658 // The day range cannot exceed more than 2 months
659 goto unsupportedRRule
;
662 // If ealier month, go through days to find the earliest day
663 if (tmp_month
== earliestMonth
) {
664 for (j
= 0; j
< tmp_daysCount
; j
++) {
665 tmp_days
[j
] = tmp_days
[j
] > 0 ? tmp_days
[j
] : MONTHLENGTH
[tmp_month
] + tmp_days
[j
] + 1;
666 earliestDay
= tmp_days
[j
] < earliestDay
? tmp_days
[j
] : earliestDay
;
669 daysCount
+= tmp_daysCount
;
671 if (daysCount
!= 7) {
672 // Number of BYMONTHDAY entries must be 7
673 goto unsupportedRRule
;
675 month
= earliestMonth
;
676 dayOfMonth
= earliestDay
;
679 // Calculate start/end year and missing fields
680 int32_t startYear
, startMonth
, startDOM
, startDOW
, startDOY
, startMID
;
681 Grego::timeToFields(start
+ fromOffset
, startYear
, startMonth
, startDOM
,
682 startDOW
, startDOY
, startMID
);
684 // If BYMONTH is not set, use the month of DTSTART
687 if (dayOfWeek
== 0 && nthDayOfWeek
== 0 && dayOfMonth
== 0) {
688 // If only YEARLY is set, use the day of DTSTART as BYMONTHDAY
689 dayOfMonth
= startDOM
;
693 if (until
!= MIN_MILLIS
) {
694 int32_t endMonth
, endDOM
, endDOW
, endDOY
, endMID
;
695 Grego::timeToFields(until
, endYear
, endMonth
, endDOM
, endDOW
, endDOY
, endMID
);
697 endYear
= AnnualTimeZoneRule::MAX_YEAR
;
700 // Create the AnnualDateTimeRule
701 if (dayOfWeek
== 0 && nthDayOfWeek
== 0 && dayOfMonth
!= 0) {
702 // Day in month rule, for example, 15th day in the month
703 adtr
= new DateTimeRule(month
, dayOfMonth
, startMID
, DateTimeRule::WALL_TIME
);
704 } else if (dayOfWeek
!= 0 && nthDayOfWeek
!= 0 && dayOfMonth
== 0) {
705 // Nth day of week rule, for example, last Sunday
706 adtr
= new DateTimeRule(month
, nthDayOfWeek
, dayOfWeek
, startMID
, DateTimeRule::WALL_TIME
);
707 } else if (dayOfWeek
!= 0 && nthDayOfWeek
== 0 && dayOfMonth
!= 0) {
708 // First day of week after day of month rule, for example,
709 // first Sunday after 15th day in the month
710 adtr
= new DateTimeRule(month
, dayOfMonth
, dayOfWeek
, TRUE
, startMID
, DateTimeRule::WALL_TIME
);
713 goto unsupportedRRule
;
715 return new AnnualTimeZoneRule(tzname
, rawOffset
, dstSavings
, adtr
, startYear
, endYear
);
718 status
= U_INVALID_STATE_ERROR
;
723 * Create a TimeZoneRule by the RDATE definition
725 static TimeZoneRule
* createRuleByRDATE(const UnicodeString
& tzname
, int32_t rawOffset
, int32_t dstSavings
,
726 UDate start
, UVector
* dates
, int32_t fromOffset
, UErrorCode
& status
) {
727 if (U_FAILURE(status
)) {
730 TimeArrayTimeZoneRule
*retVal
= NULL
;
731 if (dates
== NULL
|| dates
->size() == 0) {
732 // When no RDATE line is provided, use start (DTSTART)
733 // as the transition time
734 retVal
= new TimeArrayTimeZoneRule(tzname
, rawOffset
, dstSavings
,
735 &start
, 1, DateTimeRule::UTC_TIME
);
737 // Create an array of transition times
738 int32_t size
= dates
->size();
739 UDate
* times
= (UDate
*)uprv_malloc(sizeof(UDate
) * size
);
741 status
= U_MEMORY_ALLOCATION_ERROR
;
744 for (int32_t i
= 0; i
< size
; i
++) {
745 UnicodeString
*datestr
= (UnicodeString
*)dates
->elementAt(i
);
746 times
[i
] = parseDateTimeString(*datestr
, fromOffset
, status
);
747 if (U_FAILURE(status
)) {
752 retVal
= new TimeArrayTimeZoneRule(tzname
, rawOffset
, dstSavings
,
753 times
, size
, DateTimeRule::UTC_TIME
);
760 * Check if the DOW rule specified by month, weekInMonth and dayOfWeek is equivalent
761 * to the DateTimerule.
763 static UBool
isEquivalentDateRule(int32_t month
, int32_t weekInMonth
, int32_t dayOfWeek
, const DateTimeRule
*dtrule
) {
764 if (month
!= dtrule
->getRuleMonth() || dayOfWeek
!= dtrule
->getRuleDayOfWeek()) {
767 if (dtrule
->getTimeRuleType() != DateTimeRule::WALL_TIME
) {
768 // Do not try to do more intelligent comparison for now.
771 if (dtrule
->getDateRuleType() == DateTimeRule::DOW
772 && dtrule
->getRuleWeekInMonth() == weekInMonth
) {
775 int32_t ruleDOM
= dtrule
->getRuleDayOfMonth();
776 if (dtrule
->getDateRuleType() == DateTimeRule::DOW_GEQ_DOM
) {
777 if (ruleDOM%7
== 1 && (ruleDOM
+ 6)/7 == weekInMonth
) {
780 if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - ruleDOM
)%7
== 6
781 && weekInMonth
== -1*((MONTHLENGTH
[month
]-ruleDOM
+1)/7)) {
785 if (dtrule
->getDateRuleType() == DateTimeRule::DOW_LEQ_DOM
) {
786 if (ruleDOM%7
== 0 && ruleDOM
/7 == weekInMonth
) {
789 if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - ruleDOM
)%7
== 0
790 && weekInMonth
== -1*((MONTHLENGTH
[month
] - ruleDOM
)/7 + 1)) {
798 * Convert the rule to its equivalent rule using WALL_TIME mode.
799 * This function returns NULL when the specified DateTimeRule is already
800 * using WALL_TIME mode.
802 static DateTimeRule
* toWallTimeRule(const DateTimeRule
* rule
, int32_t rawOffset
, int32_t dstSavings
) {
803 if (rule
->getTimeRuleType() == DateTimeRule::WALL_TIME
) {
806 int32_t wallt
= rule
->getRuleMillisInDay();
807 if (rule
->getTimeRuleType() == DateTimeRule::UTC_TIME
) {
808 wallt
+= (rawOffset
+ dstSavings
);
809 } else if (rule
->getTimeRuleType() == DateTimeRule::STANDARD_TIME
) {
813 int32_t month
= -1, dom
= 0, dow
= 0;
814 DateTimeRule::DateRuleType dtype
;
818 wallt
+= U_MILLIS_PER_DAY
;
819 } else if (wallt
>= U_MILLIS_PER_DAY
) {
821 wallt
-= U_MILLIS_PER_DAY
;
824 month
= rule
->getRuleMonth();
825 dom
= rule
->getRuleDayOfMonth();
826 dow
= rule
->getRuleDayOfWeek();
827 dtype
= rule
->getDateRuleType();
830 if (dtype
== DateTimeRule::DOW
) {
831 // Convert to DOW_GEW_DOM or DOW_LEQ_DOM rule first
832 int32_t wim
= rule
->getRuleWeekInMonth();
834 dtype
= DateTimeRule::DOW_GEQ_DOM
;
835 dom
= 7 * (wim
- 1) + 1;
837 dtype
= DateTimeRule::DOW_LEQ_DOM
;
838 dom
= MONTHLENGTH
[month
] + 7 * (wim
+ 1);
841 // Shift one day before or after
845 month
= month
< UCAL_JANUARY
? UCAL_DECEMBER
: month
;
846 dom
= MONTHLENGTH
[month
];
847 } else if (dom
> MONTHLENGTH
[month
]) {
849 month
= month
> UCAL_DECEMBER
? UCAL_JANUARY
: month
;
852 if (dtype
!= DateTimeRule::DOM
) {
853 // Adjust day of week
855 if (dow
< UCAL_SUNDAY
) {
857 } else if (dow
> UCAL_SATURDAY
) {
863 DateTimeRule
*modifiedRule
;
864 if (dtype
== DateTimeRule::DOM
) {
865 modifiedRule
= new DateTimeRule(month
, dom
, wallt
, DateTimeRule::WALL_TIME
);
867 modifiedRule
= new DateTimeRule(month
, dom
, dow
,
868 (dtype
== DateTimeRule::DOW_GEQ_DOM
), wallt
, DateTimeRule::WALL_TIME
);
874 * Minumum implementations of stream writer/reader, writing/reading
875 * UnicodeString. For now, we do not want to introduce the dependency
876 * on the ICU I/O stream in this module. But we want to keep the code
877 * equivalent to the ICU4J implementation, which utilizes java.io.Writer/
882 VTZWriter(UnicodeString
& out
);
885 void write(const UnicodeString
& str
);
886 void write(UChar ch
);
887 //void write(const UChar* str, int32_t length);
892 VTZWriter::VTZWriter(UnicodeString
& output
) {
896 VTZWriter::~VTZWriter() {
900 VTZWriter::write(const UnicodeString
& str
) {
905 VTZWriter::write(UChar ch
) {
911 VTZWriter::write(const UChar* str, int32_t length) {
912 out->append(str, length);
918 VTZReader(const UnicodeString
& input
);
923 const UnicodeString
* in
;
927 VTZReader::VTZReader(const UnicodeString
& input
) {
932 VTZReader::~VTZReader() {
936 VTZReader::read(void) {
938 if (index
< in
->length()) {
939 ch
= in
->charAt(index
);
946 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(VTimeZone
)
948 VTimeZone::VTimeZone()
949 : BasicTimeZone(), tz(NULL
), vtzlines(NULL
),
950 lastmod(MAX_MILLIS
) {
953 VTimeZone::VTimeZone(const VTimeZone
& source
)
954 : BasicTimeZone(source
), tz(NULL
), vtzlines(NULL
),
955 tzurl(source
.tzurl
), lastmod(source
.lastmod
),
956 olsonzid(source
.olsonzid
), icutzver(source
.icutzver
) {
957 if (source
.tz
!= NULL
) {
958 tz
= (BasicTimeZone
*)source
.tz
->clone();
960 if (source
.vtzlines
!= NULL
) {
961 UErrorCode status
= U_ZERO_ERROR
;
962 int32_t size
= source
.vtzlines
->size();
963 vtzlines
= new UVector(uhash_deleteUnicodeString
, uhash_compareUnicodeString
, size
, status
);
964 if (U_SUCCESS(status
)) {
965 for (int32_t i
= 0; i
< size
; i
++) {
966 UnicodeString
*line
= (UnicodeString
*)source
.vtzlines
->elementAt(i
);
967 vtzlines
->addElement(line
->clone(), status
);
968 if (U_FAILURE(status
)) {
973 if (U_FAILURE(status
) && vtzlines
!= NULL
) {
979 VTimeZone::~VTimeZone() {
983 if (vtzlines
!= NULL
) {
989 VTimeZone::operator=(const VTimeZone
& right
) {
990 if (this == &right
) {
993 if (*this != right
) {
994 BasicTimeZone::operator=(right
);
999 if (right
.tz
!= NULL
) {
1000 tz
= (BasicTimeZone
*)right
.tz
->clone();
1002 if (vtzlines
!= NULL
) {
1005 if (right
.vtzlines
!= NULL
) {
1006 UErrorCode status
= U_ZERO_ERROR
;
1007 int32_t size
= right
.vtzlines
->size();
1008 vtzlines
= new UVector(uhash_deleteUnicodeString
, uhash_compareUnicodeString
, size
, status
);
1009 if (U_SUCCESS(status
)) {
1010 for (int32_t i
= 0; i
< size
; i
++) {
1011 UnicodeString
*line
= (UnicodeString
*)right
.vtzlines
->elementAt(i
);
1012 vtzlines
->addElement(line
->clone(), status
);
1013 if (U_FAILURE(status
)) {
1018 if (U_FAILURE(status
) && vtzlines
!= NULL
) {
1023 tzurl
= right
.tzurl
;
1024 lastmod
= right
.lastmod
;
1025 olsonzid
= right
.olsonzid
;
1026 icutzver
= right
.icutzver
;
1032 VTimeZone::operator==(const TimeZone
& that
) const {
1033 if (this == &that
) {
1036 if (getDynamicClassID() != that
.getDynamicClassID()
1037 || !BasicTimeZone::operator==(that
)) {
1040 VTimeZone
*vtz
= (VTimeZone
*)&that
;
1041 if (*tz
== *(vtz
->tz
)
1042 && tzurl
== vtz
->tzurl
1043 && lastmod
== vtz
->lastmod
1044 /* && olsonzid = that.olsonzid */
1045 /* && icutzver = that.icutzver */) {
1052 VTimeZone::operator!=(const TimeZone
& that
) const {
1053 return !operator==(that
);
1057 VTimeZone::createVTimeZoneByID(const UnicodeString
& ID
) {
1058 VTimeZone
*vtz
= new VTimeZone();
1059 vtz
->tz
= (BasicTimeZone
*)TimeZone::createTimeZone(ID
);
1060 vtz
->tz
->getID(vtz
->olsonzid
);
1062 // Set ICU tzdata version
1063 UErrorCode status
= U_ZERO_ERROR
;
1064 UResourceBundle
*bundle
= NULL
;
1065 const UChar
* versionStr
= NULL
;
1067 bundle
= ures_openDirect(NULL
, "zoneinfo", &status
);
1068 versionStr
= ures_getStringByKey(bundle
, "TZVersion", &len
, &status
);
1069 if (U_SUCCESS(status
)) {
1070 vtz
->icutzver
.setTo(versionStr
, len
);
1077 VTimeZone::createVTimeZone(const UnicodeString
& vtzdata
, UErrorCode
& status
) {
1078 if (U_FAILURE(status
)) {
1081 VTZReader
reader(vtzdata
);
1082 VTimeZone
*vtz
= new VTimeZone();
1083 vtz
->load(reader
, status
);
1084 if (U_FAILURE(status
)) {
1092 VTimeZone::getTZURL(UnicodeString
& url
) const {
1093 if (tzurl
.length() > 0) {
1101 VTimeZone::setTZURL(const UnicodeString
& url
) {
1106 VTimeZone::getLastModified(UDate
& lastModified
) const {
1107 if (lastmod
!= MAX_MILLIS
) {
1108 lastModified
= lastmod
;
1115 VTimeZone::setLastModified(UDate lastModified
) {
1116 lastmod
= lastModified
;
1120 VTimeZone::write(UnicodeString
& result
, UErrorCode
& status
) const {
1122 VTZWriter
writer(result
);
1123 write(writer
, status
);
1127 VTimeZone::write(UDate start
, UnicodeString
& result
, UErrorCode
& status
) /*const*/ {
1129 VTZWriter
writer(result
);
1130 write(start
, writer
, status
);
1134 VTimeZone::writeSimple(UDate time
, UnicodeString
& result
, UErrorCode
& status
) /*const*/ {
1136 VTZWriter
writer(result
);
1137 writeSimple(time
, writer
, status
);
1141 VTimeZone::clone(void) const {
1142 return new VTimeZone(*this);
1146 VTimeZone::getOffset(uint8_t era
, int32_t year
, int32_t month
, int32_t day
,
1147 uint8_t dayOfWeek
, int32_t millis
, UErrorCode
& status
) const {
1148 return tz
->getOffset(era
, year
, month
, day
, dayOfWeek
, millis
, status
);
1152 VTimeZone::getOffset(uint8_t era
, int32_t year
, int32_t month
, int32_t day
,
1153 uint8_t dayOfWeek
, int32_t millis
,
1154 int32_t monthLength
, UErrorCode
& status
) const {
1155 return tz
->getOffset(era
, year
, month
, day
, dayOfWeek
, millis
, monthLength
, status
);
1159 VTimeZone::getOffset(UDate date
, UBool local
, int32_t& rawOffset
,
1160 int32_t& dstOffset
, UErrorCode
& status
) const {
1161 return tz
->getOffset(date
, local
, rawOffset
, dstOffset
, status
);
1165 VTimeZone::setRawOffset(int32_t offsetMillis
) {
1166 tz
->setRawOffset(offsetMillis
);
1170 VTimeZone::getRawOffset(void) const {
1171 return tz
->getRawOffset();
1175 VTimeZone::useDaylightTime(void) const {
1176 return tz
->useDaylightTime();
1180 VTimeZone::inDaylightTime(UDate date
, UErrorCode
& status
) const {
1181 return tz
->inDaylightTime(date
, status
);
1185 VTimeZone::hasSameRules(const TimeZone
& other
) const {
1186 return tz
->hasSameRules(other
);
1190 VTimeZone::getNextTransition(UDate base
, UBool inclusive
, TimeZoneTransition
& result
) /*const*/ {
1191 return tz
->getNextTransition(base
, inclusive
, result
);
1195 VTimeZone::getPreviousTransition(UDate base
, UBool inclusive
, TimeZoneTransition
& result
) /*const*/ {
1196 return tz
->getPreviousTransition(base
, inclusive
, result
);
1200 VTimeZone::countTransitionRules(UErrorCode
& status
) /*const*/ {
1201 return tz
->countTransitionRules(status
);
1205 VTimeZone::getTimeZoneRules(const InitialTimeZoneRule
*& initial
,
1206 const TimeZoneRule
* trsrules
[], int32_t& trscount
,
1207 UErrorCode
& status
) /*const*/ {
1208 tz
->getTimeZoneRules(initial
, trsrules
, trscount
, status
);
1212 VTimeZone::load(VTZReader
& reader
, UErrorCode
& status
) {
1213 vtzlines
= new UVector(uhash_deleteUnicodeString
, uhash_compareUnicodeString
, DEFAULT_VTIMEZONE_LINES
, status
);
1214 if (U_FAILURE(status
)) {
1218 UBool start
= FALSE
;
1219 UBool success
= FALSE
;
1223 UChar ch
= reader
.read();
1226 if (start
&& line
.startsWith(ICAL_END_VTIMEZONE
)) {
1227 vtzlines
->addElement(new UnicodeString(line
), status
);
1228 if (U_FAILURE(status
)) {
1229 goto cleanupVtzlines
;
1236 // CR, must be followed by LF according to the definition in RFC2445
1240 if (ch
!= 0x0009 && ch
!= 0x0020) {
1241 // NOT followed by TAB/SP -> new line
1243 if (line
.length() > 0) {
1244 vtzlines
->addElement(new UnicodeString(line
), status
);
1245 if (U_FAILURE(status
)) {
1246 goto cleanupVtzlines
;
1261 if (line
.startsWith(ICAL_END_VTIMEZONE
)) {
1262 vtzlines
->addElement(new UnicodeString(line
), status
);
1263 if (U_FAILURE(status
)) {
1264 goto cleanupVtzlines
;
1270 if (line
.startsWith(ICAL_BEGIN_VTIMEZONE
)) {
1271 vtzlines
->addElement(new UnicodeString(line
), status
);
1272 if (U_FAILURE(status
)) {
1273 goto cleanupVtzlines
;
1286 if (U_SUCCESS(status
)) {
1287 status
= U_INVALID_STATE_ERROR
;
1289 goto cleanupVtzlines
;
1300 #define INI 0 // Initial state
1301 #define VTZ 1 // In VTIMEZONE
1302 #define TZI 2 // In STANDARD or DAYLIGHT
1304 #define DEF_DSTSAVINGS (60*60*1000)
1305 #define DEF_TZSTARTTIME (0.0)
1308 VTimeZone::parse(UErrorCode
& status
) {
1309 if (U_FAILURE(status
)) {
1312 if (vtzlines
== NULL
|| vtzlines
->size() == 0) {
1313 status
= U_INVALID_STATE_ERROR
;
1316 InitialTimeZoneRule
*initialRule
= NULL
;
1317 RuleBasedTimeZone
*rbtz
= NULL
;
1322 int32_t state
= INI
;
1324 UBool dst
= FALSE
; // current zone type
1325 UnicodeString from
; // current zone from offset
1326 UnicodeString to
; // current zone offset
1327 UnicodeString tzname
; // current zone name
1328 UnicodeString dtstart
; // current zone starts
1329 UBool isRRULE
= FALSE
; // true if the rule is described by RRULE
1330 int32_t initialRawOffset
= 0; // initial offset
1331 int32_t initialDSTSavings
= 0; // initial offset
1332 UDate firstStart
= MAX_MILLIS
; // the earliest rule start time
1333 UnicodeString name
; // RFC2445 prop name
1334 UnicodeString value
; // RFC2445 prop value
1336 UVector
*dates
= NULL
; // list of RDATE or RRULE strings
1337 UVector
*rules
= NULL
; // list of TimeZoneRule instances
1339 int32_t finalRuleIdx
= -1;
1340 int32_t finalRuleCount
= 0;
1342 rules
= new UVector(status
);
1343 if (U_FAILURE(status
)) {
1346 // Set the deleter to remove TimeZoneRule vectors to avoid memory leaks due to unowned TimeZoneRules.
1347 rules
->setDeleter(deleteTimeZoneRule
);
1349 dates
= new UVector(uhash_deleteUnicodeString
, uhash_compareUnicodeString
, status
);
1350 if (U_FAILURE(status
)) {
1353 if (rules
== NULL
|| dates
== NULL
) {
1354 status
= U_MEMORY_ALLOCATION_ERROR
;
1358 for (n
= 0; n
< vtzlines
->size(); n
++) {
1359 UnicodeString
*line
= (UnicodeString
*)vtzlines
->elementAt(n
);
1360 int32_t valueSep
= line
->indexOf(COLON
);
1364 name
.setTo(*line
, 0, valueSep
);
1365 value
.setTo(*line
, valueSep
+ 1);
1369 if (name
.compare(ICAL_BEGIN
) == 0
1370 && value
.compare(ICAL_VTIMEZONE
) == 0) {
1376 if (name
.compare(ICAL_TZID
) == 0) {
1378 } else if (name
.compare(ICAL_TZURL
) == 0) {
1380 } else if (name
.compare(ICAL_LASTMOD
) == 0) {
1381 // Always in 'Z' format, so the offset argument for the parse method
1382 // can be any value.
1383 lastmod
= parseDateTimeString(value
, 0, status
);
1384 if (U_FAILURE(status
)) {
1387 } else if (name
.compare(ICAL_BEGIN
) == 0) {
1388 UBool isDST
= (value
.compare(ICAL_DAYLIGHT
) == 0);
1389 if (value
.compare(ICAL_STANDARD
) == 0 || isDST
) {
1390 // tzid must be ready at this point
1391 if (tzid
.length() == 0) {
1394 // initialize current zone properties
1395 if (dates
->size() != 0) {
1396 dates
->removeAllElements();
1405 // BEGIN property other than STANDARD/DAYLIGHT
1406 // must not be there.
1409 } else if (name
.compare(ICAL_END
) == 0) {
1414 if (name
.compare(ICAL_DTSTART
) == 0) {
1416 } else if (name
.compare(ICAL_TZNAME
) == 0) {
1418 } else if (name
.compare(ICAL_TZOFFSETFROM
) == 0) {
1420 } else if (name
.compare(ICAL_TZOFFSETTO
) == 0) {
1422 } else if (name
.compare(ICAL_RDATE
) == 0) {
1423 // RDATE mixed with RRULE is not supported
1427 // RDATE value may contain multiple date delimited
1429 UBool nextDate
= TRUE
;
1431 UnicodeString
*dstr
;
1433 int32_t dend
= value
.indexOf(COMMA
, dstart
);
1435 dstr
= new UnicodeString(value
, dstart
);
1438 dstr
= new UnicodeString(value
, dstart
, dend
- dstart
);
1440 dates
->addElement(dstr
, status
);
1441 if (U_FAILURE(status
)) {
1446 } else if (name
.compare(ICAL_RRULE
) == 0) {
1447 // RRULE mixed with RDATE is not supported
1448 if (!isRRULE
&& dates
->size() != 0) {
1452 dates
->addElement(new UnicodeString(value
), status
);
1453 if (U_FAILURE(status
)) {
1456 } else if (name
.compare(ICAL_END
) == 0) {
1457 // Mandatory properties
1458 if (dtstart
.length() == 0 || from
.length() == 0 || to
.length() == 0) {
1461 // if tzname is not available, create one from tzid
1462 if (tzname
.length() == 0) {
1463 getDefaultTZName(tzid
, dst
, tzname
);
1466 // create a time zone rule
1467 TimeZoneRule
*rule
= NULL
;
1468 int32_t fromOffset
= 0;
1469 int32_t toOffset
= 0;
1470 int32_t rawOffset
= 0;
1471 int32_t dstSavings
= 0;
1474 // Parse TZOFFSETFROM/TZOFFSETTO
1475 fromOffset
= offsetStrToMillis(from
, status
);
1476 toOffset
= offsetStrToMillis(to
, status
);
1477 if (U_FAILURE(status
)) {
1482 // If daylight, use the previous offset as rawoffset if positive
1483 if (toOffset
- fromOffset
> 0) {
1484 rawOffset
= fromOffset
;
1485 dstSavings
= toOffset
- fromOffset
;
1487 // This is rare case.. just use 1 hour DST savings
1488 rawOffset
= toOffset
- DEF_DSTSAVINGS
;
1489 dstSavings
= DEF_DSTSAVINGS
;
1492 rawOffset
= toOffset
;
1497 start
= parseDateTimeString(dtstart
, fromOffset
, status
);
1498 if (U_FAILURE(status
)) {
1503 UDate actualStart
= MAX_MILLIS
;
1505 rule
= createRuleByRRULE(tzname
, rawOffset
, dstSavings
, start
, dates
, fromOffset
, status
);
1507 rule
= createRuleByRDATE(tzname
, rawOffset
, dstSavings
, start
, dates
, fromOffset
, status
);
1509 if (U_FAILURE(status
) || rule
== NULL
) {
1512 UBool startAvail
= rule
->getFirstStart(fromOffset
, 0, actualStart
);
1513 if (startAvail
&& actualStart
< firstStart
) {
1514 // save from offset information for the earliest rule
1515 firstStart
= actualStart
;
1516 // If this is STD, assume the time before this transtion
1517 // is DST when the difference is 1 hour. This might not be
1518 // accurate, but VTIMEZONE data does not have such info.
1519 if (dstSavings
> 0) {
1520 initialRawOffset
= fromOffset
;
1521 initialDSTSavings
= 0;
1523 if (fromOffset
- toOffset
== DEF_DSTSAVINGS
) {
1524 initialRawOffset
= fromOffset
- DEF_DSTSAVINGS
;
1525 initialDSTSavings
= DEF_DSTSAVINGS
;
1527 initialRawOffset
= fromOffset
;
1528 initialDSTSavings
= 0;
1533 rules
->addElement(rule
, status
);
1534 if (U_FAILURE(status
)) {
1542 // Must have at least one rule
1543 if (rules
->size() == 0) {
1547 // Create a initial rule
1548 getDefaultTZName(tzid
, FALSE
, tzname
);
1549 initialRule
= new InitialTimeZoneRule(tzname
,
1550 initialRawOffset
, initialDSTSavings
);
1551 if (initialRule
== NULL
) {
1552 status
= U_MEMORY_ALLOCATION_ERROR
;
1556 // Finally, create the RuleBasedTimeZone
1557 rbtz
= new RuleBasedTimeZone(tzid
, initialRule
);
1559 status
= U_MEMORY_ALLOCATION_ERROR
;
1562 initialRule
= NULL
; // already adopted by RBTZ, no need to delete
1564 for (n
= 0; n
< rules
->size(); n
++) {
1565 TimeZoneRule
*r
= (TimeZoneRule
*)rules
->elementAt(n
);
1566 if (r
->getDynamicClassID() == AnnualTimeZoneRule::getStaticClassID()) {
1567 if (((AnnualTimeZoneRule
*)r
)->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
) {
1573 if (finalRuleCount
> 2) {
1574 // Too many final rules
1575 status
= U_ILLEGAL_ARGUMENT_ERROR
;
1579 if (finalRuleCount
== 1) {
1580 if (rules
->size() == 1) {
1581 // Only one final rule, only governs the initial rule,
1582 // which is already initialized, thus, we do not need to
1583 // add this transition rule
1584 rules
->removeAllElements();
1586 // Normalize the final rule
1587 AnnualTimeZoneRule
*finalRule
= (AnnualTimeZoneRule
*)rules
->elementAt(finalRuleIdx
);
1588 int32_t tmpRaw
= finalRule
->getRawOffset();
1589 int32_t tmpDST
= finalRule
->getDSTSavings();
1591 // Find the last non-final rule
1592 UDate finalStart
, start
;
1593 finalRule
->getFirstStart(initialRawOffset
, initialDSTSavings
, finalStart
);
1595 for (n
= 0; n
< rules
->size(); n
++) {
1596 if (finalRuleIdx
== n
) {
1599 TimeZoneRule
*r
= (TimeZoneRule
*)rules
->elementAt(n
);
1601 r
->getFinalStart(tmpRaw
, tmpDST
, lastStart
);
1602 if (lastStart
> start
) {
1603 finalRule
->getNextStart(lastStart
,
1611 TimeZoneRule
*newRule
;
1612 UnicodeString tznam
;
1613 if (start
== finalStart
) {
1614 // Transform this into a single transition
1615 newRule
= new TimeArrayTimeZoneRule(
1616 finalRule
->getName(tznam
),
1617 finalRule
->getRawOffset(),
1618 finalRule
->getDSTSavings(),
1621 DateTimeRule::UTC_TIME
);
1623 // Update the end year
1624 int32_t y
, m
, d
, dow
, doy
, mid
;
1625 Grego::timeToFields(start
, y
, m
, d
, dow
, doy
, mid
);
1626 newRule
= new AnnualTimeZoneRule(
1627 finalRule
->getName(tznam
),
1628 finalRule
->getRawOffset(),
1629 finalRule
->getDSTSavings(),
1630 *(finalRule
->getRule()),
1631 finalRule
->getStartYear(),
1634 if (newRule
== NULL
) {
1635 status
= U_MEMORY_ALLOCATION_ERROR
;
1638 rules
->removeElementAt(finalRuleIdx
);
1639 rules
->addElement(newRule
, status
);
1640 if (U_FAILURE(status
)) {
1647 while (!rules
->isEmpty()) {
1648 TimeZoneRule
*tzr
= (TimeZoneRule
*)rules
->orphanElementAt(0);
1649 rbtz
->addTransitionRule(tzr
, status
);
1650 if (U_FAILURE(status
)) {
1654 rbtz
->complete(status
);
1655 if (U_FAILURE(status
)) {
1666 if (rules
!= NULL
) {
1667 while (!rules
->isEmpty()) {
1668 TimeZoneRule
*r
= (TimeZoneRule
*)rules
->orphanElementAt(0);
1673 if (dates
!= NULL
) {
1676 if (initialRule
!= NULL
) {
1686 VTimeZone::write(VTZWriter
& writer
, UErrorCode
& status
) const {
1687 if (vtzlines
!= NULL
) {
1688 for (int32_t i
= 0; i
< vtzlines
->size(); i
++) {
1689 UnicodeString
*line
= (UnicodeString
*)vtzlines
->elementAt(i
);
1690 if (line
->startsWith(ICAL_TZURL
)
1691 && line
->charAt(u_strlen(ICAL_TZURL
)) == COLON
) {
1692 writer
.write(ICAL_TZURL
);
1693 writer
.write(COLON
);
1694 writer
.write(tzurl
);
1695 writer
.write(ICAL_NEWLINE
);
1696 } else if (line
->startsWith(ICAL_LASTMOD
)
1697 && line
->charAt(u_strlen(ICAL_LASTMOD
)) == COLON
) {
1698 UnicodeString utcString
;
1699 writer
.write(ICAL_LASTMOD
);
1700 writer
.write(COLON
);
1701 writer
.write(getUTCDateTimeString(lastmod
, utcString
));
1702 writer
.write(ICAL_NEWLINE
);
1704 writer
.write(*line
);
1705 writer
.write(ICAL_NEWLINE
);
1709 UVector
*customProps
= NULL
;
1710 if (olsonzid
.length() > 0 && icutzver
.length() > 0) {
1711 customProps
= new UVector(uhash_deleteUnicodeString
, uhash_compareUnicodeString
, status
);
1712 if (U_FAILURE(status
)) {
1715 UnicodeString
*icutzprop
= new UnicodeString(ICU_TZINFO_PROP
);
1716 icutzprop
->append(olsonzid
);
1717 icutzprop
->append((UChar
)0x005B/*'['*/);
1718 icutzprop
->append(icutzver
);
1719 icutzprop
->append((UChar
)0x005D/*']'*/);
1720 customProps
->addElement(icutzprop
, status
);
1721 if (U_FAILURE(status
)) {
1727 writeZone(writer
, *tz
, customProps
, status
);
1733 VTimeZone::write(UDate start
, VTZWriter
& writer
, UErrorCode
& status
) /*const*/ {
1734 if (U_FAILURE(status
)) {
1737 InitialTimeZoneRule
*initial
= NULL
;
1738 UVector
*transitionRules
= NULL
;
1739 UVector
customProps(uhash_deleteUnicodeString
, uhash_compareUnicodeString
, status
);
1742 // Extract rules applicable to dates after the start time
1743 getTimeZoneRulesAfter(start
, initial
, transitionRules
, status
);
1744 if (U_FAILURE(status
)) {
1748 // Create a RuleBasedTimeZone with the subset rule
1750 RuleBasedTimeZone
rbtz(tzid
, initial
);
1751 if (transitionRules
!= NULL
) {
1752 while (!transitionRules
->isEmpty()) {
1753 TimeZoneRule
*tr
= (TimeZoneRule
*)transitionRules
->orphanElementAt(0);
1754 rbtz
.addTransitionRule(tr
, status
);
1755 if (U_FAILURE(status
)) {
1756 goto cleanupWritePartial
;
1759 delete transitionRules
;
1760 transitionRules
= NULL
;
1762 rbtz
.complete(status
);
1763 if (U_FAILURE(status
)) {
1764 goto cleanupWritePartial
;
1767 if (olsonzid
.length() > 0 && icutzver
.length() > 0) {
1768 UnicodeString
*icutzprop
= new UnicodeString(ICU_TZINFO_PROP
);
1769 icutzprop
->append(olsonzid
);
1770 icutzprop
->append((UChar
)0x005B/*'['*/);
1771 icutzprop
->append(icutzver
);
1772 icutzprop
->append(ICU_TZINFO_PARTIAL
);
1773 appendMillis(start
, *icutzprop
);
1774 icutzprop
->append((UChar
)0x005D/*']'*/);
1775 customProps
.addElement(icutzprop
, status
);
1776 if (U_FAILURE(status
)) {
1778 goto cleanupWritePartial
;
1781 writeZone(writer
, rbtz
, &customProps
, status
);
1784 cleanupWritePartial
:
1785 if (initial
!= NULL
) {
1788 if (transitionRules
!= NULL
) {
1789 while (!transitionRules
->isEmpty()) {
1790 TimeZoneRule
*tr
= (TimeZoneRule
*)transitionRules
->orphanElementAt(0);
1793 delete transitionRules
;
1798 VTimeZone::writeSimple(UDate time
, VTZWriter
& writer
, UErrorCode
& status
) /*const*/ {
1799 if (U_FAILURE(status
)) {
1803 UVector
customProps(uhash_deleteUnicodeString
, uhash_compareUnicodeString
, status
);
1806 // Extract simple rules
1807 InitialTimeZoneRule
*initial
= NULL
;
1808 AnnualTimeZoneRule
*std
= NULL
, *dst
= NULL
;
1809 getSimpleRulesNear(time
, initial
, std
, dst
, status
);
1810 if (U_SUCCESS(status
)) {
1811 // Create a RuleBasedTimeZone with the subset rule
1813 RuleBasedTimeZone
rbtz(tzid
, initial
);
1814 if (std
!= NULL
&& dst
!= NULL
) {
1815 rbtz
.addTransitionRule(std
, status
);
1816 rbtz
.addTransitionRule(dst
, status
);
1818 if (U_FAILURE(status
)) {
1819 goto cleanupWriteSimple
;
1822 if (olsonzid
.length() > 0 && icutzver
.length() > 0) {
1823 UnicodeString
*icutzprop
= new UnicodeString(ICU_TZINFO_PROP
);
1824 icutzprop
->append(olsonzid
);
1825 icutzprop
->append((UChar
)0x005B/*'['*/);
1826 icutzprop
->append(icutzver
);
1827 icutzprop
->append(ICU_TZINFO_SIMPLE
);
1828 appendMillis(time
, *icutzprop
);
1829 icutzprop
->append((UChar
)0x005D/*']'*/);
1830 customProps
.addElement(icutzprop
, status
);
1831 if (U_FAILURE(status
)) {
1833 goto cleanupWriteSimple
;
1836 writeZone(writer
, rbtz
, &customProps
, status
);
1841 if (initial
!= NULL
) {
1853 VTimeZone::writeZone(VTZWriter
& w
, BasicTimeZone
& basictz
,
1854 UVector
* customProps
, UErrorCode
& status
) const {
1855 if (U_FAILURE(status
)) {
1858 writeHeaders(w
, status
);
1859 if (U_FAILURE(status
)) {
1863 if (customProps
!= NULL
) {
1864 for (int32_t i
= 0; i
< customProps
->size(); i
++) {
1865 UnicodeString
*custprop
= (UnicodeString
*)customProps
->elementAt(i
);
1867 w
.write(ICAL_NEWLINE
);
1871 UDate t
= MIN_MILLIS
;
1872 UnicodeString dstName
;
1873 int32_t dstFromOffset
= 0;
1874 int32_t dstFromDSTSavings
= 0;
1875 int32_t dstToOffset
= 0;
1876 int32_t dstStartYear
= 0;
1877 int32_t dstMonth
= 0;
1878 int32_t dstDayOfWeek
= 0;
1879 int32_t dstWeekInMonth
= 0;
1880 int32_t dstMillisInDay
= 0;
1881 UDate dstStartTime
= 0.0;
1882 UDate dstUntilTime
= 0.0;
1883 int32_t dstCount
= 0;
1884 AnnualTimeZoneRule
*finalDstRule
= NULL
;
1886 UnicodeString stdName
;
1887 int32_t stdFromOffset
= 0;
1888 int32_t stdFromDSTSavings
= 0;
1889 int32_t stdToOffset
= 0;
1890 int32_t stdStartYear
= 0;
1891 int32_t stdMonth
= 0;
1892 int32_t stdDayOfWeek
= 0;
1893 int32_t stdWeekInMonth
= 0;
1894 int32_t stdMillisInDay
= 0;
1895 UDate stdStartTime
= 0.0;
1896 UDate stdUntilTime
= 0.0;
1897 int32_t stdCount
= 0;
1898 AnnualTimeZoneRule
*finalStdRule
= NULL
;
1900 int32_t year
, month
, dom
, dow
, doy
, mid
;
1901 UBool hasTransitions
= FALSE
;
1902 TimeZoneTransition tzt
;
1907 // Going through all transitions
1909 tztAvail
= basictz
.getNextTransition(t
, FALSE
, tzt
);
1913 hasTransitions
= TRUE
;
1915 tzt
.getTo()->getName(name
);
1916 isDst
= (tzt
.getTo()->getDSTSavings() != 0);
1917 int32_t fromOffset
= tzt
.getFrom()->getRawOffset() + tzt
.getFrom()->getDSTSavings();
1918 int32_t fromDSTSavings
= tzt
.getFrom()->getDSTSavings();
1919 int32_t toOffset
= tzt
.getTo()->getRawOffset() + tzt
.getTo()->getDSTSavings();
1920 Grego::timeToFields(tzt
.getTime() + fromOffset
, year
, month
, dom
, dow
, doy
, mid
);
1921 int32_t weekInMonth
= Grego::dayOfWeekInMonth(year
, month
, dom
);
1922 UBool sameRule
= FALSE
;
1924 if (finalDstRule
== NULL
1925 && tzt
.getTo()->getDynamicClassID() == AnnualTimeZoneRule::getStaticClassID()) {
1926 if (((AnnualTimeZoneRule
*)tzt
.getTo())->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
) {
1927 finalDstRule
= (AnnualTimeZoneRule
*)tzt
.getTo()->clone();
1931 if (year
== dstStartYear
+ dstCount
1932 && name
.compare(dstName
) == 0
1933 && dstFromOffset
== fromOffset
1934 && dstToOffset
== toOffset
1935 && dstMonth
== month
1936 && dstDayOfWeek
== dow
1937 && dstWeekInMonth
== weekInMonth
1938 && dstMillisInDay
== mid
) {
1939 // Update until time
1945 if (dstCount
== 1) {
1946 writeZonePropsByTime(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
, dstStartTime
,
1949 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
1950 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, dstUntilTime
, status
);
1952 if (U_FAILURE(status
)) {
1953 goto cleanupWriteZone
;
1958 // Reset this DST information
1960 dstFromOffset
= fromOffset
;
1961 dstFromDSTSavings
= fromDSTSavings
;
1962 dstToOffset
= toOffset
;
1963 dstStartYear
= year
;
1966 dstWeekInMonth
= weekInMonth
;
1967 dstMillisInDay
= mid
;
1968 dstStartTime
= dstUntilTime
= t
;
1971 if (finalStdRule
!= NULL
&& finalDstRule
!= NULL
) {
1975 if (finalStdRule
== NULL
1976 && tzt
.getTo()->getDynamicClassID() == AnnualTimeZoneRule::getStaticClassID()) {
1977 if (((AnnualTimeZoneRule
*)tzt
.getTo())->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
) {
1978 finalStdRule
= (AnnualTimeZoneRule
*)tzt
.getTo()->clone();
1982 if (year
== stdStartYear
+ stdCount
1983 && name
.compare(stdName
) == 0
1984 && stdFromOffset
== fromOffset
1985 && stdToOffset
== toOffset
1986 && stdMonth
== month
1987 && stdDayOfWeek
== dow
1988 && stdWeekInMonth
== weekInMonth
1989 && stdMillisInDay
== mid
) {
1990 // Update until time
1996 if (stdCount
== 1) {
1997 writeZonePropsByTime(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
, stdStartTime
,
2000 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2001 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, stdUntilTime
, status
);
2003 if (U_FAILURE(status
)) {
2004 goto cleanupWriteZone
;
2009 // Reset this STD information
2011 stdFromOffset
= fromOffset
;
2012 stdFromDSTSavings
= fromDSTSavings
;
2013 stdToOffset
= toOffset
;
2014 stdStartYear
= year
;
2017 stdWeekInMonth
= weekInMonth
;
2018 stdMillisInDay
= mid
;
2019 stdStartTime
= stdUntilTime
= t
;
2022 if (finalStdRule
!= NULL
&& finalDstRule
!= NULL
) {
2027 if (!hasTransitions
) {
2028 // No transition - put a single non transition RDATE
2029 int32_t raw
, dst
, offset
;
2030 basictz
.getOffset(0.0/*any time*/, FALSE
, raw
, dst
, status
);
2031 if (U_FAILURE(status
)) {
2032 goto cleanupWriteZone
;
2037 basictz
.getID(tzid
);
2038 getDefaultTZName(tzid
, isDst
, name
);
2039 writeZonePropsByTime(w
, isDst
, name
,
2040 offset
, offset
, DEF_TZSTARTTIME
- offset
, FALSE
, status
);
2041 if (U_FAILURE(status
)) {
2042 goto cleanupWriteZone
;
2046 if (finalDstRule
== NULL
) {
2047 if (dstCount
== 1) {
2048 writeZonePropsByTime(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
, dstStartTime
,
2051 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
2052 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, dstUntilTime
, status
);
2054 if (U_FAILURE(status
)) {
2055 goto cleanupWriteZone
;
2058 if (dstCount
== 1) {
2059 writeFinalRule(w
, TRUE
, finalDstRule
,
2060 dstFromOffset
- dstFromDSTSavings
, dstFromDSTSavings
, dstStartTime
, status
);
2062 // Use a single rule if possible
2063 if (isEquivalentDateRule(dstMonth
, dstWeekInMonth
, dstDayOfWeek
, finalDstRule
->getRule())) {
2064 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
2065 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, MAX_MILLIS
, status
);
2067 // Not equivalent rule - write out two different rules
2068 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
2069 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, dstUntilTime
, status
);
2070 if (U_FAILURE(status
)) {
2071 goto cleanupWriteZone
;
2073 writeFinalRule(w
, TRUE
, finalDstRule
,
2074 dstFromOffset
- dstFromDSTSavings
, dstFromDSTSavings
, dstStartTime
, status
);
2077 if (U_FAILURE(status
)) {
2078 goto cleanupWriteZone
;
2083 if (finalStdRule
== NULL
) {
2084 if (stdCount
== 1) {
2085 writeZonePropsByTime(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
, stdStartTime
,
2088 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2089 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, stdUntilTime
, status
);
2091 if (U_FAILURE(status
)) {
2092 goto cleanupWriteZone
;
2095 if (stdCount
== 1) {
2096 writeFinalRule(w
, FALSE
, finalStdRule
,
2097 stdFromOffset
- stdFromDSTSavings
, stdFromDSTSavings
, stdStartTime
, status
);
2099 // Use a single rule if possible
2100 if (isEquivalentDateRule(stdMonth
, stdWeekInMonth
, stdDayOfWeek
, finalStdRule
->getRule())) {
2101 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2102 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, MAX_MILLIS
, status
);
2104 // Not equivalent rule - write out two different rules
2105 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2106 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, stdUntilTime
, status
);
2107 if (U_FAILURE(status
)) {
2108 goto cleanupWriteZone
;
2110 writeFinalRule(w
, FALSE
, finalStdRule
,
2111 stdFromOffset
- stdFromDSTSavings
, stdFromDSTSavings
, stdStartTime
, status
);
2114 if (U_FAILURE(status
)) {
2115 goto cleanupWriteZone
;
2120 writeFooter(w
, status
);
2124 if (finalStdRule
!= NULL
) {
2125 delete finalStdRule
;
2127 if (finalDstRule
!= NULL
) {
2128 delete finalDstRule
;
2133 VTimeZone::writeHeaders(VTZWriter
& writer
, UErrorCode
& status
) const {
2134 if (U_FAILURE(status
)) {
2140 writer
.write(ICAL_BEGIN
);
2141 writer
.write(COLON
);
2142 writer
.write(ICAL_VTIMEZONE
);
2143 writer
.write(ICAL_NEWLINE
);
2144 writer
.write(ICAL_TZID
);
2145 writer
.write(COLON
);
2147 writer
.write(ICAL_NEWLINE
);
2148 if (tzurl
.length() != 0) {
2149 writer
.write(ICAL_TZURL
);
2150 writer
.write(COLON
);
2151 writer
.write(tzurl
);
2152 writer
.write(ICAL_NEWLINE
);
2154 if (lastmod
!= MAX_MILLIS
) {
2155 UnicodeString lastmodStr
;
2156 writer
.write(ICAL_LASTMOD
);
2157 writer
.write(COLON
);
2158 writer
.write(getUTCDateTimeString(lastmod
, lastmodStr
));
2159 writer
.write(ICAL_NEWLINE
);
2164 * Write the closing section of the VTIMEZONE definition block
2167 VTimeZone::writeFooter(VTZWriter
& writer
, UErrorCode
& status
) const {
2168 if (U_FAILURE(status
)) {
2171 writer
.write(ICAL_END
);
2172 writer
.write(COLON
);
2173 writer
.write(ICAL_VTIMEZONE
);
2174 writer
.write(ICAL_NEWLINE
);
2178 * Write a single start time
2181 VTimeZone::writeZonePropsByTime(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& tzname
,
2182 int32_t fromOffset
, int32_t toOffset
, UDate time
, UBool withRDATE
,
2183 UErrorCode
& status
) const {
2184 if (U_FAILURE(status
)) {
2187 beginZoneProps(writer
, isDst
, tzname
, fromOffset
, toOffset
, time
, status
);
2188 if (U_FAILURE(status
)) {
2192 writer
.write(ICAL_RDATE
);
2193 writer
.write(COLON
);
2194 UnicodeString timestr
;
2195 writer
.write(getDateTimeString(time
+ fromOffset
, timestr
));
2196 writer
.write(ICAL_NEWLINE
);
2198 endZoneProps(writer
, isDst
, status
);
2199 if (U_FAILURE(status
)) {
2205 * Write start times defined by a DOM rule using VTIMEZONE RRULE
2208 VTimeZone::writeZonePropsByDOM(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& tzname
,
2209 int32_t fromOffset
, int32_t toOffset
,
2210 int32_t month
, int32_t dayOfMonth
, UDate startTime
, UDate untilTime
,
2211 UErrorCode
& status
) const {
2212 if (U_FAILURE(status
)) {
2215 beginZoneProps(writer
, isDst
, tzname
, fromOffset
, toOffset
, startTime
, status
);
2216 if (U_FAILURE(status
)) {
2219 beginRRULE(writer
, month
, status
);
2220 if (U_FAILURE(status
)) {
2223 writer
.write(ICAL_BYMONTHDAY
);
2224 writer
.write(EQUALS_SIGN
);
2226 appendAsciiDigits(dayOfMonth
, 0, dstr
);
2228 if (untilTime
!= MAX_MILLIS
) {
2229 appendUNTIL(writer
, getDateTimeString(untilTime
+ fromOffset
, dstr
), status
);
2230 if (U_FAILURE(status
)) {
2234 writer
.write(ICAL_NEWLINE
);
2235 endZoneProps(writer
, isDst
, status
);
2239 * Write start times defined by a DOW rule using VTIMEZONE RRULE
2242 VTimeZone::writeZonePropsByDOW(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& tzname
,
2243 int32_t fromOffset
, int32_t toOffset
,
2244 int32_t month
, int32_t weekInMonth
, int32_t dayOfWeek
,
2245 UDate startTime
, UDate untilTime
, UErrorCode
& status
) const {
2246 if (U_FAILURE(status
)) {
2249 beginZoneProps(writer
, isDst
, tzname
, fromOffset
, toOffset
, startTime
, status
);
2250 if (U_FAILURE(status
)) {
2253 beginRRULE(writer
, month
, status
);
2254 if (U_FAILURE(status
)) {
2257 writer
.write(ICAL_BYDAY
);
2258 writer
.write(EQUALS_SIGN
);
2260 appendAsciiDigits(weekInMonth
, 0, dstr
);
2261 writer
.write(dstr
); // -4, -3, -2, -1, 1, 2, 3, 4
2262 writer
.write(ICAL_DOW_NAMES
[dayOfWeek
- 1]); // SU, MO, TU...
2264 if (untilTime
!= MAX_MILLIS
) {
2265 appendUNTIL(writer
, getDateTimeString(untilTime
+ fromOffset
, dstr
), status
);
2266 if (U_FAILURE(status
)) {
2270 writer
.write(ICAL_NEWLINE
);
2271 endZoneProps(writer
, isDst
, status
);
2275 * Write start times defined by a DOW_GEQ_DOM rule using VTIMEZONE RRULE
2278 VTimeZone::writeZonePropsByDOW_GEQ_DOM(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& tzname
,
2279 int32_t fromOffset
, int32_t toOffset
,
2280 int32_t month
, int32_t dayOfMonth
, int32_t dayOfWeek
,
2281 UDate startTime
, UDate untilTime
, UErrorCode
& status
) const {
2282 if (U_FAILURE(status
)) {
2285 // Check if this rule can be converted to DOW rule
2286 if (dayOfMonth%7
== 1) {
2287 // Can be represented by DOW rule
2288 writeZonePropsByDOW(writer
, isDst
, tzname
, fromOffset
, toOffset
,
2289 month
, (dayOfMonth
+ 6)/7, dayOfWeek
, startTime
, untilTime
, status
);
2290 if (U_FAILURE(status
)) {
2293 } else if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - dayOfMonth
)%7
== 6) {
2294 // Can be represented by DOW rule with negative week number
2295 writeZonePropsByDOW(writer
, isDst
, tzname
, fromOffset
, toOffset
,
2296 month
, -1*((MONTHLENGTH
[month
] - dayOfMonth
+ 1)/7), dayOfWeek
, startTime
, untilTime
, status
);
2297 if (U_FAILURE(status
)) {
2301 // Otherwise, use BYMONTHDAY to include all possible dates
2302 beginZoneProps(writer
, isDst
, tzname
, fromOffset
, toOffset
, startTime
, status
);
2303 if (U_FAILURE(status
)) {
2306 // Check if all days are in the same month
2307 int32_t startDay
= dayOfMonth
;
2308 int32_t currentMonthDays
= 7;
2310 if (dayOfMonth
<= 0) {
2311 // The start day is in previous month
2312 int32_t prevMonthDays
= 1 - dayOfMonth
;
2313 currentMonthDays
-= prevMonthDays
;
2315 int32_t prevMonth
= (month
- 1) < 0 ? 11 : month
- 1;
2317 // Note: When a rule is separated into two, UNTIL attribute needs to be
2318 // calculated for each of them. For now, we skip this, because we basically use this method
2319 // only for final rules, which does not have the UNTIL attribute
2320 writeZonePropsByDOW_GEQ_DOM_sub(writer
, prevMonth
, -prevMonthDays
, dayOfWeek
, prevMonthDays
,
2321 MAX_MILLIS
/* Do not use UNTIL */, fromOffset
, status
);
2322 if (U_FAILURE(status
)) {
2326 // Start from 1 for the rest
2328 } else if (dayOfMonth
+ 6 > MONTHLENGTH
[month
]) {
2329 // Note: This code does not actually work well in February. For now, days in month in
2331 int32_t nextMonthDays
= dayOfMonth
+ 6 - MONTHLENGTH
[month
];
2332 currentMonthDays
-= nextMonthDays
;
2334 int32_t nextMonth
= (month
+ 1) > 11 ? 0 : month
+ 1;
2336 writeZonePropsByDOW_GEQ_DOM_sub(writer
, nextMonth
, 1, dayOfWeek
, nextMonthDays
,
2337 MAX_MILLIS
/* Do not use UNTIL */, fromOffset
, status
);
2338 if (U_FAILURE(status
)) {
2342 writeZonePropsByDOW_GEQ_DOM_sub(writer
, month
, startDay
, dayOfWeek
, currentMonthDays
,
2343 untilTime
, fromOffset
, status
);
2344 if (U_FAILURE(status
)) {
2347 endZoneProps(writer
, isDst
, status
);
2352 * Called from writeZonePropsByDOW_GEQ_DOM
2355 VTimeZone::writeZonePropsByDOW_GEQ_DOM_sub(VTZWriter
& writer
, int32_t month
, int32_t dayOfMonth
,
2356 int32_t dayOfWeek
, int32_t numDays
,
2357 UDate untilTime
, int32_t fromOffset
, UErrorCode
& status
) const {
2359 if (U_FAILURE(status
)) {
2362 int32_t startDayNum
= dayOfMonth
;
2363 UBool isFeb
= (month
== UCAL_FEBRUARY
);
2364 if (dayOfMonth
< 0 && !isFeb
) {
2365 // Use positive number if possible
2366 startDayNum
= MONTHLENGTH
[month
] + dayOfMonth
+ 1;
2368 beginRRULE(writer
, month
, status
);
2369 if (U_FAILURE(status
)) {
2372 writer
.write(ICAL_BYDAY
);
2373 writer
.write(EQUALS_SIGN
);
2374 writer
.write(ICAL_DOW_NAMES
[dayOfWeek
- 1]); // SU, MO, TU...
2375 writer
.write(SEMICOLON
);
2376 writer
.write(ICAL_BYMONTHDAY
);
2377 writer
.write(EQUALS_SIGN
);
2380 appendAsciiDigits(startDayNum
, 0, dstr
);
2382 for (int32_t i
= 1; i
< numDays
; i
++) {
2383 writer
.write(COMMA
);
2385 appendAsciiDigits(startDayNum
+ i
, 0, dstr
);
2389 if (untilTime
!= MAX_MILLIS
) {
2390 appendUNTIL(writer
, getDateTimeString(untilTime
+ fromOffset
, dstr
), status
);
2391 if (U_FAILURE(status
)) {
2395 writer
.write(ICAL_NEWLINE
);
2399 * Write start times defined by a DOW_LEQ_DOM rule using VTIMEZONE RRULE
2402 VTimeZone::writeZonePropsByDOW_LEQ_DOM(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& tzname
,
2403 int32_t fromOffset
, int32_t toOffset
,
2404 int32_t month
, int32_t dayOfMonth
, int32_t dayOfWeek
,
2405 UDate startTime
, UDate untilTime
, UErrorCode
& status
) const {
2406 if (U_FAILURE(status
)) {
2409 // Check if this rule can be converted to DOW rule
2410 if (dayOfMonth%7
== 0) {
2411 // Can be represented by DOW rule
2412 writeZonePropsByDOW(writer
, isDst
, tzname
, fromOffset
, toOffset
,
2413 month
, dayOfMonth
/7, dayOfWeek
, startTime
, untilTime
, status
);
2414 } else if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - dayOfMonth
)%7
== 0){
2415 // Can be represented by DOW rule with negative week number
2416 writeZonePropsByDOW(writer
, isDst
, tzname
, fromOffset
, toOffset
,
2417 month
, -1*((MONTHLENGTH
[month
] - dayOfMonth
)/7 + 1), dayOfWeek
, startTime
, untilTime
, status
);
2418 } else if (month
== UCAL_FEBRUARY
&& dayOfMonth
== 29) {
2419 // Specical case for February
2420 writeZonePropsByDOW(writer
, isDst
, tzname
, fromOffset
, toOffset
,
2421 UCAL_FEBRUARY
, -1, dayOfWeek
, startTime
, untilTime
, status
);
2423 // Otherwise, convert this to DOW_GEQ_DOM rule
2424 writeZonePropsByDOW_GEQ_DOM(writer
, isDst
, tzname
, fromOffset
, toOffset
,
2425 month
, dayOfMonth
- 6, dayOfWeek
, startTime
, untilTime
, status
);
2430 * Write the final time zone rule using RRULE, with no UNTIL attribute
2433 VTimeZone::writeFinalRule(VTZWriter
& writer
, UBool isDst
, const AnnualTimeZoneRule
* rule
,
2434 int32_t fromRawOffset
, int32_t fromDSTSavings
,
2435 UDate startTime
, UErrorCode
& status
) const {
2436 if (U_FAILURE(status
)) {
2439 UBool modifiedRule
= TRUE
;
2440 const DateTimeRule
*dtrule
= toWallTimeRule(rule
->getRule(), fromRawOffset
, fromDSTSavings
);
2441 if (dtrule
== NULL
) {
2442 modifiedRule
= FALSE
;
2443 dtrule
= rule
->getRule();
2445 int32_t toOffset
= rule
->getRawOffset() + rule
->getDSTSavings();
2447 rule
->getName(name
);
2448 switch (dtrule
->getDateRuleType()) {
2449 case DateTimeRule::DOM
:
2450 writeZonePropsByDOM(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2451 dtrule
->getRuleMonth(), dtrule
->getRuleDayOfMonth(), startTime
, MAX_MILLIS
, status
);
2453 case DateTimeRule::DOW
:
2454 writeZonePropsByDOW(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2455 dtrule
->getRuleMonth(), dtrule
->getRuleWeekInMonth(), dtrule
->getRuleDayOfWeek(), startTime
, MAX_MILLIS
, status
);
2457 case DateTimeRule::DOW_GEQ_DOM
:
2458 writeZonePropsByDOW_GEQ_DOM(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2459 dtrule
->getRuleMonth(), dtrule
->getRuleDayOfMonth(), dtrule
->getRuleDayOfWeek(), startTime
, MAX_MILLIS
, status
);
2461 case DateTimeRule::DOW_LEQ_DOM
:
2462 writeZonePropsByDOW_LEQ_DOM(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2463 dtrule
->getRuleMonth(), dtrule
->getRuleDayOfMonth(), dtrule
->getRuleDayOfWeek(), startTime
, MAX_MILLIS
, status
);
2472 * Write the opening section of zone properties
2475 VTimeZone::beginZoneProps(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& tzname
,
2476 int32_t fromOffset
, int32_t toOffset
, UDate startTime
, UErrorCode
& status
) const {
2477 if (U_FAILURE(status
)) {
2480 writer
.write(ICAL_BEGIN
);
2481 writer
.write(COLON
);
2483 writer
.write(ICAL_DAYLIGHT
);
2485 writer
.write(ICAL_STANDARD
);
2487 writer
.write(ICAL_NEWLINE
);
2492 writer
.write(ICAL_TZOFFSETTO
);
2493 writer
.write(COLON
);
2494 millisToOffset(toOffset
, dstr
);
2496 writer
.write(ICAL_NEWLINE
);
2499 writer
.write(ICAL_TZOFFSETFROM
);
2500 writer
.write(COLON
);
2501 millisToOffset(fromOffset
, dstr
);
2503 writer
.write(ICAL_NEWLINE
);
2506 writer
.write(ICAL_TZNAME
);
2507 writer
.write(COLON
);
2508 writer
.write(tzname
);
2509 writer
.write(ICAL_NEWLINE
);
2512 writer
.write(ICAL_DTSTART
);
2513 writer
.write(COLON
);
2514 writer
.write(getDateTimeString(startTime
+ fromOffset
, dstr
));
2515 writer
.write(ICAL_NEWLINE
);
2519 * Writes the closing section of zone properties
2522 VTimeZone::endZoneProps(VTZWriter
& writer
, UBool isDst
, UErrorCode
& status
) const {
2523 if (U_FAILURE(status
)) {
2526 // END:STANDARD or END:DAYLIGHT
2527 writer
.write(ICAL_END
);
2528 writer
.write(COLON
);
2530 writer
.write(ICAL_DAYLIGHT
);
2532 writer
.write(ICAL_STANDARD
);
2534 writer
.write(ICAL_NEWLINE
);
2538 * Write the beggining part of RRULE line
2541 VTimeZone::beginRRULE(VTZWriter
& writer
, int32_t month
, UErrorCode
& status
) const {
2542 if (U_FAILURE(status
)) {
2546 writer
.write(ICAL_RRULE
);
2547 writer
.write(COLON
);
2548 writer
.write(ICAL_FREQ
);
2549 writer
.write(EQUALS_SIGN
);
2550 writer
.write(ICAL_YEARLY
);
2551 writer
.write(SEMICOLON
);
2552 writer
.write(ICAL_BYMONTH
);
2553 writer
.write(EQUALS_SIGN
);
2554 appendAsciiDigits(month
+ 1, 0, dstr
);
2556 writer
.write(SEMICOLON
);
2560 * Append the UNTIL attribute after RRULE line
2563 VTimeZone::appendUNTIL(VTZWriter
& writer
, const UnicodeString
& until
, UErrorCode
& status
) const {
2564 if (U_FAILURE(status
)) {
2567 if (until
.length() > 0) {
2568 writer
.write(SEMICOLON
);
2569 writer
.write(ICAL_UNTIL
);
2570 writer
.write(EQUALS_SIGN
);
2571 writer
.write(until
);
2577 #endif /* #if !UCONFIG_NO_FORMATTING */