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