Coverage Report

Created: 2025-06-24 06:43

/src/icu/source/i18n/vtzone.cpp
Line
Count
Source (jump to first uncovered line)
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
0
    }
95
0
    if (length <= 0 || str.length() < start || (start + length) > str.length()) {
96
0
        status = U_INVALID_FORMAT_ERROR;
97
0
        return 0;
98
0
    }
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
0
    }
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
0
        }
115
0
        num = 10 * num + digit;
116
0
    }
117
0
    return sign * num;    
118
0
}
119
120
0
static UnicodeString& appendAsciiDigits(int32_t number, uint8_t length, UnicodeString& str) {
121
0
    UBool negative = FALSE;
122
0
    int32_t digits[10]; // max int32_t is 10 decimal digits
123
0
    int32_t i;
124
125
0
    if (number < 0) {
126
0
        negative = TRUE;
127
0
        number *= -1;
128
0
    }
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 = static_cast<uint8_t>(i);
139
0
    } else {
140
        // fixed digits
141
0
        for (i = 0; i < length; i++) {
142
0
           digits[i] = number % 10;
143
0
           number /= 10;
144
0
        }
145
0
    }
146
0
    if (negative) {
147
0
        str.append(MINUS);
148
0
    }
149
0
    for (i = length - 1; i >= 0; i--) {
150
0
        str.append((UChar)(digits[i] + 0x0030));
151
0
    }
152
0
    return str;
153
0
}
154
155
0
static UnicodeString& appendMillis(UDate date, UnicodeString& str) {
156
0
    UBool negative = FALSE;
157
0
    int32_t digits[20]; // max int64_t is 20 decimal digits
158
0
    int32_t i;
159
0
    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
0
    } else {
166
0
        number = (int64_t)date;
167
0
    }
168
0
    if (number < 0) {
169
0
        negative = TRUE;
170
0
        number *= -1;
171
0
    }
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
0
    }
181
0
    i--;
182
0
    while (i >= 0) {
183
0
        str.append((UChar)(digits[i--] + 0x0030));
184
0
    }
185
0
    return str;
186
0
}
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
0
    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
0
}
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
0
}
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
0
    }
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
0
    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
0
        }
242
0
        if (str.charAt(8) != 0x0054) {
243
            // character "T" must be used for separating date and time
244
0
            break;
245
0
        }
246
0
        if (length == 16) {
247
0
            if (str.charAt(15) != 0x005A) {
248
                // invalid format
249
0
                break;
250
0
            }
251
0
            isUTC = TRUE;
252
0
        }
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
0
        }
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
0
            break;
270
0
        }
271
272
0
        isValid = TRUE;
273
0
    } while(false);
274
275
0
    if (!isValid) {
276
0
        status = U_INVALID_FORMAT_ERROR;
277
0
        return 0.0;
278
0
    }
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
0
    }
285
0
    return time;
286
0
}
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
0
    }
295
296
0
    UBool isValid = FALSE;
297
0
    int32_t sign = 0, hour = 0, min = 0, sec = 0;
298
299
0
    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
0
        }
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
0
        } else {
312
            // utf-offset must start with "+" or "-"
313
0
            break;
314
0
        }
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
0
        }
320
0
        if (U_FAILURE(status)) {
321
0
            break;
322
0
        }
323
0
        isValid = true;
324
0
    } while(false);
325
326
0
    if (!isValid) {
327
0
        status = U_INVALID_FORMAT_ERROR;
328
0
        return 0;
329
0
    }
330
0
    int32_t millis = sign * ((hour * 60 + min) * 60 + sec) * 1000;
331
0
    return millis;
332
0
}
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
0
    } else {
342
0
        str.append(MINUS);
343
0
        millis = -millis;
344
0
    }
345
0
    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
0
    } else {
366
0
        zonename += UNICODE_STRING_SIMPLE("(STD)");
367
0
    }
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 available 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
static void parseRRULE(const UnicodeString& rrule, int32_t& month, int32_t& dow, int32_t& wim,
383
0
                       int32_t* dom, int32_t& domCount, UDate& until, UErrorCode& status) {
384
0
    if (U_FAILURE(status)) {
385
0
        return;
386
0
    }
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
0
    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
0
        } else {
408
0
            prop.setTo(rrule, prop_start, prop_end - prop_start);
409
0
            prop_start = prop_end + 1;
410
0
        }
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
0
        } else {
416
0
            goto rruleParseError;
417
0
        }
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
0
            } else {
424
0
                goto rruleParseError;
425
0
            }
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
0
            }
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
0
            }
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
0
            }
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
0
                goto rruleParseError;
451
0
            }
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
0
                }
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
0
                }
466
0
                wim = n * sign;
467
0
                value.remove(0, length - 2);
468
0
            }
469
0
            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
0
                }
474
0
            }
475
0
            if (wday < 7) {
476
                // Sunday(1) - Saturday(7)
477
0
                dow = wday + 1;
478
0
            } else {
479
0
                goto rruleParseError;
480
0
            }
481
0
        } else if (attr.compare(ICAL_BYMONTHDAY, -1) == 0) {
482
            // Note: BYMONTHDAY may contain multiple days delimited 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
0
            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
0
                }
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
0
                    }
501
0
                    dom_idx++;
502
0
                } else {
503
0
                    status = U_BUFFER_OVERFLOW_ERROR;
504
0
                    goto rruleParseError;
505
0
                }
506
0
                dom_start = dom_end + 1;
507
0
            }
508
0
            numDom = dom_idx;
509
0
        }
510
0
    }
511
0
    if (!yearly) {
512
        // FREQ=YEARLY must be set
513
0
        goto rruleParseError;
514
0
    }
515
    // Set actual number of parsed DOM (ICAL_BYMONTHDAY)
516
0
    domCount = numDom;
517
0
    return;
518
519
0
rruleParseError:
520
0
    if (U_SUCCESS(status)) {
521
        // Set error status
522
0
        status = U_INVALID_FORMAT_ERROR;
523
0
    }
524
0
}
525
526
static TimeZoneRule* createRuleByRRULE(const UnicodeString& zonename, int rawOffset, int dstSavings, UDate start,
527
0
                                       UVector* dates, int fromOffset, UErrorCode& status) {
528
0
    if (U_FAILURE(status)) {
529
0
        return nullptr;
530
0
    }
531
0
    if (dates == nullptr || dates->size() == 0) {
532
0
        status = U_ILLEGAL_ARGUMENT_ERROR;
533
0
        return nullptr;
534
0
    }
535
536
0
    int32_t i, j;
537
0
    DateTimeRule *adtr = nullptr;
538
539
    // Parse the first rule
540
0
    UnicodeString rrule = *((UnicodeString*)dates->elementAt(0));
541
0
    int32_t month, dayOfWeek, nthDayOfWeek, dayOfMonth = 0;
542
0
    int32_t days[7];
543
0
    int32_t daysCount = UPRV_LENGTHOF(days);
544
0
    UDate until;
545
546
0
    parseRRULE(rrule, month, dayOfWeek, nthDayOfWeek, days, daysCount, until, status);
547
0
    if (U_FAILURE(status)) {
548
0
        return nullptr;
549
0
    }
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
0
                goto unsupportedRRule;
559
0
            }
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
0
                }
568
0
                if (days[i] < firstDay) {
569
0
                    firstDay = days[i];
570
0
                }
571
0
            }
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
0
                    }
580
0
                }
581
0
                if (!found) {
582
                    // days are not continuous
583
0
                    goto unsupportedRRule;
584
0
                }
585
0
            }
586
            // Use DOW_GEQ_DOM rule with firstDay as the start date
587
0
            dayOfMonth = firstDay;
588
0
        }
589
0
    } 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
0
            goto unsupportedRRule;
595
0
        }
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
0
        }
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
0
        }
613
614
0
        int32_t anotherMonth = -1;
615
0
        for (i = 1; i < dates->size(); i++) {
616
0
            rrule = *((UnicodeString*)dates->elementAt(i));
617
0
            UDate tmp_until;
618
0
            int32_t tmp_month, tmp_dayOfWeek, tmp_nthDayOfWeek;
619
0
            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 nullptr;
624
0
            }
625
            // If UNTIL is newer than previous one, use the one
626
0
            if (tmp_until > until) {
627
0
                until = tmp_until;
628
0
            }
629
            
630
            // Check if BYMONTH + BYMONTHDAY + BYDAY rule
631
0
            if (tmp_month == -1 || tmp_dayOfWeek == 0 || tmp_daysCount == 0) {
632
0
                goto unsupportedRRule;
633
0
            }
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
0
            }
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
0
            }
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
0
                    } else {
658
                        // The day range cannot exceed more than 2 months
659
0
                        goto unsupportedRRule;
660
0
                    }
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
0
                }
665
0
            }
666
            // If earlier 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
0
                }
672
0
            }
673
0
            daysCount += tmp_daysCount;
674
0
        }
675
0
        if (daysCount != 7) {
676
            // Number of BYMONTHDAY entries must be 7
677
0
            goto unsupportedRRule;
678
0
        }
679
0
        month = earliestMonth;
680
0
        dayOfMonth = earliestDay;
681
0
    }
682
683
    // Calculate start/end year and missing fields
684
0
    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
0
    }
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
0
    }
695
696
0
    int32_t endYear;
697
0
    if (until != MIN_MILLIS) {
698
0
        int32_t endMonth, endDOM, endDOW, endDOY, endMID;
699
0
        Grego::timeToFields(until, endYear, endMonth, endDOM, endDOW, endDOY, endMID);
700
0
    } else {
701
0
        endYear = AnnualTimeZoneRule::MAX_YEAR;
702
0
    }
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
0
    }
716
0
    if (adtr == nullptr) {
717
0
        goto unsupportedRRule;
718
0
    }
719
0
    return new AnnualTimeZoneRule(zonename, rawOffset, dstSavings, adtr, startYear, endYear);
720
721
0
unsupportedRRule:
722
0
    status = U_INVALID_STATE_ERROR;
723
0
    return nullptr;
724
0
}
725
726
/*
727
 * Create a TimeZoneRule by the RDATE definition
728
 */
729
static TimeZoneRule* createRuleByRDATE(const UnicodeString& zonename, int32_t rawOffset, int32_t dstSavings,
730
0
                                       UDate start, UVector* dates, int32_t fromOffset, UErrorCode& status) {
731
0
    if (U_FAILURE(status)) {
732
0
        return nullptr;
733
0
    }
734
0
    TimeArrayTimeZoneRule *retVal = nullptr;
735
0
    if (dates == nullptr || 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, &start, 1, DateTimeRule::UTC_TIME);
739
0
    } else {
740
        // Create an array of transition times
741
0
        int32_t size = dates->size();
742
0
        UDate* times = (UDate*)uprv_malloc(sizeof(UDate) * size);
743
0
        if (times == nullptr) {
744
0
            status = U_MEMORY_ALLOCATION_ERROR;
745
0
            return nullptr;
746
0
        }
747
0
        for (int32_t i = 0; i < size; i++) {
748
0
            UnicodeString *datestr = (UnicodeString*)dates->elementAt(i);
749
0
            times[i] = parseDateTimeString(*datestr, fromOffset, status);
750
0
            if (U_FAILURE(status)) {
751
0
                uprv_free(times);
752
0
                return nullptr;
753
0
            }
754
0
        }
755
0
        retVal = new TimeArrayTimeZoneRule(zonename, rawOffset, dstSavings, times, size, DateTimeRule::UTC_TIME);
756
0
        uprv_free(times);
757
0
    }
758
0
    if (retVal == nullptr) {
759
0
        status = U_MEMORY_ALLOCATION_ERROR;
760
0
    }
761
0
    return retVal;
762
0
}
763
764
/*
765
 * Check if the DOW rule specified by month, weekInMonth and dayOfWeek is equivalent
766
 * to the DateTimerule.
767
 */
