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; }
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; }
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 _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; }
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; }
/* * ICU's Universal Time Scale is designed to be tick-for-tick compatible with * .Net System.DateTime. Verify that this is so for the * .Net-supported date range (years 1-9999 AD). * This requires a proleptic Gregorian calendar because that's what .Net uses. * Proleptic: No Julian/Gregorian switchover, or a switchover before * any date that we test, that is, before 0001 AD. */ static void TestDotNet() { static const UChar utc[] = { 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0 }; /* "Etc/GMT" */ const int32_t dayMillis = 86400 * INT64_C(1000); /* 1 day = 86400 seconds */ const int64_t dayTicks = 86400 * INT64_C(10000000); const DotNetDateTimeTicks *dt; UCalendar *cal; UErrorCode errorCode; UDate icuDate; int64_t ticks, millis; int32_t i; /* Open a proleptic Gregorian calendar. */ errorCode = U_ZERO_ERROR; cal = ucal_open(utc, -1, "", UCAL_GREGORIAN, &errorCode); ucal_setGregorianChange(cal, -1000000 * (dayMillis * (UDate)1), &errorCode); if(U_FAILURE(errorCode)) { log_err("ucal_open(UTC/proleptic Gregorian) failed: %s\n", u_errorName(errorCode)); ucal_close(cal); return; } for(i = 0; i < LENGTHOF(dotNetDateTimeTicks); ++i) { /* Test conversion from .Net/Universal time to ICU time. */ dt = dotNetDateTimeTicks + i; millis = utmscale_toInt64(dt->ticks, UDTS_ICU4C_TIME, &errorCode); ucal_clear(cal); ucal_setDate(cal, dt->year, dt->month - 1, dt->day, &errorCode); /* Java & ICU use January = month 0. */ icuDate = ucal_getMillis(cal, &errorCode); if(millis != icuDate) { /* Print days not millis to stay within printf() range. */ log_err("utmscale_toInt64(ticks[%d], ICU4C)=%dd != %dd=ucal_getMillis(%04d-%02d-%02d)\n", (int)i, (int)(millis/dayMillis), (int)(icuDate/dayMillis), (int)dt->year, (int)dt->month, (int)dt->day); } /* Test conversion from ICU time to .Net/Universal time. */ ticks = utmscale_fromInt64((int64_t)icuDate, UDTS_ICU4C_TIME, &errorCode); if(ticks != dt->ticks) { /* Print days not ticks to stay within printf() range. */ log_err("utmscale_fromInt64(date[%d], ICU4C)=%dd != %dd=.Net System.DateTime(%04d-%02d-%02d).Ticks\n", (int)i, (int)(ticks/dayTicks), (int)(dt->ticks/dayTicks), (int)dt->year, (int)dt->month, (int)dt->day); } } ucal_close(cal); }
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; }
static void TestDateFormatCalendar() { UDateFormat *date=0, *time=0, *full=0; UCalendar *cal=0; UChar buf[256]; char cbuf[256]; int32_t pos; UDate when; UErrorCode ec = U_ZERO_ERROR; ctest_setTimeZone(NULL, &ec); /* Create a formatter for date fields. */ date = udat_open(UDAT_NONE, UDAT_SHORT, "en_US", NULL, 0, NULL, 0, &ec); if (U_FAILURE(ec)) { log_data_err("FAIL: udat_open(NONE, SHORT, en_US) failed with %s (Are you missing data?)\n", u_errorName(ec)); goto FAIL; } /* Create a formatter for time fields. */ time = udat_open(UDAT_SHORT, UDAT_NONE, "en_US", NULL, 0, NULL, 0, &ec); if (U_FAILURE(ec)) { log_err("FAIL: udat_open(SHORT, NONE, en_US) failed with %s\n", u_errorName(ec)); goto FAIL; } /* Create a full format for output */ full = udat_open(UDAT_FULL, UDAT_FULL, "en_US", NULL, 0, NULL, 0, &ec); if (U_FAILURE(ec)) { log_err("FAIL: udat_open(FULL, FULL, en_US) failed with %s\n", u_errorName(ec)); goto FAIL; } /* Create a calendar */ cal = ucal_open(NULL, 0, "en_US", UCAL_GREGORIAN, &ec); if (U_FAILURE(ec)) { log_err("FAIL: ucal_open(en_US) failed with %s\n", u_errorName(ec)); goto FAIL; } /* Parse the date */ ucal_clear(cal); u_uastrcpy(buf, "4/5/2001"); pos = 0; udat_parseCalendar(date, cal, buf, -1, &pos, &ec); if (U_FAILURE(ec)) { log_err("FAIL: udat_parseCalendar(4/5/2001) failed at %d with %s\n", pos, u_errorName(ec)); goto FAIL; } /* Parse the time */ u_uastrcpy(buf, "5:45 PM"); pos = 0; udat_parseCalendar(time, cal, buf, -1, &pos, &ec); if (U_FAILURE(ec)) { log_err("FAIL: udat_parseCalendar(17:45) failed at %d with %s\n", pos, u_errorName(ec)); goto FAIL; } /* Check result */ when = ucal_getMillis(cal, &ec); if (U_FAILURE(ec)) { log_err("FAIL: ucal_getMillis() failed with %s\n", u_errorName(ec)); goto FAIL; } udat_format(full, when, buf, sizeof(buf), NULL, &ec); if (U_FAILURE(ec)) { log_err("FAIL: udat_format() failed with %s\n", u_errorName(ec)); goto FAIL; } u_austrcpy(cbuf, buf); /* Thursday, April 5, 2001 5:45:00 PM PDT 986517900000 */ if (when == 986517900000.0) { log_verbose("Ok: Parsed result: %s\n", cbuf); } else { log_err("FAIL: Parsed result: %s, exp 4/5/2001 5:45 PM\n", cbuf); } FAIL: udat_close(date); udat_close(time); udat_close(full); ucal_close(cal); ctest_resetTimeZone(); }
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; }
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; }