intl/icu/source/i18n/vtzone.cpp

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

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

mercurial