768
0
static UBool isEquivalentDateRule(int32_t month, int32_t weekInMonth, int32_t dayOfWeek, const DateTimeRule *dtrule) {
769
0
    if (month != dtrule->getRuleMonth() || dayOfWeek != dtrule->getRuleDayOfWeek()) {
770
0
        return FALSE;
771
0
    }
772
0
    if (dtrule->getTimeRuleType() != DateTimeRule::WALL_TIME) {
773
        // Do not try to do more intelligent comparison for now.
774
0
        return FALSE;
775
0
    }
776
0
    if (dtrule->getDateRuleType() == DateTimeRule::DOW
777
0
            && dtrule->getRuleWeekInMonth() == weekInMonth) {
778
0
        return TRUE;
779
0
    }
780
0
    int32_t ruleDOM = dtrule->getRuleDayOfMonth();
781
0
    if (dtrule->getDateRuleType() == DateTimeRule::DOW_GEQ_DOM) {
782
0
        if (ruleDOM%7 == 1 && (ruleDOM + 6)/7 == weekInMonth) {
783
0
            return TRUE;
784
0
        }
785
0
        if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - ruleDOM)%7 == 6
786
0
                && weekInMonth == -1*((MONTHLENGTH[month]-ruleDOM+1)/7)) {
787
0
            return TRUE;
788
0
        }
789
0
    }
790
0
    if (dtrule->getDateRuleType() == DateTimeRule::DOW_LEQ_DOM) {
791
0
        if (ruleDOM%7 == 0 && ruleDOM/7 == weekInMonth) {
792
0
            return TRUE;
793
0
        }
794
0
        if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - ruleDOM)%7 == 0
795
0
                && weekInMonth == -1*((MONTHLENGTH[month] - ruleDOM)/7 + 1)) {
796
0
            return TRUE;
797
0
        }
798
0
    }
799
0
    return FALSE;
800
0
}
801
802
/*
803
 * Convert the rule to its equivalent rule using WALL_TIME mode.
804
 * This function returns nullptr when the specified DateTimeRule is already
805
 * using WALL_TIME mode.
806
 */
807
0
static DateTimeRule *toWallTimeRule(const DateTimeRule *rule, int32_t rawOffset, int32_t dstSavings, UErrorCode &status) {
808
0
    if (U_FAILURE(status)) {
809
0
        return nullptr;
810
0
    }
811
0
    if (rule->getTimeRuleType() == DateTimeRule::WALL_TIME) {
812
0
        return nullptr;
813
0
    }
814
0
    int32_t wallt = rule->getRuleMillisInDay();
815
0
    if (rule->getTimeRuleType() == DateTimeRule::UTC_TIME) {
816
0
        wallt += (rawOffset + dstSavings);
817
0
    } else if (rule->getTimeRuleType() == DateTimeRule::STANDARD_TIME) {
818
0
        wallt += dstSavings;
819
0
    }
820
821
0
    int32_t month = -1, dom = 0, dow = 0;
822
0
    DateTimeRule::DateRuleType dtype;
823
0
    int32_t dshift = 0;
824
0
    if (wallt < 0) {
825
0
        dshift = -1;
826
0
        wallt += U_MILLIS_PER_DAY;
827
0
    } else if (wallt >= U_MILLIS_PER_DAY) {
828
0
        dshift = 1;
829
0
        wallt -= U_MILLIS_PER_DAY;
830
0
    }
831
832
0
    month = rule->getRuleMonth();
833
0
    dom = rule->getRuleDayOfMonth();
834
0
    dow = rule->getRuleDayOfWeek();
835
0
    dtype = rule->getDateRuleType();
836
837
0
    if (dshift != 0) {
838
0
        if (dtype == DateTimeRule::DOW) {
839
            // Convert to DOW_GEW_DOM or DOW_LEQ_DOM rule first
840
0
            int32_t wim = rule->getRuleWeekInMonth();
841
0
            if (wim > 0) {
842
0
                dtype = DateTimeRule::DOW_GEQ_DOM;
843
0
                dom = 7 * (wim - 1) + 1;
844
0
            } else {
845
0
                dtype = DateTimeRule::DOW_LEQ_DOM;
846
0
                dom = MONTHLENGTH[month] + 7 * (wim + 1);
847
0
            }
848
0
        }
849
        // Shift one day before or after
850
0
        dom += dshift;
851
0
        if (dom == 0) {
852
0
            month--;
853
0
            month = month < UCAL_JANUARY ? UCAL_DECEMBER : month;
854
0
            dom = MONTHLENGTH[month];
855
0
        } else if (dom > MONTHLENGTH[month]) {
856
0
            month++;
857
0
            month = month > UCAL_DECEMBER ? UCAL_JANUARY : month;
858
0
            dom = 1;
859
0
        }
860
0
        if (dtype != DateTimeRule::DOM) {
861
            // Adjust day of week
862
0
            dow += dshift;
863
0
            if (dow < UCAL_SUNDAY) {
864
0
                dow = UCAL_SATURDAY;
865
0
            } else if (dow > UCAL_SATURDAY) {
866
0
                dow = UCAL_SUNDAY;
867
0
            }
868
0
        }
869
0
    }
870
    // Create a new rule
871
0
    DateTimeRule *modifiedRule = nullptr;
872
0
    if (dtype == DateTimeRule::DOM) {
873
0
        modifiedRule = new DateTimeRule(month, dom, wallt, DateTimeRule::WALL_TIME);
874
0
    } else {
875
0
        modifiedRule = new DateTimeRule(month, dom, dow, (dtype == DateTimeRule::DOW_GEQ_DOM), wallt, DateTimeRule::WALL_TIME);
876
0
    }
877
0
    if (modifiedRule == nullptr) {
878
0
        status = U_MEMORY_ALLOCATION_ERROR;
879
0
    }
880
0
    return modifiedRule;
881
0
}
882
883
/*
884
 * Minimum implementations of stream writer/reader, writing/reading
885
 * UnicodeString.  For now, we do not want to introduce the dependency
886
 * on the ICU I/O stream in this module.  But we want to keep the code
887
 * equivalent to the ICU4J implementation, which utilizes java.io.Writer/
888
 * Reader.
889
 */
890
class VTZWriter {
891
public:
892
    VTZWriter(UnicodeString& out);
893
    ~VTZWriter();
894
895
    void write(const UnicodeString& str);
896
    void write(UChar ch);
897
    void write(const UChar* str);
898
    //void write(const UChar* str, int32_t length);
899
private:
900
    UnicodeString* out;
901
};
902
903
0
VTZWriter::VTZWriter(UnicodeString& output) {
904
0
    out = &output;
905
0
}
906
907
0
VTZWriter::~VTZWriter() {
908
0
}
909
910
void
911
0
VTZWriter::write(const UnicodeString& str) {
912
0
    out->append(str);
913
0
}
914
915
void
916
0
VTZWriter::write(UChar ch) {
917
0
    out->append(ch);
918
0
}
919
920
void
921
0
VTZWriter::write(const UChar* str) {
922
0
    out->append(str, -1);
923
0
}
924
925
/*
926
void
927
VTZWriter::write(const UChar* str, int32_t length) {
928
    out->append(str, length);
929
}
930
*/
931
932
class VTZReader {
933
public:
934
    VTZReader(const UnicodeString& input);
935
    ~VTZReader();
936
937
    UChar read(void);
938
private:
939
    const UnicodeString* in;
940
    int32_t index;
941
};
942
943
0
VTZReader::VTZReader(const UnicodeString& input) {
944
0
    in = &input;
945
0
    index = 0;
946
0
}
947
948
0
VTZReader::~VTZReader() {
949
0
}
950
951
UChar
952
0
VTZReader::read(void) {
953
0
    UChar ch = 0xFFFF;
954
0
    if (index < in->length()) {
955
0
        ch = in->charAt(index);
956
0
    }
957
0
    index++;
958
0
    return ch;
959
0
}
960
961
962
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(VTimeZone)
963
964
VTimeZone::VTimeZone()
965
0
:   BasicTimeZone(), tz(nullptr), vtzlines(nullptr),
966
0
    lastmod(MAX_MILLIS) {
967
0
}
968
969
VTimeZone::VTimeZone(const VTimeZone& source)
970
0
:   BasicTimeZone(source), tz(nullptr), vtzlines(nullptr),
971
0
    tzurl(source.tzurl), lastmod(source.lastmod),
972
0
    olsonzid(source.olsonzid), icutzver(source.icutzver) {
973
0
    if (source.tz != nullptr) {
974
0
        tz = source.tz->clone();
975
0
    }
976
0
    if (source.vtzlines != nullptr) {
977
0
        UErrorCode status = U_ZERO_ERROR;
978
0
        int32_t size = source.vtzlines->size();
979
0
        vtzlines = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, size, status);
980
0
        if (vtzlines == nullptr) {
981
0
            return;
982
0
        }
983
0
        if (U_SUCCESS(status)) {
984
0
            for (int32_t i = 0; i < size; i++) {
985
0
                UnicodeString *line = (UnicodeString*)source.vtzlines->elementAt(i);
986
0
                vtzlines->addElementX(line->clone(), status);
987
0
                if (U_FAILURE(status)) {
988
0
                    break;
989
0
                }
990
0
            }
991
0
        }
992
0
        if (U_FAILURE(status) && vtzlines != nullptr) {
993
0
            delete vtzlines;
994
0
        }
995
0
    }
996
0
}
997
998
0
VTimeZone::~VTimeZone() {
999
0
    if (tz != nullptr) {
1000
0
        delete tz;
1001
0
    }
1002
0
    if (vtzlines != nullptr) {
1003
0
        delete vtzlines;
1004
0
    }
1005
0
}
1006
1007
VTimeZone&
1008
0
VTimeZone::operator=(const VTimeZone& right) {
1009
0
    if (this == &right) {
1010
0
        return *this;
1011
0
    }
1012
0
    if (*this != right) {
1013
0
        BasicTimeZone::operator=(right);
1014
0
        if (tz != nullptr) {
1015
0
            delete tz;
1016
0
            tz = nullptr;
1017
0
        }
1018
0
        if (right.tz != nullptr) {
1019
0
            tz = right.tz->clone();
1020
0
        }
1021
0
        if (vtzlines != nullptr) {
1022
0
            delete vtzlines;
1023
0
        }
1024
0
        if (right.vtzlines != nullptr) {
1025
0
            UErrorCode status = U_ZERO_ERROR;
1026
0
            int32_t size = right.vtzlines->size();
1027
0
            vtzlines = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, size, status);
1028
0
            if (vtzlines != nullptr && U_SUCCESS(status)) {
1029
0
                for (int32_t i = 0; i < size; i++) {
1030
0
                    UnicodeString *line = (UnicodeString*)right.vtzlines->elementAt(i);
1031
0
                    vtzlines->addElementX(line->clone(), status);
1032
0
                    if (U_FAILURE(status)) {
1033
0
                        break;
1034
0
                    }
1035
0
                }
1036
0
            }
1037
0
            if (U_FAILURE(status) && vtzlines != nullptr) {
1038
0
                delete vtzlines;
1039
0
                vtzlines = nullptr;
1040
0
            }
1041
0
        }
1042
0
        tzurl = right.tzurl;
1043
0
        lastmod = right.lastmod;
1044
0
        olsonzid = right.olsonzid;
1045
0
        icutzver = right.icutzver;
1046
0
    }
1047
0
    return *this;
