]> git.saurik.com Git - apple/icu.git/blame - icuSources/i18n/vtzone.cpp
ICU-461.18.tar.gz
[apple/icu.git] / icuSources / i18n / vtzone.cpp
CommitLineData
46f4442e
A
1/*
2*******************************************************************************
729e4ab9
A
3* Copyright (C) 2007-2010, International Business Machines Corporation and
4* others. All Rights Reserved.
46f4442e
A
5*******************************************************************************
6*/
7
729e4ab9
A
8#include <typeinfo> // for 'typeid' to work
9
46f4442e
A
10#include "unicode/utypes.h"
11
12#if !UCONFIG_NO_FORMATTING
13
14#include "unicode/vtzone.h"
15#include "unicode/rbtz.h"
16#include "unicode/ucal.h"
17#include "unicode/ures.h"
18#include "cmemory.h"
19#include "uvector.h"
20#include "gregoimp.h"
21#include "uhash.h"
22
23U_NAMESPACE_BEGIN
24
25// This is the deleter that will be use to remove TimeZoneRule
26U_CDECL_BEGIN
27static void U_CALLCONV
28deleteTimeZoneRule(void* obj) {
29 delete (TimeZoneRule*) obj;
30}
31U_CDECL_END
32
33// Smybol characters used by RFC2445 VTIMEZONE
34static const UChar COLON = 0x3A; /* : */
35static const UChar SEMICOLON = 0x3B; /* ; */
36static const UChar EQUALS_SIGN = 0x3D; /* = */
37static const UChar COMMA = 0x2C; /* , */
38static const UChar PLUS = 0x2B; /* + */
39static const UChar MINUS = 0x2D; /* - */
40
41// RFC2445 VTIMEZONE tokens
42static const UChar ICAL_BEGIN_VTIMEZONE[] = {0x42, 0x45, 0x47, 0x49, 0x4E, 0x3A, 0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "BEGIN:VTIMEZONE" */
43static const UChar ICAL_END_VTIMEZONE[] = {0x45, 0x4E, 0x44, 0x3A, 0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "END:VTIMEZONE" */
44static const UChar ICAL_BEGIN[] = {0x42, 0x45, 0x47, 0x49, 0x4E, 0}; /* "BEGIN" */
45static const UChar ICAL_END[] = {0x45, 0x4E, 0x44, 0}; /* "END" */
46static const UChar ICAL_VTIMEZONE[] = {0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "VTIMEZONE" */
47static const UChar ICAL_TZID[] = {0x54, 0x5A, 0x49, 0x44, 0}; /* "TZID" */
48static const UChar ICAL_STANDARD[] = {0x53, 0x54, 0x41, 0x4E, 0x44, 0x41, 0x52, 0x44, 0}; /* "STANDARD" */
49static const UChar ICAL_DAYLIGHT[] = {0x44, 0x41, 0x59, 0x4C, 0x49, 0x47, 0x48, 0x54, 0}; /* "DAYLIGHT" */
50static const UChar ICAL_DTSTART[] = {0x44, 0x54, 0x53, 0x54, 0x41, 0x52, 0x54, 0}; /* "DTSTART" */
51static const UChar ICAL_TZOFFSETFROM[] = {0x54, 0x5A, 0x4F, 0x46, 0x46, 0x53, 0x45, 0x54, 0x46, 0x52, 0x4F, 0x4D, 0}; /* "TZOFFSETFROM" */
52static const UChar ICAL_TZOFFSETTO[] = {0x54, 0x5A, 0x4F, 0x46, 0x46, 0x53, 0x45, 0x54, 0x54, 0x4F, 0}; /* "TZOFFSETTO" */
53static const UChar ICAL_RDATE[] = {0x52, 0x44, 0x41, 0x54, 0x45, 0}; /* "RDATE" */
54static const UChar ICAL_RRULE[] = {0x52, 0x52, 0x55, 0x4C, 0x45, 0}; /* "RRULE" */
55static const UChar ICAL_TZNAME[] = {0x54, 0x5A, 0x4E, 0x41, 0x4D, 0x45, 0}; /* "TZNAME" */
56static const UChar ICAL_TZURL[] = {0x54, 0x5A, 0x55, 0x52, 0x4C, 0}; /* "TZURL" */
57static const UChar ICAL_LASTMOD[] = {0x4C, 0x41, 0x53, 0x54, 0x2D, 0x4D, 0x4F, 0x44, 0x49, 0x46, 0x49, 0x45, 0x44, 0}; /* "LAST-MODIFIED" */
58
59static const UChar ICAL_FREQ[] = {0x46, 0x52, 0x45, 0x51, 0}; /* "FREQ" */
60static const UChar ICAL_UNTIL[] = {0x55, 0x4E, 0x54, 0x49, 0x4C, 0}; /* "UNTIL" */
61static const UChar ICAL_YEARLY[] = {0x59, 0x45, 0x41, 0x52, 0x4C, 0x59, 0}; /* "YEARLY" */
62static const UChar ICAL_BYMONTH[] = {0x42, 0x59, 0x4D, 0x4F, 0x4E, 0x54, 0x48, 0}; /* "BYMONTH" */
63static const UChar ICAL_BYDAY[] = {0x42, 0x59, 0x44, 0x41, 0x59, 0}; /* "BYDAY" */
64static const UChar ICAL_BYMONTHDAY[] = {0x42, 0x59, 0x4D, 0x4F, 0x4E, 0x54, 0x48, 0x44, 0x41, 0x59, 0}; /* "BYMONTHDAY" */
65
66static const UChar ICAL_NEWLINE[] = {0x0D, 0x0A, 0}; /* CRLF */
67
68static 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" */};
76
77// Month length for non-leap year
78static const int32_t MONTHLENGTH[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
79
80// ICU custom property
81static const UChar ICU_TZINFO_PROP[] = {0x58, 0x2D, 0x54, 0x5A, 0x49, 0x4E, 0x46, 0x4F, 0x3A, 0}; /* "X-TZINFO:" */
82static const UChar ICU_TZINFO_PARTIAL[] = {0x2F, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6C, 0x40, 0}; /* "/Partial@" */
83static const UChar ICU_TZINFO_SIMPLE[] = {0x2F, 0x53, 0x69, 0x6D, 0x70, 0x6C, 0x65, 0x40, 0}; /* "/Simple@" */
84
85
86/*
87 * Simple fixed digit ASCII number to integer converter
88 */
89static int32_t parseAsciiDigits(const UnicodeString& str, int32_t start, int32_t length, UErrorCode& status) {
90 if (U_FAILURE(status)) {
91 return 0;
92 }
93 if (length <= 0 || str.length() < start || (start + length) > str.length()) {
94 status = U_INVALID_FORMAT_ERROR;
95 return 0;
96 }
97 int32_t sign = 1;
98 if (str.charAt(start) == PLUS) {
99 start++;
100 length--;
101 } else if (str.charAt(start) == MINUS) {
102 sign = -1;
103 start++;
104 length--;
105 }
106 int32_t num = 0;
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;
111 return 0;
112 }
113 num = 10 * num + digit;
114 }
115 return sign * num;
116}
117
118static 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
121 int32_t i;
122
123 if (number < 0) {
124 negative = TRUE;
125 number *= -1;
126 }
127
128 length = length > 10 ? 10 : length;
129 if (length == 0) {
130 // variable length
131 i = 0;
132 do {
133 digits[i++] = number % 10;
134 number /= 10;
135 } while (number != 0);
136 length = i;
137 } else {
138 // fixed digits
139 for (i = 0; i < length; i++) {
140 digits[i] = number % 10;
141 number /= 10;
142 }
143 }
144 if (negative) {
145 str.append(MINUS);
146 }
147 for (i = length - 1; i >= 0; i--) {
148 str.append((UChar)(digits[i] + 0x0030));
149 }
150 return str;
151}
152
153static UnicodeString& appendMillis(UDate date, UnicodeString& str) {
154 UBool negative = FALSE;
155 int32_t digits[20]; // max int64_t is 20 decimal digits
156 int32_t i;
157 int64_t number;
158
159 if (date < MIN_MILLIS) {
160 number = (int64_t)MIN_MILLIS;
161 } else if (date > MAX_MILLIS) {
162 number = (int64_t)MAX_MILLIS;
163 } else {
164 number = (int64_t)date;
165 }
166 if (number < 0) {
167 negative = TRUE;
168 number *= -1;
169 }
170 i = 0;
171 do {
172 digits[i++] = (int32_t)(number % 10);
173 number /= 10;
174 } while (number != 0);
175
176 if (negative) {
177 str.append(MINUS);
178 }
179 i--;
180 while (i >= 0) {
181 str.append((UChar)(digits[i--] + 0x0030));
182 }
183 return str;
184}
185
186/*
187 * Convert date/time to RFC2445 Date-Time form #1 DATE WITH LOCAL TIME
188 */
189static 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);
192
193 str.remove();
194 appendAsciiDigits(year, 4, str);
195 appendAsciiDigits(month + 1, 2, str);
196 appendAsciiDigits(dom, 2, str);
197 str.append((UChar)0x0054 /*'T'*/);
198
199 int32_t t = mid;
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;
205
206 appendAsciiDigits(hour, 2, str);
207 appendAsciiDigits(min, 2, str);
208 appendAsciiDigits(sec, 2, str);
209 return str;
210}
211
212/*
213 * Convert date/time to RFC2445 Date-Time form #2 DATE WITH UTC TIME
214 */
215static UnicodeString& getUTCDateTimeString(UDate time, UnicodeString& str) {
216 getDateTimeString(time, str);
217 str.append((UChar)0x005A /*'Z'*/);
218 return str;
219}
220
221/*
222 * Parse RFC2445 Date-Time form #1 DATE WITH LOCAL TIME and
223 * #2 DATE WITH UTC TIME
224 */
225static UDate parseDateTimeString(const UnicodeString& str, int32_t offset, UErrorCode& status) {
226 if (U_FAILURE(status)) {
227 return 0.0;
228 }
229
230 int32_t year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0;
231 UBool isUTC = FALSE;
232 UBool isValid = FALSE;
233 do {
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"
238 break;
239 }
240 if (str.charAt(8) != 0x0054) {
241 // charcter "T" must be used for separating date and time
242 break;
243 }
244 if (length == 16) {
245 if (str.charAt(15) != 0x005A) {
246 // invalid format
247 break;
248 }
249 isUTC = TRUE;
250 }
251
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);
258
259 if (U_FAILURE(status)) {
260 break;
261 }
262
263 // check valid range
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) {
267 break;
268 }
269
270 isValid = TRUE;
271 } while(false);
272
273 if (!isValid) {
274 status = U_INVALID_FORMAT_ERROR;
275 return 0.0;
276 }
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);
280 if (!isUTC) {
281 time -= offset;
282 }
283 return time;
284}
285
286/*
287 * Convert RFC2445 utc-offset string to milliseconds
288 */
289static int32_t offsetStrToMillis(const UnicodeString& str, UErrorCode& status) {
290 if (U_FAILURE(status)) {
291 return 0;
292 }
293
294 UBool isValid = FALSE;
295 int32_t sign = 0, hour = 0, min = 0, sec = 0;
296
297 do {
298 int length = str.length();
299 if (length != 5 && length != 7) {
300 // utf-offset must be 5 or 7 characters
301 break;
302 }
303 // sign
304 UChar s = str.charAt(0);
305 if (s == PLUS) {
306 sign = 1;
307 } else if (s == MINUS) {
308 sign = -1;
309 } else {
310 // utf-offset must start with "+" or "-"
311 break;
312 }
313 hour = parseAsciiDigits(str, 1, 2, status);
314 min = parseAsciiDigits(str, 3, 2, status);
315 if (length == 7) {
316 sec = parseAsciiDigits(str, 5, 2, status);
317 }
318 if (U_FAILURE(status)) {
319 break;
320 }
321 isValid = true;
322 } while(false);
323
324 if (!isValid) {
325 status = U_INVALID_FORMAT_ERROR;
326 return 0;
327 }
328 int32_t millis = sign * ((hour * 60 + min) * 60 + sec) * 1000;
329 return millis;
330}
331
332/*
333 * Convert milliseconds to RFC2445 utc-offset string
334 */
335static void millisToOffset(int32_t millis, UnicodeString& str) {
336 str.remove();
337 if (millis >= 0) {
338 str.append(PLUS);
339 } else {
340 str.append(MINUS);
341 millis = -millis;
342 }
343 int32_t hour, min, sec;
344 int32_t t = millis / 1000;
345
346 sec = t % 60;
347 t = (t - sec) / 60;
348 min = t % 60;
349 hour = t / 60;
350
351 appendAsciiDigits(hour, 2, str);
352 appendAsciiDigits(min, 2, str);
353 appendAsciiDigits(sec, 2, str);
354}
355
356/*
357 * Create a default TZNAME from TZID
358 */
729e4ab9
A
359static void getDefaultTZName(const UnicodeString tzid, UBool isDST, UnicodeString& zonename) {
360 zonename = tzid;
46f4442e 361 if (isDST) {
729e4ab9 362 zonename += UNICODE_STRING_SIMPLE("(DST)");
46f4442e 363 } else {
729e4ab9 364 zonename += UNICODE_STRING_SIMPLE("(STD)");
46f4442e
A
365 }
366}
367
368/*
369 * Parse individual RRULE
370 *
371 * On return -
372 *
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
379 */
380static 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)) {
383 return;
384 }
385 int32_t numDom = 0;
386
387 month = -1;
388 dow = 0;
389 wim = 0;
390 until = MIN_MILLIS;
391
392 UBool yearly = FALSE;
393 //UBool parseError = FALSE;
394
395 int32_t prop_start = 0;
396 int32_t prop_end;
397 UnicodeString prop, attr, value;
398 UBool nextProp = TRUE;
399
400 while (nextProp) {
401 prop_end = rrule.indexOf(SEMICOLON, prop_start);
402 if (prop_end == -1) {
403 prop.setTo(rrule, prop_start);
404 nextProp = FALSE;
405 } else {
406 prop.setTo(rrule, prop_start, prop_end - prop_start);
407 prop_start = prop_end + 1;
408 }
409 int32_t eql = prop.indexOf(EQUALS_SIGN);
410 if (eql != -1) {
411 attr.setTo(prop, 0, eql);
412 value.setTo(prop, eql + 1);
413 } else {
414 goto rruleParseError;
415 }
416
417 if (attr.compare(ICAL_FREQ) == 0) {
418 // only support YEARLY frequency type
419 if (value.compare(ICAL_YEARLY) == 0) {
420 yearly = TRUE;
421 } else {
422 goto rruleParseError;
423 }
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;
429 }
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;
435 }
436 month = parseAsciiDigits(value, 0, value.length(), status) - 1;
437 if (U_FAILURE(status) || month < 0 || month >= 12) {
438 goto rruleParseError;
439 }
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.
443
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;
449 }
450 if (length > 2) {
451 // Nth day of week
452 int32_t sign = 1;
453 if (value.charAt(0) == PLUS) {
454 sign = 1;
455 } else if (value.charAt(0) == MINUS) {
456 sign = -1;
457 } else if (length == 4) {
458 goto rruleParseError;
459 }
460 int32_t n = parseAsciiDigits(value, length - 3, 1, status);
461 if (U_FAILURE(status) || n == 0 || n > 4) {
462 goto rruleParseError;
463 }
464 wim = n * sign;
465 value.remove(0, length - 2);
466 }
467 int32_t wday;
468 for (wday = 0; wday < 7; wday++) {
469 if (value.compare(ICAL_DOW_NAMES[wday], 2) == 0) {
470 break;
471 }
472 }
473 if (wday < 7) {
474 // Sunday(1) - Saturday(7)
475 dow = wday + 1;
476 } else {
477 goto rruleParseError;
478 }
479 } else if (attr.compare(ICAL_BYMONTHDAY) == 0) {
480 // Note: BYMONTHDAY may contain multiple days delimitted by comma
481 //
482 // A value of BYMONTHDAY could be negative, for example, -1 means
483 // the last day in a month
484 int32_t dom_idx = 0;
485 int32_t dom_start = 0;
486 int32_t dom_end;
487 UBool nextDOM = TRUE;
488 while (nextDOM) {
489 dom_end = value.indexOf(COMMA, dom_start);
490 if (dom_end == -1) {
491 dom_end = value.length();
492 nextDOM = FALSE;
493 }
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;
498 }
499 dom_idx++;
500 } else {
501 status = U_BUFFER_OVERFLOW_ERROR;
502 goto rruleParseError;
503 }
504 dom_start = dom_end + 1;
505 }
506 numDom = dom_idx;
507 }
508 }
509 if (!yearly) {
510 // FREQ=YEARLY must be set
511 goto rruleParseError;
512 }
513 // Set actual number of parsed DOM (ICAL_BYMONTHDAY)
514 domCount = numDom;
515 return;
516
517rruleParseError:
518 if (U_SUCCESS(status)) {
519 // Set error status
520 status = U_INVALID_FORMAT_ERROR;
521 }
522}
523
729e4ab9 524static TimeZoneRule* createRuleByRRULE(const UnicodeString& zonename, int rawOffset, int dstSavings, UDate start,
46f4442e
A
525 UVector* dates, int fromOffset, UErrorCode& status) {
526 if (U_FAILURE(status)) {
527 return NULL;
528 }
529 if (dates == NULL || dates->size() == 0) {
530 status = U_ILLEGAL_ARGUMENT_ERROR;
531 return NULL;
532 }
533
534 int32_t i, j;
535 DateTimeRule *adtr = NULL;
536
537 // Parse the first rule
538 UnicodeString rrule = *((UnicodeString*)dates->elementAt(0));
539 int32_t month, dayOfWeek, nthDayOfWeek, dayOfMonth = 0;
540 int32_t days[7];
541 int32_t daysCount = sizeof(days)/sizeof(days[0]);
542 UDate until;
543
544 parseRRULE(rrule, month, dayOfWeek, nthDayOfWeek, days, daysCount, until, status);
545 if (U_FAILURE(status)) {
546 return NULL;
547 }
548
549 if (dates->size() == 1) {
550 // No more rules
551 if (daysCount > 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;
557 }
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
562 // as the base.
563 if (days[i] < 0) {
564 days[i] = MONTHLENGTH[month] + days[i] + 1;
565 }
566 if (days[i] < firstDay) {
567 firstDay = days[i];
568 }
569 }
570 // Make sure days are continuous
571 for (i = 1; i < 7; i++) {
572 UBool found = FALSE;
573 for (j = 0; j < 7; j++) {
574 if (days[j] == firstDay + i) {
575 found = TRUE;
576 break;
577 }
578 }
579 if (!found) {
580 // days are not continuous
581 goto unsupportedRRule;
582 }
583 }
584 // Use DOW_GEQ_DOM rule with firstDay as the start date
585 dayOfMonth = firstDay;
586 }
587 } else {
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;
593 }
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;
598 }
599
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
603
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;
610 }
611
612 int32_t anotherMonth = -1;
613 for (i = 1; i < dates->size(); i++) {
614 rrule = *((UnicodeString*)dates->elementAt(i));
615 UDate tmp_until;
616 int32_t tmp_month, tmp_dayOfWeek, tmp_nthDayOfWeek;
617 int32_t tmp_days[7];
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)) {
621 return NULL;
622 }
623 // If UNTIL is newer than previous one, use the one
624 if (tmp_until > until) {
625 until = tmp_until;
626 }
627
628 // Check if BYMONTH + BYMONTHDAY + BYDAY rule
629 if (tmp_month == -1 || tmp_dayOfWeek == 0 || tmp_daysCount == 0) {
630 goto unsupportedRRule;
631 }
632 // Count number of BYMONTHDAY
633 if (daysCount + tmp_daysCount > 7) {
634 // We cannot support BYMONTHDAY more than 7
635 goto unsupportedRRule;
636 }
637 // Check if the same BYDAY is used. Otherwise, we cannot
638 // support the rule
639 if (tmp_dayOfWeek != dayOfWeek) {
640 goto unsupportedRRule;
641 }
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) {
647 // Previous month
648 anotherMonth = tmp_month;
649 earliestMonth = anotherMonth;
650 // Reset earliest day
651 earliestDay = 31;
652 } else if (diff == 11 || diff == 1) {
653 // Next month
654 anotherMonth = tmp_month;
655 } else {
656 // The day range cannot exceed more than 2 months
657 goto unsupportedRRule;
658 }
659 } else if (tmp_month != month && tmp_month != anotherMonth) {
660 // The day range cannot exceed more than 2 months
661 goto unsupportedRRule;
662 }
663 }
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;
669 }
670 }
671 daysCount += tmp_daysCount;
672 }
673 if (daysCount != 7) {
674 // Number of BYMONTHDAY entries must be 7
675 goto unsupportedRRule;
676 }
677 month = earliestMonth;
678 dayOfMonth = earliestDay;
679 }
680
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);
685 if (month == -1) {
686 // If BYMONTH is not set, use the month of DTSTART
687 month = startMonth;
688 }
689 if (dayOfWeek == 0 && nthDayOfWeek == 0 && dayOfMonth == 0) {
690 // If only YEARLY is set, use the day of DTSTART as BYMONTHDAY
691 dayOfMonth = startDOM;
692 }
693
694 int32_t endYear;
695 if (until != MIN_MILLIS) {
696 int32_t endMonth, endDOM, endDOW, endDOY, endMID;
697 Grego::timeToFields(until, endYear, endMonth, endDOM, endDOW, endDOY, endMID);
698 } else {
699 endYear = AnnualTimeZoneRule::MAX_YEAR;
700 }
701
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);
713 }
714 if (adtr == NULL) {
715 goto unsupportedRRule;
716 }
729e4ab9 717 return new AnnualTimeZoneRule(zonename, rawOffset, dstSavings, adtr, startYear, endYear);
46f4442e
A
718
719unsupportedRRule:
720 status = U_INVALID_STATE_ERROR;
721 return NULL;
722}
723
724/*
725 * Create a TimeZoneRule by the RDATE definition
726 */
729e4ab9 727static TimeZoneRule* createRuleByRDATE(const UnicodeString& zonename, int32_t rawOffset, int32_t dstSavings,
46f4442e
A
728 UDate start, UVector* dates, int32_t fromOffset, UErrorCode& status) {
729 if (U_FAILURE(status)) {
730 return NULL;
731 }
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
729e4ab9 736 retVal = new TimeArrayTimeZoneRule(zonename, rawOffset, dstSavings,
46f4442e
A
737 &start, 1, DateTimeRule::UTC_TIME);
738 } else {
739 // Create an array of transition times
740 int32_t size = dates->size();
741 UDate* times = (UDate*)uprv_malloc(sizeof(UDate) * size);
742 if (times == NULL) {
743 status = U_MEMORY_ALLOCATION_ERROR;
744 return NULL;
745 }
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)) {
750 uprv_free(times);
751 return NULL;
752 }
753 }
729e4ab9 754 retVal = new TimeArrayTimeZoneRule(zonename, rawOffset, dstSavings,
46f4442e
A
755 times, size, DateTimeRule::UTC_TIME);
756 uprv_free(times);
757 }
758 return retVal;
759}
760
761/*
762 * Check if the DOW rule specified by month, weekInMonth and dayOfWeek is equivalent
763 * to the DateTimerule.
764 */
765static UBool isEquivalentDateRule(int32_t month, int32_t weekInMonth, int32_t dayOfWeek, const DateTimeRule *dtrule) {
766 if (month != dtrule->getRuleMonth() || dayOfWeek != dtrule->getRuleDayOfWeek()) {
767 return FALSE;
768 }
769 if (dtrule->getTimeRuleType() != DateTimeRule::WALL_TIME) {
770 // Do not try to do more intelligent comparison for now.
771 return FALSE;
772 }
773 if (dtrule->getDateRuleType() == DateTimeRule::DOW
774 && dtrule->getRuleWeekInMonth() == weekInMonth) {
775 return TRUE;
776 }
777 int32_t ruleDOM = dtrule->getRuleDayOfMonth();
778 if (dtrule->getDateRuleType() == DateTimeRule::DOW_GEQ_DOM) {
779 if (ruleDOM%7 == 1 && (ruleDOM + 6)/7 == weekInMonth) {
780 return TRUE;
781 }
782 if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - ruleDOM)%7 == 6
783 && weekInMonth == -1*((MONTHLENGTH[month]-ruleDOM+1)/7)) {
784 return TRUE;
785 }
786 }
787 if (dtrule->getDateRuleType() == DateTimeRule::DOW_LEQ_DOM) {
788 if (ruleDOM%7 == 0 && ruleDOM/7 == weekInMonth) {
789 return TRUE;
790 }
791 if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - ruleDOM)%7 == 0
792 && weekInMonth == -1*((MONTHLENGTH[month] - ruleDOM)/7 + 1)) {
793 return TRUE;
794 }
795 }
796 return FALSE;
797}
798
799/*
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.
803 */
804static DateTimeRule* toWallTimeRule(const DateTimeRule* rule, int32_t rawOffset, int32_t dstSavings) {
805 if (rule->getTimeRuleType() == DateTimeRule::WALL_TIME) {
806 return NULL;
807 }
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) {
812 wallt += dstSavings;
813 }
814
815 int32_t month = -1, dom = 0, dow = 0;
816 DateTimeRule::DateRuleType dtype;
817 int32_t dshift = 0;
818 if (wallt < 0) {
819 dshift = -1;
820 wallt += U_MILLIS_PER_DAY;
821 } else if (wallt >= U_MILLIS_PER_DAY) {
822 dshift = 1;
823 wallt -= U_MILLIS_PER_DAY;
824 }
825
826 month = rule->getRuleMonth();
827 dom = rule->getRuleDayOfMonth();
828 dow = rule->getRuleDayOfWeek();
829 dtype = rule->getDateRuleType();
830
831 if (dshift != 0) {
832 if (dtype == DateTimeRule::DOW) {
833 // Convert to DOW_GEW_DOM or DOW_LEQ_DOM rule first
834 int32_t wim = rule->getRuleWeekInMonth();
835 if (wim > 0) {
836 dtype = DateTimeRule::DOW_GEQ_DOM;
837 dom = 7 * (wim - 1) + 1;
838 } else {
839 dtype = DateTimeRule::DOW_LEQ_DOM;
840 dom = MONTHLENGTH[month] + 7 * (wim + 1);
841 }
842 }
843 // Shift one day before or after
844 dom += dshift;
845 if (dom == 0) {
846 month--;
847 month = month < UCAL_JANUARY ? UCAL_DECEMBER : month;
848 dom = MONTHLENGTH[month];
849 } else if (dom > MONTHLENGTH[month]) {
850 month++;
851 month = month > UCAL_DECEMBER ? UCAL_JANUARY : month;
852 dom = 1;
853 }
854 if (dtype != DateTimeRule::DOM) {
855 // Adjust day of week
856 dow += dshift;
857 if (dow < UCAL_SUNDAY) {
858 dow = UCAL_SATURDAY;
859 } else if (dow > UCAL_SATURDAY) {
860 dow = UCAL_SUNDAY;
861 }
862 }
863 }
864 // Create a new rule
865 DateTimeRule *modifiedRule;
866 if (dtype == DateTimeRule::DOM) {
867 modifiedRule = new DateTimeRule(month, dom, wallt, DateTimeRule::WALL_TIME);
868 } else {
869 modifiedRule = new DateTimeRule(month, dom, dow,
870 (dtype == DateTimeRule::DOW_GEQ_DOM), wallt, DateTimeRule::WALL_TIME);
871 }
872 return modifiedRule;
873}
874
875/*
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/
880 * Reader.
881 */
882class VTZWriter {
883public:
884 VTZWriter(UnicodeString& out);
885 ~VTZWriter();
886
887 void write(const UnicodeString& str);
888 void write(UChar ch);
889 //void write(const UChar* str, int32_t length);
890private:
891 UnicodeString* out;
892};
893
894VTZWriter::VTZWriter(UnicodeString& output) {
895 out = &output;
896}
897
898VTZWriter::~VTZWriter() {
899}
900
901void
902VTZWriter::write(const UnicodeString& str) {
903 out->append(str);
904}
905
906void
907VTZWriter::write(UChar ch) {
908 out->append(ch);
909}
910
911/*
912void
913VTZWriter::write(const UChar* str, int32_t length) {
914 out->append(str, length);
915}
916*/
917
918class VTZReader {
919public:
920 VTZReader(const UnicodeString& input);
921 ~VTZReader();
922
923 UChar read(void);
924private:
925 const UnicodeString* in;
926 int32_t index;
927};
928
929VTZReader::VTZReader(const UnicodeString& input) {
930 in = &input;
931 index = 0;
932}
933
934VTZReader::~VTZReader() {
935}
936
937UChar
938VTZReader::read(void) {
939 UChar ch = 0xFFFF;
940 if (index < in->length()) {
941 ch = in->charAt(index);
942 }
943 index++;
944 return ch;
945}
946
947
948UOBJECT_DEFINE_RTTI_IMPLEMENTATION(VTimeZone)
949
950VTimeZone::VTimeZone()
951: BasicTimeZone(), tz(NULL), vtzlines(NULL),
952 lastmod(MAX_MILLIS) {
953}
954
955VTimeZone::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();
961 }
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)) {
971 break;
972 }
973 }
974 }
975 if (U_FAILURE(status) && vtzlines != NULL) {
976 delete vtzlines;
977 }
978 }
979}
980
981VTimeZone::~VTimeZone() {
982 if (tz != NULL) {
983 delete tz;
984 }
985 if (vtzlines != NULL) {
986 delete vtzlines;
987 }
988}
989
990VTimeZone&
991VTimeZone::operator=(const VTimeZone& right) {
992 if (this == &right) {
993 return *this;
994 }
995 if (*this != right) {
996 BasicTimeZone::operator=(right);
997 if (tz != NULL) {
998 delete tz;
999 tz = NULL;
1000 }
1001 if (right.tz != NULL) {
1002 tz = (BasicTimeZone*)right.tz->clone();
1003 }
1004 if (vtzlines != NULL) {
1005 delete vtzlines;
1006 }
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)) {
1016 break;
1017 }
1018 }
1019 }
1020 if (U_FAILURE(status) && vtzlines != NULL) {
1021 delete vtzlines;
1022 vtzlines = NULL;
1023 }
1024 }
1025 tzurl = right.tzurl;
1026 lastmod = right.lastmod;
1027 olsonzid = right.olsonzid;
1028 icutzver = right.icutzver;
1029 }
1030 return *this;
1031}
1032
1033UBool
1034VTimeZone::operator==(const TimeZone& that) const {
1035 if (this == &that) {
1036 return TRUE;
1037 }
729e4ab9 1038 if (typeid(*this) != typeid(that) || !BasicTimeZone::operator==(that)) {
46f4442e
A
1039 return FALSE;
1040 }
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 */) {
1047 return TRUE;
1048 }
1049 return FALSE;
1050}
1051
1052UBool
1053VTimeZone::operator!=(const TimeZone& that) const {
1054 return !operator==(that);
1055}
1056
1057VTimeZone*
1058VTimeZone::createVTimeZoneByID(const UnicodeString& ID) {
1059 VTimeZone *vtz = new VTimeZone();
1060 vtz->tz = (BasicTimeZone*)TimeZone::createTimeZone(ID);
1061 vtz->tz->getID(vtz->olsonzid);
1062
1063 // Set ICU tzdata version
1064 UErrorCode status = U_ZERO_ERROR;
1065 UResourceBundle *bundle = NULL;
1066 const UChar* versionStr = NULL;
729e4ab9
A
1067 int32_t len = 0;
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);
1072 }
1073 ures_close(bundle);
1074 return vtz;
1075}
1076
1077VTimeZone*
1078VTimeZone::createVTimeZoneFromBasicTimeZone(const BasicTimeZone& basic_time_zone, UErrorCode &status) {
1079 if (U_FAILURE(status)) {
1080 return NULL;
1081 }
1082 VTimeZone *vtz = new VTimeZone();
1083 if (vtz == NULL) {
1084 status = U_MEMORY_ALLOCATION_ERROR;
1085 return NULL;
1086 }
1087 vtz->tz = (BasicTimeZone *)basic_time_zone.clone();
1088 if (vtz->tz == NULL) {
1089 status = U_MEMORY_ALLOCATION_ERROR;
1090 delete vtz;
1091 return NULL;
1092 }
1093 vtz->tz->getID(vtz->olsonzid);
1094
1095 // Set ICU tzdata version
1096 UResourceBundle *bundle = NULL;
1097 const UChar* versionStr = NULL;
1098 int32_t len = 0;
1099 bundle = ures_openDirect(NULL, "zoneinfo64", &status);
46f4442e
A
1100 versionStr = ures_getStringByKey(bundle, "TZVersion", &len, &status);
1101 if (U_SUCCESS(status)) {
1102 vtz->icutzver.setTo(versionStr, len);
1103 }
1104 ures_close(bundle);
1105 return vtz;
1106}
1107
1108VTimeZone*
1109VTimeZone::createVTimeZone(const UnicodeString& vtzdata, UErrorCode& status) {
1110 if (U_FAILURE(status)) {
1111 return NULL;
1112 }
1113 VTZReader reader(vtzdata);
1114 VTimeZone *vtz = new VTimeZone();
1115 vtz->load(reader, status);
1116 if (U_FAILURE(status)) {
1117 delete vtz;
1118 return NULL;
1119 }
1120 return vtz;
1121}
1122
1123UBool
1124VTimeZone::getTZURL(UnicodeString& url) const {
1125 if (tzurl.length() > 0) {
1126 url = tzurl;
1127 return TRUE;
1128 }
1129 return FALSE;
1130}
1131
1132void
1133VTimeZone::setTZURL(const UnicodeString& url) {
1134 tzurl = url;
1135}
1136
1137UBool
1138VTimeZone::getLastModified(UDate& lastModified) const {
1139 if (lastmod != MAX_MILLIS) {
1140 lastModified = lastmod;
1141 return TRUE;
1142 }
1143 return FALSE;
1144}
1145
1146void
1147VTimeZone::setLastModified(UDate lastModified) {
1148 lastmod = lastModified;
1149}
1150
1151void
1152VTimeZone::write(UnicodeString& result, UErrorCode& status) const {
1153 result.remove();
1154 VTZWriter writer(result);
1155 write(writer, status);
1156}
1157
1158void
1159VTimeZone::write(UDate start, UnicodeString& result, UErrorCode& status) /*const*/ {
1160 result.remove();
1161 VTZWriter writer(result);
1162 write(start, writer, status);
1163}
1164
1165void
1166VTimeZone::writeSimple(UDate time, UnicodeString& result, UErrorCode& status) /*const*/ {
1167 result.remove();
1168 VTZWriter writer(result);
1169 writeSimple(time, writer, status);
1170}
1171
1172TimeZone*
1173VTimeZone::clone(void) const {
1174 return new VTimeZone(*this);
1175}
1176
1177int32_t
1178VTimeZone::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);
1181}
1182
1183int32_t
1184VTimeZone::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);
1188}
1189
1190void
1191VTimeZone::getOffset(UDate date, UBool local, int32_t& rawOffset,
1192 int32_t& dstOffset, UErrorCode& status) const {
1193 return tz->getOffset(date, local, rawOffset, dstOffset, status);
1194}
1195
1196void
1197VTimeZone::setRawOffset(int32_t offsetMillis) {
1198 tz->setRawOffset(offsetMillis);
1199}
1200
1201int32_t
1202VTimeZone::getRawOffset(void) const {
1203 return tz->getRawOffset();
1204}
1205
1206UBool
1207VTimeZone::useDaylightTime(void) const {
1208 return tz->useDaylightTime();
1209}
1210
1211UBool
1212VTimeZone::inDaylightTime(UDate date, UErrorCode& status) const {
1213 return tz->inDaylightTime(date, status);
1214}
1215
1216UBool
1217VTimeZone::hasSameRules(const TimeZone& other) const {
1218 return tz->hasSameRules(other);
1219}
1220
1221UBool
1222VTimeZone::getNextTransition(UDate base, UBool inclusive, TimeZoneTransition& result) /*const*/ {
1223 return tz->getNextTransition(base, inclusive, result);
1224}
1225
1226UBool
1227VTimeZone::getPreviousTransition(UDate base, UBool inclusive, TimeZoneTransition& result) /*const*/ {
1228 return tz->getPreviousTransition(base, inclusive, result);
1229}
1230
1231int32_t
1232VTimeZone::countTransitionRules(UErrorCode& status) /*const*/ {
1233 return tz->countTransitionRules(status);
1234}
1235
1236void
1237VTimeZone::getTimeZoneRules(const InitialTimeZoneRule*& initial,
1238 const TimeZoneRule* trsrules[], int32_t& trscount,
1239 UErrorCode& status) /*const*/ {
1240 tz->getTimeZoneRules(initial, trsrules, trscount, status);
1241}
1242
1243void
1244VTimeZone::load(VTZReader& reader, UErrorCode& status) {
1245 vtzlines = new UVector(uhash_deleteUnicodeString, uhash_compareUnicodeString, DEFAULT_VTIMEZONE_LINES, status);
1246 if (U_FAILURE(status)) {
1247 return;
1248 }
1249 UBool eol = FALSE;
1250 UBool start = FALSE;
1251 UBool success = FALSE;
1252 UnicodeString line;
1253
1254 while (TRUE) {
1255 UChar ch = reader.read();
1256 if (ch == 0xFFFF) {
1257 // end of file
1258 if (start && line.startsWith(ICAL_END_VTIMEZONE)) {
1259 vtzlines->addElement(new UnicodeString(line), status);
1260 if (U_FAILURE(status)) {
1261 goto cleanupVtzlines;
1262 }
1263 success = TRUE;
1264 }
1265 break;
1266 }
1267 if (ch == 0x000D) {
1268 // CR, must be followed by LF according to the definition in RFC2445
1269 continue;
1270 }
1271 if (eol) {
1272 if (ch != 0x0009 && ch != 0x0020) {
1273 // NOT followed by TAB/SP -> new line
1274 if (start) {
1275 if (line.length() > 0) {
1276 vtzlines->addElement(new UnicodeString(line), status);
1277 if (U_FAILURE(status)) {
1278 goto cleanupVtzlines;
1279 }
1280 }
1281 }
1282 line.remove();
1283 if (ch != 0x000A) {
1284 line.append(ch);
1285 }
1286 }
1287 eol = FALSE;
1288 } else {
1289 if (ch == 0x000A) {
1290 // LF
1291 eol = TRUE;
1292 if (start) {
1293 if (line.startsWith(ICAL_END_VTIMEZONE)) {
1294 vtzlines->addElement(new UnicodeString(line), status);
1295 if (U_FAILURE(status)) {
1296 goto cleanupVtzlines;
1297 }
1298 success = TRUE;
1299 break;
1300 }
1301 } else {
1302 if (line.startsWith(ICAL_BEGIN_VTIMEZONE)) {
1303 vtzlines->addElement(new UnicodeString(line), status);
1304 if (U_FAILURE(status)) {
1305 goto cleanupVtzlines;
1306 }
1307 line.remove();
1308 start = TRUE;
1309 eol = FALSE;
1310 }
1311 }
1312 } else {
1313 line.append(ch);
1314 }
1315 }
1316 }
1317 if (!success) {
1318 if (U_SUCCESS(status)) {
1319 status = U_INVALID_STATE_ERROR;
1320 }
1321 goto cleanupVtzlines;
1322 }
1323 parse(status);
1324 return;
1325
1326cleanupVtzlines:
1327 delete vtzlines;
1328 vtzlines = NULL;
1329}
1330
1331// parser state
1332#define INI 0 // Initial state
1333#define VTZ 1 // In VTIMEZONE
1334#define TZI 2 // In STANDARD or DAYLIGHT
1335
1336#define DEF_DSTSAVINGS (60*60*1000)
1337#define DEF_TZSTARTTIME (0.0)
1338
1339void
1340VTimeZone::parse(UErrorCode& status) {
1341 if (U_FAILURE(status)) {
1342 return;
1343 }
1344 if (vtzlines == NULL || vtzlines->size() == 0) {
1345 status = U_INVALID_STATE_ERROR;
1346 return;
1347 }
1348 InitialTimeZoneRule *initialRule = NULL;
1349 RuleBasedTimeZone *rbtz = NULL;
1350
1351 // timezone ID
1352 UnicodeString tzid;
1353
1354 int32_t state = INI;
1355 int32_t n = 0;
1356 UBool dst = FALSE; // current zone type
1357 UnicodeString from; // current zone from offset
1358 UnicodeString to; // current zone offset
729e4ab9 1359 UnicodeString zonename; // current zone name
46f4442e
A
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
1367
1368 UVector *dates = NULL; // list of RDATE or RRULE strings
1369 UVector *rules = NULL; // list of TimeZoneRule instances
1370
1371 int32_t finalRuleIdx = -1;
1372 int32_t finalRuleCount = 0;
1373
1374 rules = new UVector(status);
1375 if (U_FAILURE(status)) {
1376 goto cleanupParse;
1377 }
1378 // Set the deleter to remove TimeZoneRule vectors to avoid memory leaks due to unowned TimeZoneRules.
1379 rules->setDeleter(deleteTimeZoneRule);
1380
1381 dates = new UVector(uhash_deleteUnicodeString, uhash_compareUnicodeString, status);
1382 if (U_FAILURE(status)) {
1383 goto cleanupParse;
1384 }
1385 if (rules == NULL || dates == NULL) {
1386 status = U_MEMORY_ALLOCATION_ERROR;
1387 goto cleanupParse;
1388 }
1389
1390 for (n = 0; n < vtzlines->size(); n++) {
1391 UnicodeString *line = (UnicodeString*)vtzlines->elementAt(n);
1392 int32_t valueSep = line->indexOf(COLON);
1393 if (valueSep < 0) {
1394 continue;
1395 }
1396 name.setTo(*line, 0, valueSep);
1397 value.setTo(*line, valueSep + 1);
1398
1399 switch (state) {
1400 case INI:
1401 if (name.compare(ICAL_BEGIN) == 0
1402 && value.compare(ICAL_VTIMEZONE) == 0) {
1403 state = VTZ;
1404 }
1405 break;
1406
1407 case VTZ:
1408 if (name.compare(ICAL_TZID) == 0) {
1409 tzid = value;
1410 } else if (name.compare(ICAL_TZURL) == 0) {
1411 tzurl = value;
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)) {
1417 goto cleanupParse;
1418 }
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) {
1424 goto cleanupParse;
1425 }
1426 // initialize current zone properties
1427 if (dates->size() != 0) {
1428 dates->removeAllElements();
1429 }
1430 isRRULE = FALSE;
1431 from.remove();
1432 to.remove();
729e4ab9 1433 zonename.remove();
46f4442e
A
1434 dst = isDST;
1435 state = TZI;
1436 } else {
1437 // BEGIN property other than STANDARD/DAYLIGHT
1438 // must not be there.
1439 goto cleanupParse;
1440 }
1441 } else if (name.compare(ICAL_END) == 0) {
1442 break;
1443 }
1444 break;
1445 case TZI:
1446 if (name.compare(ICAL_DTSTART) == 0) {
1447 dtstart = value;
1448 } else if (name.compare(ICAL_TZNAME) == 0) {
729e4ab9 1449 zonename = value;
46f4442e
A
1450 } else if (name.compare(ICAL_TZOFFSETFROM) == 0) {
1451 from = value;
1452 } else if (name.compare(ICAL_TZOFFSETTO) == 0) {
1453 to = value;
1454 } else if (name.compare(ICAL_RDATE) == 0) {
1455 // RDATE mixed with RRULE is not supported
1456 if (isRRULE) {
1457 goto cleanupParse;
1458 }
1459 // RDATE value may contain multiple date delimited
1460 // by comma
1461 UBool nextDate = TRUE;
1462 int32_t dstart = 0;
1463 UnicodeString *dstr;
1464 while (nextDate) {
1465 int32_t dend = value.indexOf(COMMA, dstart);
1466 if (dend == -1) {
1467 dstr = new UnicodeString(value, dstart);
1468 nextDate = FALSE;
1469 } else {
1470 dstr = new UnicodeString(value, dstart, dend - dstart);
1471 }
1472 dates->addElement(dstr, status);
1473 if (U_FAILURE(status)) {
1474 goto cleanupParse;
1475 }
1476 dstart = dend + 1;
1477 }
1478 } else if (name.compare(ICAL_RRULE) == 0) {
1479 // RRULE mixed with RDATE is not supported
1480 if (!isRRULE && dates->size() != 0) {
1481 goto cleanupParse;
1482 }
1483 isRRULE = true;
1484 dates->addElement(new UnicodeString(value), status);
1485 if (U_FAILURE(status)) {
1486 goto cleanupParse;
1487 }
1488 } else if (name.compare(ICAL_END) == 0) {
1489 // Mandatory properties
1490 if (dtstart.length() == 0 || from.length() == 0 || to.length() == 0) {
1491 goto cleanupParse;
1492 }
729e4ab9
A
1493 // if zonename is not available, create one from tzid
1494 if (zonename.length() == 0) {
1495 getDefaultTZName(tzid, dst, zonename);
46f4442e
A
1496 }
1497
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;
1504 UDate start = 0;
1505
1506 // Parse TZOFFSETFROM/TZOFFSETTO
1507 fromOffset = offsetStrToMillis(from, status);
1508 toOffset = offsetStrToMillis(to, status);
1509 if (U_FAILURE(status)) {
1510 goto cleanupParse;
1511 }
1512
1513 if (dst) {
1514 // If daylight, use the previous offset as rawoffset if positive
1515 if (toOffset - fromOffset > 0) {
1516 rawOffset = fromOffset;
1517 dstSavings = toOffset - fromOffset;
1518 } else {
1519 // This is rare case.. just use 1 hour DST savings
1520 rawOffset = toOffset - DEF_DSTSAVINGS;
1521 dstSavings = DEF_DSTSAVINGS;
1522 }
1523 } else {
1524 rawOffset = toOffset;
1525 dstSavings = 0;
1526 }
1527
1528 // start time
1529 start = parseDateTimeString(dtstart, fromOffset, status);
1530 if (U_FAILURE(status)) {
1531 goto cleanupParse;
1532 }
1533
1534 // Create the rule
1535 UDate actualStart = MAX_MILLIS;
1536 if (isRRULE) {
729e4ab9 1537 rule = createRuleByRRULE(zonename, rawOffset, dstSavings, start, dates, fromOffset, status);
46f4442e 1538 } else {
729e4ab9 1539 rule = createRuleByRDATE(zonename, rawOffset, dstSavings, start, dates, fromOffset, status);
46f4442e
A
1540 }
1541 if (U_FAILURE(status) || rule == NULL) {
1542 goto cleanupParse;
1543 } else {
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;
1554 } else {
1555 if (fromOffset - toOffset == DEF_DSTSAVINGS) {
1556 initialRawOffset = fromOffset - DEF_DSTSAVINGS;
1557 initialDSTSavings = DEF_DSTSAVINGS;
1558 } else {
1559 initialRawOffset = fromOffset;
1560 initialDSTSavings = 0;
1561 }
1562 }
1563 }
1564 }
1565 rules->addElement(rule, status);
1566 if (U_FAILURE(status)) {
1567 goto cleanupParse;
1568 }
1569 state = VTZ;
1570 }
1571 break;
1572 }
1573 }
1574 // Must have at least one rule
1575 if (rules->size() == 0) {
1576 goto cleanupParse;
1577 }
1578
1579 // Create a initial rule
729e4ab9
A
1580 getDefaultTZName(tzid, FALSE, zonename);
1581 initialRule = new InitialTimeZoneRule(zonename,
46f4442e
A
1582 initialRawOffset, initialDSTSavings);
1583 if (initialRule == NULL) {
1584 status = U_MEMORY_ALLOCATION_ERROR;
1585 goto cleanupParse;
1586 }
1587
1588 // Finally, create the RuleBasedTimeZone
1589 rbtz = new RuleBasedTimeZone(tzid, initialRule);
1590 if (rbtz == NULL) {
1591 status = U_MEMORY_ALLOCATION_ERROR;
1592 goto cleanupParse;
1593 }
1594 initialRule = NULL; // already adopted by RBTZ, no need to delete
1595
1596 for (n = 0; n < rules->size(); n++) {
1597 TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n);
729e4ab9
A
1598 AnnualTimeZoneRule *atzrule = dynamic_cast<AnnualTimeZoneRule *>(r);
1599 if (atzrule != NULL) {
1600 if (atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR) {
46f4442e
A
1601 finalRuleCount++;
1602 finalRuleIdx = n;
1603 }
1604 }
1605 }
1606 if (finalRuleCount > 2) {
1607 // Too many final rules
1608 status = U_ILLEGAL_ARGUMENT_ERROR;
1609 goto cleanupParse;
1610 }
1611
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();
1618 } else {
1619 // Normalize the final rule
1620 AnnualTimeZoneRule *finalRule = (AnnualTimeZoneRule*)rules->elementAt(finalRuleIdx);
1621 int32_t tmpRaw = finalRule->getRawOffset();
1622 int32_t tmpDST = finalRule->getDSTSavings();
1623
1624 // Find the last non-final rule
1625 UDate finalStart, start;
1626 finalRule->getFirstStart(initialRawOffset, initialDSTSavings, finalStart);
1627 start = finalStart;
1628 for (n = 0; n < rules->size(); n++) {
1629 if (finalRuleIdx == n) {
1630 continue;
1631 }
1632 TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n);
1633 UDate lastStart;
1634 r->getFinalStart(tmpRaw, tmpDST, lastStart);
1635 if (lastStart > start) {
1636 finalRule->getNextStart(lastStart,
1637 r->getRawOffset(),
1638 r->getDSTSavings(),
1639 FALSE,
1640 start);
1641 }
1642 }
1643
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(),
1652 &finalStart,
1653 1,
1654 DateTimeRule::UTC_TIME);
1655 } else {
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(),
1665 y);
1666 }
1667 if (newRule == NULL) {
1668 status = U_MEMORY_ALLOCATION_ERROR;
1669 goto cleanupParse;
1670 }
1671 rules->removeElementAt(finalRuleIdx);
1672 rules->addElement(newRule, status);
1673 if (U_FAILURE(status)) {
1674 delete newRule;
1675 goto cleanupParse;
1676 }
1677 }
1678 }
1679
1680 while (!rules->isEmpty()) {
1681 TimeZoneRule *tzr = (TimeZoneRule*)rules->orphanElementAt(0);
1682 rbtz->addTransitionRule(tzr, status);
1683 if (U_FAILURE(status)) {
1684 goto cleanupParse;
1685 }
1686 }
1687 rbtz->complete(status);
1688 if (U_FAILURE(status)) {
1689 goto cleanupParse;
1690 }
1691 delete rules;
1692 delete dates;
1693
1694 tz = rbtz;
1695 setID(tzid);
1696 return;
1697
1698cleanupParse:
1699 if (rules != NULL) {
1700 while (!rules->isEmpty()) {
1701 TimeZoneRule *r = (TimeZoneRule*)rules->orphanElementAt(0);
1702 delete r;
1703 }
1704 delete rules;
1705 }
1706 if (dates != NULL) {
1707 delete dates;
1708 }
1709 if (initialRule != NULL) {
1710 delete initialRule;
1711 }
1712 if (rbtz != NULL) {
1713 delete rbtz;
1714 }
1715 return;
1716}
1717
1718void
1719VTimeZone::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);
1736 } else {
1737 writer.write(*line);
1738 writer.write(ICAL_NEWLINE);
1739 }
1740 }
1741 } else {
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)) {
1746 return;
1747 }
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)) {
1755 delete icutzprop;
1756 delete customProps;
1757 return;
1758 }
1759 }
1760 writeZone(writer, *tz, customProps, status);
1761 delete customProps;
1762 }
1763}
1764
1765void
1766VTimeZone::write(UDate start, VTZWriter& writer, UErrorCode& status) /*const*/ {
1767 if (U_FAILURE(status)) {
1768 return;
1769 }
1770 InitialTimeZoneRule *initial = NULL;
1771 UVector *transitionRules = NULL;
1772 UVector customProps(uhash_deleteUnicodeString, uhash_compareUnicodeString, status);
1773 UnicodeString tzid;
1774
1775 // Extract rules applicable to dates after the start time
1776 getTimeZoneRulesAfter(start, initial, transitionRules, status);
1777 if (U_FAILURE(status)) {
1778 return;
1779 }
1780
1781 // Create a RuleBasedTimeZone with the subset rule
1782 getID(tzid);
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;
1790 }
1791 }
1792 delete transitionRules;
1793 transitionRules = NULL;
1794 }
1795 rbtz.complete(status);
1796 if (U_FAILURE(status)) {
1797 goto cleanupWritePartial;
1798 }
1799
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)) {
1810 delete icutzprop;
1811 goto cleanupWritePartial;
1812 }
1813 }
1814 writeZone(writer, rbtz, &customProps, status);
1815 return;
1816
1817cleanupWritePartial:
1818 if (initial != NULL) {
1819 delete initial;
1820 }
1821 if (transitionRules != NULL) {
1822 while (!transitionRules->isEmpty()) {
1823 TimeZoneRule *tr = (TimeZoneRule*)transitionRules->orphanElementAt(0);
1824 delete tr;
1825 }
1826 delete transitionRules;
1827 }
1828}
1829
1830void
1831VTimeZone::writeSimple(UDate time, VTZWriter& writer, UErrorCode& status) /*const*/ {
1832 if (U_FAILURE(status)) {
1833 return;
1834 }
1835
1836 UVector customProps(uhash_deleteUnicodeString, uhash_compareUnicodeString, status);
1837 UnicodeString tzid;
1838
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
1845 getID(tzid);
1846 RuleBasedTimeZone rbtz(tzid, initial);
1847 if (std != NULL && dst != NULL) {
1848 rbtz.addTransitionRule(std, status);
1849 rbtz.addTransitionRule(dst, status);
1850 }
1851 if (U_FAILURE(status)) {
1852 goto cleanupWriteSimple;
1853 }
1854
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)) {
1865 delete icutzprop;
1866 goto cleanupWriteSimple;
1867 }
1868 }
1869 writeZone(writer, rbtz, &customProps, status);
1870 }
1871 return;
1872
1873cleanupWriteSimple:
1874 if (initial != NULL) {
1875 delete initial;
1876 }
1877 if (std != NULL) {
1878 delete std;
1879 }
1880 if (dst != NULL) {
1881 delete dst;
1882 }
1883}
1884
1885void
1886VTimeZone::writeZone(VTZWriter& w, BasicTimeZone& basictz,
1887 UVector* customProps, UErrorCode& status) const {
1888 if (U_FAILURE(status)) {
1889 return;
1890 }
1891 writeHeaders(w, status);
1892 if (U_FAILURE(status)) {
1893 return;
1894 }
1895
1896 if (customProps != NULL) {
1897 for (int32_t i = 0; i < customProps->size(); i++) {
1898 UnicodeString *custprop = (UnicodeString*)customProps->elementAt(i);
1899 w.write(*custprop);
1900 w.write(ICAL_NEWLINE);
1901 }
1902 }
1903
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;
1918
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;
1932
1933 int32_t year, month, dom, dow, doy, mid;
1934 UBool hasTransitions = FALSE;
1935 TimeZoneTransition tzt;
1936 UBool tztAvail;
1937 UnicodeString name;
1938 UBool isDst;
1939
1940 // Going through all transitions
1941 while (TRUE) {
1942 tztAvail = basictz.getNextTransition(t, FALSE, tzt);
1943 if (!tztAvail) {
1944 break;
1945 }
1946 hasTransitions = TRUE;
1947 t = tzt.getTime();
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;
729e4ab9 1956 const AnnualTimeZoneRule *atzrule;
46f4442e
A
1957 if (isDst) {
1958 if (finalDstRule == NULL
729e4ab9
A
1959 && (atzrule = dynamic_cast<const AnnualTimeZoneRule *>(tzt.getTo())) != NULL
1960 && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
1961 ) {
1962 finalDstRule = (AnnualTimeZoneRule*)tzt.getTo()->clone();
46f4442e
A
1963 }
1964 if (dstCount > 0) {
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
1974 dstUntilTime = t;
1975 dstCount++;
1976 sameRule = TRUE;
1977 }
1978 if (!sameRule) {
1979 if (dstCount == 1) {
1980 writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime,
1981 TRUE, status);
1982 } else {
1983 writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
1984 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
1985 }
1986 if (U_FAILURE(status)) {
1987 goto cleanupWriteZone;
1988 }
1989 }
1990 }
1991 if (!sameRule) {
1992 // Reset this DST information
1993 dstName = name;
1994 dstFromOffset = fromOffset;
1995 dstFromDSTSavings = fromDSTSavings;
1996 dstToOffset = toOffset;
1997 dstStartYear = year;
1998 dstMonth = month;
1999 dstDayOfWeek = dow;
2000 dstWeekInMonth = weekInMonth;
2001 dstMillisInDay = mid;
2002 dstStartTime = dstUntilTime = t;
2003 dstCount = 1;
2004 }
2005 if (finalStdRule != NULL && finalDstRule != NULL) {
2006 break;
2007 }
2008 } else {
2009 if (finalStdRule == NULL
729e4ab9
A
2010 && (atzrule = dynamic_cast<const AnnualTimeZoneRule *>(tzt.getTo())) != NULL
2011 && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
2012 ) {
2013 finalStdRule = (AnnualTimeZoneRule*)tzt.getTo()->clone();
46f4442e
A
2014 }
2015 if (stdCount > 0) {
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
2025 stdUntilTime = t;
2026 stdCount++;
2027 sameRule = TRUE;
2028 }
2029 if (!sameRule) {
2030 if (stdCount == 1) {
2031 writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime,
2032 TRUE, status);
2033 } else {
2034 writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2035 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2036 }
2037 if (U_FAILURE(status)) {
2038 goto cleanupWriteZone;
2039 }
2040 }
2041 }
2042 if (!sameRule) {
2043 // Reset this STD information
2044 stdName = name;
2045 stdFromOffset = fromOffset;
2046 stdFromDSTSavings = fromDSTSavings;
2047 stdToOffset = toOffset;
2048 stdStartYear = year;
2049 stdMonth = month;
2050 stdDayOfWeek = dow;
2051 stdWeekInMonth = weekInMonth;
2052 stdMillisInDay = mid;
2053 stdStartTime = stdUntilTime = t;
2054 stdCount = 1;
2055 }
2056 if (finalStdRule != NULL && finalDstRule != NULL) {
2057 break;
2058 }
2059 }
2060 }
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;
2067 }
2068 offset = raw + dst;
2069 isDst = (dst != 0);
2070 UnicodeString tzid;
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;
2077 }
2078 } else {
2079 if (dstCount > 0) {
2080 if (finalDstRule == NULL) {
2081 if (dstCount == 1) {
2082 writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime,
2083 TRUE, status);
2084 } else {
2085 writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
2086 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
2087 }
2088 if (U_FAILURE(status)) {
2089 goto cleanupWriteZone;
2090 }
2091 } else {
2092 if (dstCount == 1) {
2093 writeFinalRule(w, TRUE, finalDstRule,
2094 dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, dstStartTime, status);
2095 } else {
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);
2100 } else {
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;
2106 }
2107 writeFinalRule(w, TRUE, finalDstRule,
2108 dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, dstStartTime, status);
2109 }
2110 }
2111 if (U_FAILURE(status)) {
2112 goto cleanupWriteZone;
2113 }
2114 }
2115 }
2116 if (stdCount > 0) {
2117 if (finalStdRule == NULL) {
2118 if (stdCount == 1) {
2119 writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime,
2120 TRUE, status);
2121 } else {
2122 writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2123 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2124 }
2125 if (U_FAILURE(status)) {
2126 goto cleanupWriteZone;
2127 }
2128 } else {
2129 if (stdCount == 1) {
2130 writeFinalRule(w, FALSE, finalStdRule,
2131 stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, stdStartTime, status);
2132 } else {
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);
2137 } else {
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;
2143 }
2144 writeFinalRule(w, FALSE, finalStdRule,
2145 stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, stdStartTime, status);
2146 }
2147 }
2148 if (U_FAILURE(status)) {
2149 goto cleanupWriteZone;
2150 }
2151 }
2152 }
2153 }
2154 writeFooter(w, status);
2155
2156cleanupWriteZone:
2157
2158 if (finalStdRule != NULL) {
2159 delete finalStdRule;
2160 }
2161 if (finalDstRule != NULL) {
2162 delete finalDstRule;
2163 }
2164}
2165
2166void
2167VTimeZone::writeHeaders(VTZWriter& writer, UErrorCode& status) const {
2168 if (U_FAILURE(status)) {
2169 return;
2170 }
2171 UnicodeString tzid;
2172 tz->getID(tzid);
2173
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);
2180 writer.write(tzid);
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);
2187 }
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);
2194 }
2195}
2196
2197/*
2198 * Write the closing section of the VTIMEZONE definition block
2199 */
2200void
2201VTimeZone::writeFooter(VTZWriter& writer, UErrorCode& status) const {
2202 if (U_FAILURE(status)) {
2203 return;
2204 }
2205 writer.write(ICAL_END);
2206 writer.write(COLON);
2207 writer.write(ICAL_VTIMEZONE);
2208 writer.write(ICAL_NEWLINE);
2209}
2210
2211/*
2212 * Write a single start time
2213 */
2214void
729e4ab9 2215VTimeZone::writeZonePropsByTime(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
46f4442e
A
2216 int32_t fromOffset, int32_t toOffset, UDate time, UBool withRDATE,
2217 UErrorCode& status) const {
2218 if (U_FAILURE(status)) {
2219 return;
2220 }
729e4ab9 2221 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, time, status);
46f4442e
A
2222 if (U_FAILURE(status)) {
2223 return;
2224 }
2225 if (withRDATE) {
2226 writer.write(ICAL_RDATE);
2227 writer.write(COLON);
2228 UnicodeString timestr;
2229 writer.write(getDateTimeString(time + fromOffset, timestr));
2230 writer.write(ICAL_NEWLINE);
2231 }
2232 endZoneProps(writer, isDst, status);
2233 if (U_FAILURE(status)) {
2234 return;
2235 }
2236}
2237
2238/*
2239 * Write start times defined by a DOM rule using VTIMEZONE RRULE
2240 */
2241void
729e4ab9 2242VTimeZone::writeZonePropsByDOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
46f4442e
A
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)) {
2247 return;
2248 }
729e4ab9 2249 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
46f4442e
A
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_BYMONTHDAY);
2258 writer.write(EQUALS_SIGN);
2259 UnicodeString dstr;
2260 appendAsciiDigits(dayOfMonth, 0, dstr);
2261 writer.write(dstr);
2262 if (untilTime != MAX_MILLIS) {
2263 appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2264 if (U_FAILURE(status)) {
2265 return;
2266 }
2267 }
2268 writer.write(ICAL_NEWLINE);
2269 endZoneProps(writer, isDst, status);
2270}
2271
2272/*
2273 * Write start times defined by a DOW rule using VTIMEZONE RRULE
2274 */
2275void
729e4ab9 2276VTimeZone::writeZonePropsByDOW(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
46f4442e
A
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)) {
2281 return;
2282 }
729e4ab9 2283 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
46f4442e
A
2284 if (U_FAILURE(status)) {
2285 return;
2286 }
2287 beginRRULE(writer, month, status);
2288 if (U_FAILURE(status)) {
2289 return;
2290 }
2291 writer.write(ICAL_BYDAY);
2292 writer.write(EQUALS_SIGN);
2293 UnicodeString dstr;
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...
2297
2298 if (untilTime != MAX_MILLIS) {
2299 appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2300 if (U_FAILURE(status)) {
2301 return;
2302 }
2303 }
2304 writer.write(ICAL_NEWLINE);
2305 endZoneProps(writer, isDst, status);
2306}
2307
2308/*
2309 * Write start times defined by a DOW_GEQ_DOM rule using VTIMEZONE RRULE
2310 */
2311void
729e4ab9 2312VTimeZone::writeZonePropsByDOW_GEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
46f4442e
A
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)) {
2317 return;
2318 }
2319 // Check if this rule can be converted to DOW rule
2320 if (dayOfMonth%7 == 1) {
2321 // Can be represented by DOW rule
729e4ab9 2322 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
46f4442e
A
2323 month, (dayOfMonth + 6)/7, dayOfWeek, startTime, untilTime, status);
2324 if (U_FAILURE(status)) {
2325 return;
2326 }
2327 } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 6) {
2328 // Can be represented by DOW rule with negative week number
729e4ab9 2329 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
46f4442e
A
2330 month, -1*((MONTHLENGTH[month] - dayOfMonth + 1)/7), dayOfWeek, startTime, untilTime, status);
2331 if (U_FAILURE(status)) {
2332 return;
2333 }
2334 } else {
2335 // Otherwise, use BYMONTHDAY to include all possible dates
729e4ab9 2336 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
46f4442e
A
2337 if (U_FAILURE(status)) {
2338 return;
2339 }
2340 // Check if all days are in the same month
2341 int32_t startDay = dayOfMonth;
2342 int32_t currentMonthDays = 7;
2343
2344 if (dayOfMonth <= 0) {
2345 // The start day is in previous month
2346 int32_t prevMonthDays = 1 - dayOfMonth;
2347 currentMonthDays -= prevMonthDays;
2348
2349 int32_t prevMonth = (month - 1) < 0 ? 11 : month - 1;
2350
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)) {
2357 return;
2358 }
2359
2360 // Start from 1 for the rest
2361 startDay = 1;
2362 } else if (dayOfMonth + 6 > MONTHLENGTH[month]) {
2363 // Note: This code does not actually work well in February. For now, days in month in
2364 // non-leap year.
2365 int32_t nextMonthDays = dayOfMonth + 6 - MONTHLENGTH[month];
2366 currentMonthDays -= nextMonthDays;
2367
2368 int32_t nextMonth = (month + 1) > 11 ? 0 : month + 1;
2369
2370 writeZonePropsByDOW_GEQ_DOM_sub(writer, nextMonth, 1, dayOfWeek, nextMonthDays,
2371 MAX_MILLIS /* Do not use UNTIL */, fromOffset, status);
2372 if (U_FAILURE(status)) {
2373 return;
2374 }
2375 }
2376 writeZonePropsByDOW_GEQ_DOM_sub(writer, month, startDay, dayOfWeek, currentMonthDays,
2377 untilTime, fromOffset, status);
2378 if (U_FAILURE(status)) {
2379 return;
2380 }
2381 endZoneProps(writer, isDst, status);
2382 }
2383}
2384
2385/*
2386 * Called from writeZonePropsByDOW_GEQ_DOM
2387 */
2388void
2389VTimeZone::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 {
2392
2393 if (U_FAILURE(status)) {
2394 return;
2395 }
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;
2401 }
2402 beginRRULE(writer, month, status);
2403 if (U_FAILURE(status)) {
2404 return;
2405 }
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);
2412
2413 UnicodeString dstr;
2414 appendAsciiDigits(startDayNum, 0, dstr);
2415 writer.write(dstr);
2416 for (int32_t i = 1; i < numDays; i++) {
2417 writer.write(COMMA);
2418 dstr.remove();
2419 appendAsciiDigits(startDayNum + i, 0, dstr);
2420 writer.write(dstr);
2421 }
2422
2423 if (untilTime != MAX_MILLIS) {
2424 appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2425 if (U_FAILURE(status)) {
2426 return;
2427 }
2428 }
2429 writer.write(ICAL_NEWLINE);
2430}
2431
2432/*
2433 * Write start times defined by a DOW_LEQ_DOM rule using VTIMEZONE RRULE
2434 */
2435void
729e4ab9 2436VTimeZone::writeZonePropsByDOW_LEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
46f4442e
A
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)) {
2441 return;
2442 }
2443 // Check if this rule can be converted to DOW rule
2444 if (dayOfMonth%7 == 0) {
2445 // Can be represented by DOW rule
729e4ab9 2446 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
46f4442e
A
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
729e4ab9 2450 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
46f4442e
A
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
729e4ab9 2454 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
46f4442e
A
2455 UCAL_FEBRUARY, -1, dayOfWeek, startTime, untilTime, status);
2456 } else {
2457 // Otherwise, convert this to DOW_GEQ_DOM rule
729e4ab9 2458 writeZonePropsByDOW_GEQ_DOM(writer, isDst, zonename, fromOffset, toOffset,
46f4442e
A
2459 month, dayOfMonth - 6, dayOfWeek, startTime, untilTime, status);
2460 }
2461}
2462
2463/*
2464 * Write the final time zone rule using RRULE, with no UNTIL attribute
2465 */
2466void
2467VTimeZone::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)) {
2471 return;
2472 }
2473 UBool modifiedRule = TRUE;
2474 const DateTimeRule *dtrule = toWallTimeRule(rule->getRule(), fromRawOffset, fromDSTSavings);
2475 if (dtrule == NULL) {
2476 modifiedRule = FALSE;
2477 dtrule = rule->getRule();
2478 }
729e4ab9
A
2479
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
2483
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));
2489 }
2490
46f4442e
A
2491 int32_t toOffset = rule->getRawOffset() + rule->getDSTSavings();
2492 UnicodeString name;
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);
2498 break;
2499 case DateTimeRule::DOW:
2500 writeZonePropsByDOW(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2501 dtrule->getRuleMonth(), dtrule->getRuleWeekInMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
2502 break;
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);
2506 break;
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);
2510 break;
2511 }
2512 if (modifiedRule) {
2513 delete dtrule;
2514 }
2515}
2516
2517/*
2518 * Write the opening section of zone properties
2519 */
2520void
729e4ab9 2521VTimeZone::beginZoneProps(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
46f4442e
A
2522 int32_t fromOffset, int32_t toOffset, UDate startTime, UErrorCode& status) const {
2523 if (U_FAILURE(status)) {
2524 return;
2525 }
2526 writer.write(ICAL_BEGIN);
2527 writer.write(COLON);
2528 if (isDst) {
2529 writer.write(ICAL_DAYLIGHT);
2530 } else {
2531 writer.write(ICAL_STANDARD);
2532 }
2533 writer.write(ICAL_NEWLINE);
2534
2535 UnicodeString dstr;
2536
2537 // TZOFFSETTO
2538 writer.write(ICAL_TZOFFSETTO);
2539 writer.write(COLON);
2540 millisToOffset(toOffset, dstr);
2541 writer.write(dstr);
2542 writer.write(ICAL_NEWLINE);
2543
2544 // TZOFFSETFROM
2545 writer.write(ICAL_TZOFFSETFROM);
2546 writer.write(COLON);
2547 millisToOffset(fromOffset, dstr);
2548 writer.write(dstr);
2549 writer.write(ICAL_NEWLINE);
2550
2551 // TZNAME
2552 writer.write(ICAL_TZNAME);
2553 writer.write(COLON);
729e4ab9 2554 writer.write(zonename);
46f4442e
A
2555 writer.write(ICAL_NEWLINE);
2556
2557 // DTSTART
2558 writer.write(ICAL_DTSTART);
2559 writer.write(COLON);
2560 writer.write(getDateTimeString(startTime + fromOffset, dstr));
2561 writer.write(ICAL_NEWLINE);
2562}
2563
2564/*
2565 * Writes the closing section of zone properties
2566 */
2567void
2568VTimeZone::endZoneProps(VTZWriter& writer, UBool isDst, UErrorCode& status) const {
2569 if (U_FAILURE(status)) {
2570 return;
2571 }
2572 // END:STANDARD or END:DAYLIGHT
2573 writer.write(ICAL_END);
2574 writer.write(COLON);
2575 if (isDst) {
2576 writer.write(ICAL_DAYLIGHT);
2577 } else {
2578 writer.write(ICAL_STANDARD);
2579 }
2580 writer.write(ICAL_NEWLINE);
2581}
2582
2583/*
2584 * Write the beggining part of RRULE line
2585 */
2586void
2587VTimeZone::beginRRULE(VTZWriter& writer, int32_t month, UErrorCode& status) const {
2588 if (U_FAILURE(status)) {
2589 return;
2590 }
2591 UnicodeString dstr;
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);
2601 writer.write(dstr);
2602 writer.write(SEMICOLON);
2603}
2604
2605/*
2606 * Append the UNTIL attribute after RRULE line
2607 */
2608void
2609VTimeZone::appendUNTIL(VTZWriter& writer, const UnicodeString& until, UErrorCode& status) const {
2610 if (U_FAILURE(status)) {
2611 return;
2612 }
2613 if (until.length() > 0) {
2614 writer.write(SEMICOLON);
2615 writer.write(ICAL_UNTIL);
2616 writer.write(EQUALS_SIGN);
2617 writer.write(until);
2618 }
2619}
2620
2621U_NAMESPACE_END
2622
2623#endif /* #if !UCONFIG_NO_FORMATTING */
2624
2625//eof