michael@0: /* michael@0: ******************************************************************************* michael@0: * Copyright (C) 2007-2013, International Business Machines Corporation and michael@0: * others. All Rights Reserved. michael@0: ******************************************************************************* michael@0: */ michael@0: michael@0: #include "utypeinfo.h" // for 'typeid' to work michael@0: michael@0: #include "unicode/utypes.h" michael@0: michael@0: #if !UCONFIG_NO_FORMATTING michael@0: michael@0: #include "unicode/vtzone.h" michael@0: #include "unicode/rbtz.h" michael@0: #include "unicode/ucal.h" michael@0: #include "unicode/ures.h" michael@0: #include "cmemory.h" michael@0: #include "uvector.h" michael@0: #include "gregoimp.h" michael@0: #include "uassert.h" michael@0: michael@0: U_NAMESPACE_BEGIN michael@0: michael@0: // This is the deleter that will be use to remove TimeZoneRule michael@0: U_CDECL_BEGIN michael@0: static void U_CALLCONV michael@0: deleteTimeZoneRule(void* obj) { michael@0: delete (TimeZoneRule*) obj; michael@0: } michael@0: U_CDECL_END michael@0: michael@0: // Smybol characters used by RFC2445 VTIMEZONE michael@0: static const UChar COLON = 0x3A; /* : */ michael@0: static const UChar SEMICOLON = 0x3B; /* ; */ michael@0: static const UChar EQUALS_SIGN = 0x3D; /* = */ michael@0: static const UChar COMMA = 0x2C; /* , */ michael@0: static const UChar PLUS = 0x2B; /* + */ michael@0: static const UChar MINUS = 0x2D; /* - */ michael@0: michael@0: // RFC2445 VTIMEZONE tokens michael@0: static const UChar ICAL_BEGIN_VTIMEZONE[] = {0x42, 0x45, 0x47, 0x49, 0x4E, 0x3A, 0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "BEGIN:VTIMEZONE" */ michael@0: static const UChar ICAL_END_VTIMEZONE[] = {0x45, 0x4E, 0x44, 0x3A, 0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "END:VTIMEZONE" */ michael@0: static const UChar ICAL_BEGIN[] = {0x42, 0x45, 0x47, 0x49, 0x4E, 0}; /* "BEGIN" */ michael@0: static const UChar ICAL_END[] = {0x45, 0x4E, 0x44, 0}; /* "END" */ michael@0: static const UChar ICAL_VTIMEZONE[] = {0x56, 0x54, 0x49, 0x4D, 0x45, 0x5A, 0x4F, 0x4E, 0x45, 0}; /* "VTIMEZONE" */ michael@0: static const UChar ICAL_TZID[] = {0x54, 0x5A, 0x49, 0x44, 0}; /* "TZID" */ michael@0: static const UChar ICAL_STANDARD[] = {0x53, 0x54, 0x41, 0x4E, 0x44, 0x41, 0x52, 0x44, 0}; /* "STANDARD" */ michael@0: static const UChar ICAL_DAYLIGHT[] = {0x44, 0x41, 0x59, 0x4C, 0x49, 0x47, 0x48, 0x54, 0}; /* "DAYLIGHT" */ michael@0: static const UChar ICAL_DTSTART[] = {0x44, 0x54, 0x53, 0x54, 0x41, 0x52, 0x54, 0}; /* "DTSTART" */ michael@0: static const UChar ICAL_TZOFFSETFROM[] = {0x54, 0x5A, 0x4F, 0x46, 0x46, 0x53, 0x45, 0x54, 0x46, 0x52, 0x4F, 0x4D, 0}; /* "TZOFFSETFROM" */ michael@0: static const UChar ICAL_TZOFFSETTO[] = {0x54, 0x5A, 0x4F, 0x46, 0x46, 0x53, 0x45, 0x54, 0x54, 0x4F, 0}; /* "TZOFFSETTO" */ michael@0: static const UChar ICAL_RDATE[] = {0x52, 0x44, 0x41, 0x54, 0x45, 0}; /* "RDATE" */ michael@0: static const UChar ICAL_RRULE[] = {0x52, 0x52, 0x55, 0x4C, 0x45, 0}; /* "RRULE" */ michael@0: static const UChar ICAL_TZNAME[] = {0x54, 0x5A, 0x4E, 0x41, 0x4D, 0x45, 0}; /* "TZNAME" */ michael@0: static const UChar ICAL_TZURL[] = {0x54, 0x5A, 0x55, 0x52, 0x4C, 0}; /* "TZURL" */ michael@0: static const UChar ICAL_LASTMOD[] = {0x4C, 0x41, 0x53, 0x54, 0x2D, 0x4D, 0x4F, 0x44, 0x49, 0x46, 0x49, 0x45, 0x44, 0}; /* "LAST-MODIFIED" */ michael@0: michael@0: static const UChar ICAL_FREQ[] = {0x46, 0x52, 0x45, 0x51, 0}; /* "FREQ" */ michael@0: static const UChar ICAL_UNTIL[] = {0x55, 0x4E, 0x54, 0x49, 0x4C, 0}; /* "UNTIL" */ michael@0: static const UChar ICAL_YEARLY[] = {0x59, 0x45, 0x41, 0x52, 0x4C, 0x59, 0}; /* "YEARLY" */ michael@0: static const UChar ICAL_BYMONTH[] = {0x42, 0x59, 0x4D, 0x4F, 0x4E, 0x54, 0x48, 0}; /* "BYMONTH" */ michael@0: static const UChar ICAL_BYDAY[] = {0x42, 0x59, 0x44, 0x41, 0x59, 0}; /* "BYDAY" */ michael@0: static const UChar ICAL_BYMONTHDAY[] = {0x42, 0x59, 0x4D, 0x4F, 0x4E, 0x54, 0x48, 0x44, 0x41, 0x59, 0}; /* "BYMONTHDAY" */ michael@0: michael@0: static const UChar ICAL_NEWLINE[] = {0x0D, 0x0A, 0}; /* CRLF */ michael@0: michael@0: static const UChar ICAL_DOW_NAMES[7][3] = { michael@0: {0x53, 0x55, 0}, /* "SU" */ michael@0: {0x4D, 0x4F, 0}, /* "MO" */ michael@0: {0x54, 0x55, 0}, /* "TU" */ michael@0: {0x57, 0x45, 0}, /* "WE" */ michael@0: {0x54, 0x48, 0}, /* "TH" */ michael@0: {0x46, 0x52, 0}, /* "FR" */ michael@0: {0x53, 0x41, 0} /* "SA" */}; michael@0: michael@0: // Month length for non-leap year michael@0: static const int32_t MONTHLENGTH[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; michael@0: michael@0: // ICU custom property michael@0: static const UChar ICU_TZINFO_PROP[] = {0x58, 0x2D, 0x54, 0x5A, 0x49, 0x4E, 0x46, 0x4F, 0x3A, 0}; /* "X-TZINFO:" */ michael@0: static const UChar ICU_TZINFO_PARTIAL[] = {0x2F, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6C, 0x40, 0}; /* "/Partial@" */ michael@0: static const UChar ICU_TZINFO_SIMPLE[] = {0x2F, 0x53, 0x69, 0x6D, 0x70, 0x6C, 0x65, 0x40, 0}; /* "/Simple@" */ michael@0: michael@0: michael@0: /* michael@0: * Simple fixed digit ASCII number to integer converter michael@0: */ michael@0: static int32_t parseAsciiDigits(const UnicodeString& str, int32_t start, int32_t length, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return 0; michael@0: } michael@0: if (length <= 0 || str.length() < start || (start + length) > str.length()) { michael@0: status = U_INVALID_FORMAT_ERROR; michael@0: return 0; michael@0: } michael@0: int32_t sign = 1; michael@0: if (str.charAt(start) == PLUS) { michael@0: start++; michael@0: length--; michael@0: } else if (str.charAt(start) == MINUS) { michael@0: sign = -1; michael@0: start++; michael@0: length--; michael@0: } michael@0: int32_t num = 0; michael@0: for (int32_t i = 0; i < length; i++) { michael@0: int32_t digit = str.charAt(start + i) - 0x0030; michael@0: if (digit < 0 || digit > 9) { michael@0: status = U_INVALID_FORMAT_ERROR; michael@0: return 0; michael@0: } michael@0: num = 10 * num + digit; michael@0: } michael@0: return sign * num; michael@0: } michael@0: michael@0: static UnicodeString& appendAsciiDigits(int32_t number, uint8_t length, UnicodeString& str) { michael@0: UBool negative = FALSE; michael@0: int32_t digits[10]; // max int32_t is 10 decimal digits michael@0: int32_t i; michael@0: michael@0: if (number < 0) { michael@0: negative = TRUE; michael@0: number *= -1; michael@0: } michael@0: michael@0: length = length > 10 ? 10 : length; michael@0: if (length == 0) { michael@0: // variable length michael@0: i = 0; michael@0: do { michael@0: digits[i++] = number % 10; michael@0: number /= 10; michael@0: } while (number != 0); michael@0: length = i; michael@0: } else { michael@0: // fixed digits michael@0: for (i = 0; i < length; i++) { michael@0: digits[i] = number % 10; michael@0: number /= 10; michael@0: } michael@0: } michael@0: if (negative) { michael@0: str.append(MINUS); michael@0: } michael@0: for (i = length - 1; i >= 0; i--) { michael@0: str.append((UChar)(digits[i] + 0x0030)); michael@0: } michael@0: return str; michael@0: } michael@0: michael@0: static UnicodeString& appendMillis(UDate date, UnicodeString& str) { michael@0: UBool negative = FALSE; michael@0: int32_t digits[20]; // max int64_t is 20 decimal digits michael@0: int32_t i; michael@0: int64_t number; michael@0: michael@0: if (date < MIN_MILLIS) { michael@0: number = (int64_t)MIN_MILLIS; michael@0: } else if (date > MAX_MILLIS) { michael@0: number = (int64_t)MAX_MILLIS; michael@0: } else { michael@0: number = (int64_t)date; michael@0: } michael@0: if (number < 0) { michael@0: negative = TRUE; michael@0: number *= -1; michael@0: } michael@0: i = 0; michael@0: do { michael@0: digits[i++] = (int32_t)(number % 10); michael@0: number /= 10; michael@0: } while (number != 0); michael@0: michael@0: if (negative) { michael@0: str.append(MINUS); michael@0: } michael@0: i--; michael@0: while (i >= 0) { michael@0: str.append((UChar)(digits[i--] + 0x0030)); michael@0: } michael@0: return str; michael@0: } michael@0: michael@0: /* michael@0: * Convert date/time to RFC2445 Date-Time form #1 DATE WITH LOCAL TIME michael@0: */ michael@0: static UnicodeString& getDateTimeString(UDate time, UnicodeString& str) { michael@0: int32_t year, month, dom, dow, doy, mid; michael@0: Grego::timeToFields(time, year, month, dom, dow, doy, mid); michael@0: michael@0: str.remove(); michael@0: appendAsciiDigits(year, 4, str); michael@0: appendAsciiDigits(month + 1, 2, str); michael@0: appendAsciiDigits(dom, 2, str); michael@0: str.append((UChar)0x0054 /*'T'*/); michael@0: michael@0: int32_t t = mid; michael@0: int32_t hour = t / U_MILLIS_PER_HOUR; michael@0: t %= U_MILLIS_PER_HOUR; michael@0: int32_t min = t / U_MILLIS_PER_MINUTE; michael@0: t %= U_MILLIS_PER_MINUTE; michael@0: int32_t sec = t / U_MILLIS_PER_SECOND; michael@0: michael@0: appendAsciiDigits(hour, 2, str); michael@0: appendAsciiDigits(min, 2, str); michael@0: appendAsciiDigits(sec, 2, str); michael@0: return str; michael@0: } michael@0: michael@0: /* michael@0: * Convert date/time to RFC2445 Date-Time form #2 DATE WITH UTC TIME michael@0: */ michael@0: static UnicodeString& getUTCDateTimeString(UDate time, UnicodeString& str) { michael@0: getDateTimeString(time, str); michael@0: str.append((UChar)0x005A /*'Z'*/); michael@0: return str; michael@0: } michael@0: michael@0: /* michael@0: * Parse RFC2445 Date-Time form #1 DATE WITH LOCAL TIME and michael@0: * #2 DATE WITH UTC TIME michael@0: */ michael@0: static UDate parseDateTimeString(const UnicodeString& str, int32_t offset, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return 0.0; michael@0: } michael@0: michael@0: int32_t year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0; michael@0: UBool isUTC = FALSE; michael@0: UBool isValid = FALSE; michael@0: do { michael@0: int length = str.length(); michael@0: if (length != 15 && length != 16) { michael@0: // FORM#1 15 characters, such as "20060317T142115" michael@0: // FORM#2 16 characters, such as "20060317T142115Z" michael@0: break; michael@0: } michael@0: if (str.charAt(8) != 0x0054) { michael@0: // charcter "T" must be used for separating date and time michael@0: break; michael@0: } michael@0: if (length == 16) { michael@0: if (str.charAt(15) != 0x005A) { michael@0: // invalid format michael@0: break; michael@0: } michael@0: isUTC = TRUE; michael@0: } michael@0: michael@0: year = parseAsciiDigits(str, 0, 4, status); michael@0: month = parseAsciiDigits(str, 4, 2, status) - 1; // 0-based michael@0: day = parseAsciiDigits(str, 6, 2, status); michael@0: hour = parseAsciiDigits(str, 9, 2, status); michael@0: min = parseAsciiDigits(str, 11, 2, status); michael@0: sec = parseAsciiDigits(str, 13, 2, status); michael@0: michael@0: if (U_FAILURE(status)) { michael@0: break; michael@0: } michael@0: michael@0: // check valid range michael@0: int32_t maxDayOfMonth = Grego::monthLength(year, month); michael@0: if (year < 0 || month < 0 || month > 11 || day < 1 || day > maxDayOfMonth || michael@0: hour < 0 || hour >= 24 || min < 0 || min >= 60 || sec < 0 || sec >= 60) { michael@0: break; michael@0: } michael@0: michael@0: isValid = TRUE; michael@0: } while(false); michael@0: michael@0: if (!isValid) { michael@0: status = U_INVALID_FORMAT_ERROR; michael@0: return 0.0; michael@0: } michael@0: // Calculate the time michael@0: UDate time = Grego::fieldsToDay(year, month, day) * U_MILLIS_PER_DAY; michael@0: time += (hour * U_MILLIS_PER_HOUR + min * U_MILLIS_PER_MINUTE + sec * U_MILLIS_PER_SECOND); michael@0: if (!isUTC) { michael@0: time -= offset; michael@0: } michael@0: return time; michael@0: } michael@0: michael@0: /* michael@0: * Convert RFC2445 utc-offset string to milliseconds michael@0: */ michael@0: static int32_t offsetStrToMillis(const UnicodeString& str, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return 0; michael@0: } michael@0: michael@0: UBool isValid = FALSE; michael@0: int32_t sign = 0, hour = 0, min = 0, sec = 0; michael@0: michael@0: do { michael@0: int length = str.length(); michael@0: if (length != 5 && length != 7) { michael@0: // utf-offset must be 5 or 7 characters michael@0: break; michael@0: } michael@0: // sign michael@0: UChar s = str.charAt(0); michael@0: if (s == PLUS) { michael@0: sign = 1; michael@0: } else if (s == MINUS) { michael@0: sign = -1; michael@0: } else { michael@0: // utf-offset must start with "+" or "-" michael@0: break; michael@0: } michael@0: hour = parseAsciiDigits(str, 1, 2, status); michael@0: min = parseAsciiDigits(str, 3, 2, status); michael@0: if (length == 7) { michael@0: sec = parseAsciiDigits(str, 5, 2, status); michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: break; michael@0: } michael@0: isValid = true; michael@0: } while(false); michael@0: michael@0: if (!isValid) { michael@0: status = U_INVALID_FORMAT_ERROR; michael@0: return 0; michael@0: } michael@0: int32_t millis = sign * ((hour * 60 + min) * 60 + sec) * 1000; michael@0: return millis; michael@0: } michael@0: michael@0: /* michael@0: * Convert milliseconds to RFC2445 utc-offset string michael@0: */ michael@0: static void millisToOffset(int32_t millis, UnicodeString& str) { michael@0: str.remove(); michael@0: if (millis >= 0) { michael@0: str.append(PLUS); michael@0: } else { michael@0: str.append(MINUS); michael@0: millis = -millis; michael@0: } michael@0: int32_t hour, min, sec; michael@0: int32_t t = millis / 1000; michael@0: michael@0: sec = t % 60; michael@0: t = (t - sec) / 60; michael@0: min = t % 60; michael@0: hour = t / 60; michael@0: michael@0: appendAsciiDigits(hour, 2, str); michael@0: appendAsciiDigits(min, 2, str); michael@0: appendAsciiDigits(sec, 2, str); michael@0: } michael@0: michael@0: /* michael@0: * Create a default TZNAME from TZID michael@0: */ michael@0: static void getDefaultTZName(const UnicodeString tzid, UBool isDST, UnicodeString& zonename) { michael@0: zonename = tzid; michael@0: if (isDST) { michael@0: zonename += UNICODE_STRING_SIMPLE("(DST)"); michael@0: } else { michael@0: zonename += UNICODE_STRING_SIMPLE("(STD)"); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Parse individual RRULE michael@0: * michael@0: * On return - michael@0: * michael@0: * month calculated by BYMONTH-1, or -1 when not found michael@0: * dow day of week in BYDAY, or 0 when not found michael@0: * wim day of week ordinal number in BYDAY, or 0 when not found michael@0: * dom an array of day of month michael@0: * domCount number of availble days in dom (domCount is specifying the size of dom on input) michael@0: * until time defined by UNTIL attribute or MIN_MILLIS if not available michael@0: */ michael@0: static void parseRRULE(const UnicodeString& rrule, int32_t& month, int32_t& dow, int32_t& wim, michael@0: int32_t* dom, int32_t& domCount, UDate& until, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: int32_t numDom = 0; michael@0: michael@0: month = -1; michael@0: dow = 0; michael@0: wim = 0; michael@0: until = MIN_MILLIS; michael@0: michael@0: UBool yearly = FALSE; michael@0: //UBool parseError = FALSE; michael@0: michael@0: int32_t prop_start = 0; michael@0: int32_t prop_end; michael@0: UnicodeString prop, attr, value; michael@0: UBool nextProp = TRUE; michael@0: michael@0: while (nextProp) { michael@0: prop_end = rrule.indexOf(SEMICOLON, prop_start); michael@0: if (prop_end == -1) { michael@0: prop.setTo(rrule, prop_start); michael@0: nextProp = FALSE; michael@0: } else { michael@0: prop.setTo(rrule, prop_start, prop_end - prop_start); michael@0: prop_start = prop_end + 1; michael@0: } michael@0: int32_t eql = prop.indexOf(EQUALS_SIGN); michael@0: if (eql != -1) { michael@0: attr.setTo(prop, 0, eql); michael@0: value.setTo(prop, eql + 1); michael@0: } else { michael@0: goto rruleParseError; michael@0: } michael@0: michael@0: if (attr.compare(ICAL_FREQ, -1) == 0) { michael@0: // only support YEARLY frequency type michael@0: if (value.compare(ICAL_YEARLY, -1) == 0) { michael@0: yearly = TRUE; michael@0: } else { michael@0: goto rruleParseError; michael@0: } michael@0: } else if (attr.compare(ICAL_UNTIL, -1) == 0) { michael@0: // ISO8601 UTC format, for example, "20060315T020000Z" michael@0: until = parseDateTimeString(value, 0, status); michael@0: if (U_FAILURE(status)) { michael@0: goto rruleParseError; michael@0: } michael@0: } else if (attr.compare(ICAL_BYMONTH, -1) == 0) { michael@0: // Note: BYMONTH may contain multiple months, but only single month make sense for michael@0: // VTIMEZONE property. michael@0: if (value.length() > 2) { michael@0: goto rruleParseError; michael@0: } michael@0: month = parseAsciiDigits(value, 0, value.length(), status) - 1; michael@0: if (U_FAILURE(status) || month < 0 || month >= 12) { michael@0: goto rruleParseError; michael@0: } michael@0: } else if (attr.compare(ICAL_BYDAY, -1) == 0) { michael@0: // Note: BYDAY may contain multiple day of week separated by comma. It is unlikely used for michael@0: // VTIMEZONE property. We do not support the case. michael@0: michael@0: // 2-letter format is used just for representing a day of week, for example, "SU" for Sunday michael@0: // 3 or 4-letter format is used for represeinging Nth day of week, for example, "-1SA" for last Saturday michael@0: int32_t length = value.length(); michael@0: if (length < 2 || length > 4) { michael@0: goto rruleParseError; michael@0: } michael@0: if (length > 2) { michael@0: // Nth day of week michael@0: int32_t sign = 1; michael@0: if (value.charAt(0) == PLUS) { michael@0: sign = 1; michael@0: } else if (value.charAt(0) == MINUS) { michael@0: sign = -1; michael@0: } else if (length == 4) { michael@0: goto rruleParseError; michael@0: } michael@0: int32_t n = parseAsciiDigits(value, length - 3, 1, status); michael@0: if (U_FAILURE(status) || n == 0 || n > 4) { michael@0: goto rruleParseError; michael@0: } michael@0: wim = n * sign; michael@0: value.remove(0, length - 2); michael@0: } michael@0: int32_t wday; michael@0: for (wday = 0; wday < 7; wday++) { michael@0: if (value.compare(ICAL_DOW_NAMES[wday], 2) == 0) { michael@0: break; michael@0: } michael@0: } michael@0: if (wday < 7) { michael@0: // Sunday(1) - Saturday(7) michael@0: dow = wday + 1; michael@0: } else { michael@0: goto rruleParseError; michael@0: } michael@0: } else if (attr.compare(ICAL_BYMONTHDAY, -1) == 0) { michael@0: // Note: BYMONTHDAY may contain multiple days delimitted by comma michael@0: // michael@0: // A value of BYMONTHDAY could be negative, for example, -1 means michael@0: // the last day in a month michael@0: int32_t dom_idx = 0; michael@0: int32_t dom_start = 0; michael@0: int32_t dom_end; michael@0: UBool nextDOM = TRUE; michael@0: while (nextDOM) { michael@0: dom_end = value.indexOf(COMMA, dom_start); michael@0: if (dom_end == -1) { michael@0: dom_end = value.length(); michael@0: nextDOM = FALSE; michael@0: } michael@0: if (dom_idx < domCount) { michael@0: dom[dom_idx] = parseAsciiDigits(value, dom_start, dom_end - dom_start, status); michael@0: if (U_FAILURE(status)) { michael@0: goto rruleParseError; michael@0: } michael@0: dom_idx++; michael@0: } else { michael@0: status = U_BUFFER_OVERFLOW_ERROR; michael@0: goto rruleParseError; michael@0: } michael@0: dom_start = dom_end + 1; michael@0: } michael@0: numDom = dom_idx; michael@0: } michael@0: } michael@0: if (!yearly) { michael@0: // FREQ=YEARLY must be set michael@0: goto rruleParseError; michael@0: } michael@0: // Set actual number of parsed DOM (ICAL_BYMONTHDAY) michael@0: domCount = numDom; michael@0: return; michael@0: michael@0: rruleParseError: michael@0: if (U_SUCCESS(status)) { michael@0: // Set error status michael@0: status = U_INVALID_FORMAT_ERROR; michael@0: } michael@0: } michael@0: michael@0: static TimeZoneRule* createRuleByRRULE(const UnicodeString& zonename, int rawOffset, int dstSavings, UDate start, michael@0: UVector* dates, int fromOffset, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: if (dates == NULL || dates->size() == 0) { michael@0: status = U_ILLEGAL_ARGUMENT_ERROR; michael@0: return NULL; michael@0: } michael@0: michael@0: int32_t i, j; michael@0: DateTimeRule *adtr = NULL; michael@0: michael@0: // Parse the first rule michael@0: UnicodeString rrule = *((UnicodeString*)dates->elementAt(0)); michael@0: int32_t month, dayOfWeek, nthDayOfWeek, dayOfMonth = 0; michael@0: int32_t days[7]; michael@0: int32_t daysCount = sizeof(days)/sizeof(days[0]); michael@0: UDate until; michael@0: michael@0: parseRRULE(rrule, month, dayOfWeek, nthDayOfWeek, days, daysCount, until, status); michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: michael@0: if (dates->size() == 1) { michael@0: // No more rules michael@0: if (daysCount > 1) { michael@0: // Multiple BYMONTHDAY values michael@0: if (daysCount != 7 || month == -1 || dayOfWeek == 0) { michael@0: // Only support the rule using 7 continuous days michael@0: // BYMONTH and BYDAY must be set at the same time michael@0: goto unsupportedRRule; michael@0: } michael@0: int32_t firstDay = 31; // max possible number of dates in a month michael@0: for (i = 0; i < 7; i++) { michael@0: // Resolve negative day numbers. A negative day number should michael@0: // not be used in February, but if we see such case, we use 28 michael@0: // as the base. michael@0: if (days[i] < 0) { michael@0: days[i] = MONTHLENGTH[month] + days[i] + 1; michael@0: } michael@0: if (days[i] < firstDay) { michael@0: firstDay = days[i]; michael@0: } michael@0: } michael@0: // Make sure days are continuous michael@0: for (i = 1; i < 7; i++) { michael@0: UBool found = FALSE; michael@0: for (j = 0; j < 7; j++) { michael@0: if (days[j] == firstDay + i) { michael@0: found = TRUE; michael@0: break; michael@0: } michael@0: } michael@0: if (!found) { michael@0: // days are not continuous michael@0: goto unsupportedRRule; michael@0: } michael@0: } michael@0: // Use DOW_GEQ_DOM rule with firstDay as the start date michael@0: dayOfMonth = firstDay; michael@0: } michael@0: } else { michael@0: // Check if BYMONTH + BYMONTHDAY + BYDAY rule with multiple RRULE lines. michael@0: // Otherwise, not supported. michael@0: if (month == -1 || dayOfWeek == 0 || daysCount == 0) { michael@0: // This is not the case michael@0: goto unsupportedRRule; michael@0: } michael@0: // Parse the rest of rules if number of rules is not exceeding 7. michael@0: // We can only support 7 continuous days starting from a day of month. michael@0: if (dates->size() > 7) { michael@0: goto unsupportedRRule; michael@0: } michael@0: michael@0: // Note: To check valid date range across multiple rule is a little michael@0: // bit complicated. For now, this code is not doing strict range michael@0: // checking across month boundary michael@0: michael@0: int32_t earliestMonth = month; michael@0: int32_t earliestDay = 31; michael@0: for (i = 0; i < daysCount; i++) { michael@0: int32_t dom = days[i]; michael@0: dom = dom > 0 ? dom : MONTHLENGTH[month] + dom + 1; michael@0: earliestDay = dom < earliestDay ? dom : earliestDay; michael@0: } michael@0: michael@0: int32_t anotherMonth = -1; michael@0: for (i = 1; i < dates->size(); i++) { michael@0: rrule = *((UnicodeString*)dates->elementAt(i)); michael@0: UDate tmp_until; michael@0: int32_t tmp_month, tmp_dayOfWeek, tmp_nthDayOfWeek; michael@0: int32_t tmp_days[7]; michael@0: int32_t tmp_daysCount = sizeof(tmp_days)/sizeof(tmp_days[0]); michael@0: parseRRULE(rrule, tmp_month, tmp_dayOfWeek, tmp_nthDayOfWeek, tmp_days, tmp_daysCount, tmp_until, status); michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: // If UNTIL is newer than previous one, use the one michael@0: if (tmp_until > until) { michael@0: until = tmp_until; michael@0: } michael@0: michael@0: // Check if BYMONTH + BYMONTHDAY + BYDAY rule michael@0: if (tmp_month == -1 || tmp_dayOfWeek == 0 || tmp_daysCount == 0) { michael@0: goto unsupportedRRule; michael@0: } michael@0: // Count number of BYMONTHDAY michael@0: if (daysCount + tmp_daysCount > 7) { michael@0: // We cannot support BYMONTHDAY more than 7 michael@0: goto unsupportedRRule; michael@0: } michael@0: // Check if the same BYDAY is used. Otherwise, we cannot michael@0: // support the rule michael@0: if (tmp_dayOfWeek != dayOfWeek) { michael@0: goto unsupportedRRule; michael@0: } michael@0: // Check if the month is same or right next to the primary month michael@0: if (tmp_month != month) { michael@0: if (anotherMonth == -1) { michael@0: int32_t diff = tmp_month - month; michael@0: if (diff == -11 || diff == -1) { michael@0: // Previous month michael@0: anotherMonth = tmp_month; michael@0: earliestMonth = anotherMonth; michael@0: // Reset earliest day michael@0: earliestDay = 31; michael@0: } else if (diff == 11 || diff == 1) { michael@0: // Next month michael@0: anotherMonth = tmp_month; michael@0: } else { michael@0: // The day range cannot exceed more than 2 months michael@0: goto unsupportedRRule; michael@0: } michael@0: } else if (tmp_month != month && tmp_month != anotherMonth) { michael@0: // The day range cannot exceed more than 2 months michael@0: goto unsupportedRRule; michael@0: } michael@0: } michael@0: // If ealier month, go through days to find the earliest day michael@0: if (tmp_month == earliestMonth) { michael@0: for (j = 0; j < tmp_daysCount; j++) { michael@0: tmp_days[j] = tmp_days[j] > 0 ? tmp_days[j] : MONTHLENGTH[tmp_month] + tmp_days[j] + 1; michael@0: earliestDay = tmp_days[j] < earliestDay ? tmp_days[j] : earliestDay; michael@0: } michael@0: } michael@0: daysCount += tmp_daysCount; michael@0: } michael@0: if (daysCount != 7) { michael@0: // Number of BYMONTHDAY entries must be 7 michael@0: goto unsupportedRRule; michael@0: } michael@0: month = earliestMonth; michael@0: dayOfMonth = earliestDay; michael@0: } michael@0: michael@0: // Calculate start/end year and missing fields michael@0: int32_t startYear, startMonth, startDOM, startDOW, startDOY, startMID; michael@0: Grego::timeToFields(start + fromOffset, startYear, startMonth, startDOM, michael@0: startDOW, startDOY, startMID); michael@0: if (month == -1) { michael@0: // If BYMONTH is not set, use the month of DTSTART michael@0: month = startMonth; michael@0: } michael@0: if (dayOfWeek == 0 && nthDayOfWeek == 0 && dayOfMonth == 0) { michael@0: // If only YEARLY is set, use the day of DTSTART as BYMONTHDAY michael@0: dayOfMonth = startDOM; michael@0: } michael@0: michael@0: int32_t endYear; michael@0: if (until != MIN_MILLIS) { michael@0: int32_t endMonth, endDOM, endDOW, endDOY, endMID; michael@0: Grego::timeToFields(until, endYear, endMonth, endDOM, endDOW, endDOY, endMID); michael@0: } else { michael@0: endYear = AnnualTimeZoneRule::MAX_YEAR; michael@0: } michael@0: michael@0: // Create the AnnualDateTimeRule michael@0: if (dayOfWeek == 0 && nthDayOfWeek == 0 && dayOfMonth != 0) { michael@0: // Day in month rule, for example, 15th day in the month michael@0: adtr = new DateTimeRule(month, dayOfMonth, startMID, DateTimeRule::WALL_TIME); michael@0: } else if (dayOfWeek != 0 && nthDayOfWeek != 0 && dayOfMonth == 0) { michael@0: // Nth day of week rule, for example, last Sunday michael@0: adtr = new DateTimeRule(month, nthDayOfWeek, dayOfWeek, startMID, DateTimeRule::WALL_TIME); michael@0: } else if (dayOfWeek != 0 && nthDayOfWeek == 0 && dayOfMonth != 0) { michael@0: // First day of week after day of month rule, for example, michael@0: // first Sunday after 15th day in the month michael@0: adtr = new DateTimeRule(month, dayOfMonth, dayOfWeek, TRUE, startMID, DateTimeRule::WALL_TIME); michael@0: } michael@0: if (adtr == NULL) { michael@0: goto unsupportedRRule; michael@0: } michael@0: return new AnnualTimeZoneRule(zonename, rawOffset, dstSavings, adtr, startYear, endYear); michael@0: michael@0: unsupportedRRule: michael@0: status = U_INVALID_STATE_ERROR; michael@0: return NULL; michael@0: } michael@0: michael@0: /* michael@0: * Create a TimeZoneRule by the RDATE definition michael@0: */ michael@0: static TimeZoneRule* createRuleByRDATE(const UnicodeString& zonename, int32_t rawOffset, int32_t dstSavings, michael@0: UDate start, UVector* dates, int32_t fromOffset, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: TimeArrayTimeZoneRule *retVal = NULL; michael@0: if (dates == NULL || dates->size() == 0) { michael@0: // When no RDATE line is provided, use start (DTSTART) michael@0: // as the transition time michael@0: retVal = new TimeArrayTimeZoneRule(zonename, rawOffset, dstSavings, michael@0: &start, 1, DateTimeRule::UTC_TIME); michael@0: } else { michael@0: // Create an array of transition times michael@0: int32_t size = dates->size(); michael@0: UDate* times = (UDate*)uprv_malloc(sizeof(UDate) * size); michael@0: if (times == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return NULL; michael@0: } michael@0: for (int32_t i = 0; i < size; i++) { michael@0: UnicodeString *datestr = (UnicodeString*)dates->elementAt(i); michael@0: times[i] = parseDateTimeString(*datestr, fromOffset, status); michael@0: if (U_FAILURE(status)) { michael@0: uprv_free(times); michael@0: return NULL; michael@0: } michael@0: } michael@0: retVal = new TimeArrayTimeZoneRule(zonename, rawOffset, dstSavings, michael@0: times, size, DateTimeRule::UTC_TIME); michael@0: uprv_free(times); michael@0: } michael@0: return retVal; michael@0: } michael@0: michael@0: /* michael@0: * Check if the DOW rule specified by month, weekInMonth and dayOfWeek is equivalent michael@0: * to the DateTimerule. michael@0: */ michael@0: static UBool isEquivalentDateRule(int32_t month, int32_t weekInMonth, int32_t dayOfWeek, const DateTimeRule *dtrule) { michael@0: if (month != dtrule->getRuleMonth() || dayOfWeek != dtrule->getRuleDayOfWeek()) { michael@0: return FALSE; michael@0: } michael@0: if (dtrule->getTimeRuleType() != DateTimeRule::WALL_TIME) { michael@0: // Do not try to do more intelligent comparison for now. michael@0: return FALSE; michael@0: } michael@0: if (dtrule->getDateRuleType() == DateTimeRule::DOW michael@0: && dtrule->getRuleWeekInMonth() == weekInMonth) { michael@0: return TRUE; michael@0: } michael@0: int32_t ruleDOM = dtrule->getRuleDayOfMonth(); michael@0: if (dtrule->getDateRuleType() == DateTimeRule::DOW_GEQ_DOM) { michael@0: if (ruleDOM%7 == 1 && (ruleDOM + 6)/7 == weekInMonth) { michael@0: return TRUE; michael@0: } michael@0: if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - ruleDOM)%7 == 6 michael@0: && weekInMonth == -1*((MONTHLENGTH[month]-ruleDOM+1)/7)) { michael@0: return TRUE; michael@0: } michael@0: } michael@0: if (dtrule->getDateRuleType() == DateTimeRule::DOW_LEQ_DOM) { michael@0: if (ruleDOM%7 == 0 && ruleDOM/7 == weekInMonth) { michael@0: return TRUE; michael@0: } michael@0: if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - ruleDOM)%7 == 0 michael@0: && weekInMonth == -1*((MONTHLENGTH[month] - ruleDOM)/7 + 1)) { michael@0: return TRUE; michael@0: } michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: /* michael@0: * Convert the rule to its equivalent rule using WALL_TIME mode. michael@0: * This function returns NULL when the specified DateTimeRule is already michael@0: * using WALL_TIME mode. michael@0: */ michael@0: static DateTimeRule* toWallTimeRule(const DateTimeRule* rule, int32_t rawOffset, int32_t dstSavings) { michael@0: if (rule->getTimeRuleType() == DateTimeRule::WALL_TIME) { michael@0: return NULL; michael@0: } michael@0: int32_t wallt = rule->getRuleMillisInDay(); michael@0: if (rule->getTimeRuleType() == DateTimeRule::UTC_TIME) { michael@0: wallt += (rawOffset + dstSavings); michael@0: } else if (rule->getTimeRuleType() == DateTimeRule::STANDARD_TIME) { michael@0: wallt += dstSavings; michael@0: } michael@0: michael@0: int32_t month = -1, dom = 0, dow = 0; michael@0: DateTimeRule::DateRuleType dtype; michael@0: int32_t dshift = 0; michael@0: if (wallt < 0) { michael@0: dshift = -1; michael@0: wallt += U_MILLIS_PER_DAY; michael@0: } else if (wallt >= U_MILLIS_PER_DAY) { michael@0: dshift = 1; michael@0: wallt -= U_MILLIS_PER_DAY; michael@0: } michael@0: michael@0: month = rule->getRuleMonth(); michael@0: dom = rule->getRuleDayOfMonth(); michael@0: dow = rule->getRuleDayOfWeek(); michael@0: dtype = rule->getDateRuleType(); michael@0: michael@0: if (dshift != 0) { michael@0: if (dtype == DateTimeRule::DOW) { michael@0: // Convert to DOW_GEW_DOM or DOW_LEQ_DOM rule first michael@0: int32_t wim = rule->getRuleWeekInMonth(); michael@0: if (wim > 0) { michael@0: dtype = DateTimeRule::DOW_GEQ_DOM; michael@0: dom = 7 * (wim - 1) + 1; michael@0: } else { michael@0: dtype = DateTimeRule::DOW_LEQ_DOM; michael@0: dom = MONTHLENGTH[month] + 7 * (wim + 1); michael@0: } michael@0: } michael@0: // Shift one day before or after michael@0: dom += dshift; michael@0: if (dom == 0) { michael@0: month--; michael@0: month = month < UCAL_JANUARY ? UCAL_DECEMBER : month; michael@0: dom = MONTHLENGTH[month]; michael@0: } else if (dom > MONTHLENGTH[month]) { michael@0: month++; michael@0: month = month > UCAL_DECEMBER ? UCAL_JANUARY : month; michael@0: dom = 1; michael@0: } michael@0: if (dtype != DateTimeRule::DOM) { michael@0: // Adjust day of week michael@0: dow += dshift; michael@0: if (dow < UCAL_SUNDAY) { michael@0: dow = UCAL_SATURDAY; michael@0: } else if (dow > UCAL_SATURDAY) { michael@0: dow = UCAL_SUNDAY; michael@0: } michael@0: } michael@0: } michael@0: // Create a new rule michael@0: DateTimeRule *modifiedRule; michael@0: if (dtype == DateTimeRule::DOM) { michael@0: modifiedRule = new DateTimeRule(month, dom, wallt, DateTimeRule::WALL_TIME); michael@0: } else { michael@0: modifiedRule = new DateTimeRule(month, dom, dow, michael@0: (dtype == DateTimeRule::DOW_GEQ_DOM), wallt, DateTimeRule::WALL_TIME); michael@0: } michael@0: return modifiedRule; michael@0: } michael@0: michael@0: /* michael@0: * Minumum implementations of stream writer/reader, writing/reading michael@0: * UnicodeString. For now, we do not want to introduce the dependency michael@0: * on the ICU I/O stream in this module. But we want to keep the code michael@0: * equivalent to the ICU4J implementation, which utilizes java.io.Writer/ michael@0: * Reader. michael@0: */ michael@0: class VTZWriter { michael@0: public: michael@0: VTZWriter(UnicodeString& out); michael@0: ~VTZWriter(); michael@0: michael@0: void write(const UnicodeString& str); michael@0: void write(UChar ch); michael@0: void write(const UChar* str); michael@0: //void write(const UChar* str, int32_t length); michael@0: private: michael@0: UnicodeString* out; michael@0: }; michael@0: michael@0: VTZWriter::VTZWriter(UnicodeString& output) { michael@0: out = &output; michael@0: } michael@0: michael@0: VTZWriter::~VTZWriter() { michael@0: } michael@0: michael@0: void michael@0: VTZWriter::write(const UnicodeString& str) { michael@0: out->append(str); michael@0: } michael@0: michael@0: void michael@0: VTZWriter::write(UChar ch) { michael@0: out->append(ch); michael@0: } michael@0: michael@0: void michael@0: VTZWriter::write(const UChar* str) { michael@0: out->append(str, -1); michael@0: } michael@0: michael@0: /* michael@0: void michael@0: VTZWriter::write(const UChar* str, int32_t length) { michael@0: out->append(str, length); michael@0: } michael@0: */ michael@0: michael@0: class VTZReader { michael@0: public: michael@0: VTZReader(const UnicodeString& input); michael@0: ~VTZReader(); michael@0: michael@0: UChar read(void); michael@0: private: michael@0: const UnicodeString* in; michael@0: int32_t index; michael@0: }; michael@0: michael@0: VTZReader::VTZReader(const UnicodeString& input) { michael@0: in = &input; michael@0: index = 0; michael@0: } michael@0: michael@0: VTZReader::~VTZReader() { michael@0: } michael@0: michael@0: UChar michael@0: VTZReader::read(void) { michael@0: UChar ch = 0xFFFF; michael@0: if (index < in->length()) { michael@0: ch = in->charAt(index); michael@0: } michael@0: index++; michael@0: return ch; michael@0: } michael@0: michael@0: michael@0: UOBJECT_DEFINE_RTTI_IMPLEMENTATION(VTimeZone) michael@0: michael@0: VTimeZone::VTimeZone() michael@0: : BasicTimeZone(), tz(NULL), vtzlines(NULL), michael@0: lastmod(MAX_MILLIS) { michael@0: } michael@0: michael@0: VTimeZone::VTimeZone(const VTimeZone& source) michael@0: : BasicTimeZone(source), tz(NULL), vtzlines(NULL), michael@0: tzurl(source.tzurl), lastmod(source.lastmod), michael@0: olsonzid(source.olsonzid), icutzver(source.icutzver) { michael@0: if (source.tz != NULL) { michael@0: tz = (BasicTimeZone*)source.tz->clone(); michael@0: } michael@0: if (source.vtzlines != NULL) { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: int32_t size = source.vtzlines->size(); michael@0: vtzlines = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, size, status); michael@0: if (U_SUCCESS(status)) { michael@0: for (int32_t i = 0; i < size; i++) { michael@0: UnicodeString *line = (UnicodeString*)source.vtzlines->elementAt(i); michael@0: vtzlines->addElement(line->clone(), status); michael@0: if (U_FAILURE(status)) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: if (U_FAILURE(status) && vtzlines != NULL) { michael@0: delete vtzlines; michael@0: } michael@0: } michael@0: } michael@0: michael@0: VTimeZone::~VTimeZone() { michael@0: if (tz != NULL) { michael@0: delete tz; michael@0: } michael@0: if (vtzlines != NULL) { michael@0: delete vtzlines; michael@0: } michael@0: } michael@0: michael@0: VTimeZone& michael@0: VTimeZone::operator=(const VTimeZone& right) { michael@0: if (this == &right) { michael@0: return *this; michael@0: } michael@0: if (*this != right) { michael@0: BasicTimeZone::operator=(right); michael@0: if (tz != NULL) { michael@0: delete tz; michael@0: tz = NULL; michael@0: } michael@0: if (right.tz != NULL) { michael@0: tz = (BasicTimeZone*)right.tz->clone(); michael@0: } michael@0: if (vtzlines != NULL) { michael@0: delete vtzlines; michael@0: } michael@0: if (right.vtzlines != NULL) { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: int32_t size = right.vtzlines->size(); michael@0: vtzlines = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, size, status); michael@0: if (U_SUCCESS(status)) { michael@0: for (int32_t i = 0; i < size; i++) { michael@0: UnicodeString *line = (UnicodeString*)right.vtzlines->elementAt(i); michael@0: vtzlines->addElement(line->clone(), status); michael@0: if (U_FAILURE(status)) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: if (U_FAILURE(status) && vtzlines != NULL) { michael@0: delete vtzlines; michael@0: vtzlines = NULL; michael@0: } michael@0: } michael@0: tzurl = right.tzurl; michael@0: lastmod = right.lastmod; michael@0: olsonzid = right.olsonzid; michael@0: icutzver = right.icutzver; michael@0: } michael@0: return *this; michael@0: } michael@0: michael@0: UBool michael@0: VTimeZone::operator==(const TimeZone& that) const { michael@0: if (this == &that) { michael@0: return TRUE; michael@0: } michael@0: if (typeid(*this) != typeid(that) || !BasicTimeZone::operator==(that)) { michael@0: return FALSE; michael@0: } michael@0: VTimeZone *vtz = (VTimeZone*)&that; michael@0: if (*tz == *(vtz->tz) michael@0: && tzurl == vtz->tzurl michael@0: && lastmod == vtz->lastmod michael@0: /* && olsonzid = that.olsonzid */ michael@0: /* && icutzver = that.icutzver */) { michael@0: return TRUE; michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: UBool michael@0: VTimeZone::operator!=(const TimeZone& that) const { michael@0: return !operator==(that); michael@0: } michael@0: michael@0: VTimeZone* michael@0: VTimeZone::createVTimeZoneByID(const UnicodeString& ID) { michael@0: VTimeZone *vtz = new VTimeZone(); michael@0: vtz->tz = (BasicTimeZone*)TimeZone::createTimeZone(ID); michael@0: vtz->tz->getID(vtz->olsonzid); michael@0: michael@0: // Set ICU tzdata version michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: UResourceBundle *bundle = NULL; michael@0: const UChar* versionStr = NULL; michael@0: int32_t len = 0; michael@0: bundle = ures_openDirect(NULL, "zoneinfo64", &status); michael@0: versionStr = ures_getStringByKey(bundle, "TZVersion", &len, &status); michael@0: if (U_SUCCESS(status)) { michael@0: vtz->icutzver.setTo(versionStr, len); michael@0: } michael@0: ures_close(bundle); michael@0: return vtz; michael@0: } michael@0: michael@0: VTimeZone* michael@0: VTimeZone::createVTimeZoneFromBasicTimeZone(const BasicTimeZone& basic_time_zone, UErrorCode &status) { michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: VTimeZone *vtz = new VTimeZone(); michael@0: if (vtz == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return NULL; michael@0: } michael@0: vtz->tz = (BasicTimeZone *)basic_time_zone.clone(); michael@0: if (vtz->tz == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: delete vtz; michael@0: return NULL; michael@0: } michael@0: vtz->tz->getID(vtz->olsonzid); michael@0: michael@0: // Set ICU tzdata version michael@0: UResourceBundle *bundle = NULL; michael@0: const UChar* versionStr = NULL; michael@0: int32_t len = 0; michael@0: bundle = ures_openDirect(NULL, "zoneinfo64", &status); michael@0: versionStr = ures_getStringByKey(bundle, "TZVersion", &len, &status); michael@0: if (U_SUCCESS(status)) { michael@0: vtz->icutzver.setTo(versionStr, len); michael@0: } michael@0: ures_close(bundle); michael@0: return vtz; michael@0: } michael@0: michael@0: VTimeZone* michael@0: VTimeZone::createVTimeZone(const UnicodeString& vtzdata, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: VTZReader reader(vtzdata); michael@0: VTimeZone *vtz = new VTimeZone(); michael@0: vtz->load(reader, status); michael@0: if (U_FAILURE(status)) { michael@0: delete vtz; michael@0: return NULL; michael@0: } michael@0: return vtz; michael@0: } michael@0: michael@0: UBool michael@0: VTimeZone::getTZURL(UnicodeString& url) const { michael@0: if (tzurl.length() > 0) { michael@0: url = tzurl; michael@0: return TRUE; michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: void michael@0: VTimeZone::setTZURL(const UnicodeString& url) { michael@0: tzurl = url; michael@0: } michael@0: michael@0: UBool michael@0: VTimeZone::getLastModified(UDate& lastModified) const { michael@0: if (lastmod != MAX_MILLIS) { michael@0: lastModified = lastmod; michael@0: return TRUE; michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: void michael@0: VTimeZone::setLastModified(UDate lastModified) { michael@0: lastmod = lastModified; michael@0: } michael@0: michael@0: void michael@0: VTimeZone::write(UnicodeString& result, UErrorCode& status) const { michael@0: result.remove(); michael@0: VTZWriter writer(result); michael@0: write(writer, status); michael@0: } michael@0: michael@0: void michael@0: VTimeZone::write(UDate start, UnicodeString& result, UErrorCode& status) const { michael@0: result.remove(); michael@0: VTZWriter writer(result); michael@0: write(start, writer, status); michael@0: } michael@0: michael@0: void michael@0: VTimeZone::writeSimple(UDate time, UnicodeString& result, UErrorCode& status) const { michael@0: result.remove(); michael@0: VTZWriter writer(result); michael@0: writeSimple(time, writer, status); michael@0: } michael@0: michael@0: TimeZone* michael@0: VTimeZone::clone(void) const { michael@0: return new VTimeZone(*this); michael@0: } michael@0: michael@0: int32_t michael@0: VTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day, michael@0: uint8_t dayOfWeek, int32_t millis, UErrorCode& status) const { michael@0: return tz->getOffset(era, year, month, day, dayOfWeek, millis, status); michael@0: } michael@0: michael@0: int32_t michael@0: VTimeZone::getOffset(uint8_t era, int32_t year, int32_t month, int32_t day, michael@0: uint8_t dayOfWeek, int32_t millis, michael@0: int32_t monthLength, UErrorCode& status) const { michael@0: return tz->getOffset(era, year, month, day, dayOfWeek, millis, monthLength, status); michael@0: } michael@0: michael@0: void michael@0: VTimeZone::getOffset(UDate date, UBool local, int32_t& rawOffset, michael@0: int32_t& dstOffset, UErrorCode& status) const { michael@0: return tz->getOffset(date, local, rawOffset, dstOffset, status); michael@0: } michael@0: michael@0: void michael@0: VTimeZone::setRawOffset(int32_t offsetMillis) { michael@0: tz->setRawOffset(offsetMillis); michael@0: } michael@0: michael@0: int32_t michael@0: VTimeZone::getRawOffset(void) const { michael@0: return tz->getRawOffset(); michael@0: } michael@0: michael@0: UBool michael@0: VTimeZone::useDaylightTime(void) const { michael@0: return tz->useDaylightTime(); michael@0: } michael@0: michael@0: UBool michael@0: VTimeZone::inDaylightTime(UDate date, UErrorCode& status) const { michael@0: return tz->inDaylightTime(date, status); michael@0: } michael@0: michael@0: UBool michael@0: VTimeZone::hasSameRules(const TimeZone& other) const { michael@0: return tz->hasSameRules(other); michael@0: } michael@0: michael@0: UBool michael@0: VTimeZone::getNextTransition(UDate base, UBool inclusive, TimeZoneTransition& result) const { michael@0: return tz->getNextTransition(base, inclusive, result); michael@0: } michael@0: michael@0: UBool michael@0: VTimeZone::getPreviousTransition(UDate base, UBool inclusive, TimeZoneTransition& result) const { michael@0: return tz->getPreviousTransition(base, inclusive, result); michael@0: } michael@0: michael@0: int32_t michael@0: VTimeZone::countTransitionRules(UErrorCode& status) const { michael@0: return tz->countTransitionRules(status); michael@0: } michael@0: michael@0: void michael@0: VTimeZone::getTimeZoneRules(const InitialTimeZoneRule*& initial, michael@0: const TimeZoneRule* trsrules[], int32_t& trscount, michael@0: UErrorCode& status) const { michael@0: tz->getTimeZoneRules(initial, trsrules, trscount, status); michael@0: } michael@0: michael@0: void michael@0: VTimeZone::load(VTZReader& reader, UErrorCode& status) { michael@0: vtzlines = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, DEFAULT_VTIMEZONE_LINES, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: UBool eol = FALSE; michael@0: UBool start = FALSE; michael@0: UBool success = FALSE; michael@0: UnicodeString line; michael@0: michael@0: while (TRUE) { michael@0: UChar ch = reader.read(); michael@0: if (ch == 0xFFFF) { michael@0: // end of file michael@0: if (start && line.startsWith(ICAL_END_VTIMEZONE, -1)) { michael@0: vtzlines->addElement(new UnicodeString(line), status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupVtzlines; michael@0: } michael@0: success = TRUE; michael@0: } michael@0: break; michael@0: } michael@0: if (ch == 0x000D) { michael@0: // CR, must be followed by LF according to the definition in RFC2445 michael@0: continue; michael@0: } michael@0: if (eol) { michael@0: if (ch != 0x0009 && ch != 0x0020) { michael@0: // NOT followed by TAB/SP -> new line michael@0: if (start) { michael@0: if (line.length() > 0) { michael@0: vtzlines->addElement(new UnicodeString(line), status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupVtzlines; michael@0: } michael@0: } michael@0: } michael@0: line.remove(); michael@0: if (ch != 0x000A) { michael@0: line.append(ch); michael@0: } michael@0: } michael@0: eol = FALSE; michael@0: } else { michael@0: if (ch == 0x000A) { michael@0: // LF michael@0: eol = TRUE; michael@0: if (start) { michael@0: if (line.startsWith(ICAL_END_VTIMEZONE, -1)) { michael@0: vtzlines->addElement(new UnicodeString(line), status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupVtzlines; michael@0: } michael@0: success = TRUE; michael@0: break; michael@0: } michael@0: } else { michael@0: if (line.startsWith(ICAL_BEGIN_VTIMEZONE, -1)) { michael@0: vtzlines->addElement(new UnicodeString(line), status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupVtzlines; michael@0: } michael@0: line.remove(); michael@0: start = TRUE; michael@0: eol = FALSE; michael@0: } michael@0: } michael@0: } else { michael@0: line.append(ch); michael@0: } michael@0: } michael@0: } michael@0: if (!success) { michael@0: if (U_SUCCESS(status)) { michael@0: status = U_INVALID_STATE_ERROR; michael@0: } michael@0: goto cleanupVtzlines; michael@0: } michael@0: parse(status); michael@0: return; michael@0: michael@0: cleanupVtzlines: michael@0: delete vtzlines; michael@0: vtzlines = NULL; michael@0: } michael@0: michael@0: // parser state michael@0: #define INI 0 // Initial state michael@0: #define VTZ 1 // In VTIMEZONE michael@0: #define TZI 2 // In STANDARD or DAYLIGHT michael@0: michael@0: #define DEF_DSTSAVINGS (60*60*1000) michael@0: #define DEF_TZSTARTTIME (0.0) michael@0: michael@0: void michael@0: VTimeZone::parse(UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: if (vtzlines == NULL || vtzlines->size() == 0) { michael@0: status = U_INVALID_STATE_ERROR; michael@0: return; michael@0: } michael@0: InitialTimeZoneRule *initialRule = NULL; michael@0: RuleBasedTimeZone *rbtz = NULL; michael@0: michael@0: // timezone ID michael@0: UnicodeString tzid; michael@0: michael@0: int32_t state = INI; michael@0: int32_t n = 0; michael@0: UBool dst = FALSE; // current zone type michael@0: UnicodeString from; // current zone from offset michael@0: UnicodeString to; // current zone offset michael@0: UnicodeString zonename; // current zone name michael@0: UnicodeString dtstart; // current zone starts michael@0: UBool isRRULE = FALSE; // true if the rule is described by RRULE michael@0: int32_t initialRawOffset = 0; // initial offset michael@0: int32_t initialDSTSavings = 0; // initial offset michael@0: UDate firstStart = MAX_MILLIS; // the earliest rule start time michael@0: UnicodeString name; // RFC2445 prop name michael@0: UnicodeString value; // RFC2445 prop value michael@0: michael@0: UVector *dates = NULL; // list of RDATE or RRULE strings michael@0: UVector *rules = NULL; // list of TimeZoneRule instances michael@0: michael@0: int32_t finalRuleIdx = -1; michael@0: int32_t finalRuleCount = 0; michael@0: michael@0: rules = new UVector(status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupParse; michael@0: } michael@0: // Set the deleter to remove TimeZoneRule vectors to avoid memory leaks due to unowned TimeZoneRules. michael@0: rules->setDeleter(deleteTimeZoneRule); michael@0: michael@0: dates = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupParse; michael@0: } michael@0: if (rules == NULL || dates == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: goto cleanupParse; michael@0: } michael@0: michael@0: for (n = 0; n < vtzlines->size(); n++) { michael@0: UnicodeString *line = (UnicodeString*)vtzlines->elementAt(n); michael@0: int32_t valueSep = line->indexOf(COLON); michael@0: if (valueSep < 0) { michael@0: continue; michael@0: } michael@0: name.setTo(*line, 0, valueSep); michael@0: value.setTo(*line, valueSep + 1); michael@0: michael@0: switch (state) { michael@0: case INI: michael@0: if (name.compare(ICAL_BEGIN, -1) == 0 michael@0: && value.compare(ICAL_VTIMEZONE, -1) == 0) { michael@0: state = VTZ; michael@0: } michael@0: break; michael@0: michael@0: case VTZ: michael@0: if (name.compare(ICAL_TZID, -1) == 0) { michael@0: tzid = value; michael@0: } else if (name.compare(ICAL_TZURL, -1) == 0) { michael@0: tzurl = value; michael@0: } else if (name.compare(ICAL_LASTMOD, -1) == 0) { michael@0: // Always in 'Z' format, so the offset argument for the parse method michael@0: // can be any value. michael@0: lastmod = parseDateTimeString(value, 0, status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupParse; michael@0: } michael@0: } else if (name.compare(ICAL_BEGIN, -1) == 0) { michael@0: UBool isDST = (value.compare(ICAL_DAYLIGHT, -1) == 0); michael@0: if (value.compare(ICAL_STANDARD, -1) == 0 || isDST) { michael@0: // tzid must be ready at this point michael@0: if (tzid.length() == 0) { michael@0: goto cleanupParse; michael@0: } michael@0: // initialize current zone properties michael@0: if (dates->size() != 0) { michael@0: dates->removeAllElements(); michael@0: } michael@0: isRRULE = FALSE; michael@0: from.remove(); michael@0: to.remove(); michael@0: zonename.remove(); michael@0: dst = isDST; michael@0: state = TZI; michael@0: } else { michael@0: // BEGIN property other than STANDARD/DAYLIGHT michael@0: // must not be there. michael@0: goto cleanupParse; michael@0: } michael@0: } else if (name.compare(ICAL_END, -1) == 0) { michael@0: break; michael@0: } michael@0: break; michael@0: case TZI: michael@0: if (name.compare(ICAL_DTSTART, -1) == 0) { michael@0: dtstart = value; michael@0: } else if (name.compare(ICAL_TZNAME, -1) == 0) { michael@0: zonename = value; michael@0: } else if (name.compare(ICAL_TZOFFSETFROM, -1) == 0) { michael@0: from = value; michael@0: } else if (name.compare(ICAL_TZOFFSETTO, -1) == 0) { michael@0: to = value; michael@0: } else if (name.compare(ICAL_RDATE, -1) == 0) { michael@0: // RDATE mixed with RRULE is not supported michael@0: if (isRRULE) { michael@0: goto cleanupParse; michael@0: } michael@0: // RDATE value may contain multiple date delimited michael@0: // by comma michael@0: UBool nextDate = TRUE; michael@0: int32_t dstart = 0; michael@0: UnicodeString *dstr; michael@0: while (nextDate) { michael@0: int32_t dend = value.indexOf(COMMA, dstart); michael@0: if (dend == -1) { michael@0: dstr = new UnicodeString(value, dstart); michael@0: nextDate = FALSE; michael@0: } else { michael@0: dstr = new UnicodeString(value, dstart, dend - dstart); michael@0: } michael@0: dates->addElement(dstr, status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupParse; michael@0: } michael@0: dstart = dend + 1; michael@0: } michael@0: } else if (name.compare(ICAL_RRULE, -1) == 0) { michael@0: // RRULE mixed with RDATE is not supported michael@0: if (!isRRULE && dates->size() != 0) { michael@0: goto cleanupParse; michael@0: } michael@0: isRRULE = true; michael@0: dates->addElement(new UnicodeString(value), status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupParse; michael@0: } michael@0: } else if (name.compare(ICAL_END, -1) == 0) { michael@0: // Mandatory properties michael@0: if (dtstart.length() == 0 || from.length() == 0 || to.length() == 0) { michael@0: goto cleanupParse; michael@0: } michael@0: // if zonename is not available, create one from tzid michael@0: if (zonename.length() == 0) { michael@0: getDefaultTZName(tzid, dst, zonename); michael@0: } michael@0: michael@0: // create a time zone rule michael@0: TimeZoneRule *rule = NULL; michael@0: int32_t fromOffset = 0; michael@0: int32_t toOffset = 0; michael@0: int32_t rawOffset = 0; michael@0: int32_t dstSavings = 0; michael@0: UDate start = 0; michael@0: michael@0: // Parse TZOFFSETFROM/TZOFFSETTO michael@0: fromOffset = offsetStrToMillis(from, status); michael@0: toOffset = offsetStrToMillis(to, status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupParse; michael@0: } michael@0: michael@0: if (dst) { michael@0: // If daylight, use the previous offset as rawoffset if positive michael@0: if (toOffset - fromOffset > 0) { michael@0: rawOffset = fromOffset; michael@0: dstSavings = toOffset - fromOffset; michael@0: } else { michael@0: // This is rare case.. just use 1 hour DST savings michael@0: rawOffset = toOffset - DEF_DSTSAVINGS; michael@0: dstSavings = DEF_DSTSAVINGS; michael@0: } michael@0: } else { michael@0: rawOffset = toOffset; michael@0: dstSavings = 0; michael@0: } michael@0: michael@0: // start time michael@0: start = parseDateTimeString(dtstart, fromOffset, status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupParse; michael@0: } michael@0: michael@0: // Create the rule michael@0: UDate actualStart = MAX_MILLIS; michael@0: if (isRRULE) { michael@0: rule = createRuleByRRULE(zonename, rawOffset, dstSavings, start, dates, fromOffset, status); michael@0: } else { michael@0: rule = createRuleByRDATE(zonename, rawOffset, dstSavings, start, dates, fromOffset, status); michael@0: } michael@0: if (U_FAILURE(status) || rule == NULL) { michael@0: goto cleanupParse; michael@0: } else { michael@0: UBool startAvail = rule->getFirstStart(fromOffset, 0, actualStart); michael@0: if (startAvail && actualStart < firstStart) { michael@0: // save from offset information for the earliest rule michael@0: firstStart = actualStart; michael@0: // If this is STD, assume the time before this transtion michael@0: // is DST when the difference is 1 hour. This might not be michael@0: // accurate, but VTIMEZONE data does not have such info. michael@0: if (dstSavings > 0) { michael@0: initialRawOffset = fromOffset; michael@0: initialDSTSavings = 0; michael@0: } else { michael@0: if (fromOffset - toOffset == DEF_DSTSAVINGS) { michael@0: initialRawOffset = fromOffset - DEF_DSTSAVINGS; michael@0: initialDSTSavings = DEF_DSTSAVINGS; michael@0: } else { michael@0: initialRawOffset = fromOffset; michael@0: initialDSTSavings = 0; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: rules->addElement(rule, status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupParse; michael@0: } michael@0: state = VTZ; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: // Must have at least one rule michael@0: if (rules->size() == 0) { michael@0: goto cleanupParse; michael@0: } michael@0: michael@0: // Create a initial rule michael@0: getDefaultTZName(tzid, FALSE, zonename); michael@0: initialRule = new InitialTimeZoneRule(zonename, michael@0: initialRawOffset, initialDSTSavings); michael@0: if (initialRule == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: goto cleanupParse; michael@0: } michael@0: michael@0: // Finally, create the RuleBasedTimeZone michael@0: rbtz = new RuleBasedTimeZone(tzid, initialRule); michael@0: if (rbtz == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: goto cleanupParse; michael@0: } michael@0: initialRule = NULL; // already adopted by RBTZ, no need to delete michael@0: michael@0: for (n = 0; n < rules->size(); n++) { michael@0: TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n); michael@0: AnnualTimeZoneRule *atzrule = dynamic_cast(r); michael@0: if (atzrule != NULL) { michael@0: if (atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR) { michael@0: finalRuleCount++; michael@0: finalRuleIdx = n; michael@0: } michael@0: } michael@0: } michael@0: if (finalRuleCount > 2) { michael@0: // Too many final rules michael@0: status = U_ILLEGAL_ARGUMENT_ERROR; michael@0: goto cleanupParse; michael@0: } michael@0: michael@0: if (finalRuleCount == 1) { michael@0: if (rules->size() == 1) { michael@0: // Only one final rule, only governs the initial rule, michael@0: // which is already initialized, thus, we do not need to michael@0: // add this transition rule michael@0: rules->removeAllElements(); michael@0: } else { michael@0: // Normalize the final rule michael@0: AnnualTimeZoneRule *finalRule = (AnnualTimeZoneRule*)rules->elementAt(finalRuleIdx); michael@0: int32_t tmpRaw = finalRule->getRawOffset(); michael@0: int32_t tmpDST = finalRule->getDSTSavings(); michael@0: michael@0: // Find the last non-final rule michael@0: UDate finalStart, start; michael@0: finalRule->getFirstStart(initialRawOffset, initialDSTSavings, finalStart); michael@0: start = finalStart; michael@0: for (n = 0; n < rules->size(); n++) { michael@0: if (finalRuleIdx == n) { michael@0: continue; michael@0: } michael@0: TimeZoneRule *r = (TimeZoneRule*)rules->elementAt(n); michael@0: UDate lastStart; michael@0: r->getFinalStart(tmpRaw, tmpDST, lastStart); michael@0: if (lastStart > start) { michael@0: finalRule->getNextStart(lastStart, michael@0: r->getRawOffset(), michael@0: r->getDSTSavings(), michael@0: FALSE, michael@0: start); michael@0: } michael@0: } michael@0: michael@0: TimeZoneRule *newRule; michael@0: UnicodeString tznam; michael@0: if (start == finalStart) { michael@0: // Transform this into a single transition michael@0: newRule = new TimeArrayTimeZoneRule( michael@0: finalRule->getName(tznam), michael@0: finalRule->getRawOffset(), michael@0: finalRule->getDSTSavings(), michael@0: &finalStart, michael@0: 1, michael@0: DateTimeRule::UTC_TIME); michael@0: } else { michael@0: // Update the end year michael@0: int32_t y, m, d, dow, doy, mid; michael@0: Grego::timeToFields(start, y, m, d, dow, doy, mid); michael@0: newRule = new AnnualTimeZoneRule( michael@0: finalRule->getName(tznam), michael@0: finalRule->getRawOffset(), michael@0: finalRule->getDSTSavings(), michael@0: *(finalRule->getRule()), michael@0: finalRule->getStartYear(), michael@0: y); michael@0: } michael@0: if (newRule == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: goto cleanupParse; michael@0: } michael@0: rules->removeElementAt(finalRuleIdx); michael@0: rules->addElement(newRule, status); michael@0: if (U_FAILURE(status)) { michael@0: delete newRule; michael@0: goto cleanupParse; michael@0: } michael@0: } michael@0: } michael@0: michael@0: while (!rules->isEmpty()) { michael@0: TimeZoneRule *tzr = (TimeZoneRule*)rules->orphanElementAt(0); michael@0: rbtz->addTransitionRule(tzr, status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupParse; michael@0: } michael@0: } michael@0: rbtz->complete(status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupParse; michael@0: } michael@0: delete rules; michael@0: delete dates; michael@0: michael@0: tz = rbtz; michael@0: setID(tzid); michael@0: return; michael@0: michael@0: cleanupParse: michael@0: if (rules != NULL) { michael@0: while (!rules->isEmpty()) { michael@0: TimeZoneRule *r = (TimeZoneRule*)rules->orphanElementAt(0); michael@0: delete r; michael@0: } michael@0: delete rules; michael@0: } michael@0: if (dates != NULL) { michael@0: delete dates; michael@0: } michael@0: if (initialRule != NULL) { michael@0: delete initialRule; michael@0: } michael@0: if (rbtz != NULL) { michael@0: delete rbtz; michael@0: } michael@0: return; michael@0: } michael@0: michael@0: void michael@0: VTimeZone::write(VTZWriter& writer, UErrorCode& status) const { michael@0: if (vtzlines != NULL) { michael@0: for (int32_t i = 0; i < vtzlines->size(); i++) { michael@0: UnicodeString *line = (UnicodeString*)vtzlines->elementAt(i); michael@0: if (line->startsWith(ICAL_TZURL, -1) michael@0: && line->charAt(u_strlen(ICAL_TZURL)) == COLON) { michael@0: writer.write(ICAL_TZURL); michael@0: writer.write(COLON); michael@0: writer.write(tzurl); michael@0: writer.write(ICAL_NEWLINE); michael@0: } else if (line->startsWith(ICAL_LASTMOD, -1) michael@0: && line->charAt(u_strlen(ICAL_LASTMOD)) == COLON) { michael@0: UnicodeString utcString; michael@0: writer.write(ICAL_LASTMOD); michael@0: writer.write(COLON); michael@0: writer.write(getUTCDateTimeString(lastmod, utcString)); michael@0: writer.write(ICAL_NEWLINE); michael@0: } else { michael@0: writer.write(*line); michael@0: writer.write(ICAL_NEWLINE); michael@0: } michael@0: } michael@0: } else { michael@0: UVector *customProps = NULL; michael@0: if (olsonzid.length() > 0 && icutzver.length() > 0) { michael@0: customProps = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP); michael@0: icutzprop->append(olsonzid); michael@0: icutzprop->append((UChar)0x005B/*'['*/); michael@0: icutzprop->append(icutzver); michael@0: icutzprop->append((UChar)0x005D/*']'*/); michael@0: customProps->addElement(icutzprop, status); michael@0: if (U_FAILURE(status)) { michael@0: delete icutzprop; michael@0: delete customProps; michael@0: return; michael@0: } michael@0: } michael@0: writeZone(writer, *tz, customProps, status); michael@0: delete customProps; michael@0: } michael@0: } michael@0: michael@0: void michael@0: VTimeZone::write(UDate start, VTZWriter& writer, UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: InitialTimeZoneRule *initial = NULL; michael@0: UVector *transitionRules = NULL; michael@0: UVector customProps(uprv_deleteUObject, uhash_compareUnicodeString, status); michael@0: UnicodeString tzid; michael@0: michael@0: // Extract rules applicable to dates after the start time michael@0: getTimeZoneRulesAfter(start, initial, transitionRules, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: michael@0: // Create a RuleBasedTimeZone with the subset rule michael@0: getID(tzid); michael@0: RuleBasedTimeZone rbtz(tzid, initial); michael@0: if (transitionRules != NULL) { michael@0: while (!transitionRules->isEmpty()) { michael@0: TimeZoneRule *tr = (TimeZoneRule*)transitionRules->orphanElementAt(0); michael@0: rbtz.addTransitionRule(tr, status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupWritePartial; michael@0: } michael@0: } michael@0: delete transitionRules; michael@0: transitionRules = NULL; michael@0: } michael@0: rbtz.complete(status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupWritePartial; michael@0: } michael@0: michael@0: if (olsonzid.length() > 0 && icutzver.length() > 0) { michael@0: UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP); michael@0: icutzprop->append(olsonzid); michael@0: icutzprop->append((UChar)0x005B/*'['*/); michael@0: icutzprop->append(icutzver); michael@0: icutzprop->append(ICU_TZINFO_PARTIAL, -1); michael@0: appendMillis(start, *icutzprop); michael@0: icutzprop->append((UChar)0x005D/*']'*/); michael@0: customProps.addElement(icutzprop, status); michael@0: if (U_FAILURE(status)) { michael@0: delete icutzprop; michael@0: goto cleanupWritePartial; michael@0: } michael@0: } michael@0: writeZone(writer, rbtz, &customProps, status); michael@0: return; michael@0: michael@0: cleanupWritePartial: michael@0: if (initial != NULL) { michael@0: delete initial; michael@0: } michael@0: if (transitionRules != NULL) { michael@0: while (!transitionRules->isEmpty()) { michael@0: TimeZoneRule *tr = (TimeZoneRule*)transitionRules->orphanElementAt(0); michael@0: delete tr; michael@0: } michael@0: delete transitionRules; michael@0: } michael@0: } michael@0: michael@0: void michael@0: VTimeZone::writeSimple(UDate time, VTZWriter& writer, UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: michael@0: UVector customProps(uprv_deleteUObject, uhash_compareUnicodeString, status); michael@0: UnicodeString tzid; michael@0: michael@0: // Extract simple rules michael@0: InitialTimeZoneRule *initial = NULL; michael@0: AnnualTimeZoneRule *std = NULL, *dst = NULL; michael@0: getSimpleRulesNear(time, initial, std, dst, status); michael@0: if (U_SUCCESS(status)) { michael@0: // Create a RuleBasedTimeZone with the subset rule michael@0: getID(tzid); michael@0: RuleBasedTimeZone rbtz(tzid, initial); michael@0: if (std != NULL && dst != NULL) { michael@0: rbtz.addTransitionRule(std, status); michael@0: rbtz.addTransitionRule(dst, status); michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupWriteSimple; michael@0: } michael@0: michael@0: if (olsonzid.length() > 0 && icutzver.length() > 0) { michael@0: UnicodeString *icutzprop = new UnicodeString(ICU_TZINFO_PROP); michael@0: icutzprop->append(olsonzid); michael@0: icutzprop->append((UChar)0x005B/*'['*/); michael@0: icutzprop->append(icutzver); michael@0: icutzprop->append(ICU_TZINFO_SIMPLE, -1); michael@0: appendMillis(time, *icutzprop); michael@0: icutzprop->append((UChar)0x005D/*']'*/); michael@0: customProps.addElement(icutzprop, status); michael@0: if (U_FAILURE(status)) { michael@0: delete icutzprop; michael@0: goto cleanupWriteSimple; michael@0: } michael@0: } michael@0: writeZone(writer, rbtz, &customProps, status); michael@0: } michael@0: return; michael@0: michael@0: cleanupWriteSimple: michael@0: if (initial != NULL) { michael@0: delete initial; michael@0: } michael@0: if (std != NULL) { michael@0: delete std; michael@0: } michael@0: if (dst != NULL) { michael@0: delete dst; michael@0: } michael@0: } michael@0: michael@0: void michael@0: VTimeZone::writeZone(VTZWriter& w, BasicTimeZone& basictz, michael@0: UVector* customProps, UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: writeHeaders(w, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: michael@0: if (customProps != NULL) { michael@0: for (int32_t i = 0; i < customProps->size(); i++) { michael@0: UnicodeString *custprop = (UnicodeString*)customProps->elementAt(i); michael@0: w.write(*custprop); michael@0: w.write(ICAL_NEWLINE); michael@0: } michael@0: } michael@0: michael@0: UDate t = MIN_MILLIS; michael@0: UnicodeString dstName; michael@0: int32_t dstFromOffset = 0; michael@0: int32_t dstFromDSTSavings = 0; michael@0: int32_t dstToOffset = 0; michael@0: int32_t dstStartYear = 0; michael@0: int32_t dstMonth = 0; michael@0: int32_t dstDayOfWeek = 0; michael@0: int32_t dstWeekInMonth = 0; michael@0: int32_t dstMillisInDay = 0; michael@0: UDate dstStartTime = 0.0; michael@0: UDate dstUntilTime = 0.0; michael@0: int32_t dstCount = 0; michael@0: AnnualTimeZoneRule *finalDstRule = NULL; michael@0: michael@0: UnicodeString stdName; michael@0: int32_t stdFromOffset = 0; michael@0: int32_t stdFromDSTSavings = 0; michael@0: int32_t stdToOffset = 0; michael@0: int32_t stdStartYear = 0; michael@0: int32_t stdMonth = 0; michael@0: int32_t stdDayOfWeek = 0; michael@0: int32_t stdWeekInMonth = 0; michael@0: int32_t stdMillisInDay = 0; michael@0: UDate stdStartTime = 0.0; michael@0: UDate stdUntilTime = 0.0; michael@0: int32_t stdCount = 0; michael@0: AnnualTimeZoneRule *finalStdRule = NULL; michael@0: michael@0: int32_t year, month, dom, dow, doy, mid; michael@0: UBool hasTransitions = FALSE; michael@0: TimeZoneTransition tzt; michael@0: UBool tztAvail; michael@0: UnicodeString name; michael@0: UBool isDst; michael@0: michael@0: // Going through all transitions michael@0: while (TRUE) { michael@0: tztAvail = basictz.getNextTransition(t, FALSE, tzt); michael@0: if (!tztAvail) { michael@0: break; michael@0: } michael@0: hasTransitions = TRUE; michael@0: t = tzt.getTime(); michael@0: tzt.getTo()->getName(name); michael@0: isDst = (tzt.getTo()->getDSTSavings() != 0); michael@0: int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings(); michael@0: int32_t fromDSTSavings = tzt.getFrom()->getDSTSavings(); michael@0: int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings(); michael@0: Grego::timeToFields(tzt.getTime() + fromOffset, year, month, dom, dow, doy, mid); michael@0: int32_t weekInMonth = Grego::dayOfWeekInMonth(year, month, dom); michael@0: UBool sameRule = FALSE; michael@0: const AnnualTimeZoneRule *atzrule; michael@0: if (isDst) { michael@0: if (finalDstRule == NULL michael@0: && (atzrule = dynamic_cast(tzt.getTo())) != NULL michael@0: && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR michael@0: ) { michael@0: finalDstRule = (AnnualTimeZoneRule*)tzt.getTo()->clone(); michael@0: } michael@0: if (dstCount > 0) { michael@0: if (year == dstStartYear + dstCount michael@0: && name.compare(dstName) == 0 michael@0: && dstFromOffset == fromOffset michael@0: && dstToOffset == toOffset michael@0: && dstMonth == month michael@0: && dstDayOfWeek == dow michael@0: && dstWeekInMonth == weekInMonth michael@0: && dstMillisInDay == mid) { michael@0: // Update until time michael@0: dstUntilTime = t; michael@0: dstCount++; michael@0: sameRule = TRUE; michael@0: } michael@0: if (!sameRule) { michael@0: if (dstCount == 1) { michael@0: writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime, michael@0: TRUE, status); michael@0: } else { michael@0: writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset, michael@0: dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status); michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupWriteZone; michael@0: } michael@0: } michael@0: } michael@0: if (!sameRule) { michael@0: // Reset this DST information michael@0: dstName = name; michael@0: dstFromOffset = fromOffset; michael@0: dstFromDSTSavings = fromDSTSavings; michael@0: dstToOffset = toOffset; michael@0: dstStartYear = year; michael@0: dstMonth = month; michael@0: dstDayOfWeek = dow; michael@0: dstWeekInMonth = weekInMonth; michael@0: dstMillisInDay = mid; michael@0: dstStartTime = dstUntilTime = t; michael@0: dstCount = 1; michael@0: } michael@0: if (finalStdRule != NULL && finalDstRule != NULL) { michael@0: break; michael@0: } michael@0: } else { michael@0: if (finalStdRule == NULL michael@0: && (atzrule = dynamic_cast(tzt.getTo())) != NULL michael@0: && atzrule->getEndYear() == AnnualTimeZoneRule::MAX_YEAR michael@0: ) { michael@0: finalStdRule = (AnnualTimeZoneRule*)tzt.getTo()->clone(); michael@0: } michael@0: if (stdCount > 0) { michael@0: if (year == stdStartYear + stdCount michael@0: && name.compare(stdName) == 0 michael@0: && stdFromOffset == fromOffset michael@0: && stdToOffset == toOffset michael@0: && stdMonth == month michael@0: && stdDayOfWeek == dow michael@0: && stdWeekInMonth == weekInMonth michael@0: && stdMillisInDay == mid) { michael@0: // Update until time michael@0: stdUntilTime = t; michael@0: stdCount++; michael@0: sameRule = TRUE; michael@0: } michael@0: if (!sameRule) { michael@0: if (stdCount == 1) { michael@0: writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime, michael@0: TRUE, status); michael@0: } else { michael@0: writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset, michael@0: stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status); michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupWriteZone; michael@0: } michael@0: } michael@0: } michael@0: if (!sameRule) { michael@0: // Reset this STD information michael@0: stdName = name; michael@0: stdFromOffset = fromOffset; michael@0: stdFromDSTSavings = fromDSTSavings; michael@0: stdToOffset = toOffset; michael@0: stdStartYear = year; michael@0: stdMonth = month; michael@0: stdDayOfWeek = dow; michael@0: stdWeekInMonth = weekInMonth; michael@0: stdMillisInDay = mid; michael@0: stdStartTime = stdUntilTime = t; michael@0: stdCount = 1; michael@0: } michael@0: if (finalStdRule != NULL && finalDstRule != NULL) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: if (!hasTransitions) { michael@0: // No transition - put a single non transition RDATE michael@0: int32_t raw, dst, offset; michael@0: basictz.getOffset(0.0/*any time*/, FALSE, raw, dst, status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupWriteZone; michael@0: } michael@0: offset = raw + dst; michael@0: isDst = (dst != 0); michael@0: UnicodeString tzid; michael@0: basictz.getID(tzid); michael@0: getDefaultTZName(tzid, isDst, name); michael@0: writeZonePropsByTime(w, isDst, name, michael@0: offset, offset, DEF_TZSTARTTIME - offset, FALSE, status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupWriteZone; michael@0: } michael@0: } else { michael@0: if (dstCount > 0) { michael@0: if (finalDstRule == NULL) { michael@0: if (dstCount == 1) { michael@0: writeZonePropsByTime(w, TRUE, dstName, dstFromOffset, dstToOffset, dstStartTime, michael@0: TRUE, status); michael@0: } else { michael@0: writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset, michael@0: dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status); michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupWriteZone; michael@0: } michael@0: } else { michael@0: if (dstCount == 1) { michael@0: writeFinalRule(w, TRUE, finalDstRule, michael@0: dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, dstStartTime, status); michael@0: } else { michael@0: // Use a single rule if possible michael@0: if (isEquivalentDateRule(dstMonth, dstWeekInMonth, dstDayOfWeek, finalDstRule->getRule())) { michael@0: writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset, michael@0: dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, MAX_MILLIS, status); michael@0: } else { michael@0: // Not equivalent rule - write out two different rules michael@0: writeZonePropsByDOW(w, TRUE, dstName, dstFromOffset, dstToOffset, michael@0: dstMonth, dstWeekInMonth, dstDayOfWeek, dstStartTime, dstUntilTime, status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupWriteZone; michael@0: } michael@0: UDate nextStart; michael@0: UBool nextStartAvail = finalDstRule->getNextStart(dstUntilTime, dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, false, nextStart); michael@0: U_ASSERT(nextStartAvail); michael@0: if (nextStartAvail) { michael@0: writeFinalRule(w, TRUE, finalDstRule, michael@0: dstFromOffset - dstFromDSTSavings, dstFromDSTSavings, nextStart, status); michael@0: } michael@0: } michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupWriteZone; michael@0: } michael@0: } michael@0: } michael@0: if (stdCount > 0) { michael@0: if (finalStdRule == NULL) { michael@0: if (stdCount == 1) { michael@0: writeZonePropsByTime(w, FALSE, stdName, stdFromOffset, stdToOffset, stdStartTime, michael@0: TRUE, status); michael@0: } else { michael@0: writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset, michael@0: stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status); michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupWriteZone; michael@0: } michael@0: } else { michael@0: if (stdCount == 1) { michael@0: writeFinalRule(w, FALSE, finalStdRule, michael@0: stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, stdStartTime, status); michael@0: } else { michael@0: // Use a single rule if possible michael@0: if (isEquivalentDateRule(stdMonth, stdWeekInMonth, stdDayOfWeek, finalStdRule->getRule())) { michael@0: writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset, michael@0: stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, MAX_MILLIS, status); michael@0: } else { michael@0: // Not equivalent rule - write out two different rules michael@0: writeZonePropsByDOW(w, FALSE, stdName, stdFromOffset, stdToOffset, michael@0: stdMonth, stdWeekInMonth, stdDayOfWeek, stdStartTime, stdUntilTime, status); michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupWriteZone; michael@0: } michael@0: UDate nextStart; michael@0: UBool nextStartAvail = finalStdRule->getNextStart(stdUntilTime, stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, false, nextStart); michael@0: U_ASSERT(nextStartAvail); michael@0: if (nextStartAvail) { michael@0: writeFinalRule(w, FALSE, finalStdRule, michael@0: stdFromOffset - stdFromDSTSavings, stdFromDSTSavings, nextStart, status); michael@0: } michael@0: } michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: goto cleanupWriteZone; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: writeFooter(w, status); michael@0: michael@0: cleanupWriteZone: michael@0: michael@0: if (finalStdRule != NULL) { michael@0: delete finalStdRule; michael@0: } michael@0: if (finalDstRule != NULL) { michael@0: delete finalDstRule; michael@0: } michael@0: } michael@0: michael@0: void michael@0: VTimeZone::writeHeaders(VTZWriter& writer, UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: UnicodeString tzid; michael@0: tz->getID(tzid); michael@0: michael@0: writer.write(ICAL_BEGIN); michael@0: writer.write(COLON); michael@0: writer.write(ICAL_VTIMEZONE); michael@0: writer.write(ICAL_NEWLINE); michael@0: writer.write(ICAL_TZID); michael@0: writer.write(COLON); michael@0: writer.write(tzid); michael@0: writer.write(ICAL_NEWLINE); michael@0: if (tzurl.length() != 0) { michael@0: writer.write(ICAL_TZURL); michael@0: writer.write(COLON); michael@0: writer.write(tzurl); michael@0: writer.write(ICAL_NEWLINE); michael@0: } michael@0: if (lastmod != MAX_MILLIS) { michael@0: UnicodeString lastmodStr; michael@0: writer.write(ICAL_LASTMOD); michael@0: writer.write(COLON); michael@0: writer.write(getUTCDateTimeString(lastmod, lastmodStr)); michael@0: writer.write(ICAL_NEWLINE); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Write the closing section of the VTIMEZONE definition block michael@0: */ michael@0: void michael@0: VTimeZone::writeFooter(VTZWriter& writer, UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: writer.write(ICAL_END); michael@0: writer.write(COLON); michael@0: writer.write(ICAL_VTIMEZONE); michael@0: writer.write(ICAL_NEWLINE); michael@0: } michael@0: michael@0: /* michael@0: * Write a single start time michael@0: */ michael@0: void michael@0: VTimeZone::writeZonePropsByTime(VTZWriter& writer, UBool isDst, const UnicodeString& zonename, michael@0: int32_t fromOffset, int32_t toOffset, UDate time, UBool withRDATE, michael@0: UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, time, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: if (withRDATE) { michael@0: writer.write(ICAL_RDATE); michael@0: writer.write(COLON); michael@0: UnicodeString timestr; michael@0: writer.write(getDateTimeString(time + fromOffset, timestr)); michael@0: writer.write(ICAL_NEWLINE); michael@0: } michael@0: endZoneProps(writer, isDst, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Write start times defined by a DOM rule using VTIMEZONE RRULE michael@0: */ michael@0: void michael@0: VTimeZone::writeZonePropsByDOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename, michael@0: int32_t fromOffset, int32_t toOffset, michael@0: int32_t month, int32_t dayOfMonth, UDate startTime, UDate untilTime, michael@0: UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: beginRRULE(writer, month, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: writer.write(ICAL_BYMONTHDAY); michael@0: writer.write(EQUALS_SIGN); michael@0: UnicodeString dstr; michael@0: appendAsciiDigits(dayOfMonth, 0, dstr); michael@0: writer.write(dstr); michael@0: if (untilTime != MAX_MILLIS) { michael@0: appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: } michael@0: writer.write(ICAL_NEWLINE); michael@0: endZoneProps(writer, isDst, status); michael@0: } michael@0: michael@0: /* michael@0: * Write start times defined by a DOW rule using VTIMEZONE RRULE michael@0: */ michael@0: void michael@0: VTimeZone::writeZonePropsByDOW(VTZWriter& writer, UBool isDst, const UnicodeString& zonename, michael@0: int32_t fromOffset, int32_t toOffset, michael@0: int32_t month, int32_t weekInMonth, int32_t dayOfWeek, michael@0: UDate startTime, UDate untilTime, UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: beginRRULE(writer, month, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: writer.write(ICAL_BYDAY); michael@0: writer.write(EQUALS_SIGN); michael@0: UnicodeString dstr; michael@0: appendAsciiDigits(weekInMonth, 0, dstr); michael@0: writer.write(dstr); // -4, -3, -2, -1, 1, 2, 3, 4 michael@0: writer.write(ICAL_DOW_NAMES[dayOfWeek - 1]); // SU, MO, TU... michael@0: michael@0: if (untilTime != MAX_MILLIS) { michael@0: appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: } michael@0: writer.write(ICAL_NEWLINE); michael@0: endZoneProps(writer, isDst, status); michael@0: } michael@0: michael@0: /* michael@0: * Write start times defined by a DOW_GEQ_DOM rule using VTIMEZONE RRULE michael@0: */ michael@0: void michael@0: VTimeZone::writeZonePropsByDOW_GEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename, michael@0: int32_t fromOffset, int32_t toOffset, michael@0: int32_t month, int32_t dayOfMonth, int32_t dayOfWeek, michael@0: UDate startTime, UDate untilTime, UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: // Check if this rule can be converted to DOW rule michael@0: if (dayOfMonth%7 == 1) { michael@0: // Can be represented by DOW rule michael@0: writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset, michael@0: month, (dayOfMonth + 6)/7, dayOfWeek, startTime, untilTime, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 6) { michael@0: // Can be represented by DOW rule with negative week number michael@0: writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset, michael@0: month, -1*((MONTHLENGTH[month] - dayOfMonth + 1)/7), dayOfWeek, startTime, untilTime, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: } else { michael@0: // Otherwise, use BYMONTHDAY to include all possible dates michael@0: beginZoneProps(writer, isDst, zonename, fromOffset, toOffset, startTime, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: // Check if all days are in the same month michael@0: int32_t startDay = dayOfMonth; michael@0: int32_t currentMonthDays = 7; michael@0: michael@0: if (dayOfMonth <= 0) { michael@0: // The start day is in previous month michael@0: int32_t prevMonthDays = 1 - dayOfMonth; michael@0: currentMonthDays -= prevMonthDays; michael@0: michael@0: int32_t prevMonth = (month - 1) < 0 ? 11 : month - 1; michael@0: michael@0: // Note: When a rule is separated into two, UNTIL attribute needs to be michael@0: // calculated for each of them. For now, we skip this, because we basically use this method michael@0: // only for final rules, which does not have the UNTIL attribute michael@0: writeZonePropsByDOW_GEQ_DOM_sub(writer, prevMonth, -prevMonthDays, dayOfWeek, prevMonthDays, michael@0: MAX_MILLIS /* Do not use UNTIL */, fromOffset, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: michael@0: // Start from 1 for the rest michael@0: startDay = 1; michael@0: } else if (dayOfMonth + 6 > MONTHLENGTH[month]) { michael@0: // Note: This code does not actually work well in February. For now, days in month in michael@0: // non-leap year. michael@0: int32_t nextMonthDays = dayOfMonth + 6 - MONTHLENGTH[month]; michael@0: currentMonthDays -= nextMonthDays; michael@0: michael@0: int32_t nextMonth = (month + 1) > 11 ? 0 : month + 1; michael@0: michael@0: writeZonePropsByDOW_GEQ_DOM_sub(writer, nextMonth, 1, dayOfWeek, nextMonthDays, michael@0: MAX_MILLIS /* Do not use UNTIL */, fromOffset, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: } michael@0: writeZonePropsByDOW_GEQ_DOM_sub(writer, month, startDay, dayOfWeek, currentMonthDays, michael@0: untilTime, fromOffset, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: endZoneProps(writer, isDst, status); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Called from writeZonePropsByDOW_GEQ_DOM michael@0: */ michael@0: void michael@0: VTimeZone::writeZonePropsByDOW_GEQ_DOM_sub(VTZWriter& writer, int32_t month, int32_t dayOfMonth, michael@0: int32_t dayOfWeek, int32_t numDays, michael@0: UDate untilTime, int32_t fromOffset, UErrorCode& status) const { michael@0: michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: int32_t startDayNum = dayOfMonth; michael@0: UBool isFeb = (month == UCAL_FEBRUARY); michael@0: if (dayOfMonth < 0 && !isFeb) { michael@0: // Use positive number if possible michael@0: startDayNum = MONTHLENGTH[month] + dayOfMonth + 1; michael@0: } michael@0: beginRRULE(writer, month, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: writer.write(ICAL_BYDAY); michael@0: writer.write(EQUALS_SIGN); michael@0: writer.write(ICAL_DOW_NAMES[dayOfWeek - 1]); // SU, MO, TU... michael@0: writer.write(SEMICOLON); michael@0: writer.write(ICAL_BYMONTHDAY); michael@0: writer.write(EQUALS_SIGN); michael@0: michael@0: UnicodeString dstr; michael@0: appendAsciiDigits(startDayNum, 0, dstr); michael@0: writer.write(dstr); michael@0: for (int32_t i = 1; i < numDays; i++) { michael@0: writer.write(COMMA); michael@0: dstr.remove(); michael@0: appendAsciiDigits(startDayNum + i, 0, dstr); michael@0: writer.write(dstr); michael@0: } michael@0: michael@0: if (untilTime != MAX_MILLIS) { michael@0: appendUNTIL(writer, getDateTimeString(untilTime + fromOffset, dstr), status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: } michael@0: writer.write(ICAL_NEWLINE); michael@0: } michael@0: michael@0: /* michael@0: * Write start times defined by a DOW_LEQ_DOM rule using VTIMEZONE RRULE michael@0: */ michael@0: void michael@0: VTimeZone::writeZonePropsByDOW_LEQ_DOM(VTZWriter& writer, UBool isDst, const UnicodeString& zonename, michael@0: int32_t fromOffset, int32_t toOffset, michael@0: int32_t month, int32_t dayOfMonth, int32_t dayOfWeek, michael@0: UDate startTime, UDate untilTime, UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: // Check if this rule can be converted to DOW rule michael@0: if (dayOfMonth%7 == 0) { michael@0: // Can be represented by DOW rule michael@0: writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset, michael@0: month, dayOfMonth/7, dayOfWeek, startTime, untilTime, status); michael@0: } else if (month != UCAL_FEBRUARY && (MONTHLENGTH[month] - dayOfMonth)%7 == 0){ michael@0: // Can be represented by DOW rule with negative week number michael@0: writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset, michael@0: month, -1*((MONTHLENGTH[month] - dayOfMonth)/7 + 1), dayOfWeek, startTime, untilTime, status); michael@0: } else if (month == UCAL_FEBRUARY && dayOfMonth == 29) { michael@0: // Specical case for February michael@0: writeZonePropsByDOW(writer, isDst, zonename, fromOffset, toOffset, michael@0: UCAL_FEBRUARY, -1, dayOfWeek, startTime, untilTime, status); michael@0: } else { michael@0: // Otherwise, convert this to DOW_GEQ_DOM rule michael@0: writeZonePropsByDOW_GEQ_DOM(writer, isDst, zonename, fromOffset, toOffset, michael@0: month, dayOfMonth - 6, dayOfWeek, startTime, untilTime, status); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Write the final time zone rule using RRULE, with no UNTIL attribute michael@0: */ michael@0: void michael@0: VTimeZone::writeFinalRule(VTZWriter& writer, UBool isDst, const AnnualTimeZoneRule* rule, michael@0: int32_t fromRawOffset, int32_t fromDSTSavings, michael@0: UDate startTime, UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: UBool modifiedRule = TRUE; michael@0: const DateTimeRule *dtrule = toWallTimeRule(rule->getRule(), fromRawOffset, fromDSTSavings); michael@0: if (dtrule == NULL) { michael@0: modifiedRule = FALSE; michael@0: dtrule = rule->getRule(); michael@0: } michael@0: michael@0: // If the rule's mills in a day is out of range, adjust start time. michael@0: // Olson tzdata supports 24:00 of a day, but VTIMEZONE does not. michael@0: // See ticket#7008/#7518 michael@0: michael@0: int32_t timeInDay = dtrule->getRuleMillisInDay(); michael@0: if (timeInDay < 0) { michael@0: startTime = startTime + (0 - timeInDay); michael@0: } else if (timeInDay >= U_MILLIS_PER_DAY) { michael@0: startTime = startTime - (timeInDay - (U_MILLIS_PER_DAY - 1)); michael@0: } michael@0: michael@0: int32_t toOffset = rule->getRawOffset() + rule->getDSTSavings(); michael@0: UnicodeString name; michael@0: rule->getName(name); michael@0: switch (dtrule->getDateRuleType()) { michael@0: case DateTimeRule::DOM: michael@0: writeZonePropsByDOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset, michael@0: dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), startTime, MAX_MILLIS, status); michael@0: break; michael@0: case DateTimeRule::DOW: michael@0: writeZonePropsByDOW(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset, michael@0: dtrule->getRuleMonth(), dtrule->getRuleWeekInMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status); michael@0: break; michael@0: case DateTimeRule::DOW_GEQ_DOM: michael@0: writeZonePropsByDOW_GEQ_DOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset, michael@0: dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status); michael@0: break; michael@0: case DateTimeRule::DOW_LEQ_DOM: michael@0: writeZonePropsByDOW_LEQ_DOM(writer, isDst, name, fromRawOffset + fromDSTSavings, toOffset, michael@0: dtrule->getRuleMonth(), dtrule->getRuleDayOfMonth(), dtrule->getRuleDayOfWeek(), startTime, MAX_MILLIS, status); michael@0: break; michael@0: } michael@0: if (modifiedRule) { michael@0: delete dtrule; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Write the opening section of zone properties michael@0: */ michael@0: void michael@0: VTimeZone::beginZoneProps(VTZWriter& writer, UBool isDst, const UnicodeString& zonename, michael@0: int32_t fromOffset, int32_t toOffset, UDate startTime, UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: writer.write(ICAL_BEGIN); michael@0: writer.write(COLON); michael@0: if (isDst) { michael@0: writer.write(ICAL_DAYLIGHT); michael@0: } else { michael@0: writer.write(ICAL_STANDARD); michael@0: } michael@0: writer.write(ICAL_NEWLINE); michael@0: michael@0: UnicodeString dstr; michael@0: michael@0: // TZOFFSETTO michael@0: writer.write(ICAL_TZOFFSETTO); michael@0: writer.write(COLON); michael@0: millisToOffset(toOffset, dstr); michael@0: writer.write(dstr); michael@0: writer.write(ICAL_NEWLINE); michael@0: michael@0: // TZOFFSETFROM michael@0: writer.write(ICAL_TZOFFSETFROM); michael@0: writer.write(COLON); michael@0: millisToOffset(fromOffset, dstr); michael@0: writer.write(dstr); michael@0: writer.write(ICAL_NEWLINE); michael@0: michael@0: // TZNAME michael@0: writer.write(ICAL_TZNAME); michael@0: writer.write(COLON); michael@0: writer.write(zonename); michael@0: writer.write(ICAL_NEWLINE); michael@0: michael@0: // DTSTART michael@0: writer.write(ICAL_DTSTART); michael@0: writer.write(COLON); michael@0: writer.write(getDateTimeString(startTime + fromOffset, dstr)); michael@0: writer.write(ICAL_NEWLINE); michael@0: } michael@0: michael@0: /* michael@0: * Writes the closing section of zone properties michael@0: */ michael@0: void michael@0: VTimeZone::endZoneProps(VTZWriter& writer, UBool isDst, UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: // END:STANDARD or END:DAYLIGHT michael@0: writer.write(ICAL_END); michael@0: writer.write(COLON); michael@0: if (isDst) { michael@0: writer.write(ICAL_DAYLIGHT); michael@0: } else { michael@0: writer.write(ICAL_STANDARD); michael@0: } michael@0: writer.write(ICAL_NEWLINE); michael@0: } michael@0: michael@0: /* michael@0: * Write the beggining part of RRULE line michael@0: */ michael@0: void michael@0: VTimeZone::beginRRULE(VTZWriter& writer, int32_t month, UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: UnicodeString dstr; michael@0: writer.write(ICAL_RRULE); michael@0: writer.write(COLON); michael@0: writer.write(ICAL_FREQ); michael@0: writer.write(EQUALS_SIGN); michael@0: writer.write(ICAL_YEARLY); michael@0: writer.write(SEMICOLON); michael@0: writer.write(ICAL_BYMONTH); michael@0: writer.write(EQUALS_SIGN); michael@0: appendAsciiDigits(month + 1, 0, dstr); michael@0: writer.write(dstr); michael@0: writer.write(SEMICOLON); michael@0: } michael@0: michael@0: /* michael@0: * Append the UNTIL attribute after RRULE line michael@0: */ michael@0: void michael@0: VTimeZone::appendUNTIL(VTZWriter& writer, const UnicodeString& until, UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: if (until.length() > 0) { michael@0: writer.write(SEMICOLON); michael@0: writer.write(ICAL_UNTIL); michael@0: writer.write(EQUALS_SIGN); michael@0: writer.write(until); michael@0: } michael@0: } michael@0: michael@0: U_NAMESPACE_END michael@0: michael@0: #endif /* #if !UCONFIG_NO_FORMATTING */ michael@0: michael@0: //eof