1048
0
}
1049
1050
bool
1051
0
VTimeZone::operator==(const TimeZone& that) const {
1052
0
    if (this == &that) {
1053
0
        return TRUE;
1054
0
    }
1055
0
    if (typeid(*this) != typeid(that) || !BasicTimeZone::operator==(that)) {
1056
0
        return FALSE;
1057
0
    }
1058
0
    VTimeZone *vtz = (VTimeZone*)&that;
1059
0
    if (*tz == *(vtz->tz)
1060
0
        && tzurl == vtz->tzurl
1061
0
        && lastmod == vtz->lastmod
1062
        /* && olsonzid = that.olsonzid */
1063
0
        /* && icutzver = that.icutzver */) {
1064
0
        return TRUE;
1065
0
    }
1066
0
    return FALSE;
1067
0
}
1068
1069
bool
1070
0
VTimeZone::operator!=(const TimeZone& that) const {
1071
0
    return !operator==(that);
1072
0
}
1073
1074
VTimeZone*
1075
0
VTimeZone::createVTimeZoneByID(const UnicodeString& ID) {
1076
0
    VTimeZone *vtz = new VTimeZone();
1077
0
    if (vtz == nullptr) {
1078
0
        return nullptr;
1079
0
    }
1080
0
    vtz->tz = (BasicTimeZone*)TimeZone::createTimeZone(ID);
1081
0
    vtz->tz->getID(vtz->olsonzid);
1082
1083
    // Set ICU tzdata version
1084
0
    UErrorCode status = U_ZERO_ERROR;
1085
0
    UResourceBundle *bundle = nullptr;
1086
0
    const UChar* versionStr = nullptr;
1087
0
    int32_t len = 0;
1088
0
    bundle = ures_openDirect(nullptr, "zoneinfo64", &status);
1089
0
    versionStr = ures_getStringByKey(bundle, "TZVersion", &len, &status);
1090
0
    if (U_SUCCESS(status)) {
1091
0
        vtz->icutzver.setTo(versionStr, len);
1092
0
    }
1093
0
    ures_close(bundle);
1094
0
    return vtz;
1095
0
}
1096
1097
VTimeZone*
1098
0
VTimeZone::createVTimeZoneFromBasicTimeZone(const BasicTimeZone& basic_time_zone, UErrorCode &status) {
1099
0
    if (U_FAILURE(status)) {
1100
0
        return nullptr;
1101
0
    }
1102
0
    VTimeZone *vtz = new VTimeZone();
1103
0
    if (vtz == nullptr) {
1104
0
        status = U_MEMORY_ALLOCATION_ERROR;
1105
0
        return nullptr;
1106
0
    }
1107
0
    vtz->tz = basic_time_zone.clone();
1108
0
    if (vtz->tz == nullptr) {
1109
0
        status = U_MEMORY_ALLOCATION_ERROR;
1110
0
        delete vtz;
1111
0
        return nullptr;
1112
0
    }
1113
0
    vtz->tz->getID(vtz->olsonzid);
1114
1115
    // Set ICU tzdata version
1116
0
    UResourceBundle *bundle = nullptr;
1117
0
    const UChar* versionStr = nullptr;
1118
0
    int32_t len = 0;
1119
0
    bundle = ures_openDirect(nullptr, "zoneinfo64", &status);
1120
0
    versionStr = ures_getStringByKey(bundle, "TZVersion", &len, &status);
1121
0
    if (U_SUCCESS(status)) {
1122
0
        vtz->icutzver.setTo(versionStr, len);
1123
0
    }
1124
0
    ures_close(bundle);
1125
0
    return vtz;
1126
0
}
1127
1128
VTimeZone*
1129
0
VTimeZone::createVTimeZone(const UnicodeString& vtzdata, UErrorCode& status) {
1130
0
    if (U_FAILURE(status)) {
1131
0
        return nullptr;
1132
0
    }
1133
0
    VTZReader reader(vtzdata);
1134
0
    VTimeZone *vtz = new VTimeZone();
1135
0
    if (vtz == nullptr) {
1136
0
        status = U_MEMORY_ALLOCATION_ERROR;
1137
0
        return nullptr;
1138
0
    }
1139
0
    vtz->load(reader, status);
1140
0
    if (U_FAILURE(status)) {
1141
0
        delete vtz;
1142
0
        return nullptr;
1143
0
    }
1144
0
    return vtz;
1145
0
}
1146
1147
UBool
1148
0
VTimeZone::getTZURL(UnicodeString& url) const {
1149
0
    if (tzurl.length() > 0) {
1150
0
        url = tzurl;
1151
0
        return TRUE;
1152
0
    }
1153
0
    return FALSE;
1154
0
}
1155
1156
void
1157
0
VTimeZone::setTZURL(const UnicodeString& url) {
1158
0
    tzurl = url;
1159
0
}
1160
1161
UBool
1162
0
VTimeZone::getLastModified(UDate& lastModified) const {
1163
0
    if (lastmod != MAX_MILLIS) {
1164
0
        lastModified = lastmod;
1165
0
        return TRUE;
1166
0
    }
1167
0
    return FALSE;
1168
0
}
1169
1170
void
1171
0
VTimeZone::setLastModified(UDate lastModified) {
1172
0
    lastmod = lastModified;
1173
0
}
1174
1175
void
1176
0
VTimeZone::write(UnicodeString& result, UErrorCode& status) const {
1177
0
    result.remove();
1178
0
    VTZWriter writer(result);
1179
0
    write(writer, status);
1180
0
}
1181
1182
void
1183
0
VTimeZone::write(UDate start, UnicodeString& result, UErrorCode& status) const {
1184
0
    result.remove();
1185
0
    VTZWriter writer(result);
1186
0
    write(start, writer, status);
1187
0
}
1188
1189
void
1190
0
VTimeZone::writeSimple(UDate time, UnicodeString& result, UErrorCode& status) const {
1191
0
    result.remove();
1192
0
    VTZWriter writer(result);
1193
0
    writeSimple(time, writer, status);
1194
0
}
1195
1196
VTimeZone*
1197
0
VTimeZone::clone() const {
1198
0
    return new VTimeZone(*this);
1199
0
}
1200
1201
int32_t
1202
VTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day,
1203
0
                     uint8_t dayOfWeek, int32_t millis, UErrorCode& status) const {
1204
0
    return tz->getOffset(era, year, month, day, dayOfWeek, millis, status);
1205
0
}
1206
1207
int32_t
1208
VTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day,
1209
                     uint8_t dayOfWeek, int32_t millis,
1210
0
                     int32_t monthLength, UErrorCode& status) const {
1211
0
    return tz->getOffset(era, year, month, day, dayOfWeek, millis, monthLength, status);
1212
0
}
1213
1214
void
1215
VTimeZone::getOffset(UDate date, UBool local, int32_t& rawOffset,
1216
0
                     int32_t& dstOffset, UErrorCode& status) const {
1217
0
    return tz->getOffset(date, local, rawOffset, dstOffset, status);
1218
0
}
1219
1220
void VTimeZone::getOffsetFromLocal(UDate date, UTimeZoneLocalOption nonExistingTimeOpt,
1221
                                   UTimeZoneLocalOption duplicatedTimeOpt,
1222
0
                                   int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) const {
1223
0
    tz->getOffsetFromLocal(date, nonExistingTimeOpt, duplicatedTimeOpt, rawOffset, dstOffset, status);
1224
0
}
1225
1226
void
1227
0
VTimeZone::setRawOffset(int32_t offsetMillis) {
1228
0
    tz->setRawOffset(offsetMillis);
1229
0
}
1230
1231
int32_t
1232
0
VTimeZone::getRawOffset(void) const {
1233
0
    return tz->getRawOffset();
1234
0
}
1235
1236
UBool
1237
0
VTimeZone::useDaylightTime(void) const {
1238
0
    return tz->useDaylightTime();
1239
0
}
1240
1241
UBool
1242
0
VTimeZone::inDaylightTime(UDate date, UErrorCode& status) const {
1243
0
    return tz->inDaylightTime(date, status);
1244
0
}
1245
1246
UBool
1247
0
VTimeZone::hasSameRules(const TimeZone& other) const {
1248
0
    return tz->hasSameRules(other);
1249
0
}
1250
1251
UBool
1252
0
VTimeZone::getNextTransition(UDate base, UBool inclusive, TimeZoneTransition& result) const {
1253
0
    return tz->getNextTransition(base, inclusive, result);
1254
0
}
1255
1256
UBool
1257
0
VTimeZone::getPreviousTransition(UDate base, UBool inclusive, TimeZoneTransition& result) const {
1258
0
    return tz->getPreviousTransition(base, inclusive, result);
1259
0
}
1260
1261
int32_t
1262
0
VTimeZone::countTransitionRules(UErrorCode& status) const {
1263
0
    return tz->countTransitionRules(status);
1264
0
}
1265
1266
void
1267
VTimeZone::getTimeZoneRules(const InitialTimeZoneRule*& initial,
1268
                            const TimeZoneRule* trsrules[], int32_t& trscount,
1269
0
                            UErrorCode& status) const {
1270
0
    tz->getTimeZoneRules(initial, trsrules, trscount, status);
1271
0
}
1272
1273
void
1274
0
VTimeZone::load(VTZReader& reader, UErrorCode& status) {
1275
0
    vtzlines = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, DEFAULT_VTIMEZONE_LINES, status);
1276
0
    if (vtzlines == nullptr) {
1277
0
        status = U_MEMORY_ALLOCATION_ERROR;
1278
0
    }
1279
0
    if (U_FAILURE(status)) {
1280
0
        return;
1281
0
    }
1282
0
    UBool eol = FALSE;
1283
0
    UBool start = FALSE;
1284
0
    UBool success = FALSE;
1285
0
    UnicodeString line;
1286
1287
0
    while (TRUE) {
1288
0
        UChar ch = reader.read();
1289
0
        if (ch == 0xFFFF) {
1290
            // end of file
1291
0
            if (start && line.startsWith(ICAL_END_VTIMEZONE, -1)) {
1292
0
                LocalPointer<UnicodeString> element(new UnicodeString(line), status);
1293
0
                if (U_FAILURE(status)) {
1294
0
                    goto cleanupVtzlines;
1295
0
                }
1296
0
                vtzlines->addElementX(element.getAlias(), status);
1297
0
                if (U_FAILURE(status)) {
1298
0
                    goto cleanupVtzlines;
1299
0
                }
1300
0
                element.orphan(); // on success, vtzlines owns the object.
1301
0
                success = TRUE;
1302
0
            }
1303
0
            break;
1304
0
        }
1305
0
        if (ch == 0x000D) {
1306
            // CR, must be followed by LF according to the definition in RFC2445
1307
0
            continue;
1308
0
        }
1309
0
        if (eol) {
1310
0
            if (ch != 0x0009 && ch != 0x0020) {
1311
                // NOT followed by TAB/SP -> new line
1312
0
                if (start) {
1313
0
                    if (line.length() > 0) {
1314
0
                        LocalPointer<UnicodeString> element(new UnicodeString(line), status);
1315
0
                        if (U_FAILURE(status)) {
1316
0
                            goto cleanupVtzlines;
1317
0
                        }
1318
0
                        vtzlines->addElementX(element.getAlias(), status);
1319
0
                        if (U_FAILURE(status)) {
1320
0
                            goto cleanupVtzlines;
1321
0
                        }
1322
0
                        element.orphan(); // on success, vtzlines owns the object.
1323
0
                    }
1324
0
                }
1325
0
                line.remove();
1326
0
                if (ch != 0x000A) {
1327
0
                    line.append(ch);
1328
0
                }
1329
0
            }
1330
0
            eol = FALSE;
1331
0
        } else {
1332
0
            if (ch == 0x000A) {
1333
                // LF
1334
0
                eol = TRUE;
1335
0
                if (start) {
1336
0
                    if (line.startsWith(ICAL_END_VTIMEZONE, -1)) {
1337
0
                        LocalPointer<UnicodeString> element(new UnicodeString(line), status);
1338
0
                        if (U_FAILURE(status)) {
1339
0
                            goto cleanupVtzlines;
1340
0
                        }
1341
0
                        vtzlines->addElementX(element.getAlias(), status);
1342
0
                        if (U_FAILURE(status)) {
1343
0
                            goto cleanupVtzlines;
1344
0
                        }
1345
0
                        element.orphan(); // on success, vtzlines owns the object.
1346
0
                        success = TRUE;
1347
0
                        break;
1348
0
                    }
1349
0
                } else {
1350
0
                    if (line.startsWith(ICAL_BEGIN_VTIMEZONE, -1)) {
1351
0
                        LocalPointer<UnicodeString> element(new UnicodeString(line), status);
1352
0
                        if (U_FAILURE(status)) {
1353
0
                            goto cleanupVtzlines;
1354
0
                        }
1355
0
                        vtzlines->addElementX(element.getAlias(), status);
1356
0
                        if (U_FAILURE(status)) {
1357
0
                            goto cleanupVtzlines;
1358
0
                        }
1359
0
                        element.orphan(); // on success, vtzlines owns the object.
1360
0
                        line.remove();
1361
0
                        start = TRUE;
1362
0
                        eol = FALSE;
1363
0
                    }
1364
0
                }
1365
0
            } else {
1366
0
                line.append(ch);
1367
0
            }
1368
0
        }
1369
0
    }
