2 *******************************************************************************
3 * Copyright (C) 2007-2010, International Business Machines Corporation and
4 * others. All Rights Reserved.
5 *******************************************************************************
8 #include <typeinfo> // for 'typeid' to work
10 #include "unicode/utypes.h"
12 #if !UCONFIG_NO_FORMATTING
14 #include "unicode/vtzone.h"
15 #include "unicode/rbtz.h"
16 #include "unicode/ucal.h"
17 #include "unicode/ures.h"
25 // This is the deleter that will be use to remove TimeZoneRule
27 static void U_CALLCONV
28 deleteTimeZoneRule(void* obj
) {
29 delete (TimeZoneRule
*) obj
;
33 // Smybol characters used by RFC2445 VTIMEZONE
34 static const UChar COLON
= 0x3A; /* : */
35 static const UChar SEMICOLON
= 0x3B; /* ; */
36 static const UChar EQUALS_SIGN
= 0x3D; /* = */
37 static const UChar COMMA
= 0x2C; /* , */
38 static const UChar PLUS
= 0x2B; /* + */
39 static const UChar MINUS
= 0x2D; /* - */
41 // RFC2445 VTIMEZONE tokens
42 static const UChar ICAL_BEGIN_VTIMEZONE
[] = {0x42, 0x45, 0x47, 0x49, 0x4E, 0x3A, 0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "BEGIN:VTIMEZONE" */
43 static const UChar ICAL_END_VTIMEZONE
[] = {0x45, 0x4E, 0x44, 0x3A, 0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "END:VTIMEZONE" */
44 static const UChar ICAL_BEGIN
[] = {0x42, 0x45, 0x47, 0x49, 0x4E, 0}; /* "BEGIN" */
45 static const UChar ICAL_END
[] = {0x45, 0x4E, 0x44, 0}; /* "END" */
46 static const UChar ICAL_VTIMEZONE
[] = {0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "VTIMEZONE" */
47 static const UChar ICAL_TZID
[] = {0x54, 0x5A, 0x49, 0x44, 0}; /* "TZID" */
48 static const UChar ICAL_STANDARD
[] = {0x53, 0x54, 0x41, 0x4E, 0x44, 0x41, 0x52, 0x44, 0}; /* "STANDARD" */
49 static const UChar ICAL_DAYLIGHT
[] = {0x44, 0x41, 0x59, 0x4C, 0x49, 0x47, 0x48, 0x54, 0}; /* "DAYLIGHT" */
50 static const UChar ICAL_DTSTART
[] = {0x44, 0x54, 0x53, 0x54, 0x41, 0x52, 0x54, 0}; /* "DTSTART" */
51 static const UChar ICAL_TZOFFSETFROM
[] = {0x54, 0x5A, 0x4F, 0x46, 0x46, 0x53, 0x45, 0x54, 0x46, 0x52, 0x4F, 0x4D, 0}; /* "TZOFFSETFROM" */
52 static const UChar ICAL_TZOFFSETTO
[] = {0x54, 0x5A, 0x4F, 0x46, 0x46, 0x53, 0x45, 0x54, 0x54, 0x4F, 0}; /* "TZOFFSETTO" */
53 static const UChar ICAL_RDATE
[] = {0x52, 0x44, 0x41, 0x54, 0x45, 0}; /* "RDATE" */
54 static const UChar ICAL_RRULE
[] = {0x52, 0x52, 0x55, 0x4C, 0x45, 0}; /* "RRULE" */
55 static const UChar ICAL_TZNAME
[] = {0x54, 0x5A, 0x4E, 0x41, 0x4D, 0x45, 0}; /* "TZNAME" */
56 static const UChar ICAL_TZURL
[] = {0x54, 0x5A, 0x55, 0x52, 0x4C, 0}; /* "TZURL" */
57 static const UChar ICAL_LASTMOD
[] = {0x4C, 0x41, 0x53, 0x54, 0x2D, 0x4D, 0x4F, 0x44, 0x49, 0x46, 0x49, 0x45, 0x44, 0}; /* "LAST-MODIFIED" */
59 static const UChar ICAL_FREQ
[] = {0x46, 0x52, 0x45, 0x51, 0}; /* "FREQ" */
60 static const UChar ICAL_UNTIL
[] = {0x55, 0x4E, 0x54, 0x49, 0x4C, 0}; /* "UNTIL" */
61 static const UChar ICAL_YEARLY
[] = {0x59, 0x45, 0x41, 0x52, 0x4C, 0x59, 0}; /* "YEARLY" */
62 static const UChar ICAL_BYMONTH
[] = {0x42, 0x59, 0x4D, 0x4F, 0x4E, 0x54, 0x48, 0}; /* "BYMONTH" */
63 static const UChar ICAL_BYDAY
[] = {0x42, 0x59, 0x44, 0x41, 0x59, 0}; /* "BYDAY" */
64 static const UChar ICAL_BYMONTHDAY
[] = {0x42, 0x59, 0x4D, 0x4F, 0x4E, 0x54, 0x48, 0x44, 0x41, 0x59, 0}; /* "BYMONTHDAY" */
66 static const UChar ICAL_NEWLINE
[] = {0x0D, 0x0A, 0}; /* CRLF */
68 static const UChar ICAL_DOW_NAMES
[7][3] = {
69 {0x53, 0x55, 0}, /* "SU" */
70 {0x4D, 0x4F, 0}, /* "MO" */
71 {0x54, 0x55, 0}, /* "TU" */
72 {0x57, 0x45, 0}, /* "WE" */
73 {0x54, 0x48, 0}, /* "TH" */
74 {0x46, 0x52, 0}, /* "FR" */
75 {0x53, 0x41, 0} /* "SA" */};
77 // Month length for non-leap year
78 static const int32_t MONTHLENGTH
[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
80 // ICU custom property
81 static const UChar ICU_TZINFO_PROP
[] = {0x58, 0x2D, 0x54, 0x5A, 0x49, 0x4E, 0x46, 0x4F, 0x3A, 0}; /* "X-TZINFO:" */
82 static const UChar ICU_TZINFO_PARTIAL
[] = {0x2F, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6C, 0x40, 0}; /* "/Partial@" */
83 static const UChar ICU_TZINFO_SIMPLE
[] = {0x2F, 0x53, 0x69, 0x6D, 0x70, 0x6C, 0x65, 0x40, 0}; /* "/Simple@" */
87 * Simple fixed digit ASCII number to integer converter
89 static int32_t parseAsciiDigits(const UnicodeString
& str
, int32_t start
, int32_t length
, UErrorCode
& status
) {
90 if (U_FAILURE(status
)) {
93 if (length
<= 0 || str
.length() < start
|| (start
+ length
) > str
.length()) {
94 status
= U_INVALID_FORMAT_ERROR
;
98 if (str
.charAt(start
) == PLUS
) {
101 } else if (str
.charAt(start
) == MINUS
) {
107 for (int32_t i
= 0; i
< length
; i
++) {
108 int32_t digit
= str
.charAt(start
+ i
) - 0x0030;
109 if (digit
< 0 || digit
> 9) {
110 status
= U_INVALID_FORMAT_ERROR
;
113 num
= 10 * num
+ digit
;
118 static UnicodeString
& appendAsciiDigits(int32_t number
, uint8_t length
, UnicodeString
& str
) {
119 UBool negative
= FALSE
;
120 int32_t digits
[10]; // max int32_t is 10 decimal digits
128 length
= length
> 10 ? 10 : length
;
133 digits
[i
++] = number
% 10;
135 } while (number
!= 0);
139 for (i
= 0; i
< length
; i
++) {
140 digits
[i
] = number
% 10;
147 for (i
= length
- 1; i
>= 0; i
--) {
148 str
.append((UChar
)(digits
[i
] + 0x0030));
153 static UnicodeString
& appendMillis(UDate date
, UnicodeString
& str
) {
154 UBool negative
= FALSE
;
155 int32_t digits
[20]; // max int64_t is 20 decimal digits
159 if (date
< MIN_MILLIS
) {
160 number
= (int64_t)MIN_MILLIS
;
161 } else if (date
> MAX_MILLIS
) {
162 number
= (int64_t)MAX_MILLIS
;
164 number
= (int64_t)date
;
172 digits
[i
++] = (int32_t)(number
% 10);
174 } while (number
!= 0);
181 str
.append((UChar
)(digits
[i
--] + 0x0030));
187 * Convert date/time to RFC2445 Date-Time form #1 DATE WITH LOCAL TIME
189 static UnicodeString
& getDateTimeString(UDate time
, UnicodeString
& str
) {
190 int32_t year
, month
, dom
, dow
, doy
, mid
;
191 Grego::timeToFields(time
, year
, month
, dom
, dow
, doy
, mid
);
194 appendAsciiDigits(year
, 4, str
);
195 appendAsciiDigits(month
+ 1, 2, str
);
196 appendAsciiDigits(dom
, 2, str
);
197 str
.append((UChar
)0x0054 /*'T'*/);
200 int32_t hour
= t
/ U_MILLIS_PER_HOUR
;
201 t
%= U_MILLIS_PER_HOUR
;
202 int32_t min
= t
/ U_MILLIS_PER_MINUTE
;
203 t
%= U_MILLIS_PER_MINUTE
;
204 int32_t sec
= t
/ U_MILLIS_PER_SECOND
;
206 appendAsciiDigits(hour
, 2, str
);
207 appendAsciiDigits(min
, 2, str
);
208 appendAsciiDigits(sec
, 2, str
);
213 * Convert date/time to RFC2445 Date-Time form #2 DATE WITH UTC TIME
215 static UnicodeString
& getUTCDateTimeString(UDate time
, UnicodeString
& str
) {
216 getDateTimeString(time
, str
);
217 str
.append((UChar
)0x005A /*'Z'*/);
222 * Parse RFC2445 Date-Time form #1 DATE WITH LOCAL TIME and
223 * #2 DATE WITH UTC TIME
225 static UDate
parseDateTimeString(const UnicodeString
& str
, int32_t offset
, UErrorCode
& status
) {
226 if (U_FAILURE(status
)) {
230 int32_t year
= 0, month
= 0, day
= 0, hour
= 0, min
= 0, sec
= 0;
232 UBool isValid
= FALSE
;
234 int length
= str
.length();
235 if (length
!= 15 && length
!= 16) {
236 // FORM#1 15 characters, such as "20060317T142115"
237 // FORM#2 16 characters, such as "20060317T142115Z"
240 if (str
.charAt(8) != 0x0054) {
241 // charcter "T" must be used for separating date and time
245 if (str
.charAt(15) != 0x005A) {
252 year
= parseAsciiDigits(str
, 0, 4, status
);
253 month
= parseAsciiDigits(str
, 4, 2, status
) - 1; // 0-based
254 day
= parseAsciiDigits(str
, 6, 2, status
);
255 hour
= parseAsciiDigits(str
, 9, 2, status
);
256 min
= parseAsciiDigits(str
, 11, 2, status
);
257 sec
= parseAsciiDigits(str
, 13, 2, status
);
259 if (U_FAILURE(status
)) {
264 int32_t maxDayOfMonth
= Grego::monthLength(year
, month
);
265 if (year
< 0 || month
< 0 || month
> 11 || day
< 1 || day
> maxDayOfMonth
||
266 hour
< 0 || hour
>= 24 || min
< 0 || min
>= 60 || sec
< 0 || sec
>= 60) {
274 status
= U_INVALID_FORMAT_ERROR
;
277 // Calculate the time
278 UDate time
= Grego::fieldsToDay(year
, month
, day
) * U_MILLIS_PER_DAY
;
279 time
+= (hour
* U_MILLIS_PER_HOUR
+ min
* U_MILLIS_PER_MINUTE
+ sec
* U_MILLIS_PER_SECOND
);
287 * Convert RFC2445 utc-offset string to milliseconds
289 static int32_t offsetStrToMillis(const UnicodeString
& str
, UErrorCode
& status
) {
290 if (U_FAILURE(status
)) {
294 UBool isValid
= FALSE
;
295 int32_t sign
= 0, hour
= 0, min
= 0, sec
= 0;
298 int length
= str
.length();
299 if (length
!= 5 && length
!= 7) {
300 // utf-offset must be 5 or 7 characters
304 UChar s
= str
.charAt(0);
307 } else if (s
== MINUS
) {
310 // utf-offset must start with "+" or "-"
313 hour
= parseAsciiDigits(str
, 1, 2, status
);
314 min
= parseAsciiDigits(str
, 3, 2, status
);
316 sec
= parseAsciiDigits(str
, 5, 2, status
);
318 if (U_FAILURE(status
)) {
325 status
= U_INVALID_FORMAT_ERROR
;
328 int32_t millis
= sign
* ((hour
* 60 + min
) * 60 + sec
) * 1000;
333 * Convert milliseconds to RFC2445 utc-offset string
335 static void millisToOffset(int32_t millis
, UnicodeString
& str
) {
343 int32_t hour
, min
, sec
;
344 int32_t t
= millis
/ 1000;
351 appendAsciiDigits(hour
, 2, str
);
352 appendAsciiDigits(min
, 2, str
);
353 appendAsciiDigits(sec
, 2, str
);
357 * Create a default TZNAME from TZID
359 static void getDefaultTZName(const UnicodeString tzid
, UBool isDST
, UnicodeString
& zonename
) {
362 zonename
+= UNICODE_STRING_SIMPLE("(DST)");
364 zonename
+= UNICODE_STRING_SIMPLE("(STD)");
369 * Parse individual RRULE
373 * month calculated by BYMONTH-1, or -1 when not found
374 * dow day of week in BYDAY, or 0 when not found
375 * wim day of week ordinal number in BYDAY, or 0 when not found
376 * dom an array of day of month
377 * domCount number of availble days in dom (domCount is specifying the size of dom on input)
378 * until time defined by UNTIL attribute or MIN_MILLIS if not available
380 static void parseRRULE(const UnicodeString
& rrule
, int32_t& month
, int32_t& dow
, int32_t& wim
,
381 int32_t* dom
, int32_t& domCount
, UDate
& until
, UErrorCode
& status
) {
382 if (U_FAILURE(status
)) {
392 UBool yearly
= FALSE
;
393 //UBool parseError = FALSE;
395 int32_t prop_start
= 0;
397 UnicodeString prop
, attr
, value
;
398 UBool nextProp
= TRUE
;
401 prop_end
= rrule
.indexOf(SEMICOLON
, prop_start
);
402 if (prop_end
== -1) {
403 prop
.setTo(rrule
, prop_start
);
406 prop
.setTo(rrule
, prop_start
, prop_end
- prop_start
);
407 prop_start
= prop_end
+ 1;
409 int32_t eql
= prop
.indexOf(EQUALS_SIGN
);
411 attr
.setTo(prop
, 0, eql
);
412 value
.setTo(prop
, eql
+ 1);
414 goto rruleParseError
;
417 if (attr
.compare(ICAL_FREQ
) == 0) {
418 // only support YEARLY frequency type
419 if (value
.compare(ICAL_YEARLY
) == 0) {
422 goto rruleParseError
;
424 } else if (attr
.compare(ICAL_UNTIL
) == 0) {
425 // ISO8601 UTC format, for example, "20060315T020000Z"
426 until
= parseDateTimeString(value
, 0, status
);
427 if (U_FAILURE(status
)) {
428 goto rruleParseError
;
430 } else if (attr
.compare(ICAL_BYMONTH
) == 0) {
431 // Note: BYMONTH may contain multiple months, but only single month make sense for
432 // VTIMEZONE property.
433 if (value
.length() > 2) {
434 goto rruleParseError
;
436 month
= parseAsciiDigits(value
, 0, value
.length(), status
) - 1;
437 if (U_FAILURE(status
) || month
< 0 || month
>= 12) {
438 goto rruleParseError
;
440 } else if (attr
.compare(ICAL_BYDAY
) == 0) {
441 // Note: BYDAY may contain multiple day of week separated by comma. It is unlikely used for
442 // VTIMEZONE property. We do not support the case.
444 // 2-letter format is used just for representing a day of week, for example, "SU" for Sunday
445 // 3 or 4-letter format is used for represeinging Nth day of week, for example, "-1SA" for last Saturday
446 int32_t length
= value
.length();
447 if (length
< 2 || length
> 4) {
448 goto rruleParseError
;
453 if (value
.charAt(0) == PLUS
) {
455 } else if (value
.charAt(0) == MINUS
) {
457 } else if (length
== 4) {
458 goto rruleParseError
;
460 int32_t n
= parseAsciiDigits(value
, length
- 3, 1, status
);
461 if (U_FAILURE(status
) || n
== 0 || n
> 4) {
462 goto rruleParseError
;
465 value
.remove(0, length
- 2);
468 for (wday
= 0; wday
< 7; wday
++) {
469 if (value
.compare(ICAL_DOW_NAMES
[wday
], 2) == 0) {
474 // Sunday(1) - Saturday(7)
477 goto rruleParseError
;
479 } else if (attr
.compare(ICAL_BYMONTHDAY
) == 0) {
480 // Note: BYMONTHDAY may contain multiple days delimitted by comma
482 // A value of BYMONTHDAY could be negative, for example, -1 means
483 // the last day in a month
485 int32_t dom_start
= 0;
487 UBool nextDOM
= TRUE
;
489 dom_end
= value
.indexOf(COMMA
, dom_start
);
491 dom_end
= value
.length();
494 if (dom_idx
< domCount
) {
495 dom
[dom_idx
] = parseAsciiDigits(value
, dom_start
, dom_end
- dom_start
, status
);
496 if (U_FAILURE(status
)) {
497 goto rruleParseError
;
501 status
= U_BUFFER_OVERFLOW_ERROR
;
502 goto rruleParseError
;
504 dom_start
= dom_end
+ 1;
510 // FREQ=YEARLY must be set
511 goto rruleParseError
;
513 // Set actual number of parsed DOM (ICAL_BYMONTHDAY)
518 if (U_SUCCESS(status
)) {
520 status
= U_INVALID_FORMAT_ERROR
;
524 static TimeZoneRule
* createRuleByRRULE(const UnicodeString
& zonename
, int rawOffset
, int dstSavings
, UDate start
,
525 UVector
* dates
, int fromOffset
, UErrorCode
& status
) {
526 if (U_FAILURE(status
)) {
529 if (dates
== NULL
|| dates
->size() == 0) {
530 status
= U_ILLEGAL_ARGUMENT_ERROR
;
535 DateTimeRule
*adtr
= NULL
;
537 // Parse the first rule
538 UnicodeString rrule
= *((UnicodeString
*)dates
->elementAt(0));
539 int32_t month
, dayOfWeek
, nthDayOfWeek
, dayOfMonth
= 0;
541 int32_t daysCount
= sizeof(days
)/sizeof(days
[0]);
544 parseRRULE(rrule
, month
, dayOfWeek
, nthDayOfWeek
, days
, daysCount
, until
, status
);
545 if (U_FAILURE(status
)) {
549 if (dates
->size() == 1) {
552 // Multiple BYMONTHDAY values
553 if (daysCount
!= 7 || month
== -1 || dayOfWeek
== 0) {
554 // Only support the rule using 7 continuous days
555 // BYMONTH and BYDAY must be set at the same time
556 goto unsupportedRRule
;
558 int32_t firstDay
= 31; // max possible number of dates in a month
559 for (i
= 0; i
< 7; i
++) {
560 // Resolve negative day numbers. A negative day number should
561 // not be used in February, but if we see such case, we use 28
564 days
[i
] = MONTHLENGTH
[month
] + days
[i
] + 1;
566 if (days
[i
] < firstDay
) {
570 // Make sure days are continuous
571 for (i
= 1; i
< 7; i
++) {
573 for (j
= 0; j
< 7; j
++) {
574 if (days
[j
] == firstDay
+ i
) {
580 // days are not continuous
581 goto unsupportedRRule
;
584 // Use DOW_GEQ_DOM rule with firstDay as the start date
585 dayOfMonth
= firstDay
;
588 // Check if BYMONTH + BYMONTHDAY + BYDAY rule with multiple RRULE lines.
589 // Otherwise, not supported.
590 if (month
== -1 || dayOfWeek
== 0 || daysCount
== 0) {
591 // This is not the case
592 goto unsupportedRRule
;
594 // Parse the rest of rules if number of rules is not exceeding 7.
595 // We can only support 7 continuous days starting from a day of month.
596 if (dates
->size() > 7) {
597 goto unsupportedRRule
;
600 // Note: To check valid date range across multiple rule is a little
601 // bit complicated. For now, this code is not doing strict range
602 // checking across month boundary
604 int32_t earliestMonth
= month
;
605 int32_t earliestDay
= 31;
606 for (i
= 0; i
< daysCount
; i
++) {
607 int32_t dom
= days
[i
];
608 dom
= dom
> 0 ? dom
: MONTHLENGTH
[month
] + dom
+ 1;
609 earliestDay
= dom
< earliestDay
? dom
: earliestDay
;
612 int32_t anotherMonth
= -1;
613 for (i
= 1; i
< dates
->size(); i
++) {
614 rrule
= *((UnicodeString
*)dates
->elementAt(i
));
616 int32_t tmp_month
, tmp_dayOfWeek
, tmp_nthDayOfWeek
;
618 int32_t tmp_daysCount
= sizeof(tmp_days
)/sizeof(tmp_days
[0]);
619 parseRRULE(rrule
, tmp_month
, tmp_dayOfWeek
, tmp_nthDayOfWeek
, tmp_days
, tmp_daysCount
, tmp_until
, status
);
620 if (U_FAILURE(status
)) {
623 // If UNTIL is newer than previous one, use the one
624 if (tmp_until
> until
) {
628 // Check if BYMONTH + BYMONTHDAY + BYDAY rule
629 if (tmp_month
== -1 || tmp_dayOfWeek
== 0 || tmp_daysCount
== 0) {
630 goto unsupportedRRule
;
632 // Count number of BYMONTHDAY
633 if (daysCount
+ tmp_daysCount
> 7) {
634 // We cannot support BYMONTHDAY more than 7
635 goto unsupportedRRule
;
637 // Check if the same BYDAY is used. Otherwise, we cannot
639 if (tmp_dayOfWeek
!= dayOfWeek
) {
640 goto unsupportedRRule
;
642 // Check if the month is same or right next to the primary month
643 if (tmp_month
!= month
) {
644 if (anotherMonth
== -1) {
645 int32_t diff
= tmp_month
- month
;
646 if (diff
== -11 || diff
== -1) {
648 anotherMonth
= tmp_month
;
649 earliestMonth
= anotherMonth
;
650 // Reset earliest day
652 } else if (diff
== 11 || diff
== 1) {
654 anotherMonth
= tmp_month
;
656 // The day range cannot exceed more than 2 months
657 goto unsupportedRRule
;
659 } else if (tmp_month
!= month
&& tmp_month
!= anotherMonth
) {
660 // The day range cannot exceed more than 2 months
661 goto unsupportedRRule
;
664 // If ealier month, go through days to find the earliest day
665 if (tmp_month
== earliestMonth
) {
666 for (j
= 0; j
< tmp_daysCount
; j
++) {
667 tmp_days
[j
] = tmp_days
[j
] > 0 ? tmp_days
[j
] : MONTHLENGTH
[tmp_month
] + tmp_days
[j
] + 1;
668 earliestDay
= tmp_days
[j
] < earliestDay
? tmp_days
[j
] : earliestDay
;
671 daysCount
+= tmp_daysCount
;
673 if (daysCount
!= 7) {
674 // Number of BYMONTHDAY entries must be 7
675 goto unsupportedRRule
;
677 month
= earliestMonth
;
678 dayOfMonth
= earliestDay
;
681 // Calculate start/end year and missing fields
682 int32_t startYear
, startMonth
, startDOM
, startDOW
, startDOY
, startMID
;
683 Grego::timeToFields(start
+ fromOffset
, startYear
, startMonth
, startDOM
,
684 startDOW
, startDOY
, startMID
);
686 // If BYMONTH is not set, use the month of DTSTART
689 if (dayOfWeek
== 0 && nthDayOfWeek
== 0 && dayOfMonth
== 0) {
690 // If only YEARLY is set, use the day of DTSTART as BYMONTHDAY
691 dayOfMonth
= startDOM
;
695 if (until
!= MIN_MILLIS
) {
696 int32_t endMonth
, endDOM
, endDOW
, endDOY
, endMID
;
697 Grego::timeToFields(until
, endYear
, endMonth
, endDOM
, endDOW
, endDOY
, endMID
);
699 endYear
= AnnualTimeZoneRule::MAX_YEAR
;
702 // Create the AnnualDateTimeRule
703 if (dayOfWeek
== 0 && nthDayOfWeek
== 0 && dayOfMonth
!= 0) {
704 // Day in month rule, for example, 15th day in the month
705 adtr
= new DateTimeRule(month
, dayOfMonth
, startMID
, DateTimeRule::WALL_TIME
);
706 } else if (dayOfWeek
!= 0 && nthDayOfWeek
!= 0 && dayOfMonth
== 0) {
707 // Nth day of week rule, for example, last Sunday
708 adtr
= new DateTimeRule(month
, nthDayOfWeek
, dayOfWeek
, startMID
, DateTimeRule::WALL_TIME
);
709 } else if (dayOfWeek
!= 0 && nthDayOfWeek
== 0 && dayOfMonth
!= 0) {
710 // First day of week after day of month rule, for example,
711 // first Sunday after 15th day in the month
712 adtr
= new DateTimeRule(month
, dayOfMonth
, dayOfWeek
, TRUE
, startMID
, DateTimeRule::WALL_TIME
);
715 goto unsupportedRRule
;
717 return new AnnualTimeZoneRule(zonename
, rawOffset
, dstSavings
, adtr
, startYear
, endYear
);
720 status
= U_INVALID_STATE_ERROR
;
725 * Create a TimeZoneRule by the RDATE definition
727 static TimeZoneRule
* createRuleByRDATE(const UnicodeString
& zonename
, int32_t rawOffset
, int32_t dstSavings
,
728 UDate start
, UVector
* dates
, int32_t fromOffset
, UErrorCode
& status
) {
729 if (U_FAILURE(status
)) {
732 TimeArrayTimeZoneRule
*retVal
= NULL
;
733 if (dates
== NULL
|| dates
->size() == 0) {
734 // When no RDATE line is provided, use start (DTSTART)
735 // as the transition time
736 retVal
= new TimeArrayTimeZoneRule(zonename
, rawOffset
, dstSavings
,
737 &start
, 1, DateTimeRule::UTC_TIME
);
739 // Create an array of transition times
740 int32_t size
= dates
->size();
741 UDate
* times
= (UDate
*)uprv_malloc(sizeof(UDate
) * size
);
743 status
= U_MEMORY_ALLOCATION_ERROR
;
746 for (int32_t i
= 0; i
< size
; i
++) {
747 UnicodeString
*datestr
= (UnicodeString
*)dates
->elementAt(i
);
748 times
[i
] = parseDateTimeString(*datestr
, fromOffset
, status
);
749 if (U_FAILURE(status
)) {
754 retVal
= new TimeArrayTimeZoneRule(zonename
, rawOffset
, dstSavings
,
755 times
, size
, DateTimeRule::UTC_TIME
);
762 * Check if the DOW rule specified by month, weekInMonth and dayOfWeek is equivalent
763 * to the DateTimerule.
765 static UBool
isEquivalentDateRule(int32_t month
, int32_t weekInMonth
, int32_t dayOfWeek
, const DateTimeRule
*dtrule
) {
766 if (month
!= dtrule
->getRuleMonth() || dayOfWeek
!= dtrule
->getRuleDayOfWeek()) {
769 if (dtrule
->getTimeRuleType() != DateTimeRule::WALL_TIME
) {
770 // Do not try to do more intelligent comparison for now.
773 if (dtrule
->getDateRuleType() == DateTimeRule::DOW
774 && dtrule
->getRuleWeekInMonth() == weekInMonth
) {
777 int32_t ruleDOM
= dtrule
->getRuleDayOfMonth();
778 if (dtrule
->getDateRuleType() == DateTimeRule::DOW_GEQ_DOM
) {
779 if (ruleDOM%7
== 1 && (ruleDOM
+ 6)/7 == weekInMonth
) {
782 if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - ruleDOM
)%7
== 6
783 && weekInMonth
== -1*((MONTHLENGTH
[month
]-ruleDOM
+1)/7)) {
787 if (dtrule
->getDateRuleType() == DateTimeRule::DOW_LEQ_DOM
) {
788 if (ruleDOM%7
== 0 && ruleDOM
/7 == weekInMonth
) {
791 if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - ruleDOM
)%7
== 0
792 && weekInMonth
== -1*((MONTHLENGTH
[month
] - ruleDOM
)/7 + 1)) {
800 * Convert the rule to its equivalent rule using WALL_TIME mode.
801 * This function returns NULL when the specified DateTimeRule is already
802 * using WALL_TIME mode.
804 static DateTimeRule
* toWallTimeRule(const DateTimeRule
* rule
, int32_t rawOffset
, int32_t dstSavings
) {
805 if (rule
->getTimeRuleType() == DateTimeRule::WALL_TIME
) {
808 int32_t wallt
= rule
->getRuleMillisInDay();
809 if (rule
->getTimeRuleType() == DateTimeRule::UTC_TIME
) {
810 wallt
+= (rawOffset
+ dstSavings
);
811 } else if (rule
->getTimeRuleType() == DateTimeRule::STANDARD_TIME
) {
815 int32_t month
= -1, dom
= 0, dow
= 0;
816 DateTimeRule::DateRuleType dtype
;
820 wallt
+= U_MILLIS_PER_DAY
;
821 } else if (wallt
>= U_MILLIS_PER_DAY
) {
823 wallt
-= U_MILLIS_PER_DAY
;
826 month
= rule
->getRuleMonth();
827 dom
= rule
->getRuleDayOfMonth();
828 dow
= rule
->getRuleDayOfWeek();
829 dtype
= rule
->getDateRuleType();
832 if (dtype
== DateTimeRule::DOW
) {
833 // Convert to DOW_GEW_DOM or DOW_LEQ_DOM rule first
834 int32_t wim
= rule
->getRuleWeekInMonth();
836 dtype
= DateTimeRule::DOW_GEQ_DOM
;
837 dom
= 7 * (wim
- 1) + 1;
839 dtype
= DateTimeRule::DOW_LEQ_DOM
;
840 dom
= MONTHLENGTH
[month
] + 7 * (wim
+ 1);
843 // Shift one day before or after
847 month
= month
< UCAL_JANUARY
? UCAL_DECEMBER
: month
;
848 dom
= MONTHLENGTH
[month
];
849 } else if (dom
> MONTHLENGTH
[month
]) {
851 month
= month
> UCAL_DECEMBER
? UCAL_JANUARY
: month
;
854 if (dtype
!= DateTimeRule::DOM
) {
855 // Adjust day of week
857 if (dow
< UCAL_SUNDAY
) {
859 } else if (dow
> UCAL_SATURDAY
) {
865 DateTimeRule
*modifiedRule
;
866 if (dtype
== DateTimeRule::DOM
) {
867 modifiedRule
= new DateTimeRule(month
, dom
, wallt
, DateTimeRule::WALL_TIME
);
869 modifiedRule
= new DateTimeRule(month
, dom
, dow
,
870 (dtype
== DateTimeRule::DOW_GEQ_DOM
), wallt
, DateTimeRule::WALL_TIME
);
876 * Minumum implementations of stream writer/reader, writing/reading
877 * UnicodeString. For now, we do not want to introduce the dependency
878 * on the ICU I/O stream in this module. But we want to keep the code
879 * equivalent to the ICU4J implementation, which utilizes java.io.Writer/
884 VTZWriter(UnicodeString
& out
);
887 void write(const UnicodeString
& str
);
888 void write(UChar ch
);
889 //void write(const UChar* str, int32_t length);
894 VTZWriter::VTZWriter(UnicodeString
& output
) {
898 VTZWriter::~VTZWriter() {
902 VTZWriter::write(const UnicodeString
& str
) {
907 VTZWriter::write(UChar ch
) {
913 VTZWriter::write(const UChar* str, int32_t length) {
914 out->append(str, length);
920 VTZReader(const UnicodeString
& input
);
925 const UnicodeString
* in
;
929 VTZReader::VTZReader(const UnicodeString
& input
) {
934 VTZReader::~VTZReader() {
938 VTZReader::read(void) {
940 if (index
< in
->length()) {
941 ch
= in
->charAt(index
);
948 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(VTimeZone
)
950 VTimeZone::VTimeZone()
951 : BasicTimeZone(), tz(NULL
), vtzlines(NULL
),
952 lastmod(MAX_MILLIS
) {
955 VTimeZone::VTimeZone(const VTimeZone
& source
)
956 : BasicTimeZone(source
), tz(NULL
), vtzlines(NULL
),
957 tzurl(source
.tzurl
), lastmod(source
.lastmod
),
958 olsonzid(source
.olsonzid
), icutzver(source
.icutzver
) {
959 if (source
.tz
!= NULL
) {
960 tz
= (BasicTimeZone
*)source
.tz
->clone();
962 if (source
.vtzlines
!= NULL
) {
963 UErrorCode status
= U_ZERO_ERROR
;
964 int32_t size
= source
.vtzlines
->size();
965 vtzlines
= new UVector(uhash_deleteUnicodeString
, uhash_compareUnicodeString
, size
, status
);
966 if (U_SUCCESS(status
)) {
967 for (int32_t i
= 0; i
< size
; i
++) {
968 UnicodeString
*line
= (UnicodeString
*)source
.vtzlines
->elementAt(i
);
969 vtzlines
->addElement(line
->clone(), status
);
970 if (U_FAILURE(status
)) {
975 if (U_FAILURE(status
) && vtzlines
!= NULL
) {
981 VTimeZone::~VTimeZone() {
985 if (vtzlines
!= NULL
) {
991 VTimeZone::operator=(const VTimeZone
& right
) {
992 if (this == &right
) {
995 if (*this != right
) {
996 BasicTimeZone::operator=(right
);
1001 if (right
.tz
!= NULL
) {
1002 tz
= (BasicTimeZone
*)right
.tz
->clone();
1004 if (vtzlines
!= NULL
) {
1007 if (right
.vtzlines
!= NULL
) {
1008 UErrorCode status
= U_ZERO_ERROR
;
1009 int32_t size
= right
.vtzlines
->size();
1010 vtzlines
= new UVector(uhash_deleteUnicodeString
, uhash_compareUnicodeString
, size
, status
);
1011 if (U_SUCCESS(status
)) {
1012 for (int32_t i
= 0; i
< size
; i
++) {
1013 UnicodeString
*line
= (UnicodeString
*)right
.vtzlines
->elementAt(i
);
1014 vtzlines
->addElement(line
->clone(), status
);
1015 if (U_FAILURE(status
)) {
1020 if (U_FAILURE(status
) && vtzlines
!= NULL
) {
1025 tzurl
= right
.tzurl
;
1026 lastmod
= right
.lastmod
;
1027 olsonzid
= right
.olsonzid
;
1028 icutzver
= right
.icutzver
;
1034 VTimeZone::operator==(const TimeZone
& that
) const {
1035 if (this == &that
) {
1038 if (typeid(*this) != typeid(that
) || !BasicTimeZone::operator==(that
)) {
1041 VTimeZone
*vtz
= (VTimeZone
*)&that
;
1042 if (*tz
== *(vtz
->tz
)
1043 && tzurl
== vtz
->tzurl
1044 && lastmod
== vtz
->lastmod
1045 /* && olsonzid = that.olsonzid */
1046 /* && icutzver = that.icutzver */) {
1053 VTimeZone::operator!=(const TimeZone
& that
) const {
1054 return !operator==(that
);
1058 VTimeZone::createVTimeZoneByID(const UnicodeString
& ID
) {
1059 VTimeZone
*vtz
= new VTimeZone();
1060 vtz
->tz
= (BasicTimeZone
*)TimeZone::createTimeZone(ID
);
1061 vtz
->tz
->getID(vtz
->olsonzid
);
1063 // Set ICU tzdata version
1064 UErrorCode status
= U_ZERO_ERROR
;
1065 UResourceBundle
*bundle
= NULL
;
1066 const UChar
* versionStr
= NULL
;
1068 bundle
= ures_openDirect(NULL
, "zoneinfo64", &status
);
1069 versionStr
= ures_getStringByKey(bundle
, "TZVersion", &len
, &status
);
1070 if (U_SUCCESS(status
)) {
1071 vtz
->icutzver
.setTo(versionStr
, len
);
1078 VTimeZone::createVTimeZoneFromBasicTimeZone(const BasicTimeZone
& basic_time_zone
, UErrorCode
&status
) {
1079 if (U_FAILURE(status
)) {
1082 VTimeZone
*vtz
= new VTimeZone();
1084 status
= U_MEMORY_ALLOCATION_ERROR
;
1087 vtz
->tz
= (BasicTimeZone
*)basic_time_zone
.clone();
1088 if (vtz
->tz
== NULL
) {
1089 status
= U_MEMORY_ALLOCATION_ERROR
;
1093 vtz
->tz
->getID(vtz
->olsonzid
);
1095 // Set ICU tzdata version
1096 UResourceBundle
*bundle
= NULL
;
1097 const UChar
* versionStr
= NULL
;
1099 bundle
= ures_openDirect(NULL
, "zoneinfo64", &status
);
1100 versionStr
= ures_getStringByKey(bundle
, "TZVersion", &len
, &status
);
1101 if (U_SUCCESS(status
)) {
1102 vtz
->icutzver
.setTo(versionStr
, len
);
1109 VTimeZone::createVTimeZone(const UnicodeString
& vtzdata
, UErrorCode
& status
) {
1110 if (U_FAILURE(status
)) {
1113 VTZReader
reader(vtzdata
);
1114 VTimeZone
*vtz
= new VTimeZone();
1115 vtz
->load(reader
, status
);
1116 if (U_FAILURE(status
)) {
1124 VTimeZone::getTZURL(UnicodeString
& url
) const {
1125 if (tzurl
.length() > 0) {
1133 VTimeZone::setTZURL(const UnicodeString
& url
) {
1138 VTimeZone::getLastModified(UDate
& lastModified
) const {
1139 if (lastmod
!= MAX_MILLIS
) {
1140 lastModified
= lastmod
;
1147 VTimeZone::setLastModified(UDate lastModified
) {
1148 lastmod
= lastModified
;
1152 VTimeZone::write(UnicodeString
& result
, UErrorCode
& status
) const {
1154 VTZWriter
writer(result
);
1155 write(writer
, status
);
1159 VTimeZone::write(UDate start
, UnicodeString
& result
, UErrorCode
& status
) /*const*/ {
1161 VTZWriter
writer(result
);
1162 write(start
, writer
, status
);
1166 VTimeZone::writeSimple(UDate time
, UnicodeString
& result
, UErrorCode
& status
) /*const*/ {
1168 VTZWriter
writer(result
);
1169 writeSimple(time
, writer
, status
);
1173 VTimeZone::clone(void) const {
1174 return new VTimeZone(*this);
1178 VTimeZone::getOffset(uint8_t era
, int32_t year
, int32_t month
, int32_t day
,
1179 uint8_t dayOfWeek
, int32_t millis
, UErrorCode
& status
) const {
1180 return tz
->getOffset(era
, year
, month
, day
, dayOfWeek
, millis
, status
);
1184 VTimeZone::getOffset(uint8_t era
, int32_t year
, int32_t month
, int32_t day
,
1185 uint8_t dayOfWeek
, int32_t millis
,
1186 int32_t monthLength
, UErrorCode
& status
) const {
1187 return tz
->getOffset(era
, year
, month
, day
, dayOfWeek
, millis
, monthLength
, status
);
1191 VTimeZone::getOffset(UDate date
, UBool local
, int32_t& rawOffset
,
1192 int32_t& dstOffset
, UErrorCode
& status
) const {
1193 return tz
->getOffset(date
, local
, rawOffset
, dstOffset
, status
);
1197 VTimeZone::setRawOffset(int32_t offsetMillis
) {
1198 tz
->setRawOffset(offsetMillis
);
1202 VTimeZone::getRawOffset(void) const {
1203 return tz
->getRawOffset();
1207 VTimeZone::useDaylightTime(void) const {
1208 return tz
->useDaylightTime();
1212 VTimeZone::inDaylightTime(UDate date
, UErrorCode
& status
) const {
1213 return tz
->inDaylightTime(date
, status
);
1217 VTimeZone::hasSameRules(const TimeZone
& other
) const {
1218 return tz
->hasSameRules(other
);
1222 VTimeZone::getNextTransition(UDate base
, UBool inclusive
, TimeZoneTransition
& result
) /*const*/ {
1223 return tz
->getNextTransition(base
, inclusive
, result
);
1227 VTimeZone::getPreviousTransition(UDate base
, UBool inclusive
, TimeZoneTransition
& result
) /*const*/ {
1228 return tz
->getPreviousTransition(base
, inclusive
, result
);
1232 VTimeZone::countTransitionRules(UErrorCode
& status
) /*const*/ {
1233 return tz
->countTransitionRules(status
);
1237 VTimeZone::getTimeZoneRules(const InitialTimeZoneRule
*& initial
,
1238 const TimeZoneRule
* trsrules
[], int32_t& trscount
,
1239 UErrorCode
& status
) /*const*/ {
1240 tz
->getTimeZoneRules(initial
, trsrules
, trscount
, status
);
1244 VTimeZone::load(VTZReader
& reader
, UErrorCode
& status
) {
1245 vtzlines
= new UVector(uhash_deleteUnicodeString
, uhash_compareUnicodeString
, DEFAULT_VTIMEZONE_LINES
, status
);
1246 if (U_FAILURE(status
)) {
1250 UBool start
= FALSE
;
1251 UBool success
= FALSE
;
1255 UChar ch
= reader
.read();
1258 if (start
&& line
.startsWith(ICAL_END_VTIMEZONE
)) {
1259 vtzlines
->addElement(new UnicodeString(line
), status
);
1260 if (U_FAILURE(status
)) {
1261 goto cleanupVtzlines
;
1268 // CR, must be followed by LF according to the definition in RFC2445
1272 if (ch
!= 0x0009 && ch
!= 0x0020) {
1273 // NOT followed by TAB/SP -> new line
1275 if (line
.length() > 0) {
1276 vtzlines
->addElement(new UnicodeString(line
), status
);
1277 if (U_FAILURE(status
)) {
1278 goto cleanupVtzlines
;
1293 if (line
.startsWith(ICAL_END_VTIMEZONE
)) {
1294 vtzlines
->addElement(new UnicodeString(line
), status
);
1295 if (U_FAILURE(status
)) {
1296 goto cleanupVtzlines
;
1302 if (line
.startsWith(ICAL_BEGIN_VTIMEZONE
)) {
1303 vtzlines
->addElement(new UnicodeString(line
), status
);
1304 if (U_FAILURE(status
)) {
1305 goto cleanupVtzlines
;
1318 if (U_SUCCESS(status
)) {
1319 status
= U_INVALID_STATE_ERROR
;
1321 goto cleanupVtzlines
;
1332 #define INI 0 // Initial state
1333 #define VTZ 1 // In VTIMEZONE
1334 #define TZI 2 // In STANDARD or DAYLIGHT
1336 #define DEF_DSTSAVINGS (60*60*1000)
1337 #define DEF_TZSTARTTIME (0.0)
1340 VTimeZone::parse(UErrorCode
& status
) {
1341 if (U_FAILURE(status
)) {
1344 if (vtzlines
== NULL
|| vtzlines
->size() == 0) {
1345 status
= U_INVALID_STATE_ERROR
;
1348 InitialTimeZoneRule
*initialRule
= NULL
;
1349 RuleBasedTimeZone
*rbtz
= NULL
;
1354 int32_t state
= INI
;
1356 UBool dst
= FALSE
; // current zone type
1357 UnicodeString from
; // current zone from offset
1358 UnicodeString to
; // current zone offset
1359 UnicodeString zonename
; // current zone name
1360 UnicodeString dtstart
; // current zone starts
1361 UBool isRRULE
= FALSE
; // true if the rule is described by RRULE
1362 int32_t initialRawOffset
= 0; // initial offset
1363 int32_t initialDSTSavings
= 0; // initial offset
1364 UDate firstStart
= MAX_MILLIS
; // the earliest rule start time
1365 UnicodeString name
; // RFC2445 prop name
1366 UnicodeString value
; // RFC2445 prop value
1368 UVector
*dates
= NULL
; // list of RDATE or RRULE strings
1369 UVector
*rules
= NULL
; // list of TimeZoneRule instances
1371 int32_t finalRuleIdx
= -1;
1372 int32_t finalRuleCount
= 0;
1374 rules
= new UVector(status
);
1375 if (U_FAILURE(status
)) {
1378 // Set the deleter to remove TimeZoneRule vectors to avoid memory leaks due to unowned TimeZoneRules.
1379 rules
->setDeleter(deleteTimeZoneRule
);
1381 dates
= new UVector(uhash_deleteUnicodeString
, uhash_compareUnicodeString
, status
);
1382 if (U_FAILURE(status
)) {
1385 if (rules
== NULL
|| dates
== NULL
) {
1386 status
= U_MEMORY_ALLOCATION_ERROR
;
1390 for (n
= 0; n
< vtzlines
->size(); n
++) {
1391 UnicodeString
*line
= (UnicodeString
*)vtzlines
->elementAt(n
);
1392 int32_t valueSep
= line
->indexOf(COLON
);
1396 name
.setTo(*line
, 0, valueSep
);
1397 value
.setTo(*line
, valueSep
+ 1);
1401 if (name
.compare(ICAL_BEGIN
) == 0
1402 && value
.compare(ICAL_VTIMEZONE
) == 0) {
1408 if (name
.compare(ICAL_TZID
) == 0) {
1410 } else if (name
.compare(ICAL_TZURL
) == 0) {
1412 } else if (name
.compare(ICAL_LASTMOD
) == 0) {
1413 // Always in 'Z' format, so the offset argument for the parse method
1414 // can be any value.
1415 lastmod
= parseDateTimeString(value
, 0, status
);
1416 if (U_FAILURE(status
)) {
1419 } else if (name
.compare(ICAL_BEGIN
) == 0) {
1420 UBool isDST
= (value
.compare(ICAL_DAYLIGHT
) == 0);
1421 if (value
.compare(ICAL_STANDARD
) == 0 || isDST
) {
1422 // tzid must be ready at this point
1423 if (tzid
.length() == 0) {
1426 // initialize current zone properties
1427 if (dates
->size() != 0) {
1428 dates
->removeAllElements();
1437 // BEGIN property other than STANDARD/DAYLIGHT
1438 // must not be there.
1441 } else if (name
.compare(ICAL_END
) == 0) {
1446 if (name
.compare(ICAL_DTSTART
) == 0) {
1448 } else if (name
.compare(ICAL_TZNAME
) == 0) {
1450 } else if (name
.compare(ICAL_TZOFFSETFROM
) == 0) {
1452 } else if (name
.compare(ICAL_TZOFFSETTO
) == 0) {
1454 } else if (name
.compare(ICAL_RDATE
) == 0) {
1455 // RDATE mixed with RRULE is not supported
1459 // RDATE value may contain multiple date delimited
1461 UBool nextDate
= TRUE
;
1463 UnicodeString
*dstr
;
1465 int32_t dend
= value
.indexOf(COMMA
, dstart
);
1467 dstr
= new UnicodeString(value
, dstart
);
1470 dstr
= new UnicodeString(value
, dstart
, dend
- dstart
);
1472 dates
->addElement(dstr
, status
);
1473 if (U_FAILURE(status
)) {
1478 } else if (name
.compare(ICAL_RRULE
) == 0) {
1479 // RRULE mixed with RDATE is not supported
1480 if (!isRRULE
&& dates
->size() != 0) {
1484 dates
->addElement(new UnicodeString(value
), status
);
1485 if (U_FAILURE(status
)) {
1488 } else if (name
.compare(ICAL_END
) == 0) {
1489 // Mandatory properties
1490 if (dtstart
.length() == 0 || from
.length() == 0 || to
.length() == 0) {
1493 // if zonename is not available, create one from tzid
1494 if (zonename
.length() == 0) {
1495 getDefaultTZName(tzid
, dst
, zonename
);
1498 // create a time zone rule
1499 TimeZoneRule
*rule
= NULL
;
1500 int32_t fromOffset
= 0;
1501 int32_t toOffset
= 0;
1502 int32_t rawOffset
= 0;
1503 int32_t dstSavings
= 0;
1506 // Parse TZOFFSETFROM/TZOFFSETTO
1507 fromOffset
= offsetStrToMillis(from
, status
);
1508 toOffset
= offsetStrToMillis(to
, status
);
1509 if (U_FAILURE(status
)) {
1514 // If daylight, use the previous offset as rawoffset if positive
1515 if (toOffset
- fromOffset
> 0) {
1516 rawOffset
= fromOffset
;
1517 dstSavings
= toOffset
- fromOffset
;
1519 // This is rare case.. just use 1 hour DST savings
1520 rawOffset
= toOffset
- DEF_DSTSAVINGS
;
1521 dstSavings
= DEF_DSTSAVINGS
;
1524 rawOffset
= toOffset
;
1529 start
= parseDateTimeString(dtstart
, fromOffset
, status
);
1530 if (U_FAILURE(status
)) {
1535 UDate actualStart
= MAX_MILLIS
;
1537 rule
= createRuleByRRULE(zonename
, rawOffset
, dstSavings
, start
, dates
, fromOffset
, status
);
1539 rule
= createRuleByRDATE(zonename
, rawOffset
, dstSavings
, start
, dates
, fromOffset
, status
);
1541 if (U_FAILURE(status
) || rule
== NULL
) {
1544 UBool startAvail
= rule
->getFirstStart(fromOffset
, 0, actualStart
);
1545 if (startAvail
&& actualStart
< firstStart
) {
1546 // save from offset information for the earliest rule
1547 firstStart
= actualStart
;
1548 // If this is STD, assume the time before this transtion
1549 // is DST when the difference is 1 hour. This might not be
1550 // accurate, but VTIMEZONE data does not have such info.
1551 if (dstSavings
> 0) {
1552 initialRawOffset
= fromOffset
;
1553 initialDSTSavings
= 0;
1555 if (fromOffset
- toOffset
== DEF_DSTSAVINGS
) {
1556 initialRawOffset
= fromOffset
- DEF_DSTSAVINGS
;
1557 initialDSTSavings
= DEF_DSTSAVINGS
;
1559 initialRawOffset
= fromOffset
;
1560 initialDSTSavings
= 0;
1565 rules
->addElement(rule
, status
);
1566 if (U_FAILURE(status
)) {
1574 // Must have at least one rule
1575 if (rules
->size() == 0) {
1579 // Create a initial rule
1580 getDefaultTZName(tzid
, FALSE
, zonename
);
1581 initialRule
= new InitialTimeZoneRule(zonename
,
1582 initialRawOffset
, initialDSTSavings
);
1583 if (initialRule
== NULL
) {
1584 status
= U_MEMORY_ALLOCATION_ERROR
;
1588 // Finally, create the RuleBasedTimeZone
1589 rbtz
= new RuleBasedTimeZone(tzid
, initialRule
);
1591 status
= U_MEMORY_ALLOCATION_ERROR
;
1594 initialRule
= NULL
; // already adopted by RBTZ, no need to delete
1596 for (n
= 0; n
< rules
->size(); n
++) {
1597 TimeZoneRule
*r
= (TimeZoneRule
*)rules
->elementAt(n
);
1598 AnnualTimeZoneRule
*atzrule
= dynamic_cast<AnnualTimeZoneRule
*>(r
);
1599 if (atzrule
!= NULL
) {
1600 if (atzrule
->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
) {
1606 if (finalRuleCount
> 2) {
1607 // Too many final rules
1608 status
= U_ILLEGAL_ARGUMENT_ERROR
;
1612 if (finalRuleCount
== 1) {
1613 if (rules
->size() == 1) {
1614 // Only one final rule, only governs the initial rule,
1615 // which is already initialized, thus, we do not need to
1616 // add this transition rule
1617 rules
->removeAllElements();
1619 // Normalize the final rule
1620 AnnualTimeZoneRule
*finalRule
= (AnnualTimeZoneRule
*)rules
->elementAt(finalRuleIdx
);
1621 int32_t tmpRaw
= finalRule
->getRawOffset();
1622 int32_t tmpDST
= finalRule
->getDSTSavings();
1624 // Find the last non-final rule
1625 UDate finalStart
, start
;
1626 finalRule
->getFirstStart(initialRawOffset
, initialDSTSavings
, finalStart
);
1628 for (n
= 0; n
< rules
->size(); n
++) {
1629 if (finalRuleIdx
== n
) {
1632 TimeZoneRule
*r
= (TimeZoneRule
*)rules
->elementAt(n
);
1634 r
->getFinalStart(tmpRaw
, tmpDST
, lastStart
);
1635 if (lastStart
> start
) {
1636 finalRule
->getNextStart(lastStart
,
1644 TimeZoneRule
*newRule
;
1645 UnicodeString tznam
;
1646 if (start
== finalStart
) {
1647 // Transform this into a single transition
1648 newRule
= new TimeArrayTimeZoneRule(
1649 finalRule
->getName(tznam
),
1650 finalRule
->getRawOffset(),
1651 finalRule
->getDSTSavings(),
1654 DateTimeRule::UTC_TIME
);
1656 // Update the end year
1657 int32_t y
, m
, d
, dow
, doy
, mid
;
1658 Grego::timeToFields(start
, y
, m
, d
, dow
, doy
, mid
);
1659 newRule
= new AnnualTimeZoneRule(
1660 finalRule
->getName(tznam
),
1661 finalRule
->getRawOffset(),
1662 finalRule
->getDSTSavings(),
1663 *(finalRule
->getRule()),
1664 finalRule
->getStartYear(),
1667 if (newRule
== NULL
) {
1668 status
= U_MEMORY_ALLOCATION_ERROR
;
1671 rules
->removeElementAt(finalRuleIdx
);
1672 rules
->addElement(newRule
, status
);
1673 if (U_FAILURE(status
)) {
1680 while (!rules
->isEmpty()) {
1681 TimeZoneRule
*tzr
= (TimeZoneRule
*)rules
->orphanElementAt(0);
1682 rbtz
->addTransitionRule(tzr
, status
);
1683 if (U_FAILURE(status
)) {
1687 rbtz
->complete(status
);
1688 if (U_FAILURE(status
)) {
1699 if (rules
!= NULL
) {
1700 while (!rules
->isEmpty()) {
1701 TimeZoneRule
*r
= (TimeZoneRule
*)rules
->orphanElementAt(0);
1706 if (dates
!= NULL
) {
1709 if (initialRule
!= NULL
) {
1719 VTimeZone::write(VTZWriter
& writer
, UErrorCode
& status
) const {
1720 if (vtzlines
!= NULL
) {
1721 for (int32_t i
= 0; i
< vtzlines
->size(); i
++) {
1722 UnicodeString
*line
= (UnicodeString
*)vtzlines
->elementAt(i
);
1723 if (line
->startsWith(ICAL_TZURL
)
1724 && line
->charAt(u_strlen(ICAL_TZURL
)) == COLON
) {
1725 writer
.write(ICAL_TZURL
);
1726 writer
.write(COLON
);
1727 writer
.write(tzurl
);
1728 writer
.write(ICAL_NEWLINE
);
1729 } else if (line
->startsWith(ICAL_LASTMOD
)
1730 && line
->charAt(u_strlen(ICAL_LASTMOD
)) == COLON
) {
1731 UnicodeString utcString
;
1732 writer
.write(ICAL_LASTMOD
);
1733 writer
.write(COLON
);
1734 writer
.write(getUTCDateTimeString(lastmod
, utcString
));
1735 writer
.write(ICAL_NEWLINE
);
1737 writer
.write(*line
);
1738 writer
.write(ICAL_NEWLINE
);
1742 UVector
*customProps
= NULL
;
1743 if (olsonzid
.length() > 0 && icutzver
.length() > 0) {
1744 customProps
= new UVector(uhash_deleteUnicodeString
, uhash_compareUnicodeString
, status
);
1745 if (U_FAILURE(status
)) {
1748 UnicodeString
*icutzprop
= new UnicodeString(ICU_TZINFO_PROP
);
1749 icutzprop
->append(olsonzid
);
1750 icutzprop
->append((UChar
)0x005B/*'['*/);
1751 icutzprop
->append(icutzver
);
1752 icutzprop
->append((UChar
)0x005D/*']'*/);
1753 customProps
->addElement(icutzprop
, status
);
1754 if (U_FAILURE(status
)) {
1760 writeZone(writer
, *tz
, customProps
, status
);
1766 VTimeZone::write(UDate start
, VTZWriter
& writer
, UErrorCode
& status
) /*const*/ {
1767 if (U_FAILURE(status
)) {
1770 InitialTimeZoneRule
*initial
= NULL
;
1771 UVector
*transitionRules
= NULL
;
1772 UVector
customProps(uhash_deleteUnicodeString
, uhash_compareUnicodeString
, status
);
1775 // Extract rules applicable to dates after the start time
1776 getTimeZoneRulesAfter(start
, initial
, transitionRules
, status
);
1777 if (U_FAILURE(status
)) {
1781 // Create a RuleBasedTimeZone with the subset rule
1783 RuleBasedTimeZone
rbtz(tzid
, initial
);
1784 if (transitionRules
!= NULL
) {
1785 while (!transitionRules
->isEmpty()) {
1786 TimeZoneRule
*tr
= (TimeZoneRule
*)transitionRules
->orphanElementAt(0);
1787 rbtz
.addTransitionRule(tr
, status
);
1788 if (U_FAILURE(status
)) {
1789 goto cleanupWritePartial
;
1792 delete transitionRules
;
1793 transitionRules
= NULL
;
1795 rbtz
.complete(status
);
1796 if (U_FAILURE(status
)) {
1797 goto cleanupWritePartial
;
1800 if (olsonzid
.length() > 0 && icutzver
.length() > 0) {
1801 UnicodeString
*icutzprop
= new UnicodeString(ICU_TZINFO_PROP
);
1802 icutzprop
->append(olsonzid
);
1803 icutzprop
->append((UChar
)0x005B/*'['*/);
1804 icutzprop
->append(icutzver
);
1805 icutzprop
->append(ICU_TZINFO_PARTIAL
);
1806 appendMillis(start
, *icutzprop
);
1807 icutzprop
->append((UChar
)0x005D/*']'*/);
1808 customProps
.addElement(icutzprop
, status
);
1809 if (U_FAILURE(status
)) {
1811 goto cleanupWritePartial
;
1814 writeZone(writer
, rbtz
, &customProps
, status
);
1817 cleanupWritePartial
:
1818 if (initial
!= NULL
) {
1821 if (transitionRules
!= NULL
) {
1822 while (!transitionRules
->isEmpty()) {
1823 TimeZoneRule
*tr
= (TimeZoneRule
*)transitionRules
->orphanElementAt(0);
1826 delete transitionRules
;
1831 VTimeZone::writeSimple(UDate time
, VTZWriter
& writer
, UErrorCode
& status
) /*const*/ {
1832 if (U_FAILURE(status
)) {
1836 UVector
customProps(uhash_deleteUnicodeString
, uhash_compareUnicodeString
, status
);
1839 // Extract simple rules
1840 InitialTimeZoneRule
*initial
= NULL
;
1841 AnnualTimeZoneRule
*std
= NULL
, *dst
= NULL
;
1842 getSimpleRulesNear(time
, initial
, std
, dst
, status
);
1843 if (U_SUCCESS(status
)) {
1844 // Create a RuleBasedTimeZone with the subset rule
1846 RuleBasedTimeZone
rbtz(tzid
, initial
);
1847 if (std
!= NULL
&& dst
!= NULL
) {
1848 rbtz
.addTransitionRule(std
, status
);
1849 rbtz
.addTransitionRule(dst
, status
);
1851 if (U_FAILURE(status
)) {
1852 goto cleanupWriteSimple
;
1855 if (olsonzid
.length() > 0 && icutzver
.length() > 0) {
1856 UnicodeString
*icutzprop
= new UnicodeString(ICU_TZINFO_PROP
);
1857 icutzprop
->append(olsonzid
);
1858 icutzprop
->append((UChar
)0x005B/*'['*/);
1859 icutzprop
->append(icutzver
);
1860 icutzprop
->append(ICU_TZINFO_SIMPLE
);
1861 appendMillis(time
, *icutzprop
);
1862 icutzprop
->append((UChar
)0x005D/*']'*/);
1863 customProps
.addElement(icutzprop
, status
);
1864 if (U_FAILURE(status
)) {
1866 goto cleanupWriteSimple
;
1869 writeZone(writer
, rbtz
, &customProps
, status
);
1874 if (initial
!= NULL
) {
1886 VTimeZone::writeZone(VTZWriter
& w
, BasicTimeZone
& basictz
,
1887 UVector
* customProps
, UErrorCode
& status
) const {
1888 if (U_FAILURE(status
)) {
1891 writeHeaders(w
, status
);
1892 if (U_FAILURE(status
)) {
1896 if (customProps
!= NULL
) {
1897 for (int32_t i
= 0; i
< customProps
->size(); i
++) {
1898 UnicodeString
*custprop
= (UnicodeString
*)customProps
->elementAt(i
);
1900 w
.write(ICAL_NEWLINE
);
1904 UDate t
= MIN_MILLIS
;
1905 UnicodeString dstName
;
1906 int32_t dstFromOffset
= 0;
1907 int32_t dstFromDSTSavings
= 0;
1908 int32_t dstToOffset
= 0;
1909 int32_t dstStartYear
= 0;
1910 int32_t dstMonth
= 0;
1911 int32_t dstDayOfWeek
= 0;
1912 int32_t dstWeekInMonth
= 0;
1913 int32_t dstMillisInDay
= 0;
1914 UDate dstStartTime
= 0.0;
1915 UDate dstUntilTime
= 0.0;
1916 int32_t dstCount
= 0;
1917 AnnualTimeZoneRule
*finalDstRule
= NULL
;
1919 UnicodeString stdName
;
1920 int32_t stdFromOffset
= 0;
1921 int32_t stdFromDSTSavings
= 0;
1922 int32_t stdToOffset
= 0;
1923 int32_t stdStartYear
= 0;
1924 int32_t stdMonth
= 0;
1925 int32_t stdDayOfWeek
= 0;
1926 int32_t stdWeekInMonth
= 0;
1927 int32_t stdMillisInDay
= 0;
1928 UDate stdStartTime
= 0.0;
1929 UDate stdUntilTime
= 0.0;
1930 int32_t stdCount
= 0;
1931 AnnualTimeZoneRule
*finalStdRule
= NULL
;
1933 int32_t year
, month
, dom
, dow
, doy
, mid
;
1934 UBool hasTransitions
= FALSE
;
1935 TimeZoneTransition tzt
;
1940 // Going through all transitions
1942 tztAvail
= basictz
.getNextTransition(t
, FALSE
, tzt
);
1946 hasTransitions
= TRUE
;
1948 tzt
.getTo()->getName(name
);
1949 isDst
= (tzt
.getTo()->getDSTSavings() != 0);
1950 int32_t fromOffset
= tzt
.getFrom()->getRawOffset() + tzt
.getFrom()->getDSTSavings();
1951 int32_t fromDSTSavings
= tzt
.getFrom()->getDSTSavings();
1952 int32_t toOffset
= tzt
.getTo()->getRawOffset() + tzt
.getTo()->getDSTSavings();
1953 Grego::timeToFields(tzt
.getTime() + fromOffset
, year
, month
, dom
, dow
, doy
, mid
);
1954 int32_t weekInMonth
= Grego::dayOfWeekInMonth(year
, month
, dom
);
1955 UBool sameRule
= FALSE
;
1956 const AnnualTimeZoneRule
*atzrule
;
1958 if (finalDstRule
== NULL
1959 && (atzrule
= dynamic_cast<const AnnualTimeZoneRule
*>(tzt
.getTo())) != NULL
1960 && atzrule
->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
1962 finalDstRule
= (AnnualTimeZoneRule
*)tzt
.getTo()->clone();
1965 if (year
== dstStartYear
+ dstCount
1966 && name
.compare(dstName
) == 0
1967 && dstFromOffset
== fromOffset
1968 && dstToOffset
== toOffset
1969 && dstMonth
== month
1970 && dstDayOfWeek
== dow
1971 && dstWeekInMonth
== weekInMonth
1972 && dstMillisInDay
== mid
) {
1973 // Update until time
1979 if (dstCount
== 1) {
1980 writeZonePropsByTime(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
, dstStartTime
,
1983 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
1984 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, dstUntilTime
, status
);
1986 if (U_FAILURE(status
)) {
1987 goto cleanupWriteZone
;
1992 // Reset this DST information
1994 dstFromOffset
= fromOffset
;
1995 dstFromDSTSavings
= fromDSTSavings
;
1996 dstToOffset
= toOffset
;
1997 dstStartYear
= year
;
2000 dstWeekInMonth
= weekInMonth
;
2001 dstMillisInDay
= mid
;
2002 dstStartTime
= dstUntilTime
= t
;
2005 if (finalStdRule
!= NULL
&& finalDstRule
!= NULL
) {
2009 if (finalStdRule
== NULL
2010 && (atzrule
= dynamic_cast<const AnnualTimeZoneRule
*>(tzt
.getTo())) != NULL
2011 && atzrule
->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
2013 finalStdRule
= (AnnualTimeZoneRule
*)tzt
.getTo()->clone();
2016 if (year
== stdStartYear
+ stdCount
2017 && name
.compare(stdName
) == 0
2018 && stdFromOffset
== fromOffset
2019 && stdToOffset
== toOffset
2020 && stdMonth
== month
2021 && stdDayOfWeek
== dow
2022 && stdWeekInMonth
== weekInMonth
2023 && stdMillisInDay
== mid
) {
2024 // Update until time
2030 if (stdCount
== 1) {
2031 writeZonePropsByTime(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
, stdStartTime
,
2034 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2035 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, stdUntilTime
, status
);
2037 if (U_FAILURE(status
)) {
2038 goto cleanupWriteZone
;
2043 // Reset this STD information
2045 stdFromOffset
= fromOffset
;
2046 stdFromDSTSavings
= fromDSTSavings
;
2047 stdToOffset
= toOffset
;
2048 stdStartYear
= year
;
2051 stdWeekInMonth
= weekInMonth
;
2052 stdMillisInDay
= mid
;
2053 stdStartTime
= stdUntilTime
= t
;
2056 if (finalStdRule
!= NULL
&& finalDstRule
!= NULL
) {
2061 if (!hasTransitions
) {
2062 // No transition - put a single non transition RDATE
2063 int32_t raw
, dst
, offset
;
2064 basictz
.getOffset(0.0/*any time*/, FALSE
, raw
, dst
, status
);
2065 if (U_FAILURE(status
)) {
2066 goto cleanupWriteZone
;
2071 basictz
.getID(tzid
);
2072 getDefaultTZName(tzid
, isDst
, name
);
2073 writeZonePropsByTime(w
, isDst
, name
,
2074 offset
, offset
, DEF_TZSTARTTIME
- offset
, FALSE
, status
);
2075 if (U_FAILURE(status
)) {
2076 goto cleanupWriteZone
;
2080 if (finalDstRule
== NULL
) {
2081 if (dstCount
== 1) {
2082 writeZonePropsByTime(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
, dstStartTime
,
2085 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
2086 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, dstUntilTime
, status
);
2088 if (U_FAILURE(status
)) {
2089 goto cleanupWriteZone
;
2092 if (dstCount
== 1) {
2093 writeFinalRule(w
, TRUE
, finalDstRule
,
2094 dstFromOffset
- dstFromDSTSavings
, dstFromDSTSavings
, dstStartTime
, status
);
2096 // Use a single rule if possible
2097 if (isEquivalentDateRule(dstMonth
, dstWeekInMonth
, dstDayOfWeek
, finalDstRule
->getRule())) {
2098 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
2099 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, MAX_MILLIS
, status
);
2101 // Not equivalent rule - write out two different rules
2102 writeZonePropsByDOW(w
, TRUE
, dstName
, dstFromOffset
, dstToOffset
,
2103 dstMonth
, dstWeekInMonth
, dstDayOfWeek
, dstStartTime
, dstUntilTime
, status
);
2104 if (U_FAILURE(status
)) {
2105 goto cleanupWriteZone
;
2107 writeFinalRule(w
, TRUE
, finalDstRule
,
2108 dstFromOffset
- dstFromDSTSavings
, dstFromDSTSavings
, dstStartTime
, status
);
2111 if (U_FAILURE(status
)) {
2112 goto cleanupWriteZone
;
2117 if (finalStdRule
== NULL
) {
2118 if (stdCount
== 1) {
2119 writeZonePropsByTime(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
, stdStartTime
,
2122 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2123 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, stdUntilTime
, status
);
2125 if (U_FAILURE(status
)) {
2126 goto cleanupWriteZone
;
2129 if (stdCount
== 1) {
2130 writeFinalRule(w
, FALSE
, finalStdRule
,
2131 stdFromOffset
- stdFromDSTSavings
, stdFromDSTSavings
, stdStartTime
, status
);
2133 // Use a single rule if possible
2134 if (isEquivalentDateRule(stdMonth
, stdWeekInMonth
, stdDayOfWeek
, finalStdRule
->getRule())) {
2135 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2136 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, MAX_MILLIS
, status
);
2138 // Not equivalent rule - write out two different rules
2139 writeZonePropsByDOW(w
, FALSE
, stdName
, stdFromOffset
, stdToOffset
,
2140 stdMonth
, stdWeekInMonth
, stdDayOfWeek
, stdStartTime
, stdUntilTime
, status
);
2141 if (U_FAILURE(status
)) {
2142 goto cleanupWriteZone
;
2144 writeFinalRule(w
, FALSE
, finalStdRule
,
2145 stdFromOffset
- stdFromDSTSavings
, stdFromDSTSavings
, stdStartTime
, status
);
2148 if (U_FAILURE(status
)) {
2149 goto cleanupWriteZone
;
2154 writeFooter(w
, status
);
2158 if (finalStdRule
!= NULL
) {
2159 delete finalStdRule
;
2161 if (finalDstRule
!= NULL
) {
2162 delete finalDstRule
;
2167 VTimeZone::writeHeaders(VTZWriter
& writer
, UErrorCode
& status
) const {
2168 if (U_FAILURE(status
)) {
2174 writer
.write(ICAL_BEGIN
);
2175 writer
.write(COLON
);
2176 writer
.write(ICAL_VTIMEZONE
);
2177 writer
.write(ICAL_NEWLINE
);
2178 writer
.write(ICAL_TZID
);
2179 writer
.write(COLON
);
2181 writer
.write(ICAL_NEWLINE
);
2182 if (tzurl
.length() != 0) {
2183 writer
.write(ICAL_TZURL
);
2184 writer
.write(COLON
);
2185 writer
.write(tzurl
);
2186 writer
.write(ICAL_NEWLINE
);
2188 if (lastmod
!= MAX_MILLIS
) {
2189 UnicodeString lastmodStr
;
2190 writer
.write(ICAL_LASTMOD
);
2191 writer
.write(COLON
);
2192 writer
.write(getUTCDateTimeString(lastmod
, lastmodStr
));
2193 writer
.write(ICAL_NEWLINE
);
2198 * Write the closing section of the VTIMEZONE definition block
2201 VTimeZone::writeFooter(VTZWriter
& writer
, UErrorCode
& status
) const {
2202 if (U_FAILURE(status
)) {
2205 writer
.write(ICAL_END
);
2206 writer
.write(COLON
);
2207 writer
.write(ICAL_VTIMEZONE
);
2208 writer
.write(ICAL_NEWLINE
);
2212 * Write a single start time
2215 VTimeZone::writeZonePropsByTime(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2216 int32_t fromOffset
, int32_t toOffset
, UDate time
, UBool withRDATE
,
2217 UErrorCode
& status
) const {
2218 if (U_FAILURE(status
)) {
2221 beginZoneProps(writer
, isDst
, zonename
, fromOffset
, toOffset
, time
, status
);
2222 if (U_FAILURE(status
)) {
2226 writer
.write(ICAL_RDATE
);
2227 writer
.write(COLON
);
2228 UnicodeString timestr
;
2229 writer
.write(getDateTimeString(time
+ fromOffset
, timestr
));
2230 writer
.write(ICAL_NEWLINE
);
2232 endZoneProps(writer
, isDst
, status
);
2233 if (U_FAILURE(status
)) {
2239 * Write start times defined by a DOM rule using VTIMEZONE RRULE
2242 VTimeZone::writeZonePropsByDOM(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2243 int32_t fromOffset
, int32_t toOffset
,
2244 int32_t month
, int32_t dayOfMonth
, UDate startTime
, UDate untilTime
,
2245 UErrorCode
& status
) const {
2246 if (U_FAILURE(status
)) {
2249 beginZoneProps(writer
, isDst
, zonename
, fromOffset
, toOffset
, startTime
, status
);
2250 if (U_FAILURE(status
)) {
2253 beginRRULE(writer
, month
, status
);
2254 if (U_FAILURE(status
)) {
2257 writer
.write(ICAL_BYMONTHDAY
);
2258 writer
.write(EQUALS_SIGN
);
2260 appendAsciiDigits(dayOfMonth
, 0, dstr
);
2262 if (untilTime
!= MAX_MILLIS
) {
2263 appendUNTIL(writer
, getDateTimeString(untilTime
+ fromOffset
, dstr
), status
);
2264 if (U_FAILURE(status
)) {
2268 writer
.write(ICAL_NEWLINE
);
2269 endZoneProps(writer
, isDst
, status
);
2273 * Write start times defined by a DOW rule using VTIMEZONE RRULE
2276 VTimeZone::writeZonePropsByDOW(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2277 int32_t fromOffset
, int32_t toOffset
,
2278 int32_t month
, int32_t weekInMonth
, int32_t dayOfWeek
,
2279 UDate startTime
, UDate untilTime
, UErrorCode
& status
) const {
2280 if (U_FAILURE(status
)) {
2283 beginZoneProps(writer
, isDst
, zonename
, fromOffset
, toOffset
, startTime
, status
);
2284 if (U_FAILURE(status
)) {
2287 beginRRULE(writer
, month
, status
);
2288 if (U_FAILURE(status
)) {
2291 writer
.write(ICAL_BYDAY
);
2292 writer
.write(EQUALS_SIGN
);
2294 appendAsciiDigits(weekInMonth
, 0, dstr
);
2295 writer
.write(dstr
); // -4, -3, -2, -1, 1, 2, 3, 4
2296 writer
.write(ICAL_DOW_NAMES
[dayOfWeek
- 1]); // SU, MO, TU...
2298 if (untilTime
!= MAX_MILLIS
) {
2299 appendUNTIL(writer
, getDateTimeString(untilTime
+ fromOffset
, dstr
), status
);
2300 if (U_FAILURE(status
)) {
2304 writer
.write(ICAL_NEWLINE
);
2305 endZoneProps(writer
, isDst
, status
);
2309 * Write start times defined by a DOW_GEQ_DOM rule using VTIMEZONE RRULE
2312 VTimeZone::writeZonePropsByDOW_GEQ_DOM(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2313 int32_t fromOffset
, int32_t toOffset
,
2314 int32_t month
, int32_t dayOfMonth
, int32_t dayOfWeek
,
2315 UDate startTime
, UDate untilTime
, UErrorCode
& status
) const {
2316 if (U_FAILURE(status
)) {
2319 // Check if this rule can be converted to DOW rule
2320 if (dayOfMonth%7
== 1) {
2321 // Can be represented by DOW rule
2322 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2323 month
, (dayOfMonth
+ 6)/7, dayOfWeek
, startTime
, untilTime
, status
);
2324 if (U_FAILURE(status
)) {
2327 } else if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - dayOfMonth
)%7
== 6) {
2328 // Can be represented by DOW rule with negative week number
2329 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2330 month
, -1*((MONTHLENGTH
[month
] - dayOfMonth
+ 1)/7), dayOfWeek
, startTime
, untilTime
, status
);
2331 if (U_FAILURE(status
)) {
2335 // Otherwise, use BYMONTHDAY to include all possible dates
2336 beginZoneProps(writer
, isDst
, zonename
, fromOffset
, toOffset
, startTime
, status
);
2337 if (U_FAILURE(status
)) {
2340 // Check if all days are in the same month
2341 int32_t startDay
= dayOfMonth
;
2342 int32_t currentMonthDays
= 7;
2344 if (dayOfMonth
<= 0) {
2345 // The start day is in previous month
2346 int32_t prevMonthDays
= 1 - dayOfMonth
;
2347 currentMonthDays
-= prevMonthDays
;
2349 int32_t prevMonth
= (month
- 1) < 0 ? 11 : month
- 1;
2351 // Note: When a rule is separated into two, UNTIL attribute needs to be
2352 // calculated for each of them. For now, we skip this, because we basically use this method
2353 // only for final rules, which does not have the UNTIL attribute
2354 writeZonePropsByDOW_GEQ_DOM_sub(writer
, prevMonth
, -prevMonthDays
, dayOfWeek
, prevMonthDays
,
2355 MAX_MILLIS
/* Do not use UNTIL */, fromOffset
, status
);
2356 if (U_FAILURE(status
)) {
2360 // Start from 1 for the rest
2362 } else if (dayOfMonth
+ 6 > MONTHLENGTH
[month
]) {
2363 // Note: This code does not actually work well in February. For now, days in month in
2365 int32_t nextMonthDays
= dayOfMonth
+ 6 - MONTHLENGTH
[month
];
2366 currentMonthDays
-= nextMonthDays
;
2368 int32_t nextMonth
= (month
+ 1) > 11 ? 0 : month
+ 1;
2370 writeZonePropsByDOW_GEQ_DOM_sub(writer
, nextMonth
, 1, dayOfWeek
, nextMonthDays
,
2371 MAX_MILLIS
/* Do not use UNTIL */, fromOffset
, status
);
2372 if (U_FAILURE(status
)) {
2376 writeZonePropsByDOW_GEQ_DOM_sub(writer
, month
, startDay
, dayOfWeek
, currentMonthDays
,
2377 untilTime
, fromOffset
, status
);
2378 if (U_FAILURE(status
)) {
2381 endZoneProps(writer
, isDst
, status
);
2386 * Called from writeZonePropsByDOW_GEQ_DOM
2389 VTimeZone::writeZonePropsByDOW_GEQ_DOM_sub(VTZWriter
& writer
, int32_t month
, int32_t dayOfMonth
,
2390 int32_t dayOfWeek
, int32_t numDays
,
2391 UDate untilTime
, int32_t fromOffset
, UErrorCode
& status
) const {
2393 if (U_FAILURE(status
)) {
2396 int32_t startDayNum
= dayOfMonth
;
2397 UBool isFeb
= (month
== UCAL_FEBRUARY
);
2398 if (dayOfMonth
< 0 && !isFeb
) {
2399 // Use positive number if possible
2400 startDayNum
= MONTHLENGTH
[month
] + dayOfMonth
+ 1;
2402 beginRRULE(writer
, month
, status
);
2403 if (U_FAILURE(status
)) {
2406 writer
.write(ICAL_BYDAY
);
2407 writer
.write(EQUALS_SIGN
);
2408 writer
.write(ICAL_DOW_NAMES
[dayOfWeek
- 1]); // SU, MO, TU...
2409 writer
.write(SEMICOLON
);
2410 writer
.write(ICAL_BYMONTHDAY
);
2411 writer
.write(EQUALS_SIGN
);
2414 appendAsciiDigits(startDayNum
, 0, dstr
);
2416 for (int32_t i
= 1; i
< numDays
; i
++) {
2417 writer
.write(COMMA
);
2419 appendAsciiDigits(startDayNum
+ i
, 0, dstr
);
2423 if (untilTime
!= MAX_MILLIS
) {
2424 appendUNTIL(writer
, getDateTimeString(untilTime
+ fromOffset
, dstr
), status
);
2425 if (U_FAILURE(status
)) {
2429 writer
.write(ICAL_NEWLINE
);
2433 * Write start times defined by a DOW_LEQ_DOM rule using VTIMEZONE RRULE
2436 VTimeZone::writeZonePropsByDOW_LEQ_DOM(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2437 int32_t fromOffset
, int32_t toOffset
,
2438 int32_t month
, int32_t dayOfMonth
, int32_t dayOfWeek
,
2439 UDate startTime
, UDate untilTime
, UErrorCode
& status
) const {
2440 if (U_FAILURE(status
)) {
2443 // Check if this rule can be converted to DOW rule
2444 if (dayOfMonth%7
== 0) {
2445 // Can be represented by DOW rule
2446 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2447 month
, dayOfMonth
/7, dayOfWeek
, startTime
, untilTime
, status
);
2448 } else if (month
!= UCAL_FEBRUARY
&& (MONTHLENGTH
[month
] - dayOfMonth
)%7
== 0){
2449 // Can be represented by DOW rule with negative week number
2450 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2451 month
, -1*((MONTHLENGTH
[month
] - dayOfMonth
)/7 + 1), dayOfWeek
, startTime
, untilTime
, status
);
2452 } else if (month
== UCAL_FEBRUARY
&& dayOfMonth
== 29) {
2453 // Specical case for February
2454 writeZonePropsByDOW(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2455 UCAL_FEBRUARY
, -1, dayOfWeek
, startTime
, untilTime
, status
);
2457 // Otherwise, convert this to DOW_GEQ_DOM rule
2458 writeZonePropsByDOW_GEQ_DOM(writer
, isDst
, zonename
, fromOffset
, toOffset
,
2459 month
, dayOfMonth
- 6, dayOfWeek
, startTime
, untilTime
, status
);
2464 * Write the final time zone rule using RRULE, with no UNTIL attribute
2467 VTimeZone::writeFinalRule(VTZWriter
& writer
, UBool isDst
, const AnnualTimeZoneRule
* rule
,
2468 int32_t fromRawOffset
, int32_t fromDSTSavings
,
2469 UDate startTime
, UErrorCode
& status
) const {
2470 if (U_FAILURE(status
)) {
2473 UBool modifiedRule
= TRUE
;
2474 const DateTimeRule
*dtrule
= toWallTimeRule(rule
->getRule(), fromRawOffset
, fromDSTSavings
);
2475 if (dtrule
== NULL
) {
2476 modifiedRule
= FALSE
;
2477 dtrule
= rule
->getRule();
2480 // If the rule's mills in a day is out of range, adjust start time.
2481 // Olson tzdata supports 24:00 of a day, but VTIMEZONE does not.
2482 // See ticket#7008/#7518
2484 int32_t timeInDay
= dtrule
->getRuleMillisInDay();
2485 if (timeInDay
< 0) {
2486 startTime
= startTime
+ (0 - timeInDay
);
2487 } else if (timeInDay
>= U_MILLIS_PER_DAY
) {
2488 startTime
= startTime
- (timeInDay
- (U_MILLIS_PER_DAY
- 1));
2491 int32_t toOffset
= rule
->getRawOffset() + rule
->getDSTSavings();
2493 rule
->getName(name
);
2494 switch (dtrule
->getDateRuleType()) {
2495 case DateTimeRule::DOM
:
2496 writeZonePropsByDOM(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2497 dtrule
->getRuleMonth(), dtrule
->getRuleDayOfMonth(), startTime
, MAX_MILLIS
, status
);
2499 case DateTimeRule::DOW
:
2500 writeZonePropsByDOW(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2501 dtrule
->getRuleMonth(), dtrule
->getRuleWeekInMonth(), dtrule
->getRuleDayOfWeek(), startTime
, MAX_MILLIS
, status
);
2503 case DateTimeRule::DOW_GEQ_DOM
:
2504 writeZonePropsByDOW_GEQ_DOM(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2505 dtrule
->getRuleMonth(), dtrule
->getRuleDayOfMonth(), dtrule
->getRuleDayOfWeek(), startTime
, MAX_MILLIS
, status
);
2507 case DateTimeRule::DOW_LEQ_DOM
:
2508 writeZonePropsByDOW_LEQ_DOM(writer
, isDst
, name
, fromRawOffset
+ fromDSTSavings
, toOffset
,
2509 dtrule
->getRuleMonth(), dtrule
->getRuleDayOfMonth(), dtrule
->getRuleDayOfWeek(), startTime
, MAX_MILLIS
, status
);
2518 * Write the opening section of zone properties
2521 VTimeZone::beginZoneProps(VTZWriter
& writer
, UBool isDst
, const UnicodeString
& zonename
,
2522 int32_t fromOffset
, int32_t toOffset
, UDate startTime
, UErrorCode
& status
) const {
2523 if (U_FAILURE(status
)) {
2526 writer
.write(ICAL_BEGIN
);
2527 writer
.write(COLON
);
2529 writer
.write(ICAL_DAYLIGHT
);
2531 writer
.write(ICAL_STANDARD
);
2533 writer
.write(ICAL_NEWLINE
);
2538 writer
.write(ICAL_TZOFFSETTO
);
2539 writer
.write(COLON
);
2540 millisToOffset(toOffset
, dstr
);
2542 writer
.write(ICAL_NEWLINE
);
2545 writer
.write(ICAL_TZOFFSETFROM
);
2546 writer
.write(COLON
);
2547 millisToOffset(fromOffset
, dstr
);
2549 writer
.write(ICAL_NEWLINE
);
2552 writer
.write(ICAL_TZNAME
);
2553 writer
.write(COLON
);
2554 writer
.write(zonename
);
2555 writer
.write(ICAL_NEWLINE
);
2558 writer
.write(ICAL_DTSTART
);
2559 writer
.write(COLON
);
2560 writer
.write(getDateTimeString(startTime
+ fromOffset
, dstr
));
2561 writer
.write(ICAL_NEWLINE
);
2565 * Writes the closing section of zone properties
2568 VTimeZone::endZoneProps(VTZWriter
& writer
, UBool isDst
, UErrorCode
& status
) const {
2569 if (U_FAILURE(status
)) {
2572 // END:STANDARD or END:DAYLIGHT
2573 writer
.write(ICAL_END
);
2574 writer
.write(COLON
);
2576 writer
.write(ICAL_DAYLIGHT
);
2578 writer
.write(ICAL_STANDARD
);
2580 writer
.write(ICAL_NEWLINE
);
2584 * Write the beggining part of RRULE line
2587 VTimeZone::beginRRULE(VTZWriter
& writer
, int32_t month
, UErrorCode
& status
) const {
2588 if (U_FAILURE(status
)) {
2592 writer
.write(ICAL_RRULE
);
2593 writer
.write(COLON
);
2594 writer
.write(ICAL_FREQ
);
2595 writer
.write(EQUALS_SIGN
);
2596 writer
.write(ICAL_YEARLY
);
2597 writer
.write(SEMICOLON
);
2598 writer
.write(ICAL_BYMONTH
);
2599 writer
.write(EQUALS_SIGN
);
2600 appendAsciiDigits(month
+ 1, 0, dstr
);
2602 writer
.write(SEMICOLON
);
2606 * Append the UNTIL attribute after RRULE line
2609 VTimeZone::appendUNTIL(VTZWriter
& writer
, const UnicodeString
& until
, UErrorCode
& status
) const {
2610 if (U_FAILURE(status
)) {
2613 if (until
.length() > 0) {
2614 writer
.write(SEMICOLON
);
2615 writer
.write(ICAL_UNTIL
);
2616 writer
.write(EQUALS_SIGN
);
2617 writer
.write(until
);
2623 #endif /* #if !UCONFIG_NO_FORMATTING */