diff --git a/ChangeLog b/ChangeLog index 0bd27af92..ad4389c7b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2015-11-30: Niels Grewe + + * Source/NSCalendar.h + * Headers/Foundation/NSCalendar.h: + Implement -components:fromDate:toDate:options:. (Not handling + NSWrapCalendarComponents yet.) + * Tests/base/NSCalendar/component-diff.m: + Test case for new functionality. + 2015-11-22 Riccardo Mottola * Source/NSFileManager.m (createDirectoryAtPath) diff --git a/Headers/Foundation/NSCalendar.h b/Headers/Foundation/NSCalendar.h index 5663750a6..8f6bd31a1 100644 --- a/Headers/Foundation/NSCalendar.h +++ b/Headers/Foundation/NSCalendar.h @@ -194,6 +194,17 @@ enum - (NSDateComponents *) components: (NSUInteger) unitFlags fromDate: (NSDate *) date; +/** + * Compute the different between the specified components in the two dates. + * Values are summed up as long as now higher-granularity unit is specified. + * That means if you want to extract the year and the day from two dates + * which are 13 months + 1 day apart, you will get 1 as the result for the year + * but the rest of the difference in days. (29 <= x <= 32, depending on the + * month). + * + * Please note that the NSWrapCalendarComponents option that should affect the + * calculations is not presently supported. + */ - (NSDateComponents *) components: (NSUInteger) unitFlags fromDate: (NSDate *) startingDate toDate: (NSDate *) resultDate diff --git a/Source/NSCalendar.m b/Source/NSCalendar.m index 987b5c9b8..9305743d3 100644 --- a/Source/NSCalendar.m +++ b/Source/NSCalendar.m @@ -367,12 +367,81 @@ static NSRecursiveLock *classLock = nil; #endif } +/* + * Convenience macro for field extraction. + * TODO: We need to implement NSWrapCalendarComponents, but it is unclear how that + * actually works. + */ +#define COMPONENT_DIFF(cal, units, components, toDate, nsunit, setSel, uunit, err) \ +do { if (units & nsunit) \ + { \ + int32_t uunit ## Diff = ucal_getFieldDifference(cal, toDate, uunit, &err); \ + if (U_FAILURE(err)) \ + { \ + RELEASE(components); \ + return nil; \ + } \ + [components setSel uunit ## Diff]; \ + } \ +} while (0) + + - (NSDateComponents *) components: (NSUInteger) unitFlags fromDate: (NSDate *) startingDate toDate: (NSDate *) resultDate options: (NSUInteger) opts { +#if GS_USE_ICU == 1 + NSDateComponents *comps = nil; + UErrorCode err = U_ZERO_ERROR; + UDate udateFrom = (UDate)floor([startingDate timeIntervalSince1970] * 1000.0); + UDate udateTo = (UDate)floor([resultDate timeIntervalSince1970] * 1000.0); + ucal_setMillis (my->cal, udateFrom, &err); + if (U_FAILURE(err)) + { + return nil; + } + comps = [[NSDateComponents alloc] init]; + /* + * Since the ICU field difference function automatically advances the calendar as appropriate, we + * need to process the units from the largest to the smallest. + */ + COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, NSEraCalendarUnit, setEra:, UCAL_ERA, err); + COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, NSYearCalendarUnit, setYear:, UCAL_YEAR, err); + COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, NSMonthCalendarUnit, setMonth:, UCAL_MONTH, err); + COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, NSWeekOfYearCalendarUnit, setWeek:, UCAL_WEEK_OF_YEAR, err); + if (!(unitFlags & NSWeekOfYearCalendarUnit)) + { + /* We must avoid setting the same unit twice (it would be zero because + * of the automatic advancement. + */ + COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, NSWeekCalendarUnit, setWeek:, UCAL_WEEK_OF_YEAR, err); + } + COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, NSWeekOfMonthCalendarUnit, setWeekOfMonth:, UCAL_WEEK_OF_MONTH, err); + COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, NSDayCalendarUnit, setDay:, UCAL_DAY_OF_MONTH, err); + COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, NSWeekdayOrdinalCalendarUnit, setWeekdayOrdinal:, UCAL_DAY_OF_WEEK_IN_MONTH, err); + COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, NSWeekdayCalendarUnit, setWeekday:, UCAL_DAY_OF_WEEK, err); + COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, NSHourCalendarUnit, setHour:, UCAL_HOUR_OF_DAY, err); + COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, NSMinuteCalendarUnit, setMinute:, UCAL_MINUTE, err); + COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, NSSecondCalendarUnit, setSecond:, UCAL_SECOND, err); +# if 0 + if (unitFlags & NSCalendarUnitNanosecond) + { + int32_t ns = ucal_getFieldDifference(my->cal, udateTo, UCAL_MILLISECOND, &err) * 1000000; + if (U_FAILURE(err)) + { + RELEASE(comps); + return nil; + } + [comps setNanosecond: ns]; + } +# endif + return AUTORELEASE(comps); + + +#else return nil; +#endif } - (NSDate *) dateByAddingComponents: (NSDateComponents *) comps diff --git a/Tests/base/NSCalendar/component-diff.m b/Tests/base/NSCalendar/component-diff.m new file mode 100644 index 000000000..dabecdd3e --- /dev/null +++ b/Tests/base/NSCalendar/component-diff.m @@ -0,0 +1,55 @@ +#import "Testing.h" +#import "ObjectTesting.h" +#import +#import +#import +#include + +#if defined(GS_USE_ICU) +#define NSCALENDAR_SUPPORTED GS_USE_ICU +#else +#define NSCALENDAR_SUPPORTED 1 /* Assume Apple support */ +#endif + +int main() +{ + NSDateComponents *comps; + NSCalendar *cal; + NSDate *date; + NSDate *date2; + + START_SET("NSCalendar date component differences") + if (!NSCALENDAR_SUPPORTED) + SKIP("NSCalendar not supported\nThe ICU library was not available when GNUstep-base was built") + + cal = [[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar]; + [cal setFirstWeekday: 1]; + + date = [NSDate dateWithString: @"2015-01-01 01:01:01 +0100"]; + date2 = [NSDate dateWithString: @"2015-02-03 04:05:06 +0100"]; + + comps = [cal components: NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit + | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit + fromDate: date + toDate: date2 + options: 0]; + PASS([comps year] == 0, "year difference correct"); + PASS([comps month] == 1, "month difference correct"); + PASS([comps day] == 2, "day difference correct"); + PASS([comps hour] == 3, "hour difference correct"); + PASS([comps minute] == 4, "minute difference correct"); + PASS([comps second] == 5, "second difference correct"); + + comps = [cal components: NSDayCalendarUnit + fromDate: date + toDate: date2 + options: 0]; + PASS([comps month] == NSNotFound, "no month returned if not requested"); + PASS([comps day] == 33, "day difference without larger unit correct"); + + + RELEASE(cal); + + END_SET("NSCalendar date component differences") + return 0; +}