1370
0
    if (!success) {
1371
0
        if (U_SUCCESS(status)) {
1372
0
            status = U_INVALID_STATE_ERROR;
1373
0
        }
1374
0
        goto cleanupVtzlines;
1375
0
    }
1376
0
    parse(status);
1377
0
    return;
1378
1379
0
cleanupVtzlines:
1380
0
    delete vtzlines;
1381
0
    vtzlines = nullptr;
1382
0
}
1383
1384
// parser state
1385
0
#define INI 0   // Initial state
1386
0
#define VTZ 1   // In VTIMEZONE
1387
0
#define TZI 2   // In STANDARD or DAYLIGHT
1388
1389
0
#define DEF_DSTSAVINGS (60*60*1000)
1390
0
#define DEF_TZSTARTTIME (0.0)
1391
1392
void
1393
0
VTimeZone::parse(UErrorCode& status) {
1394
0
    if (U_FAILURE(status)) {
1395
0
        return;
1396
0
    }
1397
0
    if (vtzlines == nullptr || vtzlines->size() == 0) {
1398
0
        status = U_INVALID_STATE_ERROR;
1399
0
        return;
1400
0
    }
1401
0
    InitialTimeZoneRule *initialRule = nullptr;
1402
0
    RuleBasedTimeZone *rbtz = nullptr;
1403
1404
    // timezone ID
1405
0
    UnicodeString tzid;
1406
1407
0
    int32_t state = INI;
1408
0
    int32_t n = 0;
1409
0
    UBool dst = FALSE;      // current zone type
1410
0
    UnicodeString from;     // current zone from offset
1411
0
    UnicodeString to;       // current zone offset
1412
0
    UnicodeString zonename;   // current zone name
1413
0
    UnicodeString dtstart;  // current zone starts
1414
0
    UBool isRRULE = FALSE;  // true if the rule is described by RRULE
1415
0
    int32_t initialRawOffset = 0;   // initial offset
1416
0
    int32_t initialDSTSavings = 0;  // initial offset
1417
0
    UDate firstStart = MAX_MILLIS;  // the earliest rule start time
1418
0
    UnicodeString name;     // RFC2445 prop name
1419
0
    UnicodeString value;    // RFC2445 prop value
1420
1421
0
    UVector *dates = nullptr;  // list of RDATE or RRULE strings
1422
0
    UVector *rules = nullptr;  // list of TimeZoneRule instances
1423
1424
0
    int32_t finalRuleIdx = -1;
1425
0
    int32_t finalRuleCount = 0;
1426
1427
0
    rules = new UVector(status);
1428
0
    if (rules == nullptr) {
1429
0
        status = U_MEMORY_ALLOCATION_ERROR;
1430
0
    }
1431
0
    if (U_FAILURE(status)) {
1432
0
        goto cleanupParse;
1433
0
    }
1434
     // Set the deleter to remove TimeZoneRule vectors to avoid memory leaks due to unowned TimeZoneRules.
1435
0
    rules->setDeleter(deleteTimeZoneRule);
1436
    
1437
0
    dates = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, status);
1438
0
    if (dates == nullptr) {
1439
0
        status = U_MEMORY_ALLOCATION_ERROR;
1440
0
    }
1441
0
    if (U_FAILURE(status)) {
1442
0
        goto cleanupParse;
1443
0
    }
1444
    
1445
0
    for (n = 0; n < vtzlines->size(); n++) {
1446
0
        UnicodeString *line = (UnicodeString*)vtzlines->elementAt(n);
1447
0
        int32_t valueSep = line->indexOf(COLON);
1448
0
        if (valueSep < 0) {
1449
0
            continue;
1450
0
        }
1451
0
        name.setTo(*line, 0, valueSep);
1452
0
        value.setTo(*line, valueSep + 1);
1453
1454
0
        switch (state) {
1455
0
        case INI:
1456
0
            if (name.compare(ICAL_BEGIN, -1) == 0
1457
0
                && value.compare(ICAL_VTIMEZONE, -1) == 0) {
1458
0
                state = VTZ;
1459
0
            }
1460
0
            break;
1461
1462
0
        case VTZ:
1463
0
            if (name.compare(ICAL_TZID, -1) == 0) {
1464
0
                tzid = value;
1465
0
            } else if (name.compare(ICAL_TZURL, -1) == 0) {
1466
0
                tzurl = value;
1467
0
            } else if (name.compare(ICAL_LASTMOD, -1) == 0) {
1468
                // Always in 'Z' format, so the offset argument for the parse method
1469
                // can be any value.
1470
0
                lastmod = parseDateTimeString(value, 0, status);
1471
0
                if (U_FAILURE(status)) {
1472
0
                    goto cleanupParse;
1473
0
                }
1474
0
            } else if (name.compare(ICAL_BEGIN, -1) == 0) {
1475
0
                UBool isDST = (value.compare(ICAL_DAYLIGHT, -1) == 0);
1476
0
                if (value.compare(ICAL_STANDARD, -1) == 0 || isDST) {
1477
                    // tzid must be ready at this point
1478
0
                    if (tzid.length() == 0) {
1479
0
                        goto cleanupParse;
1480
0
                    }
1481
                    // initialize current zone properties
1482
0
                    if (dates->size() != 0) {
1483
0
                        dates->removeAllElements();
1484
0
                    }
1485
0
                    isRRULE = FALSE;
1486
0
                    from.remove();
1487
0
                    to.remove();
1488
0
                    zonename.remove();
1489
0
                    dst = isDST;
1490
0
                    state = TZI;
1491
0
                } else {
1492
                    // BEGIN property other than STANDARD/DAYLIGHT
1493
                    // must not be there.
1494
0
                    goto cleanupParse;
1495
0
                }
1496
0
            } else if (name.compare(ICAL_END, -1) == 0) {
1497
0
                break;
1498
0
            }
1499
0
            break;
1500
0
        case TZI:
1501
0
            if (name.compare(ICAL_DTSTART, -1) == 0) {
1502
0
                dtstart = value;
1503
0
            } else if (name.compare(ICAL_TZNAME, -1) == 0) {
1504
0
                zonename = value;
1505
0
            } else if (name.compare(ICAL_TZOFFSETFROM, -1) == 0) {
1506
0
                from = value;
1507
0
            } else if (name.compare(ICAL_TZOFFSETTO, -1) == 0) {
1508
0
                to = value;
1509
0
            } else if (name.compare(ICAL_RDATE, -1) == 0) {
1510
                // RDATE mixed with RRULE is not supported
1511
0
                if (isRRULE) {
1512
0
                    goto cleanupParse;
1513
0
                }
1514
                // RDATE value may contain multiple date delimited
1515
                // by comma
1516
0
                UBool nextDate = TRUE;
1517
0
                int32_t dstart = 0;
1518
0
                UnicodeString *dstr = nullptr;
1519
0
                while (nextDate) {
1520
0
                    int32_t dend = value.indexOf(COMMA, dstart);
1521
0
                    if (dend == -1) {
1522
0
                        dstr = new UnicodeString(value, dstart);
1523
0
                        nextDate = FALSE;
1524
0
                    } else {
1525
0
                        dstr = new UnicodeString(value, dstart, dend - dstart);
1526
0
                    }
1527
0
                    if (dstr == nullptr) {
1528
0
                        status = U_MEMORY_ALLOCATION_ERROR;
1529
0
                    } else {
1530
0
                        dates->addElementX(dstr, status);
1531
0
                    }
1532
0
                    if (U_FAILURE(status)) {
1533
0
                        goto cleanupParse;
1534
0
                    }
1535
0
                    dstart = dend + 1;
1536
0
                }
1537
0
            } else if (name.compare(ICAL_RRULE, -1) == 0) {
1538
                // RRULE mixed with RDATE is not supported
1539
0
                if (!isRRULE && dates->size() != 0) {
1540
0
                    goto cleanupParse;
1541
0
                }
1542
0
                isRRULE = true;
1543
0
                LocalPointer<UnicodeString> element(new UnicodeString(value), status);
1544
0
                if (U_FAILURE(status)) {
1545
0
                    goto cleanupParse;
1546
0
                }
1547
0
                dates->addElementX(element.getAlias(), status);
1548
0
                if (U_FAILURE(status)) {
1549
0
                    goto cleanupParse;
1550
0
                }
1551
0
                element.orphan(); // on success, dates owns the object.
1552
0
            } else if (name.compare(ICAL_END, -1) == 0) {
1553
                // Mandatory properties
1554
0
                if (dtstart.length() == 0 || from.length() == 0 || to.length() == 0) {
1555
0
                    goto cleanupParse;
1556
0
                }
1557
                // if zonename is not available, create one from tzid
1558
0
                if (zonename.length() == 0) {
1559
0
                    getDefaultTZName(tzid, dst, zonename);
1560
0
                }
1561
1562
                // create a time zone rule
1563
0
                TimeZoneRule *rule = nullptr;
1564
0
                int32_t fromOffset = 0;
1565
0
                int32_t toOffset = 0;
1566
0
                int32_t rawOffset = 0;
1567
0
                int32_t dstSavings = 0;
1568
0
                UDate start = 0;
1569
1570
                // Parse TZOFFSETFROM/TZOFFSETTO
1571
0
                fromOffset = offsetStrToMillis(from, status);
1572
0
                toOffset = offsetStrToMillis(to, status);
1573
0
                if (U_FAILURE(status)) {
1574
0
                    goto cleanupParse;
1575
0
                }
1576
1577
0
                if (dst) {
1578
                    // If daylight, use the previous offset as rawoffset if positive
1579
0
                    if (toOffset - fromOffset > 0) {
1580
0
                        rawOffset = fromOffset;
1581
0
                        dstSavings = toOffset - fromOffset;
1582
0
                    } else {
1583
                        // This is rare case..  just use 1 hour DST savings
1584
0
                        rawOffset = toOffset - DEF_DSTSAVINGS;
1585
0
                        dstSavings = DEF_DSTSAVINGS;                                
1586
0
                    }
1587
0
                } else {
1588
0
                    rawOffset = toOffset;
1589
0
                    dstSavings = 0;
1590
0
                }
1591
1592
                // start time
1593
0
                start = parseDateTimeString(dtstart, fromOffset, status);
1594
0
                if (U_FAILURE(status)) {
1595
0
                    goto cleanupParse;
1596
0
                }
1597
1598
                // Create the rule
1599
0
                UDate actualStart = MAX_MILLIS;
1600
0
                if (isRRULE) {
1601
0
                    rule = createRuleByRRULE(zonename, rawOffset, dstSavings, start, dates, fromOffset, status);
1602
0
                } else {
1603
0
                    rule = createRuleByRDATE(zonename, rawOffset, dstSavings, start, dates, fromOffset, status);
1604
0
                }
1605
0
                if (U_FAILURE(status) || rule == nullptr) {
1606
0
                    goto cleanupParse;
1607
0
                } else {
1608
0
                    UBool startAvail = rule->getFirstStart(fromOffset, 0, actualStart);
1609
0
                    if (startAvail && actualStart < firstStart) {
1610
                        // save from offset information for the earliest rule
1611
0
                        firstStart = actualStart;
1612
                        // If this is STD, assume the time before this transition
1613
                        // is DST when the difference is 1 hour.  This might not be
1614
                        // accurate, but VTIMEZONE data does not have such info.
1615
0
                        if (dstSavings > 0) {
1616
0
                            initialRawOffset = fromOffset;
1617
0
                            initialDSTSavings = 0;
1618
0
                        } else {
1619
0
                            if (fromOffset - toOffset == DEF_DSTSAVINGS) {
1620
0
                                initialRawOffset = fromOffset - DEF_DSTSAVINGS;
1621
0
                                initialDSTSavings = DEF_DSTSAVINGS;
1622
0
                            } else {
1623
0
                                initialRawOffset = fromOffset;
1624
0
                                initialDSTSavings = 0;
1625
0
                            }
1626
0
                        }
1627
0
                    }
1628
0
                }
1629
0
                rules->addElementX(rule, status);
1630
0
                if (U_FAILURE(status)) {
1631
0
                    goto cleanupParse;
1632
0
                }
1633
0
                state = VTZ;
1634
0
            }
1635
0
            break;
1636
0
        }
1637
0
    }
