]> git.saurik.com Git - apple/icu.git/blob - icuSources/i18n/vtzone.cpp
ICU-400.40.tar.gz
[apple/icu.git] / icuSources / i18n / vtzone.cpp
1 /*
2 *******************************************************************************
3 * Copyright (C) 2007-2008, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
6 */
7
8 #include "unicode/utypes.h"
9
10 #if !UCONFIG_NO_FORMATTING
11
12 #include "unicode/vtzone.h"
13 #include "unicode/rbtz.h"
14 #include "unicode/ucal.h"
15 #include "unicode/ures.h"
16 #include "cmemory.h"
17 #include "uvector.h"
18 #include "gregoimp.h"
19 #include "uhash.h"
20
21 U_NAMESPACE_BEGIN
22
23 // This is the deleter that will be use to remove TimeZoneRule
24 U_CDECL_BEGIN
25 static void U_CALLCONV
26 deleteTimeZoneRule(void* obj) {
27 delete (TimeZoneRule*) obj;
28 }
29 U_CDECL_END
30
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; /* - */
38
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" */
56
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" */
63
64 static const UChar ICAL_NEWLINE[] = {0x0D, 0x0A, 0}; /* CRLF */
65
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" */};
74
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};
77
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@" */
82
83
84 /*
85 * Simple fixed digit ASCII number to integer converter
86 */
87 static int32_t parseAsciiDigits(const UnicodeString& str, int32_t start, int32_t length, UErrorCode& status) {
88 if (U_FAILURE(status)) {
89 return 0;
90 }
91 if (length <= 0 || str.length() < start || (start + length) > str.length()) {
92 status = U_INVALID_FORMAT_ERROR;
93 return 0;
94 }
95 int32_t sign = 1;
96 if (str.charAt(start) == PLUS) {
97 start++;
98 length--;
99 } else if (str.charAt(start) == MINUS) {
100 sign = -1;
101 start++;
102 length--;
103 }
104 int32_t num = 0;
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;
109 return 0;
110 }
111 num = 10 * num + digit;
112 }
113 return sign * num;
114 }
115
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
119 int32_t i;
120
121 if (number < 0) {
122 negative = TRUE;
123 number *= -1;
124 }
125
126 length = length > 10 ? 10 : length;
127 if (length == 0) {
128 // variable length
129 i = 0;
130 do {
131 digits[i++] = number % 10;
132 number /= 10;
133 } while (number != 0);
134 length = i;
135 } else {
136 // fixed digits
137 for (i = 0; i < length; i++) {
138 digits[i] = number % 10;
139 number /= 10;
140 }
141 }
142 if (negative) {
143 str.append(MINUS);
144 }
145 for (i = length - 1; i >= 0; i--) {
146 str.append((UChar)(digits[i] + 0x0030));
147 }
148 return str;
149 }
150
151 static UnicodeString& appendMillis(UDate date, UnicodeString& str) {
152 UBool negative = FALSE;
153 int32_t digits[20]; // max int64_t is 20 decimal digits
154 int32_t i;
155 int64_t number;
156
157 if (date < MIN_MILLIS) {
158 number = (int64_t)MIN_MILLIS;
159 } else if (date > MAX_MILLIS) {
160 number = (int64_t)MAX_MILLIS;
161 } else {
162 number = (int64_t)date;
163 }
164 if (number < 0) {
165 negative = TRUE;
166 number *= -1;
167 }
168 i = 0;
169 do {
170 digits[i++] = (int32_t)(number % 10);
171 number /= 10;
172 } while (number != 0);
173
174 if (negative) {
175 str.append(MINUS);
176 }
177 i--;
178 while (i >= 0) {
179 str.append((UChar)(digits[i--] + 0x0030));
180 }
181 return str;
182 }
183
184 /*
185 * Convert date/time to RFC2445 Date-Time form #1 DATE WITH LOCAL TIME
186 */
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);
190
191 str.remove();
192 appendAsciiDigits(year, 4, str);
193 appendAsciiDigits(month + 1, 2, str);
194 appendAsciiDigits(dom, 2, str);
195 str.append((UChar)0x0054 /*'T'*/);
196
197 int32_t t = mid;
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;
203
204 appendAsciiDigits(hour, 2, str);
205 appendAsciiDigits(min, 2, str);
206 appendAsciiDigits(sec, 2, str);
207 return str;
208 }
209
210 /*
211 * Convert date/time to RFC2445 Date-Time form #2 DATE WITH UTC TIME
212 */
213 static UnicodeString& getUTCDateTimeString(UDate time, UnicodeString& str) {
214 getDateTimeString(time, str);
215 str.append((UChar)0x005A /*'Z'*/);
216 return str;
217 }
218
219 /*
220 * Parse RFC2445 Date-Time form #1 DATE WITH LOCAL TIME and
221 * #2 DATE WITH UTC TIME
222 */
223 static UDate parseDateTimeString(const UnicodeString& str, int32_t offset, UErrorCode& status) {
224 if (U_FAILURE(status)) {
225 return 0.0;
226 }
227
228 int32_t year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0;
229 UBool isUTC = FALSE;
230 UBool isValid = FALSE;
231 do {
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"
236 break;
237 }
238 if (str.charAt(8) != 0x0054) {
239 // charcter "T" must be used for separating date and time
240 break;
241 }
242 if (length == 16) {
243 if (str.charAt(15) != 0x005A) {
244 // invalid format
245 break;
246 }
247 isUTC = TRUE;
248 }
249
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);
256
257 if (U_FAILURE(status)) {
258 break;
259 }
260
261 // check valid range
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) {
265 break;
266 }
267
268 isValid = TRUE;
269 } while(false);
270
271 if (!isValid) {
272 status = U_INVALID_FORMAT_ERROR;
273 return 0.0;
274 }
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);
278 if (!isUTC) {
279 time -= offset;
280 }
281 return time;
282 }
283
284 /*
285 * Convert RFC2445 utc-offset string to milliseconds
286 */
287 static int32_t offsetStrToMillis(const UnicodeString& str, UErrorCode& status) {
288 if (U_FAILURE(status)) {
289 return 0;
290 }
291
292 UBool isValid = FALSE;
293 int32_t sign = 0, hour = 0, min = 0, sec = 0;
294
295 do {
296 int length = str.length();
297 if (length != 5 && length != 7) {
298 // utf-offset must be 5 or 7 characters
299 break;
300 }
301 // sign
302 UChar s = str.charAt(0);
303 if (s == PLUS) {
304 sign = 1;
305 } else if (s == MINUS) {
306 sign = -1;
307 } else {
308 // utf-offset must start with "+" or "-"
309 break;
310 }
311 hour = parseAsciiDigits(str, 1, 2, status);
312 min = parseAsciiDigits(str, 3, 2, status);
313 if (length == 7) {
314 sec = parseAsciiDigits(str, 5, 2, status);
315 }
316 if (U_FAILURE(status)) {
317 break;
318 }
319 isValid = true;
320 } while(false);
321
322 if (!isValid) {
323 status = U_INVALID_FORMAT_ERROR;
324 return 0;
325 }
326 int32_t millis = sign * ((hour * 60 + min) * 60 + sec) * 1000;
327 return millis;
328 }
329
330 /*
331 * Convert milliseconds to RFC2445 utc-offset string
332 */
333 static void millisToOffset(int32_t millis, UnicodeString& str) {
334 str.remove();
335 if (millis >= 0) {
336 str.append(PLUS);
337 } else {
338 str.append(MINUS);
339 millis = -millis;
340 }
341 int32_t hour, min, sec;
342 int32_t t = millis / 1000;
343
344 sec = t % 60;
345 t = (t - sec) / 60;
346 min = t % 60;
347 hour = t / 60;
348
349 appendAsciiDigits(hour, 2, str);
350 appendAsciiDigits(min, 2, str);
351 appendAsciiDigits(sec, 2, str);
352 }
353
354 /*
355 * Create a default TZNAME from TZID
356 */
357 static void getDefaultTZName(const UnicodeString tzid, UBool isDST, UnicodeString& tzname) {
358 tzname = tzid;
359 if (isDST) {
360 tzname += UNICODE_STRING_SIMPLE("(DST)");
361 } else {
362 tzname += UNICODE_STRING_SIMPLE("(STD)");
363 }
364 }
365
366 /*
367 * Parse individual RRULE
368 *
369 * On return -
370 *
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
377 */
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)) {
381 return;
382 }
383 int32_t numDom = 0;
384
385 month = -1;
386 dow = 0;
387 wim = 0;
388 until = MIN_MILLIS;
389
390 UBool yearly = FALSE;
391 //UBool parseError = FALSE;
392
393 int32_t prop_start = 0;
394 int32_t prop_end;
395 UnicodeString prop, attr, value;
396 UBool nextProp = TRUE;
397
398 while (nextProp) {
399 prop_end = rrule.indexOf(SEMICOLON, prop_start);
400 if (prop_end == -1) {
401 prop.setTo(rrule, prop_start);
402 nextProp = FALSE;
403 } else {
404 prop.setTo(rrule, prop_start, prop_end - prop_start);
405 prop_start = prop_end + 1;
406 }
407 int32_t eql = prop.indexOf(EQUALS_SIGN);
408 if (eql != -1) {
409 attr.setTo(prop, 0, eql);
410 value.setTo(prop, eql + 1);
411 } else {
412 goto rruleParseError;
413 }
414
415 if (attr.compare(ICAL_FREQ) == 0) {
416 // only support YEARLY frequency type
417 if (value.compare(ICAL_YEARLY) == 0) {
418 yearly = TRUE;
419 } else {
420 goto rruleParseError;
421 }
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;
427 }
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;
433 }
434 month = parseAsciiDigits(value, 0, value.length(), status) - 1;
435 if (U_FAILURE(status) || month < 0 || month >= 12) {
436 goto rruleParseError;
437 }
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.
441
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;
447 }
448 if (length > 2) {
449 // Nth day of week
450 int32_t sign = 1;
451 if (value.charAt(0) == PLUS) {
452 sign = 1;
453 } else if (value.charAt(0) == MINUS) {
454 sign = -1;
455 } else if (length == 4) {
456 goto rruleParseError;
457 }
458 int32_t n = parseAsciiDigits(value, length - 3, 1, status);
459 if (U_FAILURE(status) || n == 0 || n > 4) {
460 goto rruleParseError;
461 }
462 wim = n * sign;
463 value.remove(0, length - 2);
464 }
465 int32_t wday;
466 for (wday = 0; wday < 7; wday++) {
467 if (value.compare(ICAL_DOW_NAMES[wday], 2) == 0) {
468 break;
469 }
470 }
471 if (wday < 7) {
472 // Sunday(1) - Saturday(7)
473 dow = wday + 1;
474 } else {
475 goto rruleParseError;
476 }
477 } else if (attr.compare(ICAL_BYMONTHDAY) == 0) {
478 // Note: BYMONTHDAY may contain multiple days delimitted by comma
479 //
480 // A value of BYMONTHDAY could be negative, for example, -1 means
481 // the last day in a month
482 int32_t dom_idx = 0;
483 int32_t dom_start = 0;
484 int32_t dom_end;
485 UBool nextDOM = TRUE;
486 while (nextDOM) {
487 dom_end = value.indexOf(COMMA, dom_start);
488 if (dom_end == -1) {
489 dom_end = value.length();
490 nextDOM = FALSE;
491 }
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;
496 }
497 dom_idx++;
498 } else {
499 status = U_BUFFER_OVERFLOW_ERROR;
500 goto rruleParseError;
501 }
502 dom_start = dom_end + 1;
503 }
504 numDom = dom_idx;
505 }
506 }
507 if (!yearly) {
508 // FREQ=YEARLY must be set
509 goto rruleParseError;
510 }
511 // Set actual number of parsed DOM (ICAL_BYMONTHDAY)
512 domCount = numDom;
513 return;
514
515 rruleParseError:
516 if (U_SUCCESS(status)) {
517 // Set error status
518 status = U_INVALID_FORMAT_ERROR;
519 }
520 }
521
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)) {
525 return NULL;
526 }
527 if (dates == NULL || dates->size() == 0) {
528 status = U_ILLEGAL_ARGUMENT_ERROR;
529 return NULL;
530 }
531
532 int32_t i, j;
533 DateTimeRule *adtr = NULL;
534
535 // Parse the first rule
536 UnicodeString rrule = *((UnicodeString*)dates->elementAt(0));
537 int32_t month, dayOfWeek, nthDayOfWeek, dayOfMonth = 0;
538 int32_t days[7];
539 int32_t daysCount = sizeof(days)/sizeof(days[0]);
540 UDate until;
541
542 parseRRULE(rrule, month, dayOfWeek, nthDayOfWeek, days, daysCount, until, status);
543 if (U_FAILURE(status)) {
544 return NULL;
545 }
546
547 if (dates->size() == 1) {
548 // No more rules
549 if (daysCount > 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;
555 }
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
560 // as the base.
561 if (days[i] < 0) {
562 days[i] = MONTHLENGTH[month] + days[i] + 1;
563 }
564 if (days[i] < firstDay) {
565 firstDay = days[i];
566 }
567 }
568 // Make sure days are continuous
569 for (i = 1; i < 7; i++) {
570 UBool found = FALSE;
571 for (j = 0; j < 7; j++) {
572 if (days[j] == firstDay + i) {
573 found = TRUE;
574 break;
575 }
576 }
577 if (!found) {
578 // days are not continuous
579 goto unsupportedRRule;
580 }
581 }
582 // Use DOW_GEQ_DOM rule with firstDay as the start date
583 dayOfMonth = firstDay;
584 }
585 } else {
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;
591 }
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;
596 }
597
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
601
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;
608 }
609
610 int32_t anotherMonth = -1;
611 for (i = 1; i < dates->size(); i++) {
612 rrule = *((UnicodeString*)dates->elementAt(i));
613 UDate tmp_until;
614 int32_t tmp_month, tmp_dayOfWeek, tmp_nthDayOfWeek;
615 int32_t tmp_days[7];
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)) {
619 return NULL;
620 }
621 // If UNTIL is newer than previous one, use the one
622 if (tmp_until > until) {
623 until = tmp_until;
624 }
625
626 // Check if BYMONTH + BYMONTHDAY + BYDAY rule
627 if (tmp_month == -1 || tmp_dayOfWeek == 0 || tmp_daysCount == 0) {
628 goto unsupportedRRule;
629 }
630 // Count number of BYMONTHDAY
631 if (daysCount + tmp_daysCount > 7) {
632 // We cannot support BYMONTHDAY more than 7
633 goto unsupportedRRule;
634 }
635 // Check if the same BYDAY is used. Otherwise, we cannot
636 // support the rule
637 if (tmp_dayOfWeek != dayOfWeek) {
638 goto unsupportedRRule;
639 }
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) {
645 // Previous month
646 anotherMonth = tmp_month;
647 earliestMonth = anotherMonth;
648 // Reset earliest day
649 earliestDay = 31;
650 } else if (diff == 11 || diff == 1) {
651 // Next month
652 anotherMonth = tmp_month;
653 } else {
654 // The day range cannot exceed more than 2 months
655 goto unsupportedRRule;
656 }
657 } else if (tmp_month != month && tmp_month != anotherMonth) {
658 // The day range cannot exceed more than 2 months
659 goto unsupportedRRule;
660 }
661 }
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;
667 }
668 }
669 daysCount += tmp_daysCount;
670 }
671 if (daysCount != 7) {
672 // Number of BYMONTHDAY entries must be 7
673 goto unsupportedRRule;
674 }
675 month = earliestMonth;
676 dayOfMonth = earliestDay;
677 }
678
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);
683 if (month == -1) {
684 // If BYMONTH is not set, use the month of DTSTART
685 month = startMonth;
686 }
687 if (dayOfWeek == 0 && nthDayOfWeek == 0 && dayOfMonth == 0) {
688 // If only YEARLY is set, use the day of DTSTART as BYMONTHDAY
689 dayOfMonth = startDOM;
690 }
691
692 int32_t endYear;
693 if (until != MIN_MILLIS) {
694 int32_t endMonth, endDOM, endDOW, endDOY, endMID;
695 Grego::timeToFields(until, endYear, endMonth, endDOM, endDOW, endDOY, endMID);
696 } else {
697 endYear = AnnualTimeZoneRule::MAX_YEAR;
698 }
699
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);
711 }
712 if (adtr == NULL) {
713 goto unsupportedRRule;
714 }
715 return new AnnualTimeZoneRule(tzname, rawOffset, dstSavings, adtr, startYear, endYear);
716
717 unsupportedRRule:
718 status = U_INVALID_STATE_ERROR;
719 return NULL;
720 }
721
722 /*
723 * Create a TimeZoneRule by the RDATE definition
724 */
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)) {
728 return NULL;
729 }
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);
736 } else {
737 // Create an array of transition times
738 int32_t size = dates->size();
739 UDate* times = (UDate*)uprv_malloc(sizeof(UDate) * size);
740 if (times == NULL) {
741 status = U_MEMORY_ALLOCATION_ERROR;
742 return NULL;
743 }
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)) {
748 uprv_free(times);
749 return NULL;
750 }
751 }
752 retVal = new TimeArrayTimeZoneRule(tzname, rawOffset, dstSavings,
753 times, size, DateTimeRule::UTC_TIME);
754 uprv_free(times);
755 }
756 return retVal;
757 }
758
759 /*
760 * Check if the DOW rule specified by month, weekInMonth and dayOfWeek is equivalent
761 * to the DateTimerule.
762 */
763 static UBool isEquivalentDateRule(int32_t month, int32_t weekInMonth, int32_t dayOfWeek, const DateTimeRule *dtrule) {
764 if (month != dtrule->getRuleMonth() || dayOfWeek != dtrule->getRuleDayOfWeek()) {
765 return FALSE;
766 }
767 if (dtrule->getTimeRuleType() != DateTimeRule::WALL_TIME) {
768 // Do not try to do more intelligent comparison for now.
769 return FALSE;
770 }
771 if (dtrule->getDateRuleType() == DateTimeRule::DOW
772 && dtrule->getRuleWeekInMonth() == weekInMonth) {
773 return TRUE;
774 }
775 int32_t ruleDOM = dtrule->getRuleDayOfMonth();
776 if (dtrule->getDateRuleType() == DateTimeRule::DOW_GEQ_DOM) {
777 if (ruleDOM%7 == 1 && (ruleDOM + 6)/7 == weekInMonth) {
778 return TRUE;
779 }
780 if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - ruleDOM)%7 == 6
781 && weekInMonth == -1*((MONTHLENGTH[month]-ruleDOM+1)/7)) {
782 return TRUE;
783 }
784 }
785 if (dtrule->getDateRuleType() == DateTimeRule::DOW_LEQ_DOM) {
786 if (ruleDOM%7 == 0 && ruleDOM/7 == weekInMonth) {
787 return TRUE;
788 }
789 if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - ruleDOM)%7 == 0
790 && weekInMonth == -1*((MONTHLENGTH[month] - ruleDOM)/7 + 1)) {
791 return TRUE;
792 }
793 }
794 return FALSE;
795 }
796
797 /*
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.
801 */
802 static DateTimeRule* toWallTimeRule(const DateTimeRule* rule, int32_t rawOffset, int32_t dstSavings) {
803 if (rule->getTimeRuleType() == DateTimeRule::WALL_TIME) {
804 return NULL;
805 }
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) {
810 wallt += dstSavings;
811 }
812
813 int32_t month = -1, dom = 0, dow = 0;
814 DateTimeRule::DateRuleType dtype;
815 int32_t dshift = 0;
816 if (wallt < 0) {
817 dshift = -1;
818 wallt += U_MILLIS_PER_DAY;
819 } else if (wallt >= U_MILLIS_PER_DAY) {
820 dshift = 1;
821 wallt -= U_MILLIS_PER_DAY;
822 }
823
824 month = rule->getRuleMonth();
825 dom = rule->getRuleDayOfMonth();
826 dow = rule->getRuleDayOfWeek();
827 dtype = rule->getDateRuleType();
828
829 if (dshift != 0) {
830 if (dtype == DateTimeRule::DOW) {
831 // Convert to DOW_GEW_DOM or DOW_LEQ_DOM rule first
832 int32_t wim = rule->getRuleWeekInMonth();
833 if (wim > 0) {
834 dtype = DateTimeRule::DOW_GEQ_DOM;
835 dom = 7 * (wim - 1) + 1;
836 } else {
837 dtype = DateTimeRule::DOW_LEQ_DOM;
838 dom = MONTHLENGTH[month] + 7 * (wim + 1);
839 }
840 }
841 // Shift one day before or after
842 dom += dshift;
843 if (dom == 0) {
844 month--;
845 month = month < UCAL_JANUARY ? UCAL_DECEMBER : month;
846 dom = MONTHLENGTH[month];
847 } else if (dom > MONTHLENGTH[month]) {
848 month++;
849 month = month > UCAL_DECEMBER ? UCAL_JANUARY : month;
850 dom = 1;
851 }
852 if (dtype != DateTimeRule::DOM) {
853 // Adjust day of week
854 dow += dshift;
855 if (dow < UCAL_SUNDAY) {
856 dow = UCAL_SATURDAY;
857 } else if (dow > UCAL_SATURDAY) {
858 dow = UCAL_SUNDAY;
859 }
860 }
861 }
862 // Create a new rule
863 DateTimeRule *modifiedRule;
864 if (dtype == DateTimeRule::DOM) {
865 modifiedRule = new DateTimeRule(month, dom, wallt, DateTimeRule::WALL_TIME);
866 } else {
867 modifiedRule = new DateTimeRule(month, dom, dow,
868 (dtype == DateTimeRule::DOW_GEQ_DOM), wallt, DateTimeRule::WALL_TIME);
869 }
870 return modifiedRule;
871 }
872
873 /*
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/
878 * Reader.
879 */
880 class VTZWriter {
881 public:
882 VTZWriter(UnicodeString& out);
883 ~VTZWriter();
884
885 void write(const UnicodeString& str);
886 void write(UChar ch);
887 //void write(const UChar* str, int32_t length);
888 private:
889 UnicodeString* out;
890 };
891
892 VTZWriter::VTZWriter(UnicodeString& output) {
893 out = &output;
894 }
895
896 VTZWriter::~VTZWriter() {
897 }
898
899 void
900 VTZWriter::write(const UnicodeString& str) {
901 out->append(str);
902 }
903
904 void
905 VTZWriter::write(UChar ch) {
906 out->append(ch);
907 }
908
909 /*
910 void
911 VTZWriter::write(const UChar* str, int32_t length) {
912 out->append(str, length);
913 }
914 */
915
916 class VTZReader {
917 public:
918 VTZReader(const UnicodeString& input);
919 ~VTZReader();
920
921 UChar read(void);
922 private:
923 const UnicodeString* in;
924 int32_t index;
925 };
926
927 VTZReader::VTZReader(const UnicodeString& input) {
928 in = &input;
929 index = 0;
930 }
931
932 VTZReader::~VTZReader() {
933 }
934
935 UChar
936 VTZReader::read(void) {
937 UChar ch = 0xFFFF;
938 if (index < in->length()) {
939 ch = in->charAt(index);
940 }
941 index++;
942 return ch;
943 }
944
945
946 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(VTimeZone)
947
948 VTimeZone::VTimeZone()
949 : BasicTimeZone(), tz(NULL), vtzlines(NULL),
950 lastmod(MAX_MILLIS) {
951 }
952
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();
959 }
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)) {
969 break;
970 }
971 }
972 }
973 if (U_FAILURE(status) && vtzlines != NULL) {
974 delete vtzlines;
975 }
976 }
977 }
978
979 VTimeZone::~VTimeZone() {
980 if (tz != NULL) {
981 delete tz;
982 }
983 if (vtzlines != NULL) {
984 delete vtzlines;
985 }
986 }
987
988 VTimeZone&
989 VTimeZone::operator=(const VTimeZone& right) {
990 if (this == &right) {
991 return *this;
992 }
993 if (*this != right) {
994 BasicTimeZone::operator=(right);
995 if (tz != NULL) {
996 delete tz;
997 tz = NULL;
998 }
999 if (right.tz != NULL) {
1000 tz = (BasicTimeZone*)right.tz->clone();
1001 }
1002 if (vtzlines != NULL) {
1003 delete vtzlines;
1004 }
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)) {
1014 break;
1015 }
1016 }
1017 }
1018 if (U_FAILURE(status) && vtzlines != NULL) {
1019 delete vtzlines;
1020 vtzlines = NULL;
1021 }
1022 }
1023 tzurl = right.tzurl;
1024 lastmod = right.lastmod;
1025 olsonzid = right.olsonzid;
1026 icutzver = right.icutzver;
1027 }
1028 return *this;
1029 }
1030
1031 UBool
1032 VTimeZone::operator==(const TimeZone& that) const {
1033 if (this == &that) {
1034 return TRUE;
1035 }
1036 if (getDynamicClassID() != that.getDynamicClassID()
1037 || !BasicTimeZone::operator==(that)) {
1038 return FALSE;
1039 }
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 */) {
1046 return TRUE;
1047 }
1048 return FALSE;
1049 }
1050
1051 UBool
1052 VTimeZone::operator!=(const TimeZone& that) const {
1053 return !operator==(that);
1054 }
1055
1056 VTimeZone*
1057 VTimeZone::createVTimeZoneByID(const UnicodeString& ID) {
1058 VTimeZone *vtz = new VTimeZone();
1059 vtz->tz = (BasicTimeZone*)TimeZone::createTimeZone(ID);
1060 vtz->tz->getID(vtz->olsonzid);
1061
1062 // Set ICU tzdata version
1063 UErrorCode status = U_ZERO_ERROR;
1064 UResourceBundle *bundle = NULL;
1065 const UChar* versionStr = NULL;
1066 int32_t len;
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);
1071 }
1072 ures_close(bundle);
1073 return vtz;
1074 }
1075
1076 VTimeZone*
1077 VTimeZone::createVTimeZone(const UnicodeString& vtzdata, UErrorCode& status) {
1078 if (U_FAILURE(status)) {
1079 return NULL;
1080 }
1081 VTZReader reader(vtzdata);
1082 VTimeZone *vtz = new VTimeZone();
1083 vtz->load(reader, status);
1084 if (U_FAILURE(status)) {
1085 delete vtz;
1086 return NULL;
1087 }
1088 return vtz;
1089 }
1090
1091 UBool
1092 VTimeZone::getTZURL(UnicodeString& url) const {
1093 if (tzurl.length() > 0) {
1094 url = tzurl;
1095 return TRUE;
1096 }
1097 return FALSE;
1098 }
1099
1100 void
1101 VTimeZone::setTZURL(const UnicodeString& url) {
1102 tzurl = url;
1103 }
1104
1105 UBool
1106 VTimeZone::getLastModified(UDate& lastModified) const {
1107 if (lastmod != MAX_MILLIS) {
1108 lastModified = lastmod;
1109 return TRUE;
1110 }
1111 return FALSE;
1112 }
1113
1114 void
1115 VTimeZone::setLastModified(UDate lastModified) {
1116 lastmod = lastModified;
1117 }
1118
1119 void
1120 VTimeZone::write(UnicodeString& result, UErrorCode& status) const {
1121 result.remove();
1122 VTZWriter writer(result);
1123 write(writer, status);
1124 }
1125
1126 void
1127 VTimeZone::write(UDate start, UnicodeString& result, UErrorCode& status) /*const*/ {
1128 result.remove();
1129 VTZWriter writer(result);
1130 write(start, writer, status);
1131 }
1132
1133 void
1134 VTimeZone::writeSimple(UDate time, UnicodeString& result, UErrorCode& status) /*const*/ {
1135 result.remove();
1136 VTZWriter writer(result);
1137 writeSimple(time, writer, status);
1138 }
1139
1140 TimeZone*
1141 VTimeZone::clone(void) const {
1142 return new VTimeZone(*this);
1143 }
1144
1145 int32_t
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);
1149 }
1150
1151 int32_t
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);
1156 }
1157
1158 void
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);
1162 }
1163
1164 void
1165 VTimeZone::setRawOffset(int32_t offsetMillis) {
1166 tz->setRawOffset(offsetMillis);
1167 }
1168
1169 int32_t
1170 VTimeZone::getRawOffset(void) const {
1171 return tz->getRawOffset();
1172 }
1173
1174 UBool
1175 VTimeZone::useDaylightTime(void) const {
1176 return tz->useDaylightTime();
1177 }
1178
1179 UBool
1180 VTimeZone::inDaylightTime(UDate date, UErrorCode& status) const {
1181 return tz->inDaylightTime(date, status);
1182 }
1183
1184 UBool
1185 VTimeZone::hasSameRules(const TimeZone& other) const {
1186 return tz->hasSameRules(other);
1187 }
1188
1189 UBool
1190 VTimeZone::getNextTransition(UDate base, UBool inclusive, TimeZoneTransition& result) /*const*/ {
1191 return tz->getNextTransition(base, inclusive, result);
1192 }
1193
1194 UBool
1195 VTimeZone::getPreviousTransition(UDate base, UBool inclusive, TimeZoneTransition& result) /*const*/ {
1196 return tz->getPreviousTransition(base, inclusive, result);
1197 }
1198
1199 int32_t
1200 VTimeZone::countTransitionRules(UErrorCode& status) /*const*/ {
1201 return tz->countTransitionRules(status);
1202 }
1203
1204 void
1205 VTimeZone::getTimeZoneRules(const InitialTimeZoneRule*& initial,
1206 const TimeZoneRule* trsrules[], int32_t& trscount,
1207 UErrorCode& status) /*const*/ {
1208 tz->getTimeZoneRules(initial, trsrules, trscount, status);
1209 }
1210
1211 void
1212 VTimeZone::load(VTZReader& reader, UErrorCode& status) {
1213 vtzlines = new UVector(uhash_deleteUnicodeString, uhash_compareUnicodeString, DEFAULT_VTIMEZONE_LINES, status);
1214 if (U_FAILURE(status)) {
1215 return;
1216 }
1217 UBool eol = FALSE;
1218 UBool start = FALSE;
1219 UBool success = FALSE;
1220 UnicodeString line;
1221
1222 while (TRUE) {
1223 UChar ch = reader.read();
1224 if (ch == 0xFFFF) {
1225 // end of file
1226 if (start && line.startsWith(ICAL_END_VTIMEZONE)) {
1227 vtzlines->addElement(new UnicodeString(line), status);
1228 if (U_FAILURE(status)) {
1229 goto cleanupVtzlines;
1230 }
1231 success = TRUE;
1232 }
1233 break;
1234 }
1235 if (ch == 0x000D) {
1236 // CR, must be followed by LF according to the definition in RFC2445
1237 continue;
1238 }
1239 if (eol) {
1240 if (ch != 0x0009 && ch != 0x0020) {
1241 // NOT followed by TAB/SP -> new line
1242 if (start) {
1243 if (line.length() > 0) {
1244 vtzlines->addElement(new UnicodeString(line), status);
1245 if (U_FAILURE(status)) {
1246 goto cleanupVtzlines;
1247 }
1248 }
1249 }
1250 line.remove();
1251 if (ch != 0x000A) {
1252 line.append(ch);
1253 }
1254 }
1255 eol = FALSE;
1256 } else {
1257 if (ch == 0x000A) {
1258 // LF
1259 eol = TRUE;
1260 if (start) {
1261 if (line.startsWith(ICAL_END_VTIMEZONE)) {
1262 vtzlines->addElement(new UnicodeString(line), status);
1263 if (U_FAILURE(status)) {
1264 goto cleanupVtzlines;
1265 }
1266 success = TRUE;
1267 break;
1268 }
1269 } else {
1270 if (line.startsWith(ICAL_BEGIN_VTIMEZONE)) {
1271 vtzlines->addElement(new UnicodeString(line), status);
1272 if (U_FAILURE(status)) {
1273 goto cleanupVtzlines;
1274 }
1275 line.remove();
1276 start = TRUE;
1277 eol = FALSE;
1278 }
1279 }
1280 } else {
1281 line.append(ch);
1282 }
1283 }
1284 }
1285 if (!success) {
1286 if (U_SUCCESS(status)) {
1287 status = U_INVALID_STATE_ERROR;
1288 }
1289 goto cleanupVtzlines;
1290 }
1291 parse(status);
1292 return;
1293
1294 cleanupVtzlines:
1295 delete vtzlines;
1296 vtzlines = NULL;
1297 }
1298
1299 // parser state
1300 #define INI 0 // Initial state
1301 #define VTZ 1 // In VTIMEZONE
1302 #define TZI 2 // In STANDARD or DAYLIGHT
1303
1304 #define DEF_DSTSAVINGS (60*60*1000)
1305 #define DEF_TZSTARTTIME (0.0)
1306
1307 void
1308 VTimeZone::parse(UErrorCode& status) {
1309 if (U_FAILURE(status)) {
1310 return;
1311 }
1312 if (vtzlines == NULL || vtzlines->size() == 0) {
1313 status = U_INVALID_STATE_ERROR;
1314 return;
1315 }
1316 InitialTimeZoneRule *initialRule = NULL;
1317 RuleBasedTimeZone *rbtz = NULL;
1318
1319 // timezone ID
1320 UnicodeString tzid;
1321
1322 int32_t state = INI;
1323 int32_t n = 0;
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
1335
1336 UVector *dates = NULL; // list of RDATE or RRULE strings
1337 UVector *rules = NULL; // list of TimeZoneRule instances
1338
1339 int32_t finalRuleIdx = -1;
1340 int32_t finalRuleCount = 0;
1341
1342 rules = new UVector(status);
1343 if (U_FAILURE(status)) {
1344 goto cleanupParse;
1345 }
1346 // Set the deleter to remove TimeZoneRule vectors to avoid memory leaks due to unowned TimeZoneRules.
1347 rules->setDeleter(deleteTimeZoneRule);
1348
1349 dates = new UVector(uhash_deleteUnicodeString, uhash_compareUnicodeString, status);
1350 if (U_FAILURE(status)) {
1351 goto cleanupParse;
1352 }
1353 if (rules == NULL || dates == NULL) {
1354 status = U_MEMORY_ALLOCATION_ERROR;
1355 goto cleanupParse;
1356 }
1357
1358 for (n = 0; n < vtzlines->size(); n++) {
1359 UnicodeString *line = (UnicodeString*)vtzlines->elementAt(n);
1360 int32_t valueSep = line->indexOf(COLON);
1361 if (valueSep < 0) {
1362 continue;
1363 }
1364 name.setTo(*line, 0, valueSep);
1365 value.setTo(*line, valueSep + 1);
1366
1367 switch (state) {
1368 case INI:
1369 if (name.compare(ICAL_BEGIN) == 0
1370 && value.compare(ICAL_VTIMEZONE) == 0) {
1371 state = VTZ;
1372 }
1373 break;
1374
1375 case VTZ:
1376 if (name.compare(ICAL_TZID) == 0) {
1377 tzid = value;
1378 } else if (name.compare(ICAL_TZURL) == 0) {
1379 tzurl = value;
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)) {
1385 goto cleanupParse;
1386 }
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) {
1392 goto cleanupParse;
1393 }
1394 // initialize current zone properties
1395 if (dates->size() != 0) {
1396 dates->removeAllElements();
1397 }
1398 isRRULE = FALSE;
1399 from.remove();
1400 to.remove();
1401 tzname.remove();
1402 dst = isDST;
1403 state = TZI;
1404 } else {
1405 // BEGIN property other than STANDARD/DAYLIGHT
1406 // must not be there.
1407 goto cleanupParse;
1408 }
1409 } else if (name.compare(ICAL_END) == 0) {
1410 break;
1411 }
1412 break;
1413 case TZI:
1414 if (name.compare(ICAL_DTSTART) == 0) {
1415 dtstart = value;
1416 } else if (name.compare(ICAL_TZNAME) == 0) {
1417 tzname = value;
1418 } else if (name.compare(ICAL_TZOFFSETFROM) == 0) {
1419 from = value;
1420 } else if (name.compare(ICAL_TZOFFSETTO) == 0) {
1421 to = value;
1422 } else if (name.compare(ICAL_RDATE) == 0) {
1423 // RDATE mixed with RRULE is not supported
1424 if (isRRULE) {
1425 goto cleanupParse;
1426 }
1427 // RDATE value may contain multiple date delimited
1428 // by comma
1429 UBool nextDate = TRUE;
1430 int32_t dstart = 0;
1431 UnicodeString *dstr;
1432 while (nextDate) {
1433 int32_t dend = value.indexOf(COMMA, dstart);
1434 if (dend == -1) {
1435 dstr = new UnicodeString(value, dstart);
1436 nextDate = FALSE;
1437 } else {
1438 dstr = new UnicodeString(value, dstart, dend - dstart);
1439 }
1440 dates->addElement(dstr, status);
1441 if (U_FAILURE(status)) {
1442 goto cleanupParse;
1443 }
1444 dstart = dend + 1;
1445 }
1446 } else if (name.compare(ICAL_RRULE) == 0) {
1447 // RRULE mixed with RDATE is not supported
1448 if (!isRRULE && dates->size() != 0) {
1449 goto cleanupParse;
1450 }
1451 isRRULE = true;
1452 dates->addElement(new UnicodeString(value), status);
1453 if (U_FAILURE(status)) {
1454 goto cleanupParse;
1455 }
1456 } else if (name.compare(ICAL_END) == 0) {
1457 // Mandatory properties
1458 if (dtstart.length() == 0 || from.length() == 0 || to.length() == 0) {
1459 goto cleanupParse;
1460 }
1461 // if tzname is not available, create one from tzid
1462 if (tzname.length() == 0) {
1463 getDefaultTZName(tzid, dst, tzname);
1464 }
1465
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;
1472 UDate start = 0;
1473
1474 // Parse TZOFFSETFROM/TZOFFSETTO
1475 fromOffset = offsetStrToMillis(from, status);
1476 toOffset = offsetStrToMillis(to, status);
1477 if (U_FAILURE(status)) {
1478 goto cleanupParse;
1479 }
1480
1481 if (dst) {
1482 // If daylight, use the previous offset as rawoffset if positive
1483 if (toOffset - fromOffset > 0) {
1484 rawOffset = fromOffset;
1485 dstSavings = toOffset - fromOffset;
1486 } else {
1487 // This is rare case.. just use 1 hour DST savings
1488 rawOffset = toOffset - DEF_DSTSAVINGS;
1489 dstSavings = DEF_DSTSAVINGS;
1490 }
1491 } else {
1492 rawOffset = toOffset;
1493 dstSavings = 0;
1494 }
1495
1496 // start time
1497 start = parseDateTimeString(dtstart, fromOffset, status);
1498 if (U_FAILURE(status)) {
1499 goto cleanupParse;
1500 }
1501
1502 // Create the rule
1503 UDate actualStart = MAX_MILLIS;
1504 if (isRRULE) {
1505 rule = createRuleByRRULE(tzname, rawOffset, dstSavings, start, dates, fromOffset, status);
1506 } else {
1507 rule = createRuleByRDATE(tzname, rawOffset, dstSavings, start, dates, fromOffset, status);
1508 }
1509 if (U_FAILURE(status) || rule == NULL) {
1510 goto cleanupParse;
1511 } else {
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;
1522 } else {
1523 if (fromOffset - toOffset == DEF_DSTSAVINGS) {
1524 initialRawOffset = fromOffset - DEF_DSTSAVINGS;
1525 initialDSTSavings = DEF_DSTSAVINGS;
1526 } else {
1527 initialRawOffset = fromOffset;
1528 initialDSTSavings = 0;
1529 }
1530 }
1531 }
1532 }
1533 rules->addElement(rule, status);
1534 if (U_FAILURE(status)) {
1535 goto cleanupParse;
1536 }
1537 state = VTZ;
1538 }
1539 break;
1540 }
1541 }
1542 // Must have at least one rule
1543 if (rules->size() == 0) {
1544 goto cleanupParse;
1545 }
1546
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;
1553 goto cleanupParse;
1554 }
1555
1556 // Finally, create the RuleBasedTimeZone
1557 rbtz = new RuleBasedTimeZone(tzid, initialRule);
1558 if (rbtz == NULL) {
1559 status = U_MEMORY_ALLOCATION_ERROR;
1560 goto cleanupParse;
1561 }
1562 initialRule = NULL; // already adopted by RBTZ, no need to delete
1563
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) {
1568 finalRuleCount++;
1569 finalRuleIdx = n;
1570 }
1571 }
1572 }
1573 if (finalRuleCount > 2) {
1574 // Too many final rules
1575 status = U_ILLEGAL_ARGUMENT_ERROR;
1576 goto cleanupParse;
1577 }
1578
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();
1585 } else {
1586 // Normalize the final rule
1587 AnnualTimeZoneRule *finalRule = (AnnualTimeZoneRule*)rules->elementAt(finalRuleIdx);
1588 int32_t tmpRaw = finalRule->getRawOffset();
1589 int32_t tmpDST = finalRule->getDSTSavings();
1590
1591 // Find the last non-final rule
1592 UDate finalStart, start;
1593 finalRule->getFirstStart(initialRawOffset, initialDSTSavings, finalStart);
1594 start = finalStart;
1595 for (n = 0; n < rules->size(); n++) {
1596 if (finalRuleIdx == n) {
1597 continue;
1598 }
1599 TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n);
1600 UDate lastStart;
1601 r->getFinalStart(tmpRaw, tmpDST, lastStart);
1602 if (lastStart > start) {
1603 finalRule->getNextStart(lastStart,
1604 r->getRawOffset(),
1605 r->getDSTSavings(),
1606 FALSE,
1607 start);
1608 }
1609 }
1610
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(),
1619 &finalStart,
1620 1,
1621 DateTimeRule::UTC_TIME);
1622 } else {
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(),
1632 y);
1633 }
1634 if (newRule == NULL) {
1635 status = U_MEMORY_ALLOCATION_ERROR;
1636 goto cleanupParse;
1637 }
1638 rules->removeElementAt(finalRuleIdx);
1639 rules->addElement(newRule, status);
1640 if (U_FAILURE(status)) {
1641 delete newRule;
1642 goto cleanupParse;
1643 }
1644 }
1645 }
1646
1647 while (!rules->isEmpty()) {
1648 TimeZoneRule *tzr = (TimeZoneRule*)rules->orphanElementAt(0);
1649 rbtz->addTransitionRule(tzr, status);
1650 if (U_FAILURE(status)) {
1651 goto cleanupParse;
1652 }
1653 }
1654 rbtz->complete(status);
1655 if (U_FAILURE(status)) {
1656 goto cleanupParse;
1657 }
1658 delete rules;
1659 delete dates;
1660
1661 tz = rbtz;
1662 setID(tzid);
1663 return;
1664
1665 cleanupParse:
1666 if (rules != NULL) {
1667 while (!rules->isEmpty()) {
1668 TimeZoneRule *r = (TimeZoneRule*)rules->orphanElementAt(0);
1669 delete r;
1670 }
1671 delete rules;
1672 }
1673 if (dates != NULL) {
1674 delete dates;
1675 }
1676 if (initialRule != NULL) {
1677 delete initialRule;
1678 }
1679 if (rbtz != NULL) {
1680 delete rbtz;
1681 }
1682 return;
1683 }
1684
1685 void
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);
1703 } else {
1704 writer.write(*line);
1705 writer.write(ICAL_NEWLINE);
1706 }
1707 }
1708 } else {
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)) {
1713 return;
1714 }
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)) {
1722 delete icutzprop;
1723 delete customProps;
1724 return;
1725 }
1726 }
1727 writeZone(writer, *tz, customProps, status);
1728 delete customProps;
1729 }
1730 }
1731
1732 void
1733 VTimeZone::write(UDate start, VTZWriter& writer, UErrorCode& status) /*const*/ {
1734 if (U_FAILURE(status)) {
1735 return;
1736 }
1737 InitialTimeZoneRule *initial = NULL;
1738 UVector *transitionRules = NULL;
1739 UVector customProps(uhash_deleteUnicodeString, uhash_compareUnicodeString, status);
1740 UnicodeString tzid;
1741
1742 // Extract rules applicable to dates after the start time
1743 getTimeZoneRulesAfter(start, initial, transitionRules, status);
1744 if (U_FAILURE(status)) {
1745 return;
1746 }
1747
1748 // Create a RuleBasedTimeZone with the subset rule
1749 getID(tzid);
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;
1757 }
1758 }
1759 delete transitionRules;
1760 transitionRules = NULL;
1761 }
1762 rbtz.complete(status);
1763 if (U_FAILURE(status)) {
1764 goto cleanupWritePartial;
1765 }
1766
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)) {
1777 delete icutzprop;
1778 goto cleanupWritePartial;
1779 }
1780 }
1781 writeZone(writer, rbtz, &customProps, status);
1782 return;
1783
1784 cleanupWritePartial:
1785 if (initial != NULL) {
1786 delete initial;
1787 }
1788 if (transitionRules != NULL) {
1789 while (!transitionRules->isEmpty()) {
1790 TimeZoneRule *tr = (TimeZoneRule*)transitionRules->orphanElementAt(0);
1791 delete tr;
1792 }
1793 delete transitionRules;
1794 }
1795 }
1796
1797 void
1798 VTimeZone::writeSimple(UDate time, VTZWriter& writer, UErrorCode& status) /*const*/ {
1799 if (U_FAILURE(status)) {
1800 return;
1801 }
1802
1803 UVector customProps(uhash_deleteUnicodeString, uhash_compareUnicodeString, status);
1804 UnicodeString tzid;
1805
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
1812 getID(tzid);
1813 RuleBasedTimeZone rbtz(tzid, initial);
1814 if (std != NULL && dst != NULL) {
1815 rbtz.addTransitionRule(std, status);
1816 rbtz.addTransitionRule(dst, status);
1817 }
1818 if (U_FAILURE(status)) {
1819 goto cleanupWriteSimple;
1820 }
1821
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)) {
1832 delete icutzprop;
1833 goto cleanupWriteSimple;
1834 }
1835 }
1836 writeZone(writer, rbtz, &customProps, status);
1837 }
1838 return;
1839
1840 cleanupWriteSimple:
1841 if (initial != NULL) {
1842 delete initial;
1843 }
1844 if (std != NULL) {
1845 delete std;
1846 }
1847 if (dst != NULL) {
1848 delete dst;
1849 }
1850 }
1851
1852 void
1853 VTimeZone::writeZone(VTZWriter& w, BasicTimeZone& basictz,
1854 UVector* customProps, UErrorCode& status) const {
1855 if (U_FAILURE(status)) {
1856 return;
1857 }
1858 writeHeaders(w, status);
1859 if (U_FAILURE(status)) {
1860 return;
1861 }
1862
1863 if (customProps != NULL) {
1864 for (int32_t i = 0; i < customProps->size(); i++) {
1865 UnicodeString *custprop = (UnicodeString*)customProps->elementAt(i);
1866 w.write(*custprop);
1867 w.write(ICAL_NEWLINE);
1868 }
1869 }
1870
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;
1885
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;
1899
1900 int32_t year, month, dom, dow, doy, mid;
1901 UBool hasTransitions = FALSE;
1902 TimeZoneTransition tzt;
1903 UBool tztAvail;
1904 UnicodeString name;
1905 UBool isDst;
1906
1907 // Going through all transitions
1908 while (TRUE) {
1909 tztAvail = basictz.getNextTransition(t, FALSE, tzt);
1910 if (!tztAvail) {
1911 break;
1912 }
1913 hasTransitions = TRUE;
1914 t = tzt.getTime();
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;
1923 if (isDst) {
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();
1928 }
1929 }
1930 if (dstCount > 0) {
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
1940 dstUntilTime = t;
1941 dstCount++;
1942 sameRule = TRUE;
1943 }
1944 if (!sameRule) {
1945 if (dstCount == 1) {
1946 writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime,
1947 TRUE, status);
1948 } else {
1949 writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
1950 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
1951 }
1952 if (U_FAILURE(status)) {
1953 goto cleanupWriteZone;
1954 }
1955 }
1956 }
1957 if (!sameRule) {
1958 // Reset this DST information
1959 dstName = name;
1960 dstFromOffset = fromOffset;
1961 dstFromDSTSavings = fromDSTSavings;
1962 dstToOffset = toOffset;
1963 dstStartYear = year;
1964 dstMonth = month;
1965 dstDayOfWeek = dow;
1966 dstWeekInMonth = weekInMonth;
1967 dstMillisInDay = mid;
1968 dstStartTime = dstUntilTime = t;
1969 dstCount = 1;
1970 }
1971 if (finalStdRule != NULL && finalDstRule != NULL) {
1972 break;
1973 }
1974 } else {
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();
1979 }
1980 }
1981 if (stdCount > 0) {
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
1991 stdUntilTime = t;
1992 stdCount++;
1993 sameRule = TRUE;
1994 }
1995 if (!sameRule) {
1996 if (stdCount == 1) {
1997 writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime,
1998 TRUE, status);
1999 } else {
2000 writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2001 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2002 }
2003 if (U_FAILURE(status)) {
2004 goto cleanupWriteZone;
2005 }
2006 }
2007 }
2008 if (!sameRule) {
2009 // Reset this STD information
2010 stdName = name;
2011 stdFromOffset = fromOffset;
2012 stdFromDSTSavings = fromDSTSavings;
2013 stdToOffset = toOffset;
2014 stdStartYear = year;
2015 stdMonth = month;
2016 stdDayOfWeek = dow;
2017 stdWeekInMonth = weekInMonth;
2018 stdMillisInDay = mid;
2019 stdStartTime = stdUntilTime = t;
2020 stdCount = 1;
2021 }
2022 if (finalStdRule != NULL && finalDstRule != NULL) {
2023 break;
2024 }
2025 }
2026 }
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;
2033 }
2034 offset = raw + dst;
2035 isDst = (dst != 0);
2036 UnicodeString tzid;
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;
2043 }
2044 } else {
2045 if (dstCount > 0) {
2046 if (finalDstRule == NULL) {
2047 if (dstCount == 1) {
2048 writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime,
2049 TRUE, status);
2050 } else {
2051 writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
2052 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
2053 }
2054 if (U_FAILURE(status)) {
2055 goto cleanupWriteZone;
2056 }
2057 } else {
2058 if (dstCount == 1) {
2059 writeFinalRule(w, TRUE, finalDstRule,
2060 dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, dstStartTime, status);
2061 } else {
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);
2066 } else {
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;
2072 }
2073 writeFinalRule(w, TRUE, finalDstRule,
2074 dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, dstStartTime, status);
2075 }
2076 }
2077 if (U_FAILURE(status)) {
2078 goto cleanupWriteZone;
2079 }
2080 }
2081 }
2082 if (stdCount > 0) {
2083 if (finalStdRule == NULL) {
2084 if (stdCount == 1) {
2085 writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime,
2086 TRUE, status);
2087 } else {
2088 writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2089 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2090 }
2091 if (U_FAILURE(status)) {
2092 goto cleanupWriteZone;
2093 }
2094 } else {
2095 if (stdCount == 1) {
2096 writeFinalRule(w, FALSE, finalStdRule,
2097 stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, stdStartTime, status);
2098 } else {
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);
2103 } else {
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;
2109 }
2110 writeFinalRule(w, FALSE, finalStdRule,
2111 stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, stdStartTime, status);
2112 }
2113 }
2114 if (U_FAILURE(status)) {
2115 goto cleanupWriteZone;
2116 }
2117 }
2118 }
2119 }
2120 writeFooter(w, status);
2121
2122 cleanupWriteZone:
2123
2124 if (finalStdRule != NULL) {
2125 delete finalStdRule;
2126 }
2127 if (finalDstRule != NULL) {
2128 delete finalDstRule;
2129 }
2130 }
2131
2132 void
2133 VTimeZone::writeHeaders(VTZWriter& writer, UErrorCode& status) const {
2134 if (U_FAILURE(status)) {
2135 return;
2136 }
2137 UnicodeString tzid;
2138 tz->getID(tzid);
2139
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);
2146 writer.write(tzid);
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);
2153 }
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);
2160 }
2161 }
2162
2163 /*
2164 * Write the closing section of the VTIMEZONE definition block
2165 */
2166 void
2167 VTimeZone::writeFooter(VTZWriter& writer, UErrorCode& status) const {
2168 if (U_FAILURE(status)) {
2169 return;
2170 }
2171 writer.write(ICAL_END);
2172 writer.write(COLON);
2173 writer.write(ICAL_VTIMEZONE);
2174 writer.write(ICAL_NEWLINE);
2175 }
2176
2177 /*
2178 * Write a single start time
2179 */
2180 void
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)) {
2185 return;
2186 }
2187 beginZoneProps(writer, isDst, tzname, fromOffset, toOffset, time, status);
2188 if (U_FAILURE(status)) {
2189 return;
2190 }
2191 if (withRDATE) {
2192 writer.write(ICAL_RDATE);
2193 writer.write(COLON);
2194 UnicodeString timestr;
2195 writer.write(getDateTimeString(time + fromOffset, timestr));
2196 writer.write(ICAL_NEWLINE);
2197 }
2198 endZoneProps(writer, isDst, status);
2199 if (U_FAILURE(status)) {
2200 return;
2201 }
2202 }
2203
2204 /*
2205 * Write start times defined by a DOM rule using VTIMEZONE RRULE
2206 */
2207 void
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)) {
2213 return;
2214 }
2215 beginZoneProps(writer, isDst, tzname, fromOffset, toOffset, startTime, status);
2216 if (U_FAILURE(status)) {
2217 return;
2218 }
2219 beginRRULE(writer, month, status);
2220 if (U_FAILURE(status)) {
2221 return;
2222 }
2223 writer.write(ICAL_BYMONTHDAY);
2224 writer.write(EQUALS_SIGN);
2225 UnicodeString dstr;
2226 appendAsciiDigits(dayOfMonth, 0, dstr);
2227 writer.write(dstr);
2228 if (untilTime != MAX_MILLIS) {
2229 appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2230 if (U_FAILURE(status)) {
2231 return;
2232 }
2233 }
2234 writer.write(ICAL_NEWLINE);
2235 endZoneProps(writer, isDst, status);
2236 }
2237
2238 /*
2239 * Write start times defined by a DOW rule using VTIMEZONE RRULE
2240 */
2241 void
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)) {
2247 return;
2248 }
2249 beginZoneProps(writer, isDst, tzname, fromOffset, toOffset, startTime, status);
2250 if (U_FAILURE(status)) {
2251 return;
2252 }
2253 beginRRULE(writer, month, status);
2254 if (U_FAILURE(status)) {
2255 return;
2256 }
2257 writer.write(ICAL_BYDAY);
2258 writer.write(EQUALS_SIGN);
2259 UnicodeString dstr;
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...
2263
2264 if (untilTime != MAX_MILLIS) {
2265 appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2266 if (U_FAILURE(status)) {
2267 return;
2268 }
2269 }
2270 writer.write(ICAL_NEWLINE);
2271 endZoneProps(writer, isDst, status);
2272 }
2273
2274 /*
2275 * Write start times defined by a DOW_GEQ_DOM rule using VTIMEZONE RRULE
2276 */
2277 void
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)) {
2283 return;
2284 }
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)) {
2291 return;
2292 }
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)) {
2298 return;
2299 }
2300 } else {
2301 // Otherwise, use BYMONTHDAY to include all possible dates
2302 beginZoneProps(writer, isDst, tzname, fromOffset, toOffset, startTime, status);
2303 if (U_FAILURE(status)) {
2304 return;
2305 }
2306 // Check if all days are in the same month
2307 int32_t startDay = dayOfMonth;
2308 int32_t currentMonthDays = 7;
2309
2310 if (dayOfMonth <= 0) {
2311 // The start day is in previous month
2312 int32_t prevMonthDays = 1 - dayOfMonth;
2313 currentMonthDays -= prevMonthDays;
2314
2315 int32_t prevMonth = (month - 1) < 0 ? 11 : month - 1;
2316
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)) {
2323 return;
2324 }
2325
2326 // Start from 1 for the rest
2327 startDay = 1;
2328 } else if (dayOfMonth + 6 > MONTHLENGTH[month]) {
2329 // Note: This code does not actually work well in February. For now, days in month in
2330 // non-leap year.
2331 int32_t nextMonthDays = dayOfMonth + 6 - MONTHLENGTH[month];
2332 currentMonthDays -= nextMonthDays;
2333
2334 int32_t nextMonth = (month + 1) > 11 ? 0 : month + 1;
2335
2336 writeZonePropsByDOW_GEQ_DOM_sub(writer, nextMonth, 1, dayOfWeek, nextMonthDays,
2337 MAX_MILLIS /* Do not use UNTIL */, fromOffset, status);
2338 if (U_FAILURE(status)) {
2339 return;
2340 }
2341 }
2342 writeZonePropsByDOW_GEQ_DOM_sub(writer, month, startDay, dayOfWeek, currentMonthDays,
2343 untilTime, fromOffset, status);
2344 if (U_FAILURE(status)) {
2345 return;
2346 }
2347 endZoneProps(writer, isDst, status);
2348 }
2349 }
2350
2351 /*
2352 * Called from writeZonePropsByDOW_GEQ_DOM
2353 */
2354 void
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 {
2358
2359 if (U_FAILURE(status)) {
2360 return;
2361 }
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;
2367 }
2368 beginRRULE(writer, month, status);
2369 if (U_FAILURE(status)) {
2370 return;
2371 }
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);
2378
2379 UnicodeString dstr;
2380 appendAsciiDigits(startDayNum, 0, dstr);
2381 writer.write(dstr);
2382 for (int32_t i = 1; i < numDays; i++) {
2383 writer.write(COMMA);
2384 dstr.remove();
2385 appendAsciiDigits(startDayNum + i, 0, dstr);
2386 writer.write(dstr);
2387 }
2388
2389 if (untilTime != MAX_MILLIS) {
2390 appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2391 if (U_FAILURE(status)) {
2392 return;
2393 }
2394 }
2395 writer.write(ICAL_NEWLINE);
2396 }
2397
2398 /*
2399 * Write start times defined by a DOW_LEQ_DOM rule using VTIMEZONE RRULE
2400 */
2401 void
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)) {
2407 return;
2408 }
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);
2422 } else {
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);
2426 }
2427 }
2428
2429 /*
2430 * Write the final time zone rule using RRULE, with no UNTIL attribute
2431 */
2432 void
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)) {
2437 return;
2438 }
2439 UBool modifiedRule = TRUE;
2440 const DateTimeRule *dtrule = toWallTimeRule(rule->getRule(), fromRawOffset, fromDSTSavings);
2441 if (dtrule == NULL) {
2442 modifiedRule = FALSE;
2443 dtrule = rule->getRule();
2444 }
2445 int32_t toOffset = rule->getRawOffset() + rule->getDSTSavings();
2446 UnicodeString name;
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);
2452 break;
2453 case DateTimeRule::DOW:
2454 writeZonePropsByDOW(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2455 dtrule->getRuleMonth(), dtrule->getRuleWeekInMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
2456 break;
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);
2460 break;
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);
2464 break;
2465 }
2466 if (modifiedRule) {
2467 delete dtrule;
2468 }
2469 }
2470
2471 /*
2472 * Write the opening section of zone properties
2473 */
2474 void
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)) {
2478 return;
2479 }
2480 writer.write(ICAL_BEGIN);
2481 writer.write(COLON);
2482 if (isDst) {
2483 writer.write(ICAL_DAYLIGHT);
2484 } else {
2485 writer.write(ICAL_STANDARD);
2486 }
2487 writer.write(ICAL_NEWLINE);
2488
2489 UnicodeString dstr;
2490
2491 // TZOFFSETTO
2492 writer.write(ICAL_TZOFFSETTO);
2493 writer.write(COLON);
2494 millisToOffset(toOffset, dstr);
2495 writer.write(dstr);
2496 writer.write(ICAL_NEWLINE);
2497
2498 // TZOFFSETFROM
2499 writer.write(ICAL_TZOFFSETFROM);
2500 writer.write(COLON);
2501 millisToOffset(fromOffset, dstr);
2502 writer.write(dstr);
2503 writer.write(ICAL_NEWLINE);
2504
2505 // TZNAME
2506 writer.write(ICAL_TZNAME);
2507 writer.write(COLON);
2508 writer.write(tzname);
2509 writer.write(ICAL_NEWLINE);
2510
2511 // DTSTART
2512 writer.write(ICAL_DTSTART);
2513 writer.write(COLON);
2514 writer.write(getDateTimeString(startTime + fromOffset, dstr));
2515 writer.write(ICAL_NEWLINE);
2516 }
2517
2518 /*
2519 * Writes the closing section of zone properties
2520 */
2521 void
2522 VTimeZone::endZoneProps(VTZWriter& writer, UBool isDst, UErrorCode& status) const {
2523 if (U_FAILURE(status)) {
2524 return;
2525 }
2526 // END:STANDARD or END:DAYLIGHT
2527 writer.write(ICAL_END);
2528 writer.write(COLON);
2529 if (isDst) {
2530 writer.write(ICAL_DAYLIGHT);
2531 } else {
2532 writer.write(ICAL_STANDARD);
2533 }
2534 writer.write(ICAL_NEWLINE);
2535 }
2536
2537 /*
2538 * Write the beggining part of RRULE line
2539 */
2540 void
2541 VTimeZone::beginRRULE(VTZWriter& writer, int32_t month, UErrorCode& status) const {
2542 if (U_FAILURE(status)) {
2543 return;
2544 }
2545 UnicodeString dstr;
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);
2555 writer.write(dstr);
2556 writer.write(SEMICOLON);
2557 }
2558
2559 /*
2560 * Append the UNTIL attribute after RRULE line
2561 */
2562 void
2563 VTimeZone::appendUNTIL(VTZWriter& writer, const UnicodeString& until, UErrorCode& status) const {
2564 if (U_FAILURE(status)) {
2565 return;
2566 }
2567 if (until.length() > 0) {
2568 writer.write(SEMICOLON);
2569 writer.write(ICAL_UNTIL);
2570 writer.write(EQUALS_SIGN);
2571 writer.write(until);
2572 }
2573 }
2574
2575 U_NAMESPACE_END
2576
2577 #endif /* #if !UCONFIG_NO_FORMATTING */
2578
2579 //eof
2580