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