1638
    // Must have at least one rule
1639
0
    if (rules->size() == 0) {
1640
0
        goto cleanupParse;
1641
0
    }
1642
1643
    // Create a initial rule
1644
0
    getDefaultTZName(tzid, FALSE, zonename);
1645
0
    initialRule = new InitialTimeZoneRule(zonename, initialRawOffset, initialDSTSavings);
1646
0
    if (initialRule == nullptr) {
1647
0
        status = U_MEMORY_ALLOCATION_ERROR;
1648
0
        goto cleanupParse;
1649
0
    }
1650
1651
    // Finally, create the RuleBasedTimeZone
1652
0
    rbtz = new RuleBasedTimeZone(tzid, initialRule);
1653
0
    if (rbtz == nullptr) {
1654
0
        status = U_MEMORY_ALLOCATION_ERROR;
1655
0
        goto cleanupParse;
1656
0
    }
1657
0
    initialRule = nullptr; // already adopted by RBTZ, no need to delete
1658
1659
0
    for (n = 0; n < rules->size(); n++) {
1660
0
        TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n);
1661
0
        AnnualTimeZoneRule *atzrule = dynamic_cast<AnnualTimeZoneRule *>(r);
1662
0
        if (atzrule != nullptr) {
1663
0
            if (atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR) {
1664
0
                finalRuleCount++;
1665
0
                finalRuleIdx = n;
1666
0
            }
1667
0
        }
1668
0
    }
1669
0
    if (finalRuleCount > 2) {
1670
        // Too many final rules
1671
0
        status = U_ILLEGAL_ARGUMENT_ERROR;
1672
0
        goto cleanupParse;
1673
0
    }
1674
1675
0
    if (finalRuleCount == 1) {
1676
0
        if (rules->size() == 1) {
1677
            // Only one final rule, only governs the initial rule,
1678
            // which is already initialized, thus, we do not need to
1679
            // add this transition rule
1680
0
            rules->removeAllElements();
1681
0
        } else {
1682
            // Normalize the final rule
1683
0
            AnnualTimeZoneRule *finalRule = (AnnualTimeZoneRule*)rules->elementAt(finalRuleIdx);
1684
0
            int32_t tmpRaw = finalRule->getRawOffset();
1685
0
            int32_t tmpDST = finalRule->getDSTSavings();
1686
1687
            // Find the last non-final rule
1688
0
            UDate finalStart, start;
1689
0
            finalRule->getFirstStart(initialRawOffset, initialDSTSavings, finalStart);
1690
0
            start = finalStart;
1691
0
            for (n = 0; n < rules->size(); n++) {
1692
0
                if (finalRuleIdx == n) {
1693
0
                    continue;
1694
0
                }
1695
0
                TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n);
1696
0
                UDate lastStart;
1697
0
                r->getFinalStart(tmpRaw, tmpDST, lastStart);
1698
0
                if (lastStart > start) {
1699
0
                    finalRule->getNextStart(lastStart,
1700
0
                        r->getRawOffset(),
1701
0
                        r->getDSTSavings(),
1702
0
                        FALSE,
1703
0
                        start);
1704
0
                }
1705
0
            }
1706
1707
0
            TimeZoneRule *newRule = nullptr;
1708
0
            UnicodeString tznam;
1709
0
            if (start == finalStart) {
1710
                // Transform this into a single transition
1711
0
                newRule = new TimeArrayTimeZoneRule(
1712
0
                        finalRule->getName(tznam),
1713
0
                        finalRule->getRawOffset(),
1714
0
                        finalRule->getDSTSavings(),
1715
0
                        &finalStart,
1716
0
                        1,
1717
0
                        DateTimeRule::UTC_TIME);
1718
0
            } else {
1719
                // Update the end year
1720
0
                int32_t y, m, d, dow, doy, mid;
1721
0
                Grego::timeToFields(start, y, m, d, dow, doy, mid);
1722
0
                newRule = new AnnualTimeZoneRule(
1723
0
                        finalRule->getName(tznam),
1724
0
                        finalRule->getRawOffset(),
1725
0
                        finalRule->getDSTSavings(),
1726
0
                        *(finalRule->getRule()),
1727
0
                        finalRule->getStartYear(),
1728
0
                        y);
1729
0
            }
1730
0
            if (newRule == nullptr) {
1731
0
                status = U_MEMORY_ALLOCATION_ERROR;
1732
0
                goto cleanupParse;
1733
0
            }
1734
0
            rules->removeElementAt(finalRuleIdx);
1735
0
            rules->addElementX(newRule, status);
1736
0
            if (U_FAILURE(status)) {
1737
0
                delete newRule;
1738
0
                goto cleanupParse;
1739
0
            }
1740
0
        }
1741
0
    }
1742
1743
0
    while (!rules->isEmpty()) {
1744
0
        TimeZoneRule *tzr = (TimeZoneRule*)rules->orphanElementAt(0);
1745
0
        rbtz->addTransitionRule(tzr, status);
1746
0
        if (U_FAILURE(status)) {
1747
0
            goto cleanupParse;
1748
0
        }
1749
0
    }
1750
0
    rbtz->complete(status);
1751
0
    if (U_FAILURE(status)) {
1752
0
        goto cleanupParse;
1753
0
    }
1754
0
    delete rules;
1755
0
    delete dates;
1756
1757
0
    tz = rbtz;
1758
0
    setID(tzid);
1759
0
    return;
1760
1761
0
cleanupParse:
1762
0
    if (rules != nullptr) {
1763
0
        while (!rules->isEmpty()) {
1764
0
            TimeZoneRule *r = (TimeZoneRule*)rules->orphanElementAt(0);
1765
0
            delete r;
1766
0
        }
1767
0
        delete rules;
1768
0
    }
1769
0
    if (dates != nullptr) {
1770
0
        delete dates;
1771
0
    }
1772
0
    if (initialRule != nullptr) {
1773
0
        delete initialRule;
1774
0
    }
1775
0
    if (rbtz != nullptr) {
1776
0
        delete rbtz;
1777
0
    }
1778
0
    return;
1779
0
}
1780
1781
void
1782
0
VTimeZone::write(VTZWriter& writer, UErrorCode& status) const {
1783
0
    if (vtzlines != nullptr) {
1784
0
        for (int32_t i = 0; i < vtzlines->size(); i++) {
1785
0
            UnicodeString *line = (UnicodeString*)vtzlines->elementAt(i);
1786
0
            if (line->startsWith(ICAL_TZURL, -1)
1787
0
                && line->charAt(u_strlen(ICAL_TZURL)) == COLON) {
1788
0
                writer.write(ICAL_TZURL);
1789
0
                writer.write(COLON);
1790
0
                writer.write(tzurl);
1791
0
                writer.write(ICAL_NEWLINE);
1792
0
            } else if (line->startsWith(ICAL_LASTMOD, -1)
1793
0
                && line->charAt(u_strlen(ICAL_LASTMOD)) == COLON) {
1794
0
                UnicodeString utcString;
1795
0
                writer.write(ICAL_LASTMOD);
1796
0
                writer.write(COLON);
1797
0
                writer.write(getUTCDateTimeString(lastmod, utcString));
1798
0
                writer.write(ICAL_NEWLINE);
1799
0
            } else {
1800
0
                writer.write(*line);
1801
0
                writer.write(ICAL_NEWLINE);
1802
0
            }
1803
0
        }
1804
0
    } else {
1805
0
        UnicodeString icutzprop;
1806
0
        UVector customProps(nullptr, uhash_compareUnicodeString, status);
1807
0
        if (olsonzid.length() > 0 && icutzver.length() > 0) {
1808
0
            icutzprop.append(olsonzid);
1809
0
            icutzprop.append(u'[');
1810
0
            icutzprop.append(icutzver);
1811
0
            icutzprop.append(u']');
1812
0
            customProps.addElementX(&icutzprop, status);
1813
0
        }
1814
0
        writeZone(writer, *tz, &customProps, status);
1815
0
    }
1816
0
}
1817
1818
void
1819
0
VTimeZone::write(UDate start, VTZWriter& writer, UErrorCode& status) const {
1820
0
    if (U_FAILURE(status)) {
1821
0
        return;
1822
0
    }
1823
0
    InitialTimeZoneRule *initial = nullptr;
1824
0
    UVector *transitionRules = nullptr;
1825
0
    UVector customProps(uprv_deleteUObject, uhash_compareUnicodeString, status);
1826
0
    UnicodeString tzid;
1827
1828
    // Extract rules applicable to dates after the start time
1829
0
    getTimeZoneRulesAfter(start, initial, transitionRules, status);
1830
0
    if (U_FAILURE(status)) {
1831
0
        return;
1832
0
    }
1833
1834
    // Create a RuleBasedTimeZone with the subset rule
1835
0
    getID(tzid);
1836
0
    RuleBasedTimeZone rbtz(tzid, initial);
1837
0
    if (transitionRules != nullptr) {
1838
0
        while (!transitionRules->isEmpty()) {
1839
0
            TimeZoneRule *tr = (TimeZoneRule*)transitionRules->orphanElementAt(0);
1840
0
            rbtz.addTransitionRule(tr, status);
1841
0
            if (U_FAILURE(status)) {
1842
0
                goto cleanupWritePartial;
1843
0
            }
1844
0
        }
1845
0
        delete transitionRules;
1846
0
        transitionRules = nullptr;
1847
0
    }
1848
0
    rbtz.complete(status);
1849
0
    if (U_FAILURE(status)) {
1850
0
        goto cleanupWritePartial;
1851
0
    }
1852
1853
0
    if (olsonzid.length() > 0 && icutzver.length() > 0) {
1854
0
        UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP);
1855
0
        if (icutzprop == nullptr) {
1856
0
            status = U_MEMORY_ALLOCATION_ERROR;
1857
0
            goto cleanupWritePartial;
1858
0
        }
1859
0
        icutzprop->append(olsonzid);
1860
0
        icutzprop->append((UChar)0x005B/*'['*/);
1861
0
        icutzprop->append(icutzver);
1862
0
        icutzprop->append(ICU_TZINFO_PARTIAL, -1);
1863
0
        appendMillis(start, *icutzprop);
1864
0
        icutzprop->append((UChar)0x005D/*']'*/);
1865
0
        customProps.addElementX(icutzprop, status);
1866
0
        if (U_FAILURE(status)) {
1867
0
            delete icutzprop;
1868
0
            goto cleanupWritePartial;
1869
0
        }
1870
0
    }
1871
0
    writeZone(writer, rbtz, &customProps, status);
1872
0
    return;
1873
1874
0
cleanupWritePartial:
1875
0
    if (initial != nullptr) {
1876
0
        delete initial;
1877
0
    }
1878
0
    if (transitionRules != nullptr) {
1879
0
        while (!transitionRules->isEmpty()) {
1880
0
            TimeZoneRule *tr = (TimeZoneRule*)transitionRules->orphanElementAt(0);
1881
0
            delete tr;
1882
0
        }
1883
0
        delete transitionRules;
1884
0
    }
