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