1 /*
2 *******************************************************************************
3 * Copyright (C) 2007-2010, International Business Machines Corporation and
4 * others. All Rights Reserved.
5 *******************************************************************************
6 */
7
8 #include <typeinfo> // for 'typeid' to work
9
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
23 U_NAMESPACE_BEGIN
24
25 // This is the deleter that will be use to remove TimeZoneRule
26 U_CDECL_BEGIN
27 static void U_CALLCONV
deleteTimeZoneRule(void * obj)28 deleteTimeZoneRule(void* obj) {
29 delete (TimeZoneRule*) obj;
30 }
31 U_CDECL_END
32
33 // Smybol characters used by RFC2445 VTIMEZONE
34 static const UChar COLON = 0x3A; /* : */
35 static const UChar SEMICOLON = 0x3B; /* ; */
36 static const UChar EQUALS_SIGN = 0x3D; /* = */
37 static const UChar COMMA = 0x2C; /* , */
38 static const UChar PLUS = 0x2B; /* + */
39 static const UChar MINUS = 0x2D; /* - */
40
41 // RFC2445 VTIMEZONE tokens
42 static const UChar ICAL_BEGIN_VTIMEZONE[] = {0x42, 0x45, 0x47, 0x49, 0x4E, 0x3A, 0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "BEGIN:VTIMEZONE" */
43 static const UChar ICAL_END_VTIMEZONE[] = {0x45, 0x4E, 0x44, 0x3A, 0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "END:VTIMEZONE" */
44 static const UChar ICAL_BEGIN[] = {0x42, 0x45, 0x47, 0x49, 0x4E, 0}; /* "BEGIN" */
45 static const UChar ICAL_END[] = {0x45, 0x4E, 0x44, 0}; /* "END" */
46 static const UChar ICAL_VTIMEZONE[] = {0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "VTIMEZONE" */
47 static const UChar ICAL_TZID[] = {0x54, 0x5A, 0x49, 0x44, 0}; /* "TZID" */
48 static const UChar ICAL_STANDARD[] = {0x53, 0x54, 0x41, 0x4E, 0x44, 0x41, 0x52, 0x44, 0}; /* "STANDARD" */
49 static const UChar ICAL_DAYLIGHT[] = {0x44, 0x41, 0x59, 0x4C, 0x49, 0x47, 0x48, 0x54, 0}; /* "DAYLIGHT" */
50 static const UChar ICAL_DTSTART[] = {0x44, 0x54, 0x53, 0x54, 0x41, 0x52, 0x54, 0}; /* "DTSTART" */
51 static const UChar ICAL_TZOFFSETFROM[] = {0x54, 0x5A, 0x4F, 0x46, 0x46, 0x53, 0x45, 0x54, 0x46, 0x52, 0x4F, 0x4D, 0}; /* "TZOFFSETFROM" */
52 static const UChar ICAL_TZOFFSETTO[] = {0x54, 0x5A, 0x4F, 0x46, 0x46, 0x53, 0x45, 0x54, 0x54, 0x4F, 0}; /* "TZOFFSETTO" */
53 static const UChar ICAL_RDATE[] = {0x52, 0x44, 0x41, 0x54, 0x45, 0}; /* "RDATE" */
54 static const UChar ICAL_RRULE[] = {0x52, 0x52, 0x55, 0x4C, 0x45, 0}; /* "RRULE" */
55 static const UChar ICAL_TZNAME[] = {0x54, 0x5A, 0x4E, 0x41, 0x4D, 0x45, 0}; /* "TZNAME" */
56 static const UChar ICAL_TZURL[] = {0x54, 0x5A, 0x55, 0x52, 0x4C, 0}; /* "TZURL" */
57 static const UChar ICAL_LASTMOD[] = {0x4C, 0x41, 0x53, 0x54, 0x2D, 0x4D, 0x4F, 0x44, 0x49, 0x46, 0x49, 0x45, 0x44, 0}; /* "LAST-MODIFIED" */
58
59 static const UChar ICAL_FREQ[] = {0x46, 0x52, 0x45, 0x51, 0}; /* "FREQ" */
60 static const UChar ICAL_UNTIL[] = {0x55, 0x4E, 0x54, 0x49, 0x4C, 0}; /* "UNTIL" */
61 static const UChar ICAL_YEARLY[] = {0x59, 0x45, 0x41, 0x52, 0x4C, 0x59, 0}; /* "YEARLY" */
62 static const UChar ICAL_BYMONTH[] = {0x42, 0x59, 0x4D, 0x4F, 0x4E, 0x54, 0x48, 0}; /* "BYMONTH" */
63 static const UChar ICAL_BYDAY[] = {0x42, 0x59, 0x44, 0x41, 0x59, 0}; /* "BYDAY" */
64 static const UChar ICAL_BYMONTHDAY[] = {0x42, 0x59, 0x4D, 0x4F, 0x4E, 0x54, 0x48, 0x44, 0x41, 0x59, 0}; /* "BYMONTHDAY" */
65
66 static const UChar ICAL_NEWLINE[] = {0x0D, 0x0A, 0}; /* CRLF */
67
68 static const UChar ICAL_DOW_NAMES[7][3] = {
69 {0x53, 0x55, 0}, /* "SU" */
70 {0x4D, 0x4F, 0}, /* "MO" */
71 {0x54, 0x55, 0}, /* "TU" */
72 {0x57, 0x45, 0}, /* "WE" */
73 {0x54, 0x48, 0}, /* "TH" */
74 {0x46, 0x52, 0}, /* "FR" */
75 {0x53, 0x41, 0} /* "SA" */};
76
77 // Month length for non-leap year
78 static const int32_t MONTHLENGTH[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
79
80 // ICU custom property
81 static const UChar ICU_TZINFO_PROP[] = {0x58, 0x2D, 0x54, 0x5A, 0x49, 0x4E, 0x46, 0x4F, 0x3A, 0}; /* "X-TZINFO:" */
82 static const UChar ICU_TZINFO_PARTIAL[] = {0x2F, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6C, 0x40, 0}; /* "/Partial@" */
83 static const UChar ICU_TZINFO_SIMPLE[] = {0x2F, 0x53, 0x69, 0x6D, 0x70, 0x6C, 0x65, 0x40, 0}; /* "/Simple@" */
84
85
86 /*
87 * Simple fixed digit ASCII number to integer converter
88 */
parseAsciiDigits(const UnicodeString & str,int32_t start,int32_t length,UErrorCode & status)89 static 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
appendAsciiDigits(int32_t number,uint8_t length,UnicodeString & str)118 static UnicodeString& appendAsciiDigits(int32_t number, uint8_t length, UnicodeString& str) {
119 UBool negative = FALSE;
120 int32_t digits[10]; // max int32_t is 10 decimal digits
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
appendMillis(UDate date,UnicodeString & str)153 static 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 */
getDateTimeString(UDate time,UnicodeString & str)189 static UnicodeString& getDateTimeString(UDate time, UnicodeString& str) {
190 int32_t year, month, dom, dow, doy, mid;
191 Grego::timeToFields(time, year, month, dom, dow, doy, mid);
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 */
getUTCDateTimeString(UDate time,UnicodeString & str)215 static 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 */
parseDateTimeString(const UnicodeString & str,int32_t offset,UErrorCode & status)225 static 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 */
offsetStrToMillis(const UnicodeString & str,UErrorCode & status)289 static 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 */
millisToOffset(int32_t millis,UnicodeString & str)335 static 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 */
getDefaultTZName(const UnicodeString tzid,UBool isDST,UnicodeString & zonename)359 static void getDefaultTZName(const UnicodeString tzid, UBool isDST, UnicodeString& zonename) {
360 zonename = tzid;
361 if (isDST) {
362 zonename += UNICODE_STRING_SIMPLE("(DST)");
363 } else {
364 zonename += UNICODE_STRING_SIMPLE("(STD)");
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 */
parseRRULE(const UnicodeString & rrule,int32_t & month,int32_t & dow,int32_t & wim,int32_t * dom,int32_t & domCount,UDate & until,UErrorCode & status)380 static void parseRRULE(const UnicodeString& rrule, int32_t& month, int32_t& dow, int32_t& wim,
381 int32_t* dom, int32_t& domCount, UDate& until, UErrorCode& status) {
382 if (U_FAILURE(status)) {
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
517 rruleParseError:
518 if (U_SUCCESS(status)) {
519 // Set error status
520 status = U_INVALID_FORMAT_ERROR;
521 }
522 }
523
createRuleByRRULE(const UnicodeString & zonename,int rawOffset,int dstSavings,UDate start,UVector * dates,int fromOffset,UErrorCode & status)524 static TimeZoneRule* createRuleByRRULE(const UnicodeString& zonename, int rawOffset, int dstSavings, UDate start,
525 UVector* dates, int fromOffset, UErrorCode& status) {
526 if (U_FAILURE(status)) {
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 }
717 return new AnnualTimeZoneRule(zonename, rawOffset, dstSavings, adtr, startYear, endYear);
718
719 unsupportedRRule:
720 status = U_INVALID_STATE_ERROR;
721 return NULL;
722 }
723
724 /*
725 * Create a TimeZoneRule by the RDATE definition
726 */
createRuleByRDATE(const UnicodeString & zonename,int32_t rawOffset,int32_t dstSavings,UDate start,UVector * dates,int32_t fromOffset,UErrorCode & status)727 static TimeZoneRule* createRuleByRDATE(const UnicodeString& zonename, int32_t rawOffset, int32_t dstSavings,
728 UDate start, UVector* dates, int32_t fromOffset, UErrorCode& status) {
729 if (U_FAILURE(status)) {
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
736 retVal = new TimeArrayTimeZoneRule(zonename, rawOffset, dstSavings,
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 }
754 retVal = new TimeArrayTimeZoneRule(zonename, rawOffset, dstSavings,
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 */
isEquivalentDateRule(int32_t month,int32_t weekInMonth,int32_t dayOfWeek,const DateTimeRule * dtrule)765 static 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 */
toWallTimeRule(const DateTimeRule * rule,int32_t rawOffset,int32_t dstSavings)804 static 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 */
882 class VTZWriter {
883 public:
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);
890 private:
891 UnicodeString* out;
892 };
893
VTZWriter(UnicodeString & output)894 VTZWriter::VTZWriter(UnicodeString& output) {
895 out = &output;
896 }
897
~VTZWriter()898 VTZWriter::~VTZWriter() {
899 }
900
901 void
write(const UnicodeString & str)902 VTZWriter::write(const UnicodeString& str) {
903 out->append(str);
904 }
905
906 void
write(UChar ch)907 VTZWriter::write(UChar ch) {
908 out->append(ch);
909 }
910
911 /*
912 void
913 VTZWriter::write(const UChar* str, int32_t length) {
914 out->append(str, length);
915 }
916 */
917
918 class VTZReader {
919 public:
920 VTZReader(const UnicodeString& input);
921 ~VTZReader();
922
923 UChar read(void);
924 private:
925 const UnicodeString* in;
926 int32_t index;
927 };
928
VTZReader(const UnicodeString & input)929 VTZReader::VTZReader(const UnicodeString& input) {
930 in = &input;
931 index = 0;
932 }
933
~VTZReader()934 VTZReader::~VTZReader() {
935 }
936
937 UChar
read(void)938 VTZReader::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
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(VTimeZone)948 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(VTimeZone)
949
950 VTimeZone::VTimeZone()
951 : BasicTimeZone(), tz(NULL), vtzlines(NULL),
952 lastmod(MAX_MILLIS) {
953 }
954
VTimeZone(const VTimeZone & source)955 VTimeZone::VTimeZone(const VTimeZone& source)
956 : BasicTimeZone(source), tz(NULL), vtzlines(NULL),
957 tzurl(source.tzurl), lastmod(source.lastmod),
958 olsonzid(source.olsonzid), icutzver(source.icutzver) {
959 if (source.tz != NULL) {
960 tz = (BasicTimeZone*)source.tz->clone();
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
~VTimeZone()981 VTimeZone::~VTimeZone() {
982 if (tz != NULL) {
983 delete tz;
984 }
985 if (vtzlines != NULL) {
986 delete vtzlines;
987 }
988 }
989
990 VTimeZone&
operator =(const VTimeZone & right)991 VTimeZone::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
1033 UBool
operator ==(const TimeZone & that) const1034 VTimeZone::operator==(const TimeZone& that) const {
1035 if (this == &that) {
1036 return TRUE;
1037 }
1038
1039 if (typeid(*this) != typeid(that) || !BasicTimeZone::operator==(that)) {
1040 return FALSE;
1041 }
1042 VTimeZone *vtz = (VTimeZone*)&that;
1043 if (*tz == *(vtz->tz)
1044 && tzurl == vtz->tzurl
1045 && lastmod == vtz->lastmod
1046 /* && olsonzid = that.olsonzid */
1047 /* && icutzver = that.icutzver */) {
1048 return TRUE;
1049 }
1050 return FALSE;
1051 }
1052
1053 UBool
operator !=(const TimeZone & that) const1054 VTimeZone::operator!=(const TimeZone& that) const {
1055 return !operator==(that);
1056 }
1057
1058 VTimeZone*
createVTimeZoneByID(const UnicodeString & ID)1059 VTimeZone::createVTimeZoneByID(const UnicodeString& ID) {
1060 VTimeZone *vtz = new VTimeZone();
1061 vtz->tz = (BasicTimeZone*)TimeZone::createTimeZone(ID);
1062 vtz->tz->getID(vtz->olsonzid);
1063
1064 // Set ICU tzdata version
1065 UErrorCode status = U_ZERO_ERROR;
1066 UResourceBundle *bundle = NULL;
1067 const UChar* versionStr = NULL;
1068 int32_t len = 0;
1069 bundle = ures_openDirect(NULL, "zoneinfo64", &status);
1070 versionStr = ures_getStringByKey(bundle, "TZVersion", &len, &status);
1071 if (U_SUCCESS(status)) {
1072 vtz->icutzver.setTo(versionStr, len);
1073 }
1074 ures_close(bundle);
1075 return vtz;
1076 }
1077
1078 VTimeZone*
createVTimeZoneFromBasicTimeZone(const BasicTimeZone & basic_time_zone,UErrorCode & status)1079 VTimeZone::createVTimeZoneFromBasicTimeZone(const BasicTimeZone& basic_time_zone, UErrorCode &status) {
1080 if (U_FAILURE(status)) {
1081 return NULL;
1082 }
1083 VTimeZone *vtz = new VTimeZone();
1084 if (vtz == NULL) {
1085 status = U_MEMORY_ALLOCATION_ERROR;
1086 return NULL;
1087 }
1088 vtz->tz = (BasicTimeZone *)basic_time_zone.clone();
1089 if (vtz->tz == NULL) {
1090 status = U_MEMORY_ALLOCATION_ERROR;
1091 delete vtz;
1092 return NULL;
1093 }
1094 vtz->tz->getID(vtz->olsonzid);
1095
1096 // Set ICU tzdata version
1097 UResourceBundle *bundle = NULL;
1098 const UChar* versionStr = NULL;
1099 int32_t len = 0;
1100 bundle = ures_openDirect(NULL, "zoneinfo64", &status);
1101 versionStr = ures_getStringByKey(bundle, "TZVersion", &len, &status);
1102 if (U_SUCCESS(status)) {
1103 vtz->icutzver.setTo(versionStr, len);
1104 }
1105 ures_close(bundle);
1106 return vtz;
1107 }
1108
1109 VTimeZone*
createVTimeZone(const UnicodeString & vtzdata,UErrorCode & status)1110 VTimeZone::createVTimeZone(const UnicodeString& vtzdata, UErrorCode& status) {
1111 if (U_FAILURE(status)) {
1112 return NULL;
1113 }
1114 VTZReader reader(vtzdata);
1115 VTimeZone *vtz = new VTimeZone();
1116 vtz->load(reader, status);
1117 if (U_FAILURE(status)) {
1118 delete vtz;
1119 return NULL;
1120 }
1121 return vtz;
1122 }
1123
1124 UBool
getTZURL(UnicodeString & url) const1125 VTimeZone::getTZURL(UnicodeString& url) const {
1126 if (tzurl.length() > 0) {
1127 url = tzurl;
1128 return TRUE;
1129 }
1130 return FALSE;
1131 }
1132
1133 void
setTZURL(const UnicodeString & url)1134 VTimeZone::setTZURL(const UnicodeString& url) {
1135 tzurl = url;
1136 }
1137
1138 UBool
getLastModified(UDate & lastModified) const1139 VTimeZone::getLastModified(UDate& lastModified) const {
1140 if (lastmod != MAX_MILLIS) {
1141 lastModified = lastmod;
1142 return TRUE;
1143 }
1144 return FALSE;
1145 }
1146
1147 void
setLastModified(UDate lastModified)1148 VTimeZone::setLastModified(UDate lastModified) {
1149 lastmod = lastModified;
1150 }
1151
1152 void
write(UnicodeString & result,UErrorCode & status) const1153 VTimeZone::write(UnicodeString& result, UErrorCode& status) const {
1154 result.remove();
1155 VTZWriter writer(result);
1156 write(writer, status);
1157 }
1158
1159 void
write(UDate start,UnicodeString & result,UErrorCode & status)1160 VTimeZone::write(UDate start, UnicodeString& result, UErrorCode& status) /*const*/ {
1161 result.remove();
1162 VTZWriter writer(result);
1163 write(start, writer, status);
1164 }
1165
1166 void
writeSimple(UDate time,UnicodeString & result,UErrorCode & status)1167 VTimeZone::writeSimple(UDate time, UnicodeString& result, UErrorCode& status) /*const*/ {
1168 result.remove();
1169 VTZWriter writer(result);
1170 writeSimple(time, writer, status);
1171 }
1172
1173 TimeZone*
clone(void) const1174 VTimeZone::clone(void) const {
1175 return new VTimeZone(*this);
1176 }
1177
1178 int32_t
getOffset(uint8_t era,int32_t year,int32_t month,int32_t day,uint8_t dayOfWeek,int32_t millis,UErrorCode & status) const1179 VTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day,
1180 uint8_t dayOfWeek, int32_t millis, UErrorCode& status) const {
1181 return tz->getOffset(era, year, month, day, dayOfWeek, millis, status);
1182 }
1183
1184 int32_t
getOffset(uint8_t era,int32_t year,int32_t month,int32_t day,uint8_t dayOfWeek,int32_t millis,int32_t monthLength,UErrorCode & status) const1185 VTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day,
1186 uint8_t dayOfWeek, int32_t millis,
1187 int32_t monthLength, UErrorCode& status) const {
1188 return tz->getOffset(era, year, month, day, dayOfWeek, millis, monthLength, status);
1189 }
1190
1191 void
getOffset(UDate date,UBool local,int32_t & rawOffset,int32_t & dstOffset,UErrorCode & status) const1192 VTimeZone::getOffset(UDate date, UBool local, int32_t& rawOffset,
1193 int32_t& dstOffset, UErrorCode& status) const {
1194 return tz->getOffset(date, local, rawOffset, dstOffset, status);
1195 }
1196
1197 void
setRawOffset(int32_t offsetMillis)1198 VTimeZone::setRawOffset(int32_t offsetMillis) {
1199 tz->setRawOffset(offsetMillis);
1200 }
1201
1202 int32_t
getRawOffset(void) const1203 VTimeZone::getRawOffset(void) const {
1204 return tz->getRawOffset();
1205 }
1206
1207 UBool
useDaylightTime(void) const1208 VTimeZone::useDaylightTime(void) const {
1209 return tz->useDaylightTime();
1210 }
1211
1212 UBool
inDaylightTime(UDate date,UErrorCode & status) const1213 VTimeZone::inDaylightTime(UDate date, UErrorCode& status) const {
1214 return tz->inDaylightTime(date, status);
1215 }
1216
1217 UBool
hasSameRules(const TimeZone & other) const1218 VTimeZone::hasSameRules(const TimeZone& other) const {
1219 return tz->hasSameRules(other);
1220 }
1221
1222 UBool
getNextTransition(UDate base,UBool inclusive,TimeZoneTransition & result)1223 VTimeZone::getNextTransition(UDate base, UBool inclusive, TimeZoneTransition& result) /*const*/ {
1224 return tz->getNextTransition(base, inclusive, result);
1225 }
1226
1227 UBool
getPreviousTransition(UDate base,UBool inclusive,TimeZoneTransition & result)1228 VTimeZone::getPreviousTransition(UDate base, UBool inclusive, TimeZoneTransition& result) /*const*/ {
1229 return tz->getPreviousTransition(base, inclusive, result);
1230 }
1231
1232 int32_t
countTransitionRules(UErrorCode & status)1233 VTimeZone::countTransitionRules(UErrorCode& status) /*const*/ {
1234 return tz->countTransitionRules(status);
1235 }
1236
1237 void
getTimeZoneRules(const InitialTimeZoneRule * & initial,const TimeZoneRule * trsrules[],int32_t & trscount,UErrorCode & status)1238 VTimeZone::getTimeZoneRules(const InitialTimeZoneRule*& initial,
1239 const TimeZoneRule* trsrules[], int32_t& trscount,
1240 UErrorCode& status) /*const*/ {
1241 tz->getTimeZoneRules(initial, trsrules, trscount, status);
1242 }
1243
1244 void
load(VTZReader & reader,UErrorCode & status)1245 VTimeZone::load(VTZReader& reader, UErrorCode& status) {
1246 vtzlines = new UVector(uhash_deleteUnicodeString, uhash_compareUnicodeString, DEFAULT_VTIMEZONE_LINES, status);
1247 if (U_FAILURE(status)) {
1248 return;
1249 }
1250 UBool eol = FALSE;
1251 UBool start = FALSE;
1252 UBool success = FALSE;
1253 UnicodeString line;
1254
1255 while (TRUE) {
1256 UChar ch = reader.read();
1257 if (ch == 0xFFFF) {
1258 // end of file
1259 if (start && line.startsWith(ICAL_END_VTIMEZONE)) {
1260 vtzlines->addElement(new UnicodeString(line), status);
1261 if (U_FAILURE(status)) {
1262 goto cleanupVtzlines;
1263 }
1264 success = TRUE;
1265 }
1266 break;
1267 }
1268 if (ch == 0x000D) {
1269 // CR, must be followed by LF according to the definition in RFC2445
1270 continue;
1271 }
1272 if (eol) {
1273 if (ch != 0x0009 && ch != 0x0020) {
1274 // NOT followed by TAB/SP -> new line
1275 if (start) {
1276 if (line.length() > 0) {
1277 vtzlines->addElement(new UnicodeString(line), status);
1278 if (U_FAILURE(status)) {
1279 goto cleanupVtzlines;
1280 }
1281 }
1282 }
1283 line.remove();
1284 if (ch != 0x000A) {
1285 line.append(ch);
1286 }
1287 }
1288 eol = FALSE;
1289 } else {
1290 if (ch == 0x000A) {
1291 // LF
1292 eol = TRUE;
1293 if (start) {
1294 if (line.startsWith(ICAL_END_VTIMEZONE)) {
1295 vtzlines->addElement(new UnicodeString(line), status);
1296 if (U_FAILURE(status)) {
1297 goto cleanupVtzlines;
1298 }
1299 success = TRUE;
1300 break;
1301 }
1302 } else {
1303 if (line.startsWith(ICAL_BEGIN_VTIMEZONE)) {
1304 vtzlines->addElement(new UnicodeString(line), status);
1305 if (U_FAILURE(status)) {
1306 goto cleanupVtzlines;
1307 }
1308 line.remove();
1309 start = TRUE;
1310 eol = FALSE;
1311 }
1312 }
1313 } else {
1314 line.append(ch);
1315 }
1316 }
1317 }
1318 if (!success) {
1319 if (U_SUCCESS(status)) {
1320 status = U_INVALID_STATE_ERROR;
1321 }
1322 goto cleanupVtzlines;
1323 }
1324 parse(status);
1325 return;
1326
1327 cleanupVtzlines:
1328 delete vtzlines;
1329 vtzlines = NULL;
1330 }
1331
1332 // parser state
1333 #define INI 0 // Initial state
1334 #define VTZ 1 // In VTIMEZONE
1335 #define TZI 2 // In STANDARD or DAYLIGHT
1336
1337 #define DEF_DSTSAVINGS (60*60*1000)
1338 #define DEF_TZSTARTTIME (0.0)
1339
1340 void
parse(UErrorCode & status)1341 VTimeZone::parse(UErrorCode& status) {
1342 if (U_FAILURE(status)) {
1343 return;
1344 }
1345 if (vtzlines == NULL || vtzlines->size() == 0) {
1346 status = U_INVALID_STATE_ERROR;
1347 return;
1348 }
1349 InitialTimeZoneRule *initialRule = NULL;
1350 RuleBasedTimeZone *rbtz = NULL;
1351
1352 // timezone ID
1353 UnicodeString tzid;
1354
1355 int32_t state = INI;
1356 int32_t n = 0;
1357 UBool dst = FALSE; // current zone type
1358 UnicodeString from; // current zone from offset
1359 UnicodeString to; // current zone offset
1360 UnicodeString zonename; // current zone name
1361 UnicodeString dtstart; // current zone starts
1362 UBool isRRULE = FALSE; // true if the rule is described by RRULE
1363 int32_t initialRawOffset = 0; // initial offset
1364 int32_t initialDSTSavings = 0; // initial offset
1365 UDate firstStart = MAX_MILLIS; // the earliest rule start time
1366 UnicodeString name; // RFC2445 prop name
1367 UnicodeString value; // RFC2445 prop value
1368
1369 UVector *dates = NULL; // list of RDATE or RRULE strings
1370 UVector *rules = NULL; // list of TimeZoneRule instances
1371
1372 int32_t finalRuleIdx = -1;
1373 int32_t finalRuleCount = 0;
1374
1375 rules = new UVector(status);
1376 if (U_FAILURE(status)) {
1377 goto cleanupParse;
1378 }
1379 // Set the deleter to remove TimeZoneRule vectors to avoid memory leaks due to unowned TimeZoneRules.
1380 rules->setDeleter(deleteTimeZoneRule);
1381
1382 dates = new UVector(uhash_deleteUnicodeString, uhash_compareUnicodeString, status);
1383 if (U_FAILURE(status)) {
1384 goto cleanupParse;
1385 }
1386 if (rules == NULL || dates == NULL) {
1387 status = U_MEMORY_ALLOCATION_ERROR;
1388 goto cleanupParse;
1389 }
1390
1391 for (n = 0; n < vtzlines->size(); n++) {
1392 UnicodeString *line = (UnicodeString*)vtzlines->elementAt(n);
1393 int32_t valueSep = line->indexOf(COLON);
1394 if (valueSep < 0) {
1395 continue;
1396 }
1397 name.setTo(*line, 0, valueSep);
1398 value.setTo(*line, valueSep + 1);
1399
1400 switch (state) {
1401 case INI:
1402 if (name.compare(ICAL_BEGIN) == 0
1403 && value.compare(ICAL_VTIMEZONE) == 0) {
1404 state = VTZ;
1405 }
1406 break;
1407
1408 case VTZ:
1409 if (name.compare(ICAL_TZID) == 0) {
1410 tzid = value;
1411 } else if (name.compare(ICAL_TZURL) == 0) {
1412 tzurl = value;
1413 } else if (name.compare(ICAL_LASTMOD) == 0) {
1414 // Always in 'Z' format, so the offset argument for the parse method
1415 // can be any value.
1416 lastmod = parseDateTimeString(value, 0, status);
1417 if (U_FAILURE(status)) {
1418 goto cleanupParse;
1419 }
1420 } else if (name.compare(ICAL_BEGIN) == 0) {
1421 UBool isDST = (value.compare(ICAL_DAYLIGHT) == 0);
1422 if (value.compare(ICAL_STANDARD) == 0 || isDST) {
1423 // tzid must be ready at this point
1424 if (tzid.length() == 0) {
1425 goto cleanupParse;
1426 }
1427 // initialize current zone properties
1428 if (dates->size() != 0) {
1429 dates->removeAllElements();
1430 }
1431 isRRULE = FALSE;
1432 from.remove();
1433 to.remove();
1434 zonename.remove();
1435 dst = isDST;
1436 state = TZI;
1437 } else {
1438 // BEGIN property other than STANDARD/DAYLIGHT
1439 // must not be there.
1440 goto cleanupParse;
1441 }
1442 } else if (name.compare(ICAL_END) == 0) {
1443 break;
1444 }
1445 break;
1446 case TZI:
1447 if (name.compare(ICAL_DTSTART) == 0) {
1448 dtstart = value;
1449 } else if (name.compare(ICAL_TZNAME) == 0) {
1450 zonename = value;
1451 } else if (name.compare(ICAL_TZOFFSETFROM) == 0) {
1452 from = value;
1453 } else if (name.compare(ICAL_TZOFFSETTO) == 0) {
1454 to = value;
1455 } else if (name.compare(ICAL_RDATE) == 0) {
1456 // RDATE mixed with RRULE is not supported
1457 if (isRRULE) {
1458 goto cleanupParse;
1459 }
1460 // RDATE value may contain multiple date delimited
1461 // by comma
1462 UBool nextDate = TRUE;
1463 int32_t dstart = 0;
1464 UnicodeString *dstr;
1465 while (nextDate) {
1466 int32_t dend = value.indexOf(COMMA, dstart);
1467 if (dend == -1) {
1468 dstr = new UnicodeString(value, dstart);
1469 nextDate = FALSE;
1470 } else {
1471 dstr = new UnicodeString(value, dstart, dend - dstart);
1472 }
1473 dates->addElement(dstr, status);
1474 if (U_FAILURE(status)) {
1475 goto cleanupParse;
1476 }
1477 dstart = dend + 1;
1478 }
1479 } else if (name.compare(ICAL_RRULE) == 0) {
1480 // RRULE mixed with RDATE is not supported
1481 if (!isRRULE && dates->size() != 0) {
1482 goto cleanupParse;
1483 }
1484 isRRULE = true;
1485 dates->addElement(new UnicodeString(value), status);
1486 if (U_FAILURE(status)) {
1487 goto cleanupParse;
1488 }
1489 } else if (name.compare(ICAL_END) == 0) {
1490 // Mandatory properties
1491 if (dtstart.length() == 0 || from.length() == 0 || to.length() == 0) {
1492 goto cleanupParse;
1493 }
1494 // if zonename is not available, create one from tzid
1495 if (zonename.length() == 0) {
1496 getDefaultTZName(tzid, dst, zonename);
1497 }
1498
1499 // create a time zone rule
1500 TimeZoneRule *rule = NULL;
1501 int32_t fromOffset = 0;
1502 int32_t toOffset = 0;
1503 int32_t rawOffset = 0;
1504 int32_t dstSavings = 0;
1505 UDate start = 0;
1506
1507 // Parse TZOFFSETFROM/TZOFFSETTO
1508 fromOffset = offsetStrToMillis(from, status);
1509 toOffset = offsetStrToMillis(to, status);
1510 if (U_FAILURE(status)) {
1511 goto cleanupParse;
1512 }
1513
1514 if (dst) {
1515 // If daylight, use the previous offset as rawoffset if positive
1516 if (toOffset - fromOffset > 0) {
1517 rawOffset = fromOffset;
1518 dstSavings = toOffset - fromOffset;
1519 } else {
1520 // This is rare case.. just use 1 hour DST savings
1521 rawOffset = toOffset - DEF_DSTSAVINGS;
1522 dstSavings = DEF_DSTSAVINGS;
1523 }
1524 } else {
1525 rawOffset = toOffset;
1526 dstSavings = 0;
1527 }
1528
1529 // start time
1530 start = parseDateTimeString(dtstart, fromOffset, status);
1531 if (U_FAILURE(status)) {
1532 goto cleanupParse;
1533 }
1534
1535 // Create the rule
1536 UDate actualStart = MAX_MILLIS;
1537 if (isRRULE) {
1538 rule = createRuleByRRULE(zonename, rawOffset, dstSavings, start, dates, fromOffset, status);
1539 } else {
1540 rule = createRuleByRDATE(zonename, rawOffset, dstSavings, start, dates, fromOffset, status);
1541 }
1542 if (U_FAILURE(status) || rule == NULL) {
1543 goto cleanupParse;
1544 } else {
1545 UBool startAvail = rule->getFirstStart(fromOffset, 0, actualStart);
1546 if (startAvail && actualStart < firstStart) {
1547 // save from offset information for the earliest rule
1548 firstStart = actualStart;
1549 // If this is STD, assume the time before this transtion
1550 // is DST when the difference is 1 hour. This might not be
1551 // accurate, but VTIMEZONE data does not have such info.
1552 if (dstSavings > 0) {
1553 initialRawOffset = fromOffset;
1554 initialDSTSavings = 0;
1555 } else {
1556 if (fromOffset - toOffset == DEF_DSTSAVINGS) {
1557 initialRawOffset = fromOffset - DEF_DSTSAVINGS;
1558 initialDSTSavings = DEF_DSTSAVINGS;
1559 } else {
1560 initialRawOffset = fromOffset;
1561 initialDSTSavings = 0;
1562 }
1563 }
1564 }
1565 }
1566 rules->addElement(rule, status);
1567 if (U_FAILURE(status)) {
1568 goto cleanupParse;
1569 }
1570 state = VTZ;
1571 }
1572 break;
1573 }
1574 }
1575 // Must have at least one rule
1576 if (rules->size() == 0) {
1577 goto cleanupParse;
1578 }
1579
1580 // Create a initial rule
1581 getDefaultTZName(tzid, FALSE, zonename);
1582 initialRule = new InitialTimeZoneRule(zonename,
1583 initialRawOffset, initialDSTSavings);
1584 if (initialRule == NULL) {
1585 status = U_MEMORY_ALLOCATION_ERROR;
1586 goto cleanupParse;
1587 }
1588
1589 // Finally, create the RuleBasedTimeZone
1590 rbtz = new RuleBasedTimeZone(tzid, initialRule);
1591 if (rbtz == NULL) {
1592 status = U_MEMORY_ALLOCATION_ERROR;
1593 goto cleanupParse;
1594 }
1595 initialRule = NULL; // already adopted by RBTZ, no need to delete
1596
1597 for (n = 0; n < rules->size(); n++) {
1598 TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n);
1599 AnnualTimeZoneRule *atzrule = dynamic_cast<AnnualTimeZoneRule *>(r);
1600 if (atzrule != NULL) {
1601 if (atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR) {
1602 finalRuleCount++;
1603 finalRuleIdx = n;
1604 }
1605 }
1606 }
1607 if (finalRuleCount > 2) {
1608 // Too many final rules
1609 status = U_ILLEGAL_ARGUMENT_ERROR;
1610 goto cleanupParse;
1611 }
1612
1613 if (finalRuleCount == 1) {
1614 if (rules->size() == 1) {
1615 // Only one final rule, only governs the initial rule,
1616 // which is already initialized, thus, we do not need to
1617 // add this transition rule
1618 rules->removeAllElements();
1619 } else {
1620 // Normalize the final rule
1621 AnnualTimeZoneRule *finalRule = (AnnualTimeZoneRule*)rules->elementAt(finalRuleIdx);
1622 int32_t tmpRaw = finalRule->getRawOffset();
1623 int32_t tmpDST = finalRule->getDSTSavings();
1624
1625 // Find the last non-final rule
1626 UDate finalStart, start;
1627 finalRule->getFirstStart(initialRawOffset, initialDSTSavings, finalStart);
1628 start = finalStart;
1629 for (n = 0; n < rules->size(); n++) {
1630 if (finalRuleIdx == n) {
1631 continue;
1632 }
1633 TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n);
1634 UDate lastStart;
1635 r->getFinalStart(tmpRaw, tmpDST, lastStart);
1636 if (lastStart > start) {
1637 finalRule->getNextStart(lastStart,
1638 r->getRawOffset(),
1639 r->getDSTSavings(),
1640 FALSE,
1641 start);
1642 }
1643 }
1644
1645 TimeZoneRule *newRule;
1646 UnicodeString tznam;
1647 if (start == finalStart) {
1648 // Transform this into a single transition
1649 newRule = new TimeArrayTimeZoneRule(
1650 finalRule->getName(tznam),
1651 finalRule->getRawOffset(),
1652 finalRule->getDSTSavings(),
1653 &finalStart,
1654 1,
1655 DateTimeRule::UTC_TIME);
1656 } else {
1657 // Update the end year
1658 int32_t y, m, d, dow, doy, mid;
1659 Grego::timeToFields(start, y, m, d, dow, doy, mid);
1660 newRule = new AnnualTimeZoneRule(
1661 finalRule->getName(tznam),
1662 finalRule->getRawOffset(),
1663 finalRule->getDSTSavings(),
1664 *(finalRule->getRule()),
1665 finalRule->getStartYear(),
1666 y);
1667 }
1668 if (newRule == NULL) {
1669 status = U_MEMORY_ALLOCATION_ERROR;
1670 goto cleanupParse;
1671 }
1672 rules->removeElementAt(finalRuleIdx);
1673 rules->addElement(newRule, status);
1674 if (U_FAILURE(status)) {
1675 delete newRule;
1676 goto cleanupParse;
1677 }
1678 }
1679 }
1680
1681 while (!rules->isEmpty()) {
1682 TimeZoneRule *tzr = (TimeZoneRule*)rules->orphanElementAt(0);
1683 rbtz->addTransitionRule(tzr, status);
1684 if (U_FAILURE(status)) {
1685 goto cleanupParse;
1686 }
1687 }
1688 rbtz->complete(status);
1689 if (U_FAILURE(status)) {
1690 goto cleanupParse;
1691 }
1692 delete rules;
1693 delete dates;
1694
1695 tz = rbtz;
1696 setID(tzid);
1697 return;
1698
1699 cleanupParse:
1700 if (rules != NULL) {
1701 while (!rules->isEmpty()) {
1702 TimeZoneRule *r = (TimeZoneRule*)rules->orphanElementAt(0);
1703 delete r;
1704 }
1705 delete rules;
1706 }
1707 if (dates != NULL) {
1708 delete dates;
1709 }
1710 if (initialRule != NULL) {
1711 delete initialRule;
1712 }
1713 if (rbtz != NULL) {
1714 delete rbtz;
1715 }
1716 return;
1717 }
1718
1719 void
write(VTZWriter & writer,UErrorCode & status) const1720 VTimeZone::write(VTZWriter& writer, UErrorCode& status) const {
1721 if (vtzlines != NULL) {
1722 for (int32_t i = 0; i < vtzlines->size(); i++) {
1723 UnicodeString *line = (UnicodeString*)vtzlines->elementAt(i);
1724 if (line->startsWith(ICAL_TZURL)
1725 && line->charAt(u_strlen(ICAL_TZURL)) == COLON) {
1726 writer.write(ICAL_TZURL);
1727 writer.write(COLON);
1728 writer.write(tzurl);
1729 writer.write(ICAL_NEWLINE);
1730 } else if (line->startsWith(ICAL_LASTMOD)
1731 && line->charAt(u_strlen(ICAL_LASTMOD)) == COLON) {
1732 UnicodeString utcString;
1733 writer.write(ICAL_LASTMOD);
1734 writer.write(COLON);
1735 writer.write(getUTCDateTimeString(lastmod, utcString));
1736 writer.write(ICAL_NEWLINE);
1737 } else {
1738 writer.write(*line);
1739 writer.write(ICAL_NEWLINE);
1740 }
1741 }
1742 } else {
1743 UVector *customProps = NULL;
1744 if (olsonzid.length() > 0 && icutzver.length() > 0) {
1745 customProps = new UVector(uhash_deleteUnicodeString, uhash_compareUnicodeString, status);
1746 if (U_FAILURE(status)) {
1747 return;
1748 }
1749 UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP);
1750 icutzprop->append(olsonzid);
1751 icutzprop->append((UChar)0x005B/*'['*/);
1752 icutzprop->append(icutzver);
1753 icutzprop->append((UChar)0x005D/*']'*/);
1754 customProps->addElement(icutzprop, status);
1755 if (U_FAILURE(status)) {
1756 delete icutzprop;
1757 delete customProps;
1758 return;
1759 }
1760 }
1761 writeZone(writer, *tz, customProps, status);
1762 delete customProps;
1763 }
1764 }
1765
1766 void
write(UDate start,VTZWriter & writer,UErrorCode & status)1767 VTimeZone::write(UDate start, VTZWriter& writer, UErrorCode& status) /*const*/ {
1768 if (U_FAILURE(status)) {
1769 return;
1770 }
1771 InitialTimeZoneRule *initial = NULL;
1772 UVector *transitionRules = NULL;
1773 UVector customProps(uhash_deleteUnicodeString, uhash_compareUnicodeString, status);
1774 UnicodeString tzid;
1775
1776 // Extract rules applicable to dates after the start time
1777 getTimeZoneRulesAfter(start, initial, transitionRules, status);
1778 if (U_FAILURE(status)) {
1779 return;
1780 }
1781
1782 // Create a RuleBasedTimeZone with the subset rule
1783 getID(tzid);
1784 RuleBasedTimeZone rbtz(tzid, initial);
1785 if (transitionRules != NULL) {
1786 while (!transitionRules->isEmpty()) {
1787 TimeZoneRule *tr = (TimeZoneRule*)transitionRules->orphanElementAt(0);
1788 rbtz.addTransitionRule(tr, status);
1789 if (U_FAILURE(status)) {
1790 goto cleanupWritePartial;
1791 }
1792 }
1793 delete transitionRules;
1794 transitionRules = NULL;
1795 }
1796 rbtz.complete(status);
1797 if (U_FAILURE(status)) {
1798 goto cleanupWritePartial;
1799 }
1800
1801 if (olsonzid.length() > 0 && icutzver.length() > 0) {
1802 UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP);
1803 icutzprop->append(olsonzid);
1804 icutzprop->append((UChar)0x005B/*'['*/);
1805 icutzprop->append(icutzver);
1806 icutzprop->append(ICU_TZINFO_PARTIAL);
1807 appendMillis(start, *icutzprop);
1808 icutzprop->append((UChar)0x005D/*']'*/);
1809 customProps.addElement(icutzprop, status);
1810 if (U_FAILURE(status)) {
1811 delete icutzprop;
1812 goto cleanupWritePartial;
1813 }
1814 }
1815 writeZone(writer, rbtz, &customProps, status);
1816 return;
1817
1818 cleanupWritePartial:
1819 if (initial != NULL) {
1820 delete initial;
1821 }
1822 if (transitionRules != NULL) {
1823 while (!transitionRules->isEmpty()) {
1824 TimeZoneRule *tr = (TimeZoneRule*)transitionRules->orphanElementAt(0);
1825 delete tr;
1826 }
1827 delete transitionRules;
1828 }
1829 }
1830
1831 void
writeSimple(UDate time,VTZWriter & writer,UErrorCode & status)1832 VTimeZone::writeSimple(UDate time, VTZWriter& writer, UErrorCode& status) /*const*/ {
1833 if (U_FAILURE(status)) {
1834 return;
1835 }
1836
1837 UVector customProps(uhash_deleteUnicodeString, uhash_compareUnicodeString, status);
1838 UnicodeString tzid;
1839
1840 // Extract simple rules
1841 InitialTimeZoneRule *initial = NULL;
1842 AnnualTimeZoneRule *std = NULL, *dst = NULL;
1843 getSimpleRulesNear(time, initial, std, dst, status);
1844 if (U_SUCCESS(status)) {
1845 // Create a RuleBasedTimeZone with the subset rule
1846 getID(tzid);
1847 RuleBasedTimeZone rbtz(tzid, initial);
1848 if (std != NULL && dst != NULL) {
1849 rbtz.addTransitionRule(std, status);
1850 rbtz.addTransitionRule(dst, status);
1851 }
1852 if (U_FAILURE(status)) {
1853 goto cleanupWriteSimple;
1854 }
1855
1856 if (olsonzid.length() > 0 && icutzver.length() > 0) {
1857 UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP);
1858 icutzprop->append(olsonzid);
1859 icutzprop->append((UChar)0x005B/*'['*/);
1860 icutzprop->append(icutzver);
1861 icutzprop->append(ICU_TZINFO_SIMPLE);
1862 appendMillis(time, *icutzprop);
1863 icutzprop->append((UChar)0x005D/*']'*/);
1864 customProps.addElement(icutzprop, status);
1865 if (U_FAILURE(status)) {
1866 delete icutzprop;
1867 goto cleanupWriteSimple;
1868 }
1869 }
1870 writeZone(writer, rbtz, &customProps, status);
1871 }
1872 return;
1873
1874 cleanupWriteSimple:
1875 if (initial != NULL) {
1876 delete initial;
1877 }
1878 if (std != NULL) {
1879 delete std;
1880 }
1881 if (dst != NULL) {
1882 delete dst;
1883 }
1884 }
1885
1886 void
writeZone(VTZWriter & w,BasicTimeZone & basictz,UVector * customProps,UErrorCode & status) const1887 VTimeZone::writeZone(VTZWriter& w, BasicTimeZone& basictz,
1888 UVector* customProps, UErrorCode& status) const {
1889 if (U_FAILURE(status)) {
1890 return;
1891 }
1892 writeHeaders(w, status);
1893 if (U_FAILURE(status)) {
1894 return;
1895 }
1896
1897 if (customProps != NULL) {
1898 for (int32_t i = 0; i < customProps->size(); i++) {
1899 UnicodeString *custprop = (UnicodeString*)customProps->elementAt(i);
1900 w.write(*custprop);
1901 w.write(ICAL_NEWLINE);
1902 }
1903 }
1904
1905 UDate t = MIN_MILLIS;
1906 UnicodeString dstName;
1907 int32_t dstFromOffset = 0;
1908 int32_t dstFromDSTSavings = 0;
1909 int32_t dstToOffset = 0;
1910 int32_t dstStartYear = 0;
1911 int32_t dstMonth = 0;
1912 int32_t dstDayOfWeek = 0;
1913 int32_t dstWeekInMonth = 0;
1914 int32_t dstMillisInDay = 0;
1915 UDate dstStartTime = 0.0;
1916 UDate dstUntilTime = 0.0;
1917 int32_t dstCount = 0;
1918 AnnualTimeZoneRule *finalDstRule = NULL;
1919
1920 UnicodeString stdName;
1921 int32_t stdFromOffset = 0;
1922 int32_t stdFromDSTSavings = 0;
1923 int32_t stdToOffset = 0;
1924 int32_t stdStartYear = 0;
1925 int32_t stdMonth = 0;
1926 int32_t stdDayOfWeek = 0;
1927 int32_t stdWeekInMonth = 0;
1928 int32_t stdMillisInDay = 0;
1929 UDate stdStartTime = 0.0;
1930 UDate stdUntilTime = 0.0;
1931 int32_t stdCount = 0;
1932 AnnualTimeZoneRule *finalStdRule = NULL;
1933
1934 int32_t year, month, dom, dow, doy, mid;
1935 UBool hasTransitions = FALSE;
1936 TimeZoneTransition tzt;
1937 UBool tztAvail;
1938 UnicodeString name;
1939 UBool isDst;
1940
1941 // Going through all transitions
1942 while (TRUE) {
1943 tztAvail = basictz.getNextTransition(t, FALSE, tzt);
1944 if (!tztAvail) {
1945 break;
1946 }
1947 hasTransitions = TRUE;
1948 t = tzt.getTime();
1949 tzt.getTo()->getName(name);
1950 isDst = (tzt.getTo()->getDSTSavings() != 0);
1951 int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings();
1952 int32_t fromDSTSavings = tzt.getFrom()->getDSTSavings();
1953 int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings();
1954 Grego::timeToFields(tzt.getTime() + fromOffset, year, month, dom, dow, doy, mid);
1955 int32_t weekInMonth = Grego::dayOfWeekInMonth(year, month, dom);
1956 UBool sameRule = FALSE;
1957 const AnnualTimeZoneRule *atzrule;
1958 if (isDst) {
1959 if (finalDstRule == NULL
1960 && (atzrule = dynamic_cast<const AnnualTimeZoneRule *>(tzt.getTo())) != NULL
1961 && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
1962 ) {
1963 finalDstRule = (AnnualTimeZoneRule*)tzt.getTo()->clone();
1964 }
1965 if (dstCount > 0) {
1966 if (year == dstStartYear + dstCount
1967 && name.compare(dstName) == 0
1968 && dstFromOffset == fromOffset
1969 && dstToOffset == toOffset
1970 && dstMonth == month
1971 && dstDayOfWeek == dow
1972 && dstWeekInMonth == weekInMonth
1973 && dstMillisInDay == mid) {
1974 // Update until time
1975 dstUntilTime = t;
1976 dstCount++;
1977 sameRule = TRUE;
1978 }
1979 if (!sameRule) {
1980 if (dstCount == 1) {
1981 writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime,
1982 TRUE, status);
1983 } else {
1984 writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
1985 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
1986 }
1987 if (U_FAILURE(status)) {
1988 goto cleanupWriteZone;
1989 }
1990 }
1991 }
1992 if (!sameRule) {
1993 // Reset this DST information
1994 dstName = name;
1995 dstFromOffset = fromOffset;
1996 dstFromDSTSavings = fromDSTSavings;
1997 dstToOffset = toOffset;
1998 dstStartYear = year;
1999 dstMonth = month;
2000 dstDayOfWeek = dow;
2001 dstWeekInMonth = weekInMonth;
2002 dstMillisInDay = mid;
2003 dstStartTime = dstUntilTime = t;
2004 dstCount = 1;
2005 }
2006 if (finalStdRule != NULL && finalDstRule != NULL) {
2007 break;
2008 }
2009 } else {
2010 if (finalStdRule == NULL
2011 && (atzrule = dynamic_cast<const AnnualTimeZoneRule *>(tzt.getTo())) != NULL
2012 && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
2013 ) {
2014 finalStdRule = (AnnualTimeZoneRule*)tzt.getTo()->clone();
2015 }
2016 if (stdCount > 0) {
2017 if (year == stdStartYear + stdCount
2018 && name.compare(stdName) == 0
2019 && stdFromOffset == fromOffset
2020 && stdToOffset == toOffset
2021 && stdMonth == month
2022 && stdDayOfWeek == dow
2023 && stdWeekInMonth == weekInMonth
2024 && stdMillisInDay == mid) {
2025 // Update until time
2026 stdUntilTime = t;
2027 stdCount++;
2028 sameRule = TRUE;
2029 }
2030 if (!sameRule) {
2031 if (stdCount == 1) {
2032 writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime,
2033 TRUE, status);
2034 } else {
2035 writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2036 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2037 }
2038 if (U_FAILURE(status)) {
2039 goto cleanupWriteZone;
2040 }
2041 }
2042 }
2043 if (!sameRule) {
2044 // Reset this STD information
2045 stdName = name;
2046 stdFromOffset = fromOffset;
2047 stdFromDSTSavings = fromDSTSavings;
2048 stdToOffset = toOffset;
2049 stdStartYear = year;
2050 stdMonth = month;
2051 stdDayOfWeek = dow;
2052 stdWeekInMonth = weekInMonth;
2053 stdMillisInDay = mid;
2054 stdStartTime = stdUntilTime = t;
2055 stdCount = 1;
2056 }
2057 if (finalStdRule != NULL && finalDstRule != NULL) {
2058 break;
2059 }
2060 }
2061 }
2062 if (!hasTransitions) {
2063 // No transition - put a single non transition RDATE
2064 int32_t raw, dst, offset;
2065 basictz.getOffset(0.0/*any time*/, FALSE, raw, dst, status);
2066 if (U_FAILURE(status)) {
2067 goto cleanupWriteZone;
2068 }
2069 offset = raw + dst;
2070 isDst = (dst != 0);
2071 UnicodeString tzid;
2072 basictz.getID(tzid);
2073 getDefaultTZName(tzid, isDst, name);
2074 writeZonePropsByTime(w, isDst, name,
2075 offset, offset, DEF_TZSTARTTIME - offset, FALSE, status);
2076 if (U_FAILURE(status)) {
2077 goto cleanupWriteZone;
2078 }
2079 } else {
2080 if (dstCount > 0) {
2081 if (finalDstRule == NULL) {
2082 if (dstCount == 1) {
2083 writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime,
2084 TRUE, status);
2085 } else {
2086 writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
2087 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
2088 }
2089 if (U_FAILURE(status)) {
2090 goto cleanupWriteZone;
2091 }
2092 } else {
2093 if (dstCount == 1) {
2094 writeFinalRule(w, TRUE, finalDstRule,
2095 dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, dstStartTime, status);
2096 } else {
2097 // Use a single rule if possible
2098 if (isEquivalentDateRule(dstMonth, dstWeekInMonth, dstDayOfWeek, finalDstRule->getRule())) {
2099 writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
2100 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, MAX_MILLIS, status);
2101 } else {
2102 // Not equivalent rule - write out two different rules
2103 writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
2104 dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
2105 if (U_FAILURE(status)) {
2106 goto cleanupWriteZone;
2107 }
2108 writeFinalRule(w, TRUE, finalDstRule,
2109 dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, dstStartTime, status);
2110 }
2111 }
2112 if (U_FAILURE(status)) {
2113 goto cleanupWriteZone;
2114 }
2115 }
2116 }
2117 if (stdCount > 0) {
2118 if (finalStdRule == NULL) {
2119 if (stdCount == 1) {
2120 writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime,
2121 TRUE, status);
2122 } else {
2123 writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2124 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2125 }
2126 if (U_FAILURE(status)) {
2127 goto cleanupWriteZone;
2128 }
2129 } else {
2130 if (stdCount == 1) {
2131 writeFinalRule(w, FALSE, finalStdRule,
2132 stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, stdStartTime, status);
2133 } else {
2134 // Use a single rule if possible
2135 if (isEquivalentDateRule(stdMonth, stdWeekInMonth, stdDayOfWeek, finalStdRule->getRule())) {
2136 writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2137 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, MAX_MILLIS, status);
2138 } else {
2139 // Not equivalent rule - write out two different rules
2140 writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2141 stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2142 if (U_FAILURE(status)) {
2143 goto cleanupWriteZone;
2144 }
2145 writeFinalRule(w, FALSE, finalStdRule,
2146 stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, stdStartTime, status);
2147 }
2148 }
2149 if (U_FAILURE(status)) {
2150 goto cleanupWriteZone;
2151 }
2152 }
2153 }
2154 }
2155 writeFooter(w, status);
2156
2157 cleanupWriteZone:
2158
2159 if (finalStdRule != NULL) {
2160 delete finalStdRule;
2161 }
2162 if (finalDstRule != NULL) {
2163 delete finalDstRule;
2164 }
2165 }
2166
2167 void
writeHeaders(VTZWriter & writer,UErrorCode & status) const2168 VTimeZone::writeHeaders(VTZWriter& writer, UErrorCode& status) const {
2169 if (U_FAILURE(status)) {
2170 return;
2171 }
2172 UnicodeString tzid;
2173 tz->getID(tzid);
2174
2175 writer.write(ICAL_BEGIN);
2176 writer.write(COLON);
2177 writer.write(ICAL_VTIMEZONE);
2178 writer.write(ICAL_NEWLINE);
2179 writer.write(ICAL_TZID);
2180 writer.write(COLON);
2181 writer.write(tzid);
2182 writer.write(ICAL_NEWLINE);
2183 if (tzurl.length() != 0) {
2184 writer.write(ICAL_TZURL);
2185 writer.write(COLON);
2186 writer.write(tzurl);
2187 writer.write(ICAL_NEWLINE);
2188 }
2189 if (lastmod != MAX_MILLIS) {
2190 UnicodeString lastmodStr;
2191 writer.write(ICAL_LASTMOD);
2192 writer.write(COLON);
2193 writer.write(getUTCDateTimeString(lastmod, lastmodStr));
2194 writer.write(ICAL_NEWLINE);
2195 }
2196 }
2197
2198 /*
2199 * Write the closing section of the VTIMEZONE definition block
2200 */
2201 void
writeFooter(VTZWriter & writer,UErrorCode & status) const2202 VTimeZone::writeFooter(VTZWriter& writer, UErrorCode& status) const {
2203 if (U_FAILURE(status)) {
2204 return;
2205 }
2206 writer.write(ICAL_END);
2207 writer.write(COLON);
2208 writer.write(ICAL_VTIMEZONE);
2209 writer.write(ICAL_NEWLINE);
2210 }
2211
2212 /*
2213 * Write a single start time
2214 */
2215 void
writeZonePropsByTime(VTZWriter & writer,UBool isDst,const UnicodeString & zonename,int32_t fromOffset,int32_t toOffset,UDate time,UBool withRDATE,UErrorCode & status) const2216 VTimeZone::writeZonePropsByTime(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2217 int32_t fromOffset, int32_t toOffset, UDate time, UBool withRDATE,
2218 UErrorCode& status) const {
2219 if (U_FAILURE(status)) {
2220 return;
2221 }
2222 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, time, status);
2223 if (U_FAILURE(status)) {
2224 return;
2225 }
2226 if (withRDATE) {
2227 writer.write(ICAL_RDATE);
2228 writer.write(COLON);
2229 UnicodeString timestr;
2230 writer.write(getDateTimeString(time + fromOffset, timestr));
2231 writer.write(ICAL_NEWLINE);
2232 }
2233 endZoneProps(writer, isDst, status);
2234 if (U_FAILURE(status)) {
2235 return;
2236 }
2237 }
2238
2239 /*
2240 * Write start times defined by a DOM rule using VTIMEZONE RRULE
2241 */
2242 void
writeZonePropsByDOM(VTZWriter & writer,UBool isDst,const UnicodeString & zonename,int32_t fromOffset,int32_t toOffset,int32_t month,int32_t dayOfMonth,UDate startTime,UDate untilTime,UErrorCode & status) const2243 VTimeZone::writeZonePropsByDOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2244 int32_t fromOffset, int32_t toOffset,
2245 int32_t month, int32_t dayOfMonth, UDate startTime, UDate untilTime,
2246 UErrorCode& status) const {
2247 if (U_FAILURE(status)) {
2248 return;
2249 }
2250 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
2251 if (U_FAILURE(status)) {
2252 return;
2253 }
2254 beginRRULE(writer, month, status);
2255 if (U_FAILURE(status)) {
2256 return;
2257 }
2258 writer.write(ICAL_BYMONTHDAY);
2259 writer.write(EQUALS_SIGN);
2260 UnicodeString dstr;
2261 appendAsciiDigits(dayOfMonth, 0, dstr);
2262 writer.write(dstr);
2263 if (untilTime != MAX_MILLIS) {
2264 appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2265 if (U_FAILURE(status)) {
2266 return;
2267 }
2268 }
2269 writer.write(ICAL_NEWLINE);
2270 endZoneProps(writer, isDst, status);
2271 }
2272
2273 /*
2274 * Write start times defined by a DOW rule using VTIMEZONE RRULE
2275 */
2276 void
writeZonePropsByDOW(VTZWriter & writer,UBool isDst,const UnicodeString & zonename,int32_t fromOffset,int32_t toOffset,int32_t month,int32_t weekInMonth,int32_t dayOfWeek,UDate startTime,UDate untilTime,UErrorCode & status) const2277 VTimeZone::writeZonePropsByDOW(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2278 int32_t fromOffset, int32_t toOffset,
2279 int32_t month, int32_t weekInMonth, int32_t dayOfWeek,
2280 UDate startTime, UDate untilTime, UErrorCode& status) const {
2281 if (U_FAILURE(status)) {
2282 return;
2283 }
2284 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
2285 if (U_FAILURE(status)) {
2286 return;
2287 }
2288 beginRRULE(writer, month, status);
2289 if (U_FAILURE(status)) {
2290 return;
2291 }
2292 writer.write(ICAL_BYDAY);
2293 writer.write(EQUALS_SIGN);
2294 UnicodeString dstr;
2295 appendAsciiDigits(weekInMonth, 0, dstr);
2296 writer.write(dstr); // -4, -3, -2, -1, 1, 2, 3, 4
2297 writer.write(ICAL_DOW_NAMES[dayOfWeek - 1]); // SU, MO, TU...
2298
2299 if (untilTime != MAX_MILLIS) {
2300 appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2301 if (U_FAILURE(status)) {
2302 return;
2303 }
2304 }
2305 writer.write(ICAL_NEWLINE);
2306 endZoneProps(writer, isDst, status);
2307 }
2308
2309 /*
2310 * Write start times defined by a DOW_GEQ_DOM rule using VTIMEZONE RRULE
2311 */
2312 void
writeZonePropsByDOW_GEQ_DOM(VTZWriter & writer,UBool isDst,const UnicodeString & zonename,int32_t fromOffset,int32_t toOffset,int32_t month,int32_t dayOfMonth,int32_t dayOfWeek,UDate startTime,UDate untilTime,UErrorCode & status) const2313 VTimeZone::writeZonePropsByDOW_GEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2314 int32_t fromOffset, int32_t toOffset,
2315 int32_t month, int32_t dayOfMonth, int32_t dayOfWeek,
2316 UDate startTime, UDate untilTime, UErrorCode& status) const {
2317 if (U_FAILURE(status)) {
2318 return;
2319 }
2320 // Check if this rule can be converted to DOW rule
2321 if (dayOfMonth%7 == 1) {
2322 // Can be represented by DOW rule
2323 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2324 month, (dayOfMonth + 6)/7, dayOfWeek, startTime, untilTime, status);
2325 if (U_FAILURE(status)) {
2326 return;
2327 }
2328 } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 6) {
2329 // Can be represented by DOW rule with negative week number
2330 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2331 month, -1*((MONTHLENGTH[month] - dayOfMonth + 1)/7), dayOfWeek, startTime, untilTime, status);
2332 if (U_FAILURE(status)) {
2333 return;
2334 }
2335 } else {
2336 // Otherwise, use BYMONTHDAY to include all possible dates
2337 beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
2338 if (U_FAILURE(status)) {
2339 return;
2340 }
2341 // Check if all days are in the same month
2342 int32_t startDay = dayOfMonth;
2343 int32_t currentMonthDays = 7;
2344
2345 if (dayOfMonth <= 0) {
2346 // The start day is in previous month
2347 int32_t prevMonthDays = 1 - dayOfMonth;
2348 currentMonthDays -= prevMonthDays;
2349
2350 int32_t prevMonth = (month - 1) < 0 ? 11 : month - 1;
2351
2352 // Note: When a rule is separated into two, UNTIL attribute needs to be
2353 // calculated for each of them. For now, we skip this, because we basically use this method
2354 // only for final rules, which does not have the UNTIL attribute
2355 writeZonePropsByDOW_GEQ_DOM_sub(writer, prevMonth, -prevMonthDays, dayOfWeek, prevMonthDays,
2356 MAX_MILLIS /* Do not use UNTIL */, fromOffset, status);
2357 if (U_FAILURE(status)) {
2358 return;
2359 }
2360
2361 // Start from 1 for the rest
2362 startDay = 1;
2363 } else if (dayOfMonth + 6 > MONTHLENGTH[month]) {
2364 // Note: This code does not actually work well in February. For now, days in month in
2365 // non-leap year.
2366 int32_t nextMonthDays = dayOfMonth + 6 - MONTHLENGTH[month];
2367 currentMonthDays -= nextMonthDays;
2368
2369 int32_t nextMonth = (month + 1) > 11 ? 0 : month + 1;
2370
2371 writeZonePropsByDOW_GEQ_DOM_sub(writer, nextMonth, 1, dayOfWeek, nextMonthDays,
2372 MAX_MILLIS /* Do not use UNTIL */, fromOffset, status);
2373 if (U_FAILURE(status)) {
2374 return;
2375 }
2376 }
2377 writeZonePropsByDOW_GEQ_DOM_sub(writer, month, startDay, dayOfWeek, currentMonthDays,
2378 untilTime, fromOffset, status);
2379 if (U_FAILURE(status)) {
2380 return;
2381 }
2382 endZoneProps(writer, isDst, status);
2383 }
2384 }
2385
2386 /*
2387 * Called from writeZonePropsByDOW_GEQ_DOM
2388 */
2389 void
writeZonePropsByDOW_GEQ_DOM_sub(VTZWriter & writer,int32_t month,int32_t dayOfMonth,int32_t dayOfWeek,int32_t numDays,UDate untilTime,int32_t fromOffset,UErrorCode & status) const2390 VTimeZone::writeZonePropsByDOW_GEQ_DOM_sub(VTZWriter& writer, int32_t month, int32_t dayOfMonth,
2391 int32_t dayOfWeek, int32_t numDays,
2392 UDate untilTime, int32_t fromOffset, UErrorCode& status) const {
2393
2394 if (U_FAILURE(status)) {
2395 return;
2396 }
2397 int32_t startDayNum = dayOfMonth;
2398 UBool isFeb = (month == UCAL_FEBRUARY);
2399 if (dayOfMonth < 0 && !isFeb) {
2400 // Use positive number if possible
2401 startDayNum = MONTHLENGTH[month] + dayOfMonth + 1;
2402 }
2403 beginRRULE(writer, month, status);
2404 if (U_FAILURE(status)) {
2405 return;
2406 }
2407 writer.write(ICAL_BYDAY);
2408 writer.write(EQUALS_SIGN);
2409 writer.write(ICAL_DOW_NAMES[dayOfWeek - 1]); // SU, MO, TU...
2410 writer.write(SEMICOLON);
2411 writer.write(ICAL_BYMONTHDAY);
2412 writer.write(EQUALS_SIGN);
2413
2414 UnicodeString dstr;
2415 appendAsciiDigits(startDayNum, 0, dstr);
2416 writer.write(dstr);
2417 for (int32_t i = 1; i < numDays; i++) {
2418 writer.write(COMMA);
2419 dstr.remove();
2420 appendAsciiDigits(startDayNum + i, 0, dstr);
2421 writer.write(dstr);
2422 }
2423
2424 if (untilTime != MAX_MILLIS) {
2425 appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2426 if (U_FAILURE(status)) {
2427 return;
2428 }
2429 }
2430 writer.write(ICAL_NEWLINE);
2431 }
2432
2433 /*
2434 * Write start times defined by a DOW_LEQ_DOM rule using VTIMEZONE RRULE
2435 */
2436 void
writeZonePropsByDOW_LEQ_DOM(VTZWriter & writer,UBool isDst,const UnicodeString & zonename,int32_t fromOffset,int32_t toOffset,int32_t month,int32_t dayOfMonth,int32_t dayOfWeek,UDate startTime,UDate untilTime,UErrorCode & status) const2437 VTimeZone::writeZonePropsByDOW_LEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2438 int32_t fromOffset, int32_t toOffset,
2439 int32_t month, int32_t dayOfMonth, int32_t dayOfWeek,
2440 UDate startTime, UDate untilTime, UErrorCode& status) const {
2441 if (U_FAILURE(status)) {
2442 return;
2443 }
2444 // Check if this rule can be converted to DOW rule
2445 if (dayOfMonth%7 == 0) {
2446 // Can be represented by DOW rule
2447 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2448 month, dayOfMonth/7, dayOfWeek, startTime, untilTime, status);
2449 } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 0){
2450 // Can be represented by DOW rule with negative week number
2451 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2452 month, -1*((MONTHLENGTH[month] - dayOfMonth)/7 + 1), dayOfWeek, startTime, untilTime, status);
2453 } else if (month == UCAL_FEBRUARY && dayOfMonth == 29) {
2454 // Specical case for February
2455 writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2456 UCAL_FEBRUARY, -1, dayOfWeek, startTime, untilTime, status);
2457 } else {
2458 // Otherwise, convert this to DOW_GEQ_DOM rule
2459 writeZonePropsByDOW_GEQ_DOM(writer, isDst, zonename, fromOffset, toOffset,
2460 month, dayOfMonth - 6, dayOfWeek, startTime, untilTime, status);
2461 }
2462 }
2463
2464 /*
2465 * Write the final time zone rule using RRULE, with no UNTIL attribute
2466 */
2467 void
writeFinalRule(VTZWriter & writer,UBool isDst,const AnnualTimeZoneRule * rule,int32_t fromRawOffset,int32_t fromDSTSavings,UDate startTime,UErrorCode & status) const2468 VTimeZone::writeFinalRule(VTZWriter& writer, UBool isDst, const AnnualTimeZoneRule* rule,
2469 int32_t fromRawOffset, int32_t fromDSTSavings,
2470 UDate startTime, UErrorCode& status) const {
2471 if (U_FAILURE(status)) {
2472 return;
2473 }
2474 UBool modifiedRule = TRUE;
2475 const DateTimeRule *dtrule = toWallTimeRule(rule->getRule(), fromRawOffset, fromDSTSavings);
2476 if (dtrule == NULL) {
2477 modifiedRule = FALSE;
2478 dtrule = rule->getRule();
2479 }
2480
2481 // If the rule's mills in a day is out of range, adjust start time.
2482 // Olson tzdata supports 24:00 of a day, but VTIMEZONE does not.
2483 // See ticket#7008/#7518
2484
2485 int32_t timeInDay = dtrule->getRuleMillisInDay();
2486 if (timeInDay < 0) {
2487 startTime = startTime + (0 - timeInDay);
2488 } else if (timeInDay >= U_MILLIS_PER_DAY) {
2489 startTime = startTime - (timeInDay - (U_MILLIS_PER_DAY - 1));
2490 }
2491
2492 int32_t toOffset = rule->getRawOffset() + rule->getDSTSavings();
2493 UnicodeString name;
2494 rule->getName(name);
2495 switch (dtrule->getDateRuleType()) {
2496 case DateTimeRule::DOM:
2497 writeZonePropsByDOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2498 dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), startTime, MAX_MILLIS, status);
2499 break;
2500 case DateTimeRule::DOW:
2501 writeZonePropsByDOW(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2502 dtrule->getRuleMonth(), dtrule->getRuleWeekInMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
2503 break;
2504 case DateTimeRule::DOW_GEQ_DOM:
2505 writeZonePropsByDOW_GEQ_DOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2506 dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
2507 break;
2508 case DateTimeRule::DOW_LEQ_DOM:
2509 writeZonePropsByDOW_LEQ_DOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2510 dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
2511 break;
2512 }
2513 if (modifiedRule) {
2514 delete dtrule;
2515 }
2516 }
2517
2518 /*
2519 * Write the opening section of zone properties
2520 */
2521 void
beginZoneProps(VTZWriter & writer,UBool isDst,const UnicodeString & zonename,int32_t fromOffset,int32_t toOffset,UDate startTime,UErrorCode & status) const2522 VTimeZone::beginZoneProps(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2523 int32_t fromOffset, int32_t toOffset, UDate startTime, UErrorCode& status) const {
2524 if (U_FAILURE(status)) {
2525 return;
2526 }
2527 writer.write(ICAL_BEGIN);
2528 writer.write(COLON);
2529 if (isDst) {
2530 writer.write(ICAL_DAYLIGHT);
2531 } else {
2532 writer.write(ICAL_STANDARD);
2533 }
2534 writer.write(ICAL_NEWLINE);
2535
2536 UnicodeString dstr;
2537
2538 // TZOFFSETTO
2539 writer.write(ICAL_TZOFFSETTO);
2540 writer.write(COLON);
2541 millisToOffset(toOffset, dstr);
2542 writer.write(dstr);
2543 writer.write(ICAL_NEWLINE);
2544
2545 // TZOFFSETFROM
2546 writer.write(ICAL_TZOFFSETFROM);
2547 writer.write(COLON);
2548 millisToOffset(fromOffset, dstr);
2549 writer.write(dstr);
2550 writer.write(ICAL_NEWLINE);
2551
2552 // TZNAME
2553 writer.write(ICAL_TZNAME);
2554 writer.write(COLON);
2555 writer.write(zonename);
2556 writer.write(ICAL_NEWLINE);
2557
2558 // DTSTART
2559 writer.write(ICAL_DTSTART);
2560 writer.write(COLON);
2561 writer.write(getDateTimeString(startTime + fromOffset, dstr));
2562 writer.write(ICAL_NEWLINE);
2563 }
2564
2565 /*
2566 * Writes the closing section of zone properties
2567 */
2568 void
endZoneProps(VTZWriter & writer,UBool isDst,UErrorCode & status) const2569 VTimeZone::endZoneProps(VTZWriter& writer, UBool isDst, UErrorCode& status) const {
2570 if (U_FAILURE(status)) {
2571 return;
2572 }
2573 // END:STANDARD or END:DAYLIGHT
2574 writer.write(ICAL_END);
2575 writer.write(COLON);
2576 if (isDst) {
2577 writer.write(ICAL_DAYLIGHT);
2578 } else {
2579 writer.write(ICAL_STANDARD);
2580 }
2581 writer.write(ICAL_NEWLINE);
2582 }
2583
2584 /*
2585 * Write the beggining part of RRULE line
2586 */
2587 void
beginRRULE(VTZWriter & writer,int32_t month,UErrorCode & status) const2588 VTimeZone::beginRRULE(VTZWriter& writer, int32_t month, UErrorCode& status) const {
2589 if (U_FAILURE(status)) {
2590 return;
2591 }
2592 UnicodeString dstr;
2593 writer.write(ICAL_RRULE);
2594 writer.write(COLON);
2595 writer.write(ICAL_FREQ);
2596 writer.write(EQUALS_SIGN);
2597 writer.write(ICAL_YEARLY);
2598 writer.write(SEMICOLON);
2599 writer.write(ICAL_BYMONTH);
2600 writer.write(EQUALS_SIGN);
2601 appendAsciiDigits(month + 1, 0, dstr);
2602 writer.write(dstr);
2603 writer.write(SEMICOLON);
2604 }
2605
2606 /*
2607 * Append the UNTIL attribute after RRULE line
2608 */
2609 void
appendUNTIL(VTZWriter & writer,const UnicodeString & until,UErrorCode & status) const2610 VTimeZone::appendUNTIL(VTZWriter& writer, const UnicodeString& until, UErrorCode& status) const {
2611 if (U_FAILURE(status)) {
2612 return;
2613 }
2614 if (until.length() > 0) {
2615 writer.write(SEMICOLON);
2616 writer.write(ICAL_UNTIL);
2617 writer.write(EQUALS_SIGN);
2618 writer.write(until);
2619 }
2620 }
2621
2622 U_NAMESPACE_END
2623
2624 #endif /* #if !UCONFIG_NO_FORMATTING */
2625
2626 //eof
2627