1885
0
}
1886
1887
void
1888
0
VTimeZone::writeSimple(UDate time, VTZWriter& writer, UErrorCode& status) const {
1889
0
    if (U_FAILURE(status)) {
1890
0
        return;
1891
0
    }
1892
1893
0
    UVector customProps(uprv_deleteUObject, uhash_compareUnicodeString, status);
1894
0
    UnicodeString tzid;
1895
1896
    // Extract simple rules
1897
0
    InitialTimeZoneRule *initial = nullptr;
1898
0
    AnnualTimeZoneRule *std = nullptr, *dst = nullptr;
1899
0
    getSimpleRulesNear(time, initial, std, dst, status);
1900
0
    if (U_SUCCESS(status)) {
1901
        // Create a RuleBasedTimeZone with the subset rule
1902
0
        getID(tzid);
1903
0
        RuleBasedTimeZone rbtz(tzid, initial);
1904
0
        if (std != nullptr && dst != nullptr) {
1905
0
            rbtz.addTransitionRule(std, status);
1906
0
            rbtz.addTransitionRule(dst, status);
1907
0
        }
1908
0
        if (U_FAILURE(status)) {
1909
0
            goto cleanupWriteSimple;
1910
0
        }
1911
1912
0
        if (olsonzid.length() > 0 && icutzver.length() > 0) {
1913
0
            UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP);
1914
0
            if (icutzprop == nullptr) {
1915
0
               status = U_MEMORY_ALLOCATION_ERROR;
1916
0
               goto cleanupWriteSimple;
1917
0
            }
1918
0
            icutzprop->append(olsonzid);
1919
0
            icutzprop->append((UChar)0x005B/*'['*/);
1920
0
            icutzprop->append(icutzver);
1921
0
            icutzprop->append(ICU_TZINFO_SIMPLE, -1);
1922
0
            appendMillis(time, *icutzprop);
1923
0
            icutzprop->append((UChar)0x005D/*']'*/);
1924
0
            customProps.addElementX(icutzprop, status);
1925
0
            if (U_FAILURE(status)) {
1926
0
                delete icutzprop;
1927
0
                goto cleanupWriteSimple;
1928
0
            }
1929
0
        }
1930
0
        writeZone(writer, rbtz, &customProps, status);
1931
0
    }
1932
0
    return;
1933
1934
0
cleanupWriteSimple:
1935
0
    if (initial != nullptr) {
1936
0
        delete initial;
1937
0
    }
1938
0
    if (std != nullptr) {
1939
0
        delete std;
1940
0
    }
1941
0
    if (dst != nullptr) {
1942
0
        delete dst;
1943
0
    }
1944
0
}
1945
1946
void
1947
VTimeZone::writeZone(VTZWriter& w, BasicTimeZone& basictz,
1948
0
                     UVector* customProps, UErrorCode& status) const {
1949
0
    if (U_FAILURE(status)) {
1950
0
        return;
1951
0
    }
1952
0
    writeHeaders(w, status);
1953
0
    if (U_FAILURE(status)) {
1954
0
        return;
1955
0
    }
1956
1957
0
    if (customProps != nullptr) {
1958
0
        for (int32_t i = 0; i < customProps->size(); i++) {
1959
0
            UnicodeString *custprop = (UnicodeString*)customProps->elementAt(i);
1960
0
            w.write(*custprop);
1961
0
            w.write(ICAL_NEWLINE);
1962
0
        }
1963
0
    }
1964
1965
0
    UDate t = MIN_MILLIS;
1966
0
    UnicodeString dstName;
1967
0
    int32_t dstFromOffset = 0;
1968
0
    int32_t dstFromDSTSavings = 0;
1969
0
    int32_t dstToOffset = 0;
1970
0
    int32_t dstStartYear = 0;
1971
0
    int32_t dstMonth = 0;
1972
0
    int32_t dstDayOfWeek = 0;
1973
0
    int32_t dstWeekInMonth = 0;
1974
0
    int32_t dstMillisInDay = 0;
1975
0
    UDate dstStartTime = 0.0;
1976
0
    UDate dstUntilTime = 0.0;
1977
0
    int32_t dstCount = 0;
1978
0
    AnnualTimeZoneRule *finalDstRule = nullptr;
1979
1980
0
    UnicodeString stdName;
1981
0
    int32_t stdFromOffset = 0;
1982
0
    int32_t stdFromDSTSavings = 0;
1983
0
    int32_t stdToOffset = 0;
1984
0
    int32_t stdStartYear = 0;
1985
0
    int32_t stdMonth = 0;
1986
0
    int32_t stdDayOfWeek = 0;
1987
0
    int32_t stdWeekInMonth = 0;
1988
0
    int32_t stdMillisInDay = 0;
1989
0
    UDate stdStartTime = 0.0;
1990
0
    UDate stdUntilTime = 0.0;
1991
0
    int32_t stdCount = 0;
1992
0
    AnnualTimeZoneRule *finalStdRule = nullptr;
1993
1994
0
    int32_t year, month, dom, dow, doy, mid;
1995
0
    UBool hasTransitions = FALSE;
1996
0
    TimeZoneTransition tzt;
1997
0
    UBool tztAvail;
1998
0
    UnicodeString name;
1999
0
    UBool isDst;
2000
2001
    // Going through all transitions
2002
0
    while (TRUE) {
2003
0
        tztAvail = basictz.getNextTransition(t, FALSE, tzt);
2004
0
        if (!tztAvail) {
2005
0
            break;
2006
0
        }
2007
0
        hasTransitions = TRUE;
2008
0
        t = tzt.getTime();
2009
0
        tzt.getTo()->getName(name);
2010
0
        isDst = (tzt.getTo()->getDSTSavings() != 0);
2011
0
        int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings();
2012
0
        int32_t fromDSTSavings = tzt.getFrom()->getDSTSavings();
2013
0
        int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings();
2014
0
        Grego::timeToFields(tzt.getTime() + fromOffset, year, month, dom, dow, doy, mid);
2015
0
        int32_t weekInMonth = Grego::dayOfWeekInMonth(year, month, dom);
2016
0
        UBool sameRule = FALSE;
2017
0
        const AnnualTimeZoneRule *atzrule;
2018
0
        if (isDst) {
2019
0
            if (finalDstRule == nullptr
2020
0
                && (atzrule = dynamic_cast<const AnnualTimeZoneRule *>(tzt.getTo())) != nullptr
2021
0
                && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
2022
0
            ) {
2023
0
                finalDstRule = atzrule->clone();
2024
0
            }
2025
0
            if (dstCount > 0) {
2026
0
                if (year == dstStartYear + dstCount
2027
0
                        && name.compare(dstName) == 0
2028
0
                        && dstFromOffset == fromOffset
2029
0
                        && dstToOffset == toOffset
2030
0
                        && dstMonth == month
2031
0
                        && dstDayOfWeek == dow
2032
0
                        && dstWeekInMonth == weekInMonth
2033
0
                        && dstMillisInDay == mid) {
2034
                    // Update until time
2035
0
                    dstUntilTime = t;
2036
0
                    dstCount++;
2037
0
                    sameRule = TRUE;
2038
0
                }
2039
0
                if (!sameRule) {
2040
0
                    if (dstCount == 1) {
2041
0
                        writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime,
2042
0
                                TRUE, status);
2043
0
                    } else {
2044
0
                        writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
2045
0
                                dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
2046
0
                    }
2047
0
                    if (U_FAILURE(status)) {
2048
0
                        goto cleanupWriteZone;
2049
0
                    }
2050
0
                }
2051
0
            } 
2052
0
            if (!sameRule) {
2053
                // Reset this DST information
2054
0
                dstName = name;
2055
0
                dstFromOffset = fromOffset;
2056
0
                dstFromDSTSavings = fromDSTSavings;
2057
0
                dstToOffset = toOffset;
2058
0
                dstStartYear = year;
2059
0
                dstMonth = month;
2060
0
                dstDayOfWeek = dow;
2061
0
                dstWeekInMonth = weekInMonth;
2062
0
                dstMillisInDay = mid;
2063
0
                dstStartTime = dstUntilTime = t;
2064
0
                dstCount = 1;
2065
0
            }
2066
0
            if (finalStdRule != nullptr && finalDstRule != nullptr) {
2067
0
                break;
2068
0
            }
2069
0
        } else {
2070
0
            if (finalStdRule == nullptr
2071
0
                && (atzrule = dynamic_cast<const AnnualTimeZoneRule *>(tzt.getTo())) != nullptr
2072
0
                && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR
2073
0
            ) {
2074
0
                finalStdRule = atzrule->clone();
2075
0
            }
2076
0
            if (stdCount > 0) {
2077
0
                if (year == stdStartYear + stdCount
2078
0
                        && name.compare(stdName) == 0
2079
0
                        && stdFromOffset == fromOffset
2080
0
                        && stdToOffset == toOffset
2081
0
                        && stdMonth == month
2082
0
                        && stdDayOfWeek == dow
2083
0
                        && stdWeekInMonth == weekInMonth
2084
0
                        && stdMillisInDay == mid) {
2085
                    // Update until time
2086
0
                    stdUntilTime = t;
2087
0
                    stdCount++;
2088
0
                    sameRule = TRUE;
2089
0
                }
2090
0
                if (!sameRule) {
2091
0
                    if (stdCount == 1) {
2092
0
                        writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime,
2093
0
                                TRUE, status);
2094
0
                    } else {
2095
0
                        writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2096
0
                                stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2097
0
                    }
2098
0
                    if (U_FAILURE(status)) {
2099
0
                        goto cleanupWriteZone;
2100
0
                    }
2101
0
                }
2102
0
            }
2103
0
            if (!sameRule) {
2104
                // Reset this STD information
2105
0
                stdName = name;
2106
0
                stdFromOffset = fromOffset;
2107
0
                stdFromDSTSavings = fromDSTSavings;
2108
0
                stdToOffset = toOffset;
2109
0
                stdStartYear = year;
2110
0
                stdMonth = month;
2111
0
                stdDayOfWeek = dow;
2112
0
                stdWeekInMonth = weekInMonth;
2113
0
                stdMillisInDay = mid;
2114
0
                stdStartTime = stdUntilTime = t;
2115
0
                stdCount = 1;
2116
0
            }
2117
0
            if (finalStdRule != nullptr && finalDstRule != nullptr) {
2118
0
                break;
2119
0
            }
2120
0
        }
2121
0
    }
2122
0
    if (!hasTransitions) {
2123
        // No transition - put a single non transition RDATE
2124
0
        int32_t raw, dst, offset;
2125
0
        basictz.getOffset(0.0/*any time*/, FALSE, raw, dst, status);
2126
0
        if (U_FAILURE(status)) {
2127
0
            goto cleanupWriteZone;
2128
0
        }
2129
0
        offset = raw + dst;
2130
0
        isDst = (dst != 0);
2131
0
        UnicodeString tzid;
2132
0
        basictz.getID(tzid);
2133
0
        getDefaultTZName(tzid, isDst, name);        
2134
0
        writeZonePropsByTime(w, isDst, name,
2135
0
                offset, offset, DEF_TZSTARTTIME - offset, FALSE, status);    
2136
0
        if (U_FAILURE(status)) {
2137
0
            goto cleanupWriteZone;
2138
0
        }
2139
0
    } else {
2140
0
        if (dstCount > 0) {
2141
0
            if (finalDstRule == nullptr) {
2142
0
                if (dstCount == 1) {
2143
0
                    writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime,
2144
0
                            TRUE, status);
2145
0
                } else {
2146
0
                    writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
2147
0
                            dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
2148
0
                }
2149
0
                if (U_FAILURE(status)) {
2150
0
                    goto cleanupWriteZone;
2151
0
                }
2152
0
            } else {
2153
0
                if (dstCount == 1) {
2154
0
                    writeFinalRule(w, TRUE, finalDstRule,
2155
0
                            dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, dstStartTime, status);
2156
0
                } else {
2157
                    // Use a single rule if possible
2158
0
                    if (isEquivalentDateRule(dstMonth, dstWeekInMonth, dstDayOfWeek, finalDstRule->getRule())) {
2159
0
                        writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
2160
0
                                dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, MAX_MILLIS, status);
2161
0
                    } else {
2162
                        // Not equivalent rule - write out two different rules
2163
0
                        writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset,
2164
0
                                dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status);
2165
0
                        if (U_FAILURE(status)) {
2166
0
                            goto cleanupWriteZone;
2167
0
                        }
2168
0
                        UDate nextStart;
2169
0
                        UBool nextStartAvail = finalDstRule->getNextStart(dstUntilTime, dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, false, nextStart);
2170
0
                        U_ASSERT(nextStartAvail);
2171
0
                        if (nextStartAvail) {
2172
0
                            writeFinalRule(w, TRUE, finalDstRule,
2173
0
                                    dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, nextStart, status);
2174
0
                        }
2175
0
                    }
2176
0
                }
