From aa481bef5106f98a186f7a2bff5acf29dbb33005 Mon Sep 17 00:00:00 2001 From: Todd White Date: Wed, 22 Jul 2026 10:05:06 -0400 Subject: [PATCH] CFCalendar: implement ordinality and range of a unit CFCalendarGetOrdinalityOfUnit and CFCalendarGetRangeOfUnit were stubs that returned kCFNotFound. Map the smaller unit measured within the bigger one to the corresponding ICU field and read its value, or its actual minimum and maximum, for the date in question. --- Source/CFCalendar.c | 65 +++++++++++++++++++++++++++++++++-- Tests/CFCalendar/ordinality.m | 41 ++++++++++++++++++++++ 2 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 Tests/CFCalendar/ordinality.m diff --git a/Source/CFCalendar.c b/Source/CFCalendar.c index 3c44716..3c7507c 100644 --- a/Source/CFCalendar.c +++ b/Source/CFCalendar.c @@ -795,18 +795,79 @@ CFCalendarGetTimeRangeOfUnit (CFCalendarRef cal, CFCalendarUnit unit, return true; } +/* The ICU field that represents smallerUnit measured within biggerUnit. */ +static UCalendarDateFields +CFCalendarCompoundField (CFCalendarUnit smaller, CFCalendarUnit bigger) +{ + switch (smaller) + { + case kCFCalendarUnitDay: + case kCFCalendarUnitWeekday: + if (bigger == kCFCalendarUnitYear) + return UCAL_DAY_OF_YEAR; + if (bigger == kCFCalendarUnitWeek) + return UCAL_DAY_OF_WEEK; + return UCAL_DATE; + case kCFCalendarUnitMonth: + return UCAL_MONTH; + case kCFCalendarUnitWeek: + if (bigger == kCFCalendarUnitMonth) + return UCAL_WEEK_OF_MONTH; + return UCAL_WEEK_OF_YEAR; + case kCFCalendarUnitHour: + return UCAL_HOUR_OF_DAY; + case kCFCalendarUnitMinute: + return UCAL_MINUTE; + case kCFCalendarUnitSecond: + return UCAL_SECOND; + default: + return (UCalendarDateFields) -1; + } +} + CFRange CFCalendarGetRangeOfUnit (CFCalendarRef cal, CFCalendarUnit smallerUnit, CFCalendarUnit biggerUnit, CFAbsoluteTime at) { - return CFRangeMake (kCFNotFound, kCFNotFound); + UCalendarDateFields field; + UErrorCode err = U_ZERO_ERROR; + int32_t min, max; + + field = CFCalendarCompoundField (smallerUnit, biggerUnit); + if (field == (UCalendarDateFields) -1) + return CFRangeMake (kCFNotFound, kCFNotFound); + + CFCalendarOpenUCalendar (cal); + ucal_setMillis (cal->_ucal, ABSOLUTETIME_TO_UDATE (at), &err); + min = ucal_getLimit (cal->_ucal, field, UCAL_ACTUAL_MINIMUM, &err); + max = ucal_getLimit (cal->_ucal, field, UCAL_ACTUAL_MAXIMUM, &err); + if (U_FAILURE (err)) + return CFRangeMake (kCFNotFound, kCFNotFound); + + /* ICU numbers months from zero. */ + return CFRangeMake (field == UCAL_MONTH ? min + 1 : min, max - min + 1); } CFIndex CFCalendarGetOrdinalityOfUnit (CFCalendarRef cal, CFCalendarUnit smallerUnit, CFCalendarUnit biggerUnit, CFAbsoluteTime at) { - return kCFNotFound; + UCalendarDateFields field; + UErrorCode err = U_ZERO_ERROR; + int32_t value; + + field = CFCalendarCompoundField (smallerUnit, biggerUnit); + if (field == (UCalendarDateFields) -1) + return kCFNotFound; + + CFCalendarOpenUCalendar (cal); + ucal_setMillis (cal->_ucal, ABSOLUTETIME_TO_UDATE (at), &err); + value = ucal_get (cal->_ucal, field, &err); + if (U_FAILURE (err)) + return kCFNotFound; + + /* ICU numbers months from zero. */ + return field == UCAL_MONTH ? value + 1 : value; } static CFRange diff --git a/Tests/CFCalendar/ordinality.m b/Tests/CFCalendar/ordinality.m new file mode 100644 index 0000000..1e2ca5a --- /dev/null +++ b/Tests/CFCalendar/ordinality.m @@ -0,0 +1,41 @@ +#include "CoreFoundation/CFCalendar.h" +#include "CoreFoundation/CFTimeZone.h" +#include "../CFTesting.h" + +/* CFCalendarGetOrdinalityOfUnit and CFCalendarGetRangeOfUnit. Times are + built directly as offsets from the reference date. */ + +int main (void) +{ + double day = 86400.0; + CFTimeZoneRef gmt = CFTimeZoneCreateWithTimeIntervalFromGMT (NULL, 0.0); + CFCalendarRef cal = CFCalendarCreateWithIdentifier (NULL, + kCFGregorianCalendar); + CFRange r; + + CFCalendarSetTimeZone (cal, gmt); + + r = CFCalendarGetRangeOfUnit (cal, kCFCalendarUnitDay, kCFCalendarUnitMonth, + 45 * day); /* 2001-02-15 */ + PASS_CF (r.location == 1 && r.length == 28, + "The days of February 2001 range from 1 to 28."); + + r = CFCalendarGetRangeOfUnit (cal, kCFCalendarUnitDay, kCFCalendarUnitMonth, + 63 * day); /* 2001-03-05 */ + PASS_CF (r.location == 1 && r.length == 31, + "The days of March 2001 range from 1 to 31."); + + PASS_CF (CFCalendarGetOrdinalityOfUnit (cal, kCFCalendarUnitDay, + kCFCalendarUnitMonth, 45 * day) == 15, + "2001-02-15 is the 15th day of the month."); + PASS_CF (CFCalendarGetOrdinalityOfUnit (cal, kCFCalendarUnitDay, + kCFCalendarUnitYear, 45 * day) == 46, + "2001-02-15 is the 46th day of the year."); + PASS_CF (CFCalendarGetOrdinalityOfUnit (cal, kCFCalendarUnitMonth, + kCFCalendarUnitYear, 45 * day) == 2, + "2001-02-15 is in the 2nd month of the year."); + + CFRelease (cal); + CFRelease (gmt); + return 0; +}