Coverage Report

Created: 2025-06-13 06:34

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