2177
0
                if (U_FAILURE(status)) {
2178
0
                    goto cleanupWriteZone;
2179
0
                }
2180
0
            }
2181
0
        }
2182
0
        if (stdCount > 0) {
2183
0
            if (finalStdRule == nullptr) {
2184
0
                if (stdCount == 1) {
2185
0
                    writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime,
2186
0
                            TRUE, status);
2187
0
                } else {
2188
0
                    writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2189
0
                            stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2190
0
                }
2191
0
                if (U_FAILURE(status)) {
2192
0
                    goto cleanupWriteZone;
2193
0
                }
2194
0
            } else {
2195
0
                if (stdCount == 1) {
2196
0
                    writeFinalRule(w, FALSE, finalStdRule,
2197
0
                            stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, stdStartTime, status);
2198
0
                } else {
2199
                    // Use a single rule if possible
2200
0
                    if (isEquivalentDateRule(stdMonth, stdWeekInMonth, stdDayOfWeek, finalStdRule->getRule())) {
2201
0
                        writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2202
0
                                stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, MAX_MILLIS, status);
2203
0
                    } else {
2204
                        // Not equivalent rule - write out two different rules
2205
0
                        writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset,
2206
0
                                stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status);
2207
0
                        if (U_FAILURE(status)) {
2208
0
                            goto cleanupWriteZone;
2209
0
                        }
2210
0
                        UDate nextStart;
2211
0
                        UBool nextStartAvail = finalStdRule->getNextStart(stdUntilTime, stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, false, nextStart);
2212
0
                        U_ASSERT(nextStartAvail);
2213
0
                        if (nextStartAvail) {
2214
0
                            writeFinalRule(w, FALSE, finalStdRule,
2215
0
                                    stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, nextStart, status);
2216
0
                        }
2217
0
                    }
2218
0
                }
2219
0
                if (U_FAILURE(status)) {
2220
0
                    goto cleanupWriteZone;
2221
0
                }
2222
0
            }
2223
0
        }            
2224
0
    }
2225
0
    writeFooter(w, status);
2226
2227
0
cleanupWriteZone:
2228
2229
0
    if (finalStdRule != nullptr) {
2230
0
        delete finalStdRule;
2231
0
    }
2232
0
    if (finalDstRule != nullptr) {
2233
0
        delete finalDstRule;
2234
0
    }
2235
0
}
2236
2237
void
2238
0
VTimeZone::writeHeaders(VTZWriter& writer, UErrorCode& status) const {
2239
0
    if (U_FAILURE(status)) {
2240
0
        return;
2241
0
    }
2242
0
    UnicodeString tzid;
2243
0
    tz->getID(tzid);
2244
2245
0
    writer.write(ICAL_BEGIN);
2246
0
    writer.write(COLON);
2247
0
    writer.write(ICAL_VTIMEZONE);
2248
0
    writer.write(ICAL_NEWLINE);
2249
0
    writer.write(ICAL_TZID);
2250
0
    writer.write(COLON);
2251
0
    writer.write(tzid);
2252
0
    writer.write(ICAL_NEWLINE);
2253
0
    if (tzurl.length() != 0) {
2254
0
        writer.write(ICAL_TZURL);
2255
0
        writer.write(COLON);
2256
0
        writer.write(tzurl);
2257
0
        writer.write(ICAL_NEWLINE);
2258
0
    }
2259
0
    if (lastmod != MAX_MILLIS) {
2260
0
        UnicodeString lastmodStr;
2261
0
        writer.write(ICAL_LASTMOD);
2262
0
        writer.write(COLON);
2263
0
        writer.write(getUTCDateTimeString(lastmod, lastmodStr));
2264
0
        writer.write(ICAL_NEWLINE);
2265
0
    }
2266
0
}
2267
2268
/*
2269
 * Write the closing section of the VTIMEZONE definition block
2270
 */
2271
void
2272
0
VTimeZone::writeFooter(VTZWriter& writer, UErrorCode& status) const {
2273
0
    if (U_FAILURE(status)) {
2274
0
        return;
2275
0
    }
2276
0
    writer.write(ICAL_END);
2277
0
    writer.write(COLON);
2278
0
    writer.write(ICAL_VTIMEZONE);
2279
0
    writer.write(ICAL_NEWLINE);
2280
0
}
2281
2282
/*
2283
 * Write a single start time
2284
 */
2285
void
2286
VTimeZone::writeZonePropsByTime(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2287
                                int32_t fromOffset, int32_t toOffset, UDate time, UBool withRDATE,
2288
0
                                UErrorCode& status) const {
2289
0
    if (U_FAILURE(status)) {
2290
0
        return;
2291
0
    }
2292
0
    beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, time, status);
2293
0
    if (U_FAILURE(status)) {
2294
0
        return;
2295
0
    }
2296
0
    if (withRDATE) {
2297
0
        writer.write(ICAL_RDATE);
2298
0
        writer.write(COLON);
2299
0
        UnicodeString timestr;
2300
0
        writer.write(getDateTimeString(time + fromOffset, timestr));
2301
0
        writer.write(ICAL_NEWLINE);
2302
0
    }
2303
0
    endZoneProps(writer, isDst, status);
2304
0
    if (U_FAILURE(status)) {
2305
0
        return;
2306
0
    }
2307
0
}
2308
2309
/*
2310
 * Write start times defined by a DOM rule using VTIMEZONE RRULE
2311
 */
2312
void
2313
VTimeZone::writeZonePropsByDOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2314
                               int32_t fromOffset, int32_t toOffset,
2315
                               int32_t month, int32_t dayOfMonth, UDate startTime, UDate untilTime,
2316
0
                               UErrorCode& status) const {
2317
0
    if (U_FAILURE(status)) {
2318
0
        return;
2319
0
    }
2320
0
    beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
2321
0
    if (U_FAILURE(status)) {
2322
0
        return;
2323
0
    }
2324
0
    beginRRULE(writer, month, status);
2325
0
    if (U_FAILURE(status)) {
2326
0
        return;
2327
0
    }
2328
0
    writer.write(ICAL_BYMONTHDAY);
2329
0
    writer.write(EQUALS_SIGN);
2330
0
    UnicodeString dstr;
2331
0
    appendAsciiDigits(dayOfMonth, 0, dstr);
2332
0
    writer.write(dstr);
2333
0
    if (untilTime != MAX_MILLIS) {
2334
0
        appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2335
0
        if (U_FAILURE(status)) {
2336
0
            return;
2337
0
        }
2338
0
    }
2339
0
    writer.write(ICAL_NEWLINE);
2340
0
    endZoneProps(writer, isDst, status);
2341
0
}
2342
2343
/*
2344
 * Write start times defined by a DOW rule using VTIMEZONE RRULE
2345
 */
2346
void
2347
VTimeZone::writeZonePropsByDOW(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2348
                               int32_t fromOffset, int32_t toOffset,
2349
                               int32_t month, int32_t weekInMonth, int32_t dayOfWeek,
2350
0
                               UDate startTime, UDate untilTime, UErrorCode& status) const {
2351
0
    if (U_FAILURE(status)) {
2352
0
        return;
2353
0
    }
2354
0
    beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
2355
0
    if (U_FAILURE(status)) {
2356
0
        return;
2357
0
    }
2358
0
    beginRRULE(writer, month, status);
2359
0
    if (U_FAILURE(status)) {
2360
0
        return;
2361
0
    }
2362
0
    writer.write(ICAL_BYDAY);
2363
0
    writer.write(EQUALS_SIGN);
2364
0
    UnicodeString dstr;
2365
0
    appendAsciiDigits(weekInMonth, 0, dstr);
2366
0
    writer.write(dstr);    // -4, -3, -2, -1, 1, 2, 3, 4
2367
0
    writer.write(ICAL_DOW_NAMES[dayOfWeek - 1]);    // SU, MO, TU...
2368
2369
0
    if (untilTime != MAX_MILLIS) {
2370
0
        appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2371
0
        if (U_FAILURE(status)) {
2372
0
            return;
2373
0
        }
2374
0
    }
2375
0
    writer.write(ICAL_NEWLINE);
2376
0
    endZoneProps(writer, isDst, status);
2377
0
}
2378
2379
/*
2380
 * Write start times defined by a DOW_GEQ_DOM rule using VTIMEZONE RRULE
2381
 */
2382
void
2383
VTimeZone::writeZonePropsByDOW_GEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2384
                                       int32_t fromOffset, int32_t toOffset,
2385
                                       int32_t month, int32_t dayOfMonth, int32_t dayOfWeek,
2386
0
                                       UDate startTime, UDate untilTime, UErrorCode& status) const {
2387
0
    if (U_FAILURE(status)) {
2388
0
        return;
2389
0
    }
2390
    // Check if this rule can be converted to DOW rule
2391
0
    if (dayOfMonth%7 == 1) {
2392
        // Can be represented by DOW rule
2393
0
        writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2394
0
                month, (dayOfMonth + 6)/7, dayOfWeek, startTime, untilTime, status);
2395
0
        if (U_FAILURE(status)) {
2396
0
            return;
2397
0
        }
2398
0
    } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 6) {
2399
        // Can be represented by DOW rule with negative week number
2400
0
        writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2401
0
                month, -1*((MONTHLENGTH[month] - dayOfMonth + 1)/7), dayOfWeek, startTime, untilTime, status);
2402
0
        if (U_FAILURE(status)) {
2403
0
            return;
2404
0
        }
2405
0
    } else {
2406
        // Otherwise, use BYMONTHDAY to include all possible dates
2407
0
        beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status);
2408
0
        if (U_FAILURE(status)) {
2409
0
            return;
2410
0
        }
2411
        // Check if all days are in the same month
2412
0
        int32_t startDay = dayOfMonth;
2413
0
        int32_t currentMonthDays = 7;
2414
    
2415
0
        if (dayOfMonth <= 0) {
2416
            // The start day is in previous month
2417
0
            int32_t prevMonthDays = 1 - dayOfMonth;
2418
0
            currentMonthDays -= prevMonthDays;
2419
2420
0
            int32_t prevMonth = (month - 1) < 0 ? 11 : month - 1;
2421
2422
            // Note: When a rule is separated into two, UNTIL attribute needs to be
2423
            // calculated for each of them.  For now, we skip this, because we basically use this method
2424
            // only for final rules, which does not have the UNTIL attribute
2425
0
            writeZonePropsByDOW_GEQ_DOM_sub(writer, prevMonth, -prevMonthDays, dayOfWeek, prevMonthDays,
2426
0
                MAX_MILLIS /* Do not use UNTIL */, fromOffset, status);
2427
0
            if (U_FAILURE(status)) {
2428
0
                return;
2429
0
            }
2430
2431
            // Start from 1 for the rest
2432
0
            startDay = 1;
2433
0
        } else if (dayOfMonth + 6 > MONTHLENGTH[month]) {
2434
            // Note: This code does not actually work well in February.  For now, days in month in
2435
            // non-leap year.
2436
0
            int32_t nextMonthDays = dayOfMonth + 6 - MONTHLENGTH[month];
2437
0
            currentMonthDays -= nextMonthDays;
2438
2439
0
            int32_t nextMonth = (month + 1) > 11 ? 0 : month + 1;
2440
            
2441
0
            writeZonePropsByDOW_GEQ_DOM_sub(writer, nextMonth, 1, dayOfWeek, nextMonthDays,
2442
0
                MAX_MILLIS /* Do not use UNTIL */, fromOffset, status);
2443
0
            if (U_FAILURE(status)) {
2444
0
                return;
2445
0
            }
2446
0
        }
