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