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