/* * Copyright (c) 2010 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* CFCalendar.c Copyright 2004-2004, Apple Computer, Inc. All rights reserved. Responsibility: Christopher Kane */ #include #include #include "CFInternal.h" #include "CFPriv.h" #include #define BUFFER_SIZE 512 struct __CFCalendar { CFRuntimeBase _base; CFStringRef _identifier; // canonical identifier, never NULL CFLocaleRef _locale; CFStringRef _localeID; CFTimeZoneRef _tz; UCalendar *_cal; }; static Boolean __CFCalendarEqual(CFTypeRef cf1, CFTypeRef cf2) { CFCalendarRef calendar1 = (CFCalendarRef)cf1; CFCalendarRef calendar2 = (CFCalendarRef)cf2; return CFEqual(calendar1->_identifier, calendar2->_identifier); } static CFHashCode __CFCalendarHash(CFTypeRef cf) { CFCalendarRef calendar = (CFCalendarRef)cf; return CFHash(calendar->_identifier); } static CFStringRef __CFCalendarCopyDescription(CFTypeRef cf) { CFCalendarRef calendar = (CFCalendarRef)cf; return CFStringCreateWithFormat(CFGetAllocator(calendar), NULL, CFSTR("{identifier = '%@'}"), cf, CFGetAllocator(calendar), calendar->_identifier); } static void __CFCalendarDeallocate(CFTypeRef cf) { CFCalendarRef calendar = (CFCalendarRef)cf; CFRelease(calendar->_identifier); if (calendar->_locale) CFRelease(calendar->_locale); if (calendar->_localeID) CFRelease(calendar->_localeID); CFRelease(calendar->_tz); if (calendar->_cal) ucal_close(calendar->_cal); } static CFTypeID __kCFCalendarTypeID = _kCFRuntimeNotATypeID; static const CFRuntimeClass __CFCalendarClass = { 0, "CFCalendar", NULL, // init NULL, // copy __CFCalendarDeallocate, __CFCalendarEqual, __CFCalendarHash, NULL, // __CFCalendarCopyDescription }; static void __CFCalendarInitialize(void) { __kCFCalendarTypeID = _CFRuntimeRegisterClass(&__CFCalendarClass); } CFTypeID CFCalendarGetTypeID(void) { if (_kCFRuntimeNotATypeID == __kCFCalendarTypeID) __CFCalendarInitialize(); return __kCFCalendarTypeID; } __private_extern__ UCalendar *__CFCalendarCreateUCalendar(CFStringRef calendarID, CFStringRef localeID, CFTimeZoneRef tz) { if (calendarID) { CFDictionaryRef components = CFLocaleCreateComponentsFromLocaleIdentifier(kCFAllocatorSystemDefault, localeID); CFMutableDictionaryRef mcomponents = CFDictionaryCreateMutableCopy(kCFAllocatorSystemDefault, 0, components); CFDictionarySetValue(mcomponents, kCFLocaleCalendarIdentifier, calendarID); localeID = CFLocaleCreateLocaleIdentifierFromComponents(kCFAllocatorSystemDefault, mcomponents); CFRelease(mcomponents); CFRelease(components); } char buffer[BUFFER_SIZE]; const char *cstr = CFStringGetCStringPtr(localeID, kCFStringEncodingASCII); if (NULL == cstr) { if (CFStringGetCString(localeID, buffer, BUFFER_SIZE, kCFStringEncodingASCII)) cstr = buffer; } if (NULL == cstr) { if (calendarID) CFRelease(localeID); return NULL; } UChar ubuffer[BUFFER_SIZE]; CFStringRef tznam = CFTimeZoneGetName(tz); CFIndex cnt = CFStringGetLength(tznam); if (BUFFER_SIZE < cnt) cnt = BUFFER_SIZE; CFStringGetCharacters(tznam, CFRangeMake(0, cnt), (UniChar *)ubuffer); UErrorCode status = U_ZERO_ERROR; UCalendar *cal = ucal_open(ubuffer, cnt, cstr, UCAL_TRADITIONAL, &status); if (calendarID) CFRelease(localeID); return cal; } static void __CFCalendarSetupCal(CFCalendarRef calendar) { calendar->_cal = __CFCalendarCreateUCalendar(calendar->_identifier, calendar->_localeID, calendar->_tz); } static void __CFCalendarZapCal(CFCalendarRef calendar) { ucal_close(calendar->_cal); calendar->_cal = NULL; } CFCalendarRef CFCalendarCopyCurrent(void) { CFLocaleRef locale = CFLocaleCopyCurrent(); CFCalendarRef calID = (CFCalendarRef)CFLocaleGetValue(locale, kCFLocaleCalendarIdentifier); if (calID) { CFCalendarRef calendar = CFCalendarCreateWithIdentifier(kCFAllocatorSystemDefault, (CFStringRef)calID); CFCalendarSetLocale(calendar, locale); CFRelease(locale); return calendar; } return NULL; } CFCalendarRef CFCalendarCreateWithIdentifier(CFAllocatorRef allocator, CFStringRef identifier) { if (allocator == NULL) allocator = __CFGetDefaultAllocator(); __CFGenericValidateType(allocator, CFAllocatorGetTypeID()); __CFGenericValidateType(identifier, CFStringGetTypeID()); // return NULL until Chinese calendar is available if (identifier != kCFGregorianCalendar && identifier != kCFBuddhistCalendar && identifier != kCFJapaneseCalendar && identifier != kCFIslamicCalendar && identifier != kCFIslamicCivilCalendar && identifier != kCFHebrewCalendar) { // if (identifier != kCFGregorianCalendar && identifier != kCFBuddhistCalendar && identifier != kCFJapaneseCalendar && identifier != kCFIslamicCalendar && identifier != kCFIslamicCivilCalendar && identifier != kCFHebrewCalendar && identifier != kCFChineseCalendar) { if (CFEqual(kCFGregorianCalendar, identifier)) identifier = kCFGregorianCalendar; else if (CFEqual(kCFBuddhistCalendar, identifier)) identifier = kCFBuddhistCalendar; else if (CFEqual(kCFJapaneseCalendar, identifier)) identifier = kCFJapaneseCalendar; else if (CFEqual(kCFIslamicCalendar, identifier)) identifier = kCFIslamicCalendar; else if (CFEqual(kCFIslamicCivilCalendar, identifier)) identifier = kCFIslamicCivilCalendar; else if (CFEqual(kCFHebrewCalendar, identifier)) identifier = kCFHebrewCalendar; // else if (CFEqual(kCFChineseCalendar, identifier)) identifier = kCFChineseCalendar; else return NULL; } struct __CFCalendar *calendar = NULL; uint32_t size = sizeof(struct __CFCalendar) - sizeof(CFRuntimeBase); calendar = (struct __CFCalendar *)_CFRuntimeCreateInstance(allocator, CFCalendarGetTypeID(), size, NULL); if (NULL == calendar) { return NULL; } calendar->_identifier = (CFStringRef)CFRetain(identifier); calendar->_locale = NULL; calendar->_localeID = CFLocaleGetIdentifier(CFLocaleGetSystem()); calendar->_tz = CFTimeZoneCopyDefault(); calendar->_cal = NULL; return (CFCalendarRef)calendar; } CFStringRef CFCalendarGetIdentifier(CFCalendarRef calendar) { CF_OBJC_FUNCDISPATCH0(CFCalendarGetTypeID(), CFStringRef, calendar, "calendarIdentifier"); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); return calendar->_identifier; } CFLocaleRef CFCalendarCopyLocale(CFCalendarRef calendar) { CF_OBJC_FUNCDISPATCH0(CFCalendarGetTypeID(), CFLocaleRef, calendar, "_copyLocale"); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); return (CFLocaleRef)CFLocaleCreate(kCFAllocatorSystemDefault, calendar->_localeID); } void CFCalendarSetLocale(CFCalendarRef calendar, CFLocaleRef locale) { CF_OBJC_FUNCDISPATCH1(CFCalendarGetTypeID(), void, calendar, "setLocale:", locale); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); __CFGenericValidateType(locale, CFLocaleGetTypeID()); CFStringRef localeID = CFLocaleGetIdentifier(locale); if (localeID != calendar->_localeID) { CFRelease(calendar->_localeID); CFRetain(localeID); calendar->_localeID = localeID; if (calendar->_cal) __CFCalendarZapCal(calendar); } } CFTimeZoneRef CFCalendarCopyTimeZone(CFCalendarRef calendar) { CF_OBJC_FUNCDISPATCH0(CFCalendarGetTypeID(), CFTimeZoneRef, calendar, "_copyTimeZone"); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); return (CFTimeZoneRef)CFRetain(calendar->_tz); } void CFCalendarSetTimeZone(CFCalendarRef calendar, CFTimeZoneRef tz) { CF_OBJC_FUNCDISPATCH1(CFCalendarGetTypeID(), void, calendar, "setTimeZone:", tz); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); if (tz) __CFGenericValidateType(tz, CFTimeZoneGetTypeID()); if (tz != calendar->_tz) { CFRelease(calendar->_tz); calendar->_tz = tz ? (CFTimeZoneRef)CFRetain(tz) : CFTimeZoneCopyDefault(); if (calendar->_cal) __CFCalendarZapCal(calendar); } } CFIndex CFCalendarGetFirstWeekday(CFCalendarRef calendar) { CF_OBJC_FUNCDISPATCH0(CFCalendarGetTypeID(), CFIndex, calendar, "firstWeekday"); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); if (!calendar->_cal) __CFCalendarSetupCal(calendar); if (calendar->_cal) { return ucal_getAttribute(calendar->_cal, UCAL_FIRST_DAY_OF_WEEK); } return -1; } void CFCalendarSetFirstWeekday(CFCalendarRef calendar, CFIndex wkdy) { CF_OBJC_FUNCDISPATCH1(CFCalendarGetTypeID(), void, calendar, "setFirstWeekday:", wkdy); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); if (!calendar->_cal) __CFCalendarSetupCal(calendar); if (calendar->_cal) { ucal_setAttribute(calendar->_cal, UCAL_FIRST_DAY_OF_WEEK, wkdy); } } CFIndex CFCalendarGetMinimumDaysInFirstWeek(CFCalendarRef calendar) { CF_OBJC_FUNCDISPATCH0(CFCalendarGetTypeID(), CFIndex, calendar, "minimumDaysInFirstWeek"); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); if (!calendar->_cal) __CFCalendarSetupCal(calendar); return calendar->_cal ? ucal_getAttribute(calendar->_cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK) : -1; } void CFCalendarSetMinimumDaysInFirstWeek(CFCalendarRef calendar, CFIndex mwd) { CF_OBJC_FUNCDISPATCH1(CFCalendarGetTypeID(), void, calendar, "setMinimumDaysInFirstWeek:", mwd); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); if (!calendar->_cal) __CFCalendarSetupCal(calendar); if (calendar->_cal) ucal_setAttribute(calendar->_cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK, mwd); } CFDateRef CFCalendarCopyGregorianStartDate(CFCalendarRef calendar) { CF_OBJC_FUNCDISPATCH0(CFCalendarGetTypeID(), CFDateRef, calendar, "_gregorianStartDate"); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); if (!calendar->_cal) __CFCalendarSetupCal(calendar); UErrorCode status = U_ZERO_ERROR; UDate udate = calendar->_cal ? ucal_getGregorianChange(calendar->_cal, &status) : 0; if (calendar->_cal && U_SUCCESS(status)) { CFAbsoluteTime at = (double)udate / 1000.0 - kCFAbsoluteTimeIntervalSince1970; return CFDateCreate(CFGetAllocator(calendar), at); } return NULL; } void CFCalendarSetGregorianStartDate(CFCalendarRef calendar, CFDateRef date) { CF_OBJC_FUNCDISPATCH1(CFCalendarGetTypeID(), void, calendar, "_setGregorianStartDate:", date); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); if (date) __CFGenericValidateType(date, CFDateGetTypeID()); if (!calendar->_cal) __CFCalendarSetupCal(calendar); if (!calendar->_cal) return; if (!date) { UErrorCode status = U_ZERO_ERROR; UCalendar *cal = __CFCalendarCreateUCalendar(calendar->_identifier, calendar->_localeID, calendar->_tz); UDate udate = cal ? ucal_getGregorianChange(cal, &status) : 0; if (cal && U_SUCCESS(status)) { status = U_ZERO_ERROR; if (calendar->_cal) ucal_setGregorianChange(calendar->_cal, udate, &status); } if (cal) ucal_close(cal); } else { CFAbsoluteTime at = CFDateGetAbsoluteTime(date); UDate udate = (at + kCFAbsoluteTimeIntervalSince1970) * 1000.0; UErrorCode status = U_ZERO_ERROR; if (calendar->_cal) ucal_setGregorianChange(calendar->_cal, udate, &status); } } static UCalendarDateFields __CFCalendarGetICUFieldCode(CFCalendarUnit unit) { switch (unit) { case kCFCalendarUnitEra: return UCAL_ERA; case kCFCalendarUnitYear: return UCAL_YEAR; case kCFCalendarUnitMonth: return UCAL_MONTH; case kCFCalendarUnitDay: return UCAL_DAY_OF_MONTH; case kCFCalendarUnitHour: return UCAL_HOUR_OF_DAY; case kCFCalendarUnitMinute: return UCAL_MINUTE; case kCFCalendarUnitSecond: return UCAL_SECOND; case kCFCalendarUnitWeek: return UCAL_WEEK_OF_YEAR; case kCFCalendarUnitWeekday: return UCAL_DAY_OF_WEEK; case kCFCalendarUnitWeekdayOrdinal: return UCAL_DAY_OF_WEEK_IN_MONTH; } return (UCalendarDateFields)-1; } static UCalendarDateFields __CFCalendarGetICUFieldCodeFromChar(char ch) { switch (ch) { case 'G': return UCAL_ERA; case 'y': return UCAL_YEAR; case 'M': return UCAL_MONTH; case 'd': return UCAL_DAY_OF_MONTH; case 'h': return UCAL_HOUR; case 'H': return UCAL_HOUR_OF_DAY; case 'm': return UCAL_MINUTE; case 's': return UCAL_SECOND; case 'S': return UCAL_MILLISECOND; case 'w': return UCAL_WEEK_OF_YEAR; case 'W': return UCAL_WEEK_OF_MONTH; case 'E': return UCAL_DAY_OF_WEEK; case 'D': return UCAL_DAY_OF_YEAR; case 'F': return UCAL_DAY_OF_WEEK_IN_MONTH; case 'a': return UCAL_AM_PM; case 'g': return UCAL_JULIAN_DAY; } return (UCalendarDateFields)-1; } static UCalendarDateFields __CFCalendarGetCalendarUnitFromChar(char ch) { switch (ch) { case 'G': return (UCalendarDateFields)kCFCalendarUnitEra; case 'y': return (UCalendarDateFields)kCFCalendarUnitYear; case 'M': return (UCalendarDateFields)kCFCalendarUnitMonth; case 'd': return (UCalendarDateFields)kCFCalendarUnitDay; case 'H': return (UCalendarDateFields)kCFCalendarUnitHour; case 'm': return (UCalendarDateFields)kCFCalendarUnitMinute; case 's': return (UCalendarDateFields)kCFCalendarUnitSecond; case 'w': return (UCalendarDateFields)kCFCalendarUnitWeek; case 'E': return (UCalendarDateFields)kCFCalendarUnitWeekday; case 'F': return (UCalendarDateFields)kCFCalendarUnitWeekdayOrdinal; } return (UCalendarDateFields)-1; } CFRange CFCalendarGetMinimumRangeOfUnit(CFCalendarRef calendar, CFCalendarUnit unit) { CF_OBJC_FUNCDISPATCH1(CFCalendarGetTypeID(), CFRange, calendar, "_minimumRangeOfUnit:", unit); CFRange range = {kCFNotFound, kCFNotFound}; __CFGenericValidateType(calendar, CFCalendarGetTypeID()); if (!calendar->_cal) __CFCalendarSetupCal(calendar); if (calendar->_cal) { ucal_clear(calendar->_cal); UCalendarDateFields field = __CFCalendarGetICUFieldCode(unit); UErrorCode status = U_ZERO_ERROR; range.location = ucal_getLimit(calendar->_cal, field, UCAL_GREATEST_MINIMUM, &status); range.length = ucal_getLimit(calendar->_cal, field, UCAL_LEAST_MAXIMUM, &status) - range.location + 1; if (UCAL_MONTH == field) range.location++; if (100000 < range.length) range.length = 100000; } return range; } CFRange CFCalendarGetMaximumRangeOfUnit(CFCalendarRef calendar, CFCalendarUnit unit) { CF_OBJC_FUNCDISPATCH1(CFCalendarGetTypeID(), CFRange, calendar, "_maximumRangeOfUnit:", unit); CFRange range = {kCFNotFound, kCFNotFound}; __CFGenericValidateType(calendar, CFCalendarGetTypeID()); if (!calendar->_cal) __CFCalendarSetupCal(calendar); if (calendar->_cal) { ucal_clear(calendar->_cal); UCalendarDateFields field = __CFCalendarGetICUFieldCode(unit); UErrorCode status = U_ZERO_ERROR; range.location = ucal_getLimit(calendar->_cal, field, UCAL_MINIMUM, &status); range.length = ucal_getLimit(calendar->_cal, field, UCAL_MAXIMUM, &status) - range.location + 1; if (UCAL_MONTH == field) range.location++; if (100000 < range.length) range.length = 100000; } return range; } static void __CFCalendarSetToFirstInstant(CFCalendarRef calendar, CFCalendarUnit unit, CFAbsoluteTime at) { // Set UCalendar to first instant of unit prior to 'at' UErrorCode status = U_ZERO_ERROR; UDate udate = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0); ucal_setMillis(calendar->_cal, udate, &status); int target_era = INT_MIN; switch (unit) { // largest to smallest, we set the fields to their minimum value case kCFCalendarUnitWeek: { // reduce to first day of week, then reduce the rest of the day int32_t goal = ucal_getAttribute(calendar->_cal, UCAL_FIRST_DAY_OF_WEEK); int32_t dow = ucal_get(calendar->_cal, UCAL_DAY_OF_WEEK, &status); while (dow != goal) { ucal_add(calendar->_cal, UCAL_DAY_OF_MONTH, -1, &status); dow = ucal_get(calendar->_cal, UCAL_DAY_OF_WEEK, &status); } goto day; } case kCFCalendarUnitEra: { target_era = ucal_get(calendar->_cal, UCAL_ERA, &status); ucal_set(calendar->_cal, UCAL_YEAR, ucal_getLimit(calendar->_cal, UCAL_YEAR, UCAL_ACTUAL_MINIMUM, &status)); } case kCFCalendarUnitYear: ucal_set(calendar->_cal, UCAL_MONTH, ucal_getLimit(calendar->_cal, UCAL_MONTH, UCAL_ACTUAL_MINIMUM, &status)); case kCFCalendarUnitMonth: ucal_set(calendar->_cal, UCAL_DAY_OF_MONTH, ucal_getLimit(calendar->_cal, UCAL_DAY_OF_MONTH, UCAL_ACTUAL_MINIMUM, &status)); case kCFCalendarUnitWeekday: case kCFCalendarUnitDay: day:; ucal_set(calendar->_cal, UCAL_HOUR_OF_DAY, ucal_getLimit(calendar->_cal, UCAL_HOUR_OF_DAY, UCAL_ACTUAL_MINIMUM, &status)); case kCFCalendarUnitHour: ucal_set(calendar->_cal, UCAL_MINUTE, ucal_getLimit(calendar->_cal, UCAL_MINUTE, UCAL_ACTUAL_MINIMUM, &status)); case kCFCalendarUnitMinute: ucal_set(calendar->_cal, UCAL_SECOND, ucal_getLimit(calendar->_cal, UCAL_SECOND, UCAL_ACTUAL_MINIMUM, &status)); case kCFCalendarUnitSecond: ucal_set(calendar->_cal, UCAL_MILLISECOND, 0); } if (INT_MIN != target_era && ucal_get(calendar->_cal, UCAL_ERA, &status) < target_era) { // In the Japanese calendar, and possibly others, eras don't necessarily // start on the first day of a year, so the previous code may have backed // up into the previous era, and we have to correct forward. UDate bad_udate = ucal_getMillis(calendar->_cal, &status); ucal_add(calendar->_cal, UCAL_MONTH, 1, &status); while (ucal_get(calendar->_cal, UCAL_ERA, &status) < target_era) { bad_udate = ucal_getMillis(calendar->_cal, &status); ucal_add(calendar->_cal, UCAL_MONTH, 1, &status); } udate = ucal_getMillis(calendar->_cal, &status); // target date is between bad_udate and udate for (;;) { UDate test_udate = (udate + bad_udate) / 2; ucal_setMillis(calendar->_cal, test_udate, &status); if (ucal_get(calendar->_cal, UCAL_ERA, &status) < target_era) { bad_udate = test_udate; } else { udate = test_udate; } if (fabs(udate - bad_udate) < 1000) break; } do { bad_udate = floor((bad_udate + 1000) / 1000) * 1000; ucal_setMillis(calendar->_cal, bad_udate, &status); } while (ucal_get(calendar->_cal, UCAL_ERA, &status) < target_era); } } static Boolean __validUnits(CFCalendarUnit smaller, CFCalendarUnit bigger) { switch (bigger) { case kCFCalendarUnitEra: if (kCFCalendarUnitEra == smaller) return false; if (kCFCalendarUnitWeekday == smaller) return false; if (kCFCalendarUnitMinute == smaller) return false; // this causes CFIndex overflow in range.length if (kCFCalendarUnitSecond == smaller) return false; // this causes CFIndex overflow in range.length return true; case kCFCalendarUnitYear: if (kCFCalendarUnitEra == smaller) return false; if (kCFCalendarUnitYear == smaller) return false; if (kCFCalendarUnitWeekday == smaller) return false; return true; case kCFCalendarUnitMonth: if (kCFCalendarUnitEra == smaller) return false; if (kCFCalendarUnitYear == smaller) return false; if (kCFCalendarUnitMonth == smaller) return false; if (kCFCalendarUnitWeekday == smaller) return false; return true; case kCFCalendarUnitDay: if (kCFCalendarUnitHour == smaller) return true; if (kCFCalendarUnitMinute == smaller) return true; if (kCFCalendarUnitSecond == smaller) return true; return false; case kCFCalendarUnitHour: if (kCFCalendarUnitMinute == smaller) return true; if (kCFCalendarUnitSecond == smaller) return true; return false; case kCFCalendarUnitMinute: if (kCFCalendarUnitSecond == smaller) return true; return false; case kCFCalendarUnitWeek: if (kCFCalendarUnitWeekday == smaller) return true; if (kCFCalendarUnitDay == smaller) return true; if (kCFCalendarUnitHour == smaller) return true; if (kCFCalendarUnitMinute == smaller) return true; if (kCFCalendarUnitSecond == smaller) return true; return false; case kCFCalendarUnitSecond: case kCFCalendarUnitWeekday: case kCFCalendarUnitWeekdayOrdinal: return false; } return false; }; static CFRange __CFCalendarGetRangeOfUnit1(CFCalendarRef calendar, CFCalendarUnit smallerUnit, CFCalendarUnit biggerUnit, CFAbsoluteTime at) { CFRange range = {kCFNotFound, kCFNotFound}; if (!__validUnits(smallerUnit, biggerUnit)) return range; CF_OBJC_FUNCDISPATCH3(CFCalendarGetTypeID(), CFRange, calendar, "_rangeOfUnit:inUnit:forAT:", smallerUnit, biggerUnit, at); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); if (!calendar->_cal) __CFCalendarSetupCal(calendar); if (calendar->_cal) { int32_t dow = -1; ucal_clear(calendar->_cal); UCalendarDateFields smallField = __CFCalendarGetICUFieldCode(smallerUnit); UCalendarDateFields bigField = __CFCalendarGetICUFieldCode(biggerUnit); if (kCFCalendarUnitWeekdayOrdinal == smallerUnit) { UErrorCode status = U_ZERO_ERROR; UDate udate = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0); ucal_setMillis(calendar->_cal, udate, &status); dow = ucal_get(calendar->_cal, UCAL_DAY_OF_WEEK, &status); } // Set calendar to first instant of big unit __CFCalendarSetToFirstInstant(calendar, biggerUnit, at); UErrorCode status = U_ZERO_ERROR; UDate start = ucal_getMillis(calendar->_cal, &status); if (kCFCalendarUnitWeek == biggerUnit) { range.location = ucal_get(calendar->_cal, smallField, &status); if (kCFCalendarUnitMonth == smallerUnit) range.location++; } else { range.location = (kCFCalendarUnitHour == smallerUnit || kCFCalendarUnitMinute == smallerUnit || kCFCalendarUnitSecond == smallerUnit) ? 0 : 1; } // Set calendar to first instant of next value of big unit if (UCAL_ERA == bigField) { // ICU refuses to do the addition, probably because we are // at the limit of UCAL_ERA. Use alternate strategy. CFIndex limit = ucal_getLimit(calendar->_cal, UCAL_YEAR, UCAL_MAXIMUM, &status); if (100000 < limit) limit = 100000; ucal_add(calendar->_cal, UCAL_YEAR, limit, &status); } else { ucal_add(calendar->_cal, bigField, 1, &status); } if (kCFCalendarUnitWeek == smallerUnit && kCFCalendarUnitYear == biggerUnit) { ucal_add(calendar->_cal, UCAL_SECOND, -1, &status); range.length = ucal_get(calendar->_cal, UCAL_WEEK_OF_YEAR, &status); while (1 == range.length) { ucal_add(calendar->_cal, UCAL_DAY_OF_MONTH, -1, &status); range.length = ucal_get(calendar->_cal, UCAL_WEEK_OF_YEAR, &status); } range.location = 1; return range; } else if (kCFCalendarUnitWeek == smallerUnit && kCFCalendarUnitMonth == biggerUnit) { ucal_add(calendar->_cal, UCAL_SECOND, -1, &status); range.length = ucal_get(calendar->_cal, UCAL_WEEK_OF_YEAR, &status); range.location = 1; return range; } UDate goal = ucal_getMillis(calendar->_cal, &status); // Set calendar back to first instant of big unit ucal_setMillis(calendar->_cal, start, &status); if (kCFCalendarUnitWeekdayOrdinal == smallerUnit) { // roll day forward to first 'dow' while (ucal_get(calendar->_cal, (kCFCalendarUnitMonth == biggerUnit) ? UCAL_WEEK_OF_MONTH : UCAL_WEEK_OF_YEAR, &status) != 1) { ucal_add(calendar->_cal, UCAL_DAY_OF_MONTH, 1, &status); } while (ucal_get(calendar->_cal, UCAL_DAY_OF_WEEK, &status) != dow) { ucal_add(calendar->_cal, UCAL_DAY_OF_MONTH, 1, &status); } start = ucal_getMillis(calendar->_cal, &status); goal -= 1000; range.location = 1; // constant here works around ICU -- see 3948293 } UDate curr = start; range.length = (kCFCalendarUnitWeekdayOrdinal == smallerUnit) ? 1 : 0; const int multiple_table[] = {0, 0, 16, 19, 24, 26, 24, 28, 14, 14, 14}; int multiple = (1 << multiple_table[flsl(smallerUnit) - 1]); Boolean divide = false, alwaysDivide = false; while (curr < goal) { ucal_add(calendar->_cal, smallField, multiple, &status); UDate newcurr = ucal_getMillis(calendar->_cal, &status); if (curr < newcurr && newcurr <= goal) { range.length += multiple; curr = newcurr; } else { // Either newcurr is going backwards, or not making // progress, or has overshot the goal; reset date // and try smaller multiples. ucal_setMillis(calendar->_cal, curr, &status); divide = true; // once we start overshooting the goal, the add at // smaller multiples will succeed at most once for // each multiple, so we reduce it every time through // the loop. if (goal < newcurr) alwaysDivide = true; } if (divide) { multiple = multiple / 2; if (0 == multiple) break; divide = alwaysDivide; } } } return range; } static CFRange __CFCalendarGetRangeOfUnit2(CFCalendarRef calendar, CFCalendarUnit smallerUnit, CFCalendarUnit biggerUnit, CFAbsoluteTime at) __attribute__((noinline)); static CFRange __CFCalendarGetRangeOfUnit2(CFCalendarRef calendar, CFCalendarUnit smallerUnit, CFCalendarUnit biggerUnit, CFAbsoluteTime at) { CF_OBJC_FUNCDISPATCH3(CFCalendarGetTypeID(), CFRange, calendar, "_rangeOfUnit:inUnit:forAT:", smallerUnit, biggerUnit, at); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); CFRange range = {kCFNotFound, kCFNotFound}; if (!calendar->_cal) __CFCalendarSetupCal(calendar); if (calendar->_cal) { switch (smallerUnit) { case kCFCalendarUnitSecond: switch (biggerUnit) { case kCFCalendarUnitMinute: case kCFCalendarUnitHour: case kCFCalendarUnitDay: case kCFCalendarUnitWeekday: case kCFCalendarUnitWeek: case kCFCalendarUnitMonth: case kCFCalendarUnitYear: case kCFCalendarUnitEra: // goto calculate; range.location = 0; range.length = 60; break; } break; case kCFCalendarUnitMinute: switch (biggerUnit) { case kCFCalendarUnitHour: case kCFCalendarUnitDay: case kCFCalendarUnitWeekday: case kCFCalendarUnitWeek: case kCFCalendarUnitMonth: case kCFCalendarUnitYear: case kCFCalendarUnitEra: // goto calculate; range.location = 0; range.length = 60; break; } break; case kCFCalendarUnitHour: switch (biggerUnit) { case kCFCalendarUnitDay: case kCFCalendarUnitWeekday: case kCFCalendarUnitWeek: case kCFCalendarUnitMonth: case kCFCalendarUnitYear: case kCFCalendarUnitEra: // goto calculate; range.location = 0; range.length = 24; break; } break; case kCFCalendarUnitDay: switch (biggerUnit) { case kCFCalendarUnitWeek: case kCFCalendarUnitMonth: case kCFCalendarUnitYear: case kCFCalendarUnitEra: goto calculate; break; } break; case kCFCalendarUnitWeekday: switch (biggerUnit) { case kCFCalendarUnitWeek: case kCFCalendarUnitMonth: case kCFCalendarUnitYear: case kCFCalendarUnitEra: goto calculate; break; } break; case kCFCalendarUnitWeekdayOrdinal: switch (biggerUnit) { case kCFCalendarUnitMonth: case kCFCalendarUnitYear: case kCFCalendarUnitEra: goto calculate; break; } break; case kCFCalendarUnitWeek: switch (biggerUnit) { case kCFCalendarUnitMonth: case kCFCalendarUnitYear: case kCFCalendarUnitEra: goto calculate; break; } break; case kCFCalendarUnitMonth: switch (biggerUnit) { case kCFCalendarUnitYear: case kCFCalendarUnitEra: goto calculate; break; } break; case kCFCalendarUnitYear: switch (biggerUnit) { case kCFCalendarUnitEra: goto calculate; break; } break; case kCFCalendarUnitEra: break; } } return range; calculate:; ucal_clear(calendar->_cal); UCalendarDateFields smallField = __CFCalendarGetICUFieldCode(smallerUnit); UCalendarDateFields bigField = __CFCalendarGetICUFieldCode(biggerUnit); UCalendarDateFields yearField = __CFCalendarGetICUFieldCode(kCFCalendarUnitYear); UCalendarDateFields fieldToAdd = smallField; if (kCFCalendarUnitWeekday == smallerUnit) { fieldToAdd = __CFCalendarGetICUFieldCode(kCFCalendarUnitDay); } int32_t dow = -1; if (kCFCalendarUnitWeekdayOrdinal == smallerUnit) { UErrorCode status = U_ZERO_ERROR; UDate udate = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0); ucal_setMillis(calendar->_cal, udate, &status); dow = ucal_get(calendar->_cal, UCAL_DAY_OF_WEEK, &status); fieldToAdd = __CFCalendarGetICUFieldCode(kCFCalendarUnitWeek); } // Set calendar to first instant of big unit __CFCalendarSetToFirstInstant(calendar, biggerUnit, at); if (kCFCalendarUnitWeekdayOrdinal == smallerUnit) { UErrorCode status = U_ZERO_ERROR; // roll day forward to first 'dow' while (ucal_get(calendar->_cal, (kCFCalendarUnitMonth == biggerUnit) ? UCAL_WEEK_OF_MONTH : UCAL_WEEK_OF_YEAR, &status) != 1) { ucal_add(calendar->_cal, UCAL_DAY_OF_MONTH, 1, &status); } while (ucal_get(calendar->_cal, UCAL_DAY_OF_WEEK, &status) != dow) { ucal_add(calendar->_cal, UCAL_DAY_OF_MONTH, 1, &status); } } int32_t minSmallValue = INT32_MAX; int32_t maxSmallValue = INT32_MIN; UErrorCode status = U_ZERO_ERROR; int32_t bigValue = ucal_get(calendar->_cal, bigField, &status); for (;;) { int32_t smallValue = ucal_get(calendar->_cal, smallField, &status); if (smallValue < minSmallValue) minSmallValue = smallValue; if (smallValue > maxSmallValue) maxSmallValue = smallValue; ucal_add(calendar->_cal, fieldToAdd, 1, &status); if (bigValue != ucal_get(calendar->_cal, bigField, &status)) break; if (biggerUnit == kCFCalendarUnitEra && ucal_get(calendar->_cal, yearField, &status) > 10000) break; // we assume an answer for 10000 years can be extrapolated to 100000 years, to save time } status = U_ZERO_ERROR; range.location = minSmallValue; if (smallerUnit == kCFCalendarUnitMonth) range.location = 1; range.length = maxSmallValue - minSmallValue + 1; if (biggerUnit == kCFCalendarUnitEra && ucal_get(calendar->_cal, yearField, &status) > 10000) range.length = 100000; return range; } CFRange CFCalendarGetRangeOfUnit(CFCalendarRef calendar, CFCalendarUnit smallerUnit, CFCalendarUnit biggerUnit, CFAbsoluteTime at) { if (_CFExecutableLinkedOnOrAfter(CFSystemVersionLeopard)) { return __CFCalendarGetRangeOfUnit2(calendar, smallerUnit, biggerUnit, at); } else { return __CFCalendarGetRangeOfUnit1(calendar, smallerUnit, biggerUnit, at); } } CFIndex CFCalendarGetOrdinalityOfUnit(CFCalendarRef calendar, CFCalendarUnit smallerUnit, CFCalendarUnit biggerUnit, CFAbsoluteTime at) { CFIndex result = kCFNotFound; if (!__validUnits(smallerUnit, biggerUnit)) return result; CF_OBJC_FUNCDISPATCH3(CFCalendarGetTypeID(), CFIndex, calendar, "_ordinalityOfUnit:inUnit:forAT:", smallerUnit, biggerUnit, at); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); if (!calendar->_cal) __CFCalendarSetupCal(calendar); if (calendar->_cal) { UErrorCode status = U_ZERO_ERROR; ucal_clear(calendar->_cal); if (kCFCalendarUnitWeek == smallerUnit && kCFCalendarUnitYear == biggerUnit) { UDate udate = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0); ucal_setMillis(calendar->_cal, udate, &status); int32_t val = ucal_get(calendar->_cal, UCAL_WEEK_OF_YEAR, &status); return val; } else if (kCFCalendarUnitWeek == smallerUnit && kCFCalendarUnitMonth == biggerUnit) { UDate udate = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0); ucal_setMillis(calendar->_cal, udate, &status); int32_t val = ucal_get(calendar->_cal, UCAL_WEEK_OF_MONTH, &status); return val; } UCalendarDateFields smallField = __CFCalendarGetICUFieldCode(smallerUnit); // Set calendar to first instant of big unit __CFCalendarSetToFirstInstant(calendar, biggerUnit, at); UDate curr = ucal_getMillis(calendar->_cal, &status); UDate goal = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0); result = 1; const int multiple_table[] = {0, 0, 16, 19, 24, 26, 24, 28, 14, 14, 14}; int multiple = (1 << multiple_table[flsl(smallerUnit) - 1]); Boolean divide = false, alwaysDivide = false; while (curr < goal) { ucal_add(calendar->_cal, smallField, multiple, &status); UDate newcurr = ucal_getMillis(calendar->_cal, &status); if (curr < newcurr && newcurr <= goal) { result += multiple; curr = newcurr; } else { // Either newcurr is going backwards, or not making // progress, or has overshot the goal; reset date // and try smaller multiples. ucal_setMillis(calendar->_cal, curr, &status); divide = true; // once we start overshooting the goal, the add at // smaller multiples will succeed at most once for // each multiple, so we reduce it every time through // the loop. if (goal < newcurr) alwaysDivide = true; } if (divide) { multiple = multiple / 2; if (0 == multiple) break; divide = alwaysDivide; } } } return result; } Boolean _CFCalendarComposeAbsoluteTimeV(CFCalendarRef calendar, /* out */ CFAbsoluteTime *atp, const char *componentDesc, int *vector, int count) { if (!calendar->_cal) __CFCalendarSetupCal(calendar); if (calendar->_cal) { UErrorCode status = U_ZERO_ERROR; ucal_clear(calendar->_cal); ucal_set(calendar->_cal, UCAL_YEAR, 1); ucal_set(calendar->_cal, UCAL_MONTH, 0); ucal_set(calendar->_cal, UCAL_DAY_OF_MONTH, 1); ucal_set(calendar->_cal, UCAL_HOUR_OF_DAY, 0); ucal_set(calendar->_cal, UCAL_MINUTE, 0); ucal_set(calendar->_cal, UCAL_SECOND, 0); const char *desc = componentDesc; Boolean doWOY = false; char ch = *desc; while (ch) { UCalendarDateFields field = __CFCalendarGetICUFieldCodeFromChar(ch); if (UCAL_WEEK_OF_YEAR == field) { doWOY = true; } desc++; ch = *desc; } desc = componentDesc; ch = *desc; while (ch) { UCalendarDateFields field = __CFCalendarGetICUFieldCodeFromChar(ch); int value = *vector; if (UCAL_YEAR == field && doWOY) field = UCAL_YEAR_WOY; if (UCAL_MONTH == field) value--; ucal_set(calendar->_cal, field, value); vector++; desc++; ch = *desc; } UDate udate = ucal_getMillis(calendar->_cal, &status); CFAbsoluteTime at = (udate / 1000.0) - kCFAbsoluteTimeIntervalSince1970; if (atp) *atp = at; return U_SUCCESS(status) ? true : false; } return false; } Boolean _CFCalendarDecomposeAbsoluteTimeV(CFCalendarRef calendar, CFAbsoluteTime at, const char *componentDesc, int **vector, int count) { if (!calendar->_cal) __CFCalendarSetupCal(calendar); if (calendar->_cal) { UErrorCode status = U_ZERO_ERROR; ucal_clear(calendar->_cal); UDate udate = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0); ucal_setMillis(calendar->_cal, udate, &status); char ch = *componentDesc; while (ch) { UCalendarDateFields field = __CFCalendarGetICUFieldCodeFromChar(ch); int value = ucal_get(calendar->_cal, field, &status); if (UCAL_MONTH == field) value++; *(*vector) = value; vector++; componentDesc++; ch = *componentDesc; } return U_SUCCESS(status) ? true : false; } return false; } Boolean _CFCalendarAddComponentsV(CFCalendarRef calendar, /* inout */ CFAbsoluteTime *atp, CFOptionFlags options, const char *componentDesc, int *vector, int count) { if (!calendar->_cal) __CFCalendarSetupCal(calendar); if (calendar->_cal) { UErrorCode status = U_ZERO_ERROR; ucal_clear(calendar->_cal); UDate udate = floor((*atp + kCFAbsoluteTimeIntervalSince1970) * 1000.0); ucal_setMillis(calendar->_cal, udate, &status); char ch = *componentDesc; while (ch) { UCalendarDateFields field = __CFCalendarGetICUFieldCodeFromChar(ch); int amount = *vector; if (options & kCFCalendarComponentsWrap) { ucal_roll(calendar->_cal, field, amount, &status); } else { ucal_add(calendar->_cal, field, amount, &status); } vector++; componentDesc++; ch = *componentDesc; } udate = ucal_getMillis(calendar->_cal, &status); *atp = (udate / 1000.0) - kCFAbsoluteTimeIntervalSince1970; return U_SUCCESS(status) ? true : false; } return false; } Boolean _CFCalendarGetComponentDifferenceV(CFCalendarRef calendar, CFAbsoluteTime startingAT, CFAbsoluteTime resultAT, CFOptionFlags options, const char *componentDesc, int **vector, int count) { if (!calendar->_cal) __CFCalendarSetupCal(calendar); if (calendar->_cal) { UErrorCode status = U_ZERO_ERROR; ucal_clear(calendar->_cal); UDate curr = floor((startingAT + kCFAbsoluteTimeIntervalSince1970) * 1000.0); UDate goal = floor((resultAT + kCFAbsoluteTimeIntervalSince1970) * 1000.0); ucal_setMillis(calendar->_cal, curr, &status); int direction = (startingAT <= resultAT) ? 1 : -1; char ch = *componentDesc; while (ch) { UCalendarDateFields field = __CFCalendarGetICUFieldCodeFromChar(ch); const int multiple_table[] = {0, 0, 16, 19, 24, 26, 24, 28, 14, 14, 14}; int multiple = direction * (1 << multiple_table[flsl(__CFCalendarGetCalendarUnitFromChar(ch)) - 1]); Boolean divide = false, alwaysDivide = false; int result = 0; while ((direction > 0 && curr < goal) || (direction < 0 && goal < curr)) { ucal_add(calendar->_cal, field, multiple, &status); UDate newcurr = ucal_getMillis(calendar->_cal, &status); if ((direction > 0 && curr < newcurr && newcurr <= goal) || (direction < 0 && newcurr < curr && goal <= newcurr)) { result += multiple; curr = newcurr; } else { // Either newcurr is going backwards, or not making // progress, or has overshot the goal; reset date // and try smaller multiples. ucal_setMillis(calendar->_cal, curr, &status); divide = true; // once we start overshooting the goal, the add at // smaller multiples will succeed at most once for // each multiple, so we reduce it every time through // the loop. if ((direction > 0 && goal < newcurr) || (direction < 0 && newcurr < goal)) alwaysDivide = true; } if (divide) { multiple = multiple / 2; if (0 == multiple) break; divide = alwaysDivide; } } *(*vector) = result; vector++; componentDesc++; ch = *componentDesc; } return U_SUCCESS(status) ? true : false; } return false; } Boolean CFCalendarComposeAbsoluteTime(CFCalendarRef calendar, /* out */ CFAbsoluteTime *atp, const char *componentDesc, ...) { va_list args; va_start(args, componentDesc); CF_OBJC_FUNCDISPATCH3(CFCalendarGetTypeID(), Boolean, calendar, "_composeAbsoluteTime:::", atp, componentDesc, args); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); int idx, cnt = strlen((char *)componentDesc); STACK_BUFFER_DECL(int, vector, cnt); for (idx = 0; idx < cnt; idx++) { int arg = va_arg(args, int); vector[idx] = arg; } va_end(args); return _CFCalendarComposeAbsoluteTimeV(calendar, atp, componentDesc, vector, cnt); } Boolean CFCalendarDecomposeAbsoluteTime(CFCalendarRef calendar, CFAbsoluteTime at, const char *componentDesc, ...) { va_list args; va_start(args, componentDesc); CF_OBJC_FUNCDISPATCH3(CFCalendarGetTypeID(), Boolean, calendar, "_decomposeAbsoluteTime:::", at, componentDesc, args); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); int idx, cnt = strlen((char *)componentDesc); STACK_BUFFER_DECL(int *, vector, cnt); for (idx = 0; idx < cnt; idx++) { int *arg = va_arg(args, int *); vector[idx] = arg; } va_end(args); return _CFCalendarDecomposeAbsoluteTimeV(calendar, at, componentDesc, vector, cnt); } Boolean CFCalendarAddComponents(CFCalendarRef calendar, /* inout */ CFAbsoluteTime *atp, CFOptionFlags options, const char *componentDesc, ...) { va_list args; va_start(args, componentDesc); CF_OBJC_FUNCDISPATCH4(CFCalendarGetTypeID(), Boolean, calendar, "_addComponents::::", atp, options, componentDesc, args); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); int idx, cnt = strlen((char *)componentDesc); STACK_BUFFER_DECL(int, vector, cnt); for (idx = 0; idx < cnt; idx++) { int arg = va_arg(args, int); vector[idx] = arg; } va_end(args); return _CFCalendarAddComponentsV(calendar, atp, options, componentDesc, vector, cnt); } Boolean CFCalendarGetComponentDifference(CFCalendarRef calendar, CFAbsoluteTime startingAT, CFAbsoluteTime resultAT, CFOptionFlags options, const char *componentDesc, ...) { va_list args; va_start(args, componentDesc); CF_OBJC_FUNCDISPATCH5(CFCalendarGetTypeID(), Boolean, calendar, "_diffComponents:::::", startingAT, resultAT, options, componentDesc, args); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); int idx, cnt = strlen((char *)componentDesc); STACK_BUFFER_DECL(int *, vector, cnt); for (idx = 0; idx < cnt; idx++) { int *arg = va_arg(args, int *); vector[idx] = arg; } va_end(args); Boolean ret = _CFCalendarGetComponentDifferenceV(calendar, startingAT, resultAT, options, componentDesc, vector, cnt); return ret; } Boolean CFCalendarGetTimeRangeOfUnit(CFCalendarRef calendar, CFCalendarUnit unit, CFAbsoluteTime at, CFAbsoluteTime *startp, CFTimeInterval *tip) { CF_OBJC_FUNCDISPATCH4(CFCalendarGetTypeID(), Boolean, calendar, "_rangeOfUnit:startTime:interval:forAT:", unit, startp, tip, at); __CFGenericValidateType(calendar, CFCalendarGetTypeID()); if (kCFCalendarUnitWeekdayOrdinal == unit) return false; if (kCFCalendarUnitWeekday == unit) unit = kCFCalendarUnitDay; if (!calendar->_cal) __CFCalendarSetupCal(calendar); if (calendar->_cal) { ucal_clear(calendar->_cal); __CFCalendarSetToFirstInstant(calendar, unit, at); UErrorCode status = U_ZERO_ERROR; UDate start = ucal_getMillis(calendar->_cal, &status); UCalendarDateFields field = __CFCalendarGetICUFieldCode(unit); ucal_add(calendar->_cal, field, 1, &status); UDate end = ucal_getMillis(calendar->_cal, &status); if (end == start && kCFCalendarUnitEra == unit) { // ICU refuses to do the addition, probably because we are // at the limit of UCAL_ERA. Use alternate strategy. CFIndex limit = ucal_getLimit(calendar->_cal, UCAL_YEAR, UCAL_MAXIMUM, &status); if (100000 < limit) limit = 100000; ucal_add(calendar->_cal, UCAL_YEAR, limit, &status); end = ucal_getMillis(calendar->_cal, &status); } if (U_SUCCESS(status)) { if (startp) *startp = (double)start / 1000.0 - kCFAbsoluteTimeIntervalSince1970; if (tip) *tip = (double)(end - start) / 1000.0; return true; } } return false; } #undef BUFFER_SIZE