2447
0
        writeZonePropsByDOW_GEQ_DOM_sub(writer, month, startDay, dayOfWeek, currentMonthDays,
2448
0
            untilTime, fromOffset, status);
2449
0
        if (U_FAILURE(status)) {
2450
0
            return;
2451
0
        }
2452
0
        endZoneProps(writer, isDst, status);
2453
0
    }
2454
0
}
2455
2456
/*
2457
 * Called from writeZonePropsByDOW_GEQ_DOM
2458
 */
2459
void
2460
VTimeZone::writeZonePropsByDOW_GEQ_DOM_sub(VTZWriter& writer, int32_t month, int32_t dayOfMonth,
2461
                                           int32_t dayOfWeek, int32_t numDays,
2462
0
                                           UDate untilTime, int32_t fromOffset, UErrorCode& status) const {
2463
2464
0
    if (U_FAILURE(status)) {
2465
0
        return;
2466
0
    }
2467
0
    int32_t startDayNum = dayOfMonth;
2468
0
    UBool isFeb = (month == UCAL_FEBRUARY);
2469
0
    if (dayOfMonth < 0 && !isFeb) {
2470
        // Use positive number if possible
2471
0
        startDayNum = MONTHLENGTH[month] + dayOfMonth + 1;
2472
0
    }
2473
0
    beginRRULE(writer, month, status);
2474
0
    if (U_FAILURE(status)) {
2475
0
        return;
2476
0
    }
2477
0
    writer.write(ICAL_BYDAY);
2478
0
    writer.write(EQUALS_SIGN);
2479
0
    writer.write(ICAL_DOW_NAMES[dayOfWeek - 1]);    // SU, MO, TU...
2480
0
    writer.write(SEMICOLON);
2481
0
    writer.write(ICAL_BYMONTHDAY);
2482
0
    writer.write(EQUALS_SIGN);
2483
2484
0
    UnicodeString dstr;
2485
0
    appendAsciiDigits(startDayNum, 0, dstr);
2486
0
    writer.write(dstr);
2487
0
    for (int32_t i = 1; i < numDays; i++) {
2488
0
        writer.write(COMMA);
2489
0
        dstr.remove();
2490
0
        appendAsciiDigits(startDayNum + i, 0, dstr);
2491
0
        writer.write(dstr);
2492
0
    }
2493
2494
0
    if (untilTime != MAX_MILLIS) {
2495
0
        appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status);
2496
0
        if (U_FAILURE(status)) {
2497
0
            return;
2498
0
        }
2499
0
    }
2500
0
    writer.write(ICAL_NEWLINE);
2501
0
}
2502
2503
/*
2504
 * Write start times defined by a DOW_LEQ_DOM rule using VTIMEZONE RRULE
2505
 */
2506
void
2507
VTimeZone::writeZonePropsByDOW_LEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2508
                                       int32_t fromOffset, int32_t toOffset,
2509
                                       int32_t month, int32_t dayOfMonth, int32_t dayOfWeek,
2510
0
                                       UDate startTime, UDate untilTime, UErrorCode& status) const {
2511
0
    if (U_FAILURE(status)) {
2512
0
        return;
2513
0
    }
2514
    // Check if this rule can be converted to DOW rule
2515
0
    if (dayOfMonth%7 == 0) {
2516
        // Can be represented by DOW rule
2517
0
        writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2518
0
                month, dayOfMonth/7, dayOfWeek, startTime, untilTime, status);
2519
0
    } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 0){
2520
        // Can be represented by DOW rule with negative week number
2521
0
        writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2522
0
                month, -1*((MONTHLENGTH[month] - dayOfMonth)/7 + 1), dayOfWeek, startTime, untilTime, status);
2523
0
    } else if (month == UCAL_FEBRUARY && dayOfMonth == 29) {
2524
        // Special case for February
2525
0
        writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset,
2526
0
                UCAL_FEBRUARY, -1, dayOfWeek, startTime, untilTime, status);
2527
0
    } else {
2528
        // Otherwise, convert this to DOW_GEQ_DOM rule
2529
0
        writeZonePropsByDOW_GEQ_DOM(writer, isDst, zonename, fromOffset, toOffset,
2530
0
                month, dayOfMonth - 6, dayOfWeek, startTime, untilTime, status);
2531
0
    }
2532
0
}
2533
2534
/*
2535
 * Write the final time zone rule using RRULE, with no UNTIL attribute
2536
 */
2537
void
2538
VTimeZone::writeFinalRule(VTZWriter& writer, UBool isDst, const AnnualTimeZoneRule* rule,
2539
                          int32_t fromRawOffset, int32_t fromDSTSavings,
2540
0
                          UDate startTime, UErrorCode& status) const {
2541
0
    if (U_FAILURE(status)) {
2542
0
        return;
2543
0
    }
2544
0
    UBool modifiedRule = TRUE;
2545
0
    const DateTimeRule *dtrule = toWallTimeRule(rule->getRule(), fromRawOffset, fromDSTSavings, status);
2546
0
    if (U_FAILURE(status)) {
2547
0
        return;
2548
0
    }
2549
0
    if (dtrule == nullptr) {
2550
0
        modifiedRule = FALSE;
2551
0
        dtrule = rule->getRule();
2552
0
    }
2553
2554
    // If the rule's mills in a day is out of range, adjust start time.
2555
    // Olson tzdata supports 24:00 of a day, but VTIMEZONE does not.
2556
    // See ticket#7008/#7518
2557
2558
0
    int32_t timeInDay = dtrule->getRuleMillisInDay();
2559
0
    if (timeInDay < 0) {
2560
0
        startTime = startTime + (0 - timeInDay);
2561
0
    } else if (timeInDay >= U_MILLIS_PER_DAY) {
2562
0
        startTime = startTime - (timeInDay - (U_MILLIS_PER_DAY - 1));
2563
0
    }
2564
2565
0
    int32_t toOffset = rule->getRawOffset() + rule->getDSTSavings();
2566
0
    UnicodeString name;
2567
0
    rule->getName(name);
2568
0
    switch (dtrule->getDateRuleType()) {
2569
0
    case DateTimeRule::DOM:
2570
0
        writeZonePropsByDOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2571
0
                dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), startTime, MAX_MILLIS, status);
2572
0
        break;
2573
0
    case DateTimeRule::DOW:
2574
0
        writeZonePropsByDOW(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2575
0
                dtrule->getRuleMonth(), dtrule->getRuleWeekInMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
2576
0
        break;
2577
0
    case DateTimeRule::DOW_GEQ_DOM:
2578
0
        writeZonePropsByDOW_GEQ_DOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2579
0
                dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
2580
0
        break;
2581
0
    case DateTimeRule::DOW_LEQ_DOM:
2582
0
        writeZonePropsByDOW_LEQ_DOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset,
2583
0
                dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status);
2584
0
        break;
2585
0
    }
2586
0
    if (modifiedRule) {
2587
0
        delete dtrule;
2588
0
    }
2589
0
}
2590
2591
/*
2592
 * Write the opening section of zone properties
2593
 */
2594
void
2595
VTimeZone::beginZoneProps(VTZWriter& writer, UBool isDst, const UnicodeString& zonename,
2596
0
                          int32_t fromOffset, int32_t toOffset, UDate startTime, UErrorCode& status) const {
2597
0
    if (U_FAILURE(status)) {
2598
0
        return;
2599
0
    }
2600
0
    writer.write(ICAL_BEGIN);
2601
0
    writer.write(COLON);
2602
0
    if (isDst) {
2603
0
        writer.write(ICAL_DAYLIGHT);
2604
0
    } else {
2605
0
        writer.write(ICAL_STANDARD);
2606
0
    }
2607
0
    writer.write(ICAL_NEWLINE);
2608
2609
0
    UnicodeString dstr;
2610
2611
    // TZOFFSETTO
2612
0
    writer.write(ICAL_TZOFFSETTO);
2613
0
    writer.write(COLON);
2614
0
    millisToOffset(toOffset, dstr);
2615
0
    writer.write(dstr);
2616
0
    writer.write(ICAL_NEWLINE);
2617
2618
    // TZOFFSETFROM
2619
0
    writer.write(ICAL_TZOFFSETFROM);
2620
0
    writer.write(COLON);
2621
0
    millisToOffset(fromOffset, dstr);
2622
0
    writer.write(dstr);
2623
0
    writer.write(ICAL_NEWLINE);
2624
2625
    // TZNAME
2626
0
    writer.write(ICAL_TZNAME);
2627
0
    writer.write(COLON);
2628
0
    writer.write(zonename);
2629
0
    writer.write(ICAL_NEWLINE);
2630
    
2631
    // DTSTART
2632
0
    writer.write(ICAL_DTSTART);
2633
0
    writer.write(COLON);
2634
0
    writer.write(getDateTimeString(startTime + fromOffset, dstr));
2635
0
    writer.write(ICAL_NEWLINE);        
2636
0
}
2637
2638
/*
2639
 * Writes the closing section of zone properties
2640
 */
2641
void
2642
0
VTimeZone::endZoneProps(VTZWriter& writer, UBool isDst, UErrorCode& status) const {
2643
0
    if (U_FAILURE(status)) {
2644
0
        return;
2645
0
    }
2646
    // END:STANDARD or END:DAYLIGHT
2647
0
    writer.write(ICAL_END);
2648
0
    writer.write(COLON);
2649
0
    if (isDst) {
2650
0
        writer.write(ICAL_DAYLIGHT);
2651
0
    } else {
2652
0
        writer.write(ICAL_STANDARD);
2653
0
    }
2654
0
    writer.write(ICAL_NEWLINE);
2655
0
}
2656
2657
/*
2658
 * Write the beginning part of RRULE line
2659
 */
2660
void
2661
0
VTimeZone::beginRRULE(VTZWriter& writer, int32_t month, UErrorCode& status) const {
2662
0
    if (U_FAILURE(status)) {
2663
0
        return;
2664
0
    }
2665
0
    UnicodeString dstr;
2666
0
    writer.write(ICAL_RRULE);
2667
0
    writer.write(COLON);
2668
0
    writer.write(ICAL_FREQ);
2669
0
    writer.write(EQUALS_SIGN);
2670
0
    writer.write(ICAL_YEARLY);
2671
0
    writer.write(SEMICOLON);
2672
0
    writer.write(ICAL_BYMONTH);
2673
0
    writer.write(EQUALS_SIGN);
2674
0
    appendAsciiDigits(month + 1, 0, dstr);
2675
0
    writer.write(dstr);
2676
0
    writer.write(SEMICOLON);
2677
0
}
2678
2679
/*
2680
 * Append the UNTIL attribute after RRULE line
2681
 */
2682
void
2683
0
VTimeZone::appendUNTIL(VTZWriter& writer, const UnicodeString& until,  UErrorCode& status) const {
2684
0
    if (U_FAILURE(status)) {
2685
0
        return;
2686
0
    }
2687
0
    if (until.length() > 0) {
2688
0
        writer.write(SEMICOLON);
2689
0
        writer.write(ICAL_UNTIL);
2690
0
        writer.write(EQUALS_SIGN);
2691
0
        writer.write(until);
2692
0
    }
2693
0
}
2694
2695
U_NAMESPACE_END
2696
2697
#endif /* #if !UCONFIG_NO_FORMATTING */
2698
2699
//eof