Started to rewrite NSCalendar to handle more components correctly.

This commit is contained in:
fredkiefer 2020-04-26 00:32:49 +02:00
parent 82e9ddc21f
commit 4fe6cb20a1

View file

@ -46,31 +46,26 @@
#if GS_USE_ICU == 1 #if GS_USE_ICU == 1
static UCalendarDateFields _NSCalendarUnitToDateField (NSCalendarUnit unit) static UCalendarDateFields _NSCalendarUnitToDateField (NSCalendarUnit unit)
{ {
// I'm just going to go in the order they appear in Apple's documentation if (unit & NSCalendarUnitEra)
if (unit & NSEraCalendarUnit)
return UCAL_ERA; return UCAL_ERA;
if (unit & NSYearCalendarUnit) if (unit & NSCalendarUnitYear)
return UCAL_YEAR; return UCAL_YEAR;
if (unit & NSMonthCalendarUnit) if (unit & NSCalendarUnitMonth)
return UCAL_MONTH; return UCAL_MONTH;
if (unit & NSDayCalendarUnit) if (unit & NSCalendarUnitDay)
return UCAL_DAY_OF_MONTH; return UCAL_DAY_OF_MONTH;
if (unit & NSHourCalendarUnit) if (unit & NSCalendarUnitHour)
return UCAL_HOUR_OF_DAY; return UCAL_HOUR_OF_DAY;
if (unit & NSMinuteCalendarUnit) if (unit & NSCalendarUnitMinute)
return UCAL_MINUTE; return UCAL_MINUTE;
if (unit & NSSecondCalendarUnit) if (unit & NSCalendarUnitSecond)
return UCAL_SECOND; return UCAL_SECOND;
if (unit & NSWeekCalendarUnit) if (unit & NSCalendarUnitWeekOfYear)
return UCAL_WEEK_OF_YEAR; return UCAL_WEEK_OF_YEAR;
if (unit & NSWeekdayCalendarUnit) if (unit & NSCalendarUnitWeekday)
return UCAL_DAY_OF_WEEK; return UCAL_DAY_OF_WEEK;
if (unit & NSWeekdayOrdinalCalendarUnit) if (unit & NSCalendarUnitWeekdayOrdinal)
// FIXME: Is this right???
return UCAL_DAY_OF_WEEK_IN_MONTH; return UCAL_DAY_OF_WEEK_IN_MONTH;
// ICU doesn't include a quarter DateField...
if (unit & NSQuarterCalendarUnit)
return (UCAL_MONTH + 2) / 3;
return -1; return -1;
} }
#endif /* GS_USE_ICU */ #endif /* GS_USE_ICU */
@ -97,25 +92,32 @@ typedef struct {
#define TZ_NAME_LENGTH 1024 #define TZ_NAME_LENGTH 1024
@implementation NSCalendar (PrivateMethods) @implementation NSCalendar (PrivateMethods)
- (void) _resetCalendar
{
#if GS_USE_ICU == 1 #if GS_USE_ICU == 1
- (void *) _openCalendarFor: (NSTimeZone *)timeZone
{
NSString *tzName; NSString *tzName;
NSUInteger tzLen; NSUInteger tzLen;
unichar cTzId[TZ_NAME_LENGTH]; unichar cTzId[TZ_NAME_LENGTH];
const char *cLocaleId; const char *cLocaleId;
UErrorCode err = U_ZERO_ERROR; UErrorCode err = U_ZERO_ERROR;
UCalendarType type;
if (my->cal != NULL)
ucal_close (my->cal);
cLocaleId = [my->localeID UTF8String]; cLocaleId = [my->localeID UTF8String];
tzName = [my->tz name]; tzName = [timeZone name];
tzLen = [tzName length]; tzLen = [tzName length];
if (tzLen > TZ_NAME_LENGTH) if (tzLen > TZ_NAME_LENGTH)
tzLen = TZ_NAME_LENGTH; {
tzLen = TZ_NAME_LENGTH;
}
[tzName getCharacters: cTzId range: NSMakeRange(0, tzLen)]; [tzName getCharacters: cTzId range: NSMakeRange(0, tzLen)];
if ([NSGregorianCalendar isEqualToString: my->identifier])
{
type = UCAL_GREGORIAN;
}
else
{
#ifndef UCAL_DEFAULT #ifndef UCAL_DEFAULT
/* /*
* Older versions of ICU used UCAL_TRADITIONAL rather than UCAL_DEFAULT * Older versions of ICU used UCAL_TRADITIONAL rather than UCAL_DEFAULT
@ -123,8 +125,24 @@ typedef struct {
*/ */
#define UCAL_DEFAULT UCAL_TRADITIONAL #define UCAL_DEFAULT UCAL_TRADITIONAL
#endif #endif
my->cal = type = UCAL_DEFAULT;
ucal_open ((const UChar *)cTzId, tzLen, cLocaleId, UCAL_DEFAULT, &err); // FIXME: Should use uloc_setKeywordValue() here
}
return ucal_open ((const UChar *)cTzId, tzLen, cLocaleId, type, &err);
}
#endif
- (void) _resetCalendar
{
#if GS_USE_ICU == 1
if (my->cal != NULL)
{
ucal_close (my->cal);
}
my->cal = [self _openCalendarFor: my->tz];
if (NSNotFound == my->firstWeekday) if (NSNotFound == my->firstWeekday)
{ {
@ -195,8 +213,10 @@ static NSRecursiveLock *classLock = nil;
+ (void) initialize + (void) initialize
{ {
if (self == [NSLocale class]) if (self == [NSCalendar class])
classLock = [NSRecursiveLock new]; {
classLock = [NSRecursiveLock new];
}
} }
+ (void) defaultsDidChange: (NSNotification*)n + (void) defaultsDidChange: (NSNotification*)n
@ -246,15 +266,14 @@ static NSRecursiveLock *classLock = nil;
return AUTORELEASE(result); return AUTORELEASE(result);
} }
- (id) init
{
self = [self initWithCalendarIdentifier: nil];
return self;
}
+ (id) calendarWithIdentifier: (NSString *) string + (id) calendarWithIdentifier: (NSString *) string
{ {
return [[[self alloc] initWithCalendarIdentifier: string] autorelease]; return AUTORELEASE([[self alloc] initWithCalendarIdentifier: string]);
}
- (id) init
{
return [self initWithCalendarIdentifier: nil];
} }
- (id) initWithCalendarIdentifier: (NSString *) string - (id) initWithCalendarIdentifier: (NSString *) string
@ -265,44 +284,7 @@ static NSRecursiveLock *classLock = nil;
my->firstWeekday = NSNotFound; my->firstWeekday = NSNotFound;
my->minimumDaysInFirstWeek = NSNotFound; my->minimumDaysInFirstWeek = NSNotFound;
ASSIGN(my->identifier, string);
if ([string isEqualToString: NSGregorianCalendar])
my->identifier = NSGregorianCalendar;
else if ([string isEqualToString: NSBuddhistCalendar])
my->identifier = NSBuddhistCalendar;
else if ([string isEqualToString: NSChineseCalendar])
my->identifier = NSChineseCalendar;
else if ([string isEqualToString: NSHebrewCalendar])
my->identifier = NSHebrewCalendar;
else if ([string isEqualToString: NSIslamicCalendar])
my->identifier = NSIslamicCalendar;
else if ([string isEqualToString: NSIslamicCivilCalendar])
my->identifier = NSIslamicCivilCalendar;
else if ([string isEqualToString: NSJapaneseCalendar])
my->identifier = NSJapaneseCalendar;
else if ([string isEqualToString: NSRepublicOfChinaCalendar])
my->identifier = NSRepublicOfChinaCalendar;
else if ([string isEqualToString: NSPersianCalendar])
my->identifier = NSPersianCalendar;
else if ([string isEqualToString: NSIndianCalendar])
my->identifier = NSIndianCalendar;
else if ([string isEqualToString: NSISO8601Calendar])
my->identifier = NSISO8601Calendar;
else if ([string isEqualToString: NSCalendarIdentifierCoptic])
my->identifier = NSGregorianCalendar; // TODO: unimplemented
else if ([string isEqualToString: NSCalendarIdentifierEthiopicAmeteMihret])
my->identifier = NSGregorianCalendar; // TODO: unimplemented
else if ([string isEqualToString: NSCalendarIdentifierEthiopicAmeteAlem])
my->identifier = NSGregorianCalendar; // TODO: unimplemented
else if ([string isEqualToString: NSCalendarIdentifierIslamicTabular])
my->identifier = NSGregorianCalendar; // TODO: unimplemented
else if ([string isEqualToString: NSCalendarIdentifierIslamicUmmAlQura])
my->identifier = NSGregorianCalendar; // TODO: unimplemented
else
{
RELEASE(self);
return nil;
}
// It's much easier to keep a copy of the NSLocale's string representation // It's much easier to keep a copy of the NSLocale's string representation
// than to have to build it everytime we have to open a UCalendar. // than to have to build it everytime we have to open a UCalendar.
@ -345,34 +327,68 @@ static NSRecursiveLock *classLock = nil;
udate = (UDate)floor([date timeIntervalSince1970] * 1000.0); udate = (UDate)floor([date timeIntervalSince1970] * 1000.0);
ucal_setMillis (my->cal, udate, &err); ucal_setMillis (my->cal, udate, &err);
if (U_FAILURE(err)) if (U_FAILURE(err))
return nil; {
return nil;
}
comps = [[NSDateComponents alloc] init]; comps = [[NSDateComponents alloc] init];
if (unitFlags & NSEraCalendarUnit) if (unitFlags & NSCalendarUnitEra)
[comps setEra: ucal_get(my->cal, UCAL_ERA, &err)]; {
if (unitFlags & NSYearCalendarUnit) [comps setEra: ucal_get(my->cal, UCAL_ERA, &err)];
[comps setYear: ucal_get(my->cal, UCAL_YEAR, &err)]; }
if (unitFlags & NSMonthCalendarUnit) if (unitFlags & NSCalendarUnitYear)
[comps setMonth: ucal_get(my->cal, UCAL_MONTH, &err)+1]; {
if (unitFlags & NSDayCalendarUnit) [comps setYear: ucal_get(my->cal, UCAL_YEAR, &err)];
[comps setDay: ucal_get(my->cal, UCAL_DAY_OF_MONTH, &err)]; }
if (unitFlags & NSHourCalendarUnit) if (unitFlags & NSCalendarUnitMonth)
[comps setHour: ucal_get(my->cal, UCAL_HOUR_OF_DAY, &err)]; {
if (unitFlags & NSMinuteCalendarUnit) [comps setMonth: ucal_get(my->cal, UCAL_MONTH, &err)+1];
[comps setMinute: ucal_get(my->cal, UCAL_MINUTE, &err)]; }
if (unitFlags & NSSecondCalendarUnit) if (unitFlags & NSCalendarUnitDay)
[comps setSecond: ucal_get(my->cal, UCAL_SECOND, &err)]; {
if (unitFlags & (NSWeekCalendarUnit|NSWeekOfYearCalendarUnit)) [comps setDay: ucal_get(my->cal, UCAL_DAY_OF_MONTH, &err)];
[comps setWeek: ucal_get(my->cal, UCAL_WEEK_OF_YEAR, &err)]; }
if (unitFlags & NSWeekdayCalendarUnit) if (unitFlags & NSCalendarUnitHour)
[comps setWeekday: ucal_get(my->cal, UCAL_DAY_OF_WEEK, &err)]; {
if (unitFlags & NSWeekdayOrdinalCalendarUnit) [comps setHour: ucal_get(my->cal, UCAL_HOUR_OF_DAY, &err)];
[comps setWeekdayOrdinal: }
ucal_get(my->cal, UCAL_DAY_OF_WEEK_IN_MONTH, &err)]; if (unitFlags & NSCalendarUnitMinute)
if (unitFlags & NSWeekOfMonthCalendarUnit) {
[comps setWeekOfMonth: ucal_get(my->cal, UCAL_WEEK_OF_MONTH, &err)]; [comps setMinute: ucal_get(my->cal, UCAL_MINUTE, &err)];
if (unitFlags & NSYearForWeekOfYearCalendarUnit) }
[comps setYearForWeekOfYear: ucal_get(my->cal, UCAL_YEAR_WOY, &err)]; if (unitFlags & NSCalendarUnitSecond)
{
[comps setSecond: ucal_get(my->cal, UCAL_SECOND, &err)];
}
if (unitFlags & (NSWeekCalendarUnit | NSCalendarUnitWeekOfYear))
{
[comps setWeek: ucal_get(my->cal, UCAL_WEEK_OF_YEAR, &err)];
}
if (unitFlags & NSCalendarUnitWeekday)
{
[comps setWeekday: ucal_get(my->cal, UCAL_DAY_OF_WEEK, &err)];
}
if (unitFlags & NSCalendarUnitWeekdayOrdinal)
{
[comps setWeekdayOrdinal:
ucal_get(my->cal, UCAL_DAY_OF_WEEK_IN_MONTH, &err)];
}
if (unitFlags & NSCalendarUnitQuarter)
{
[comps setQuarter: (ucal_get(my->cal, UCAL_MONTH, &err) + 2) / 3];
}
if (unitFlags & NSCalendarUnitWeekOfMonth)
{
[comps setWeekOfMonth: ucal_get(my->cal, UCAL_WEEK_OF_MONTH, &err)];
}
if (unitFlags & NSCalendarUnitYearForWeekOfYear)
{
[comps setYearForWeekOfYear: ucal_get(my->cal, UCAL_YEAR_WOY, &err)];
}
if (unitFlags & NSCalendarUnitNanosecond)
{
[comps setNanosecond: ucal_get(my->cal, UCAL_MILLISECOND, &err) * 1000];
}
return AUTORELEASE(comps); return AUTORELEASE(comps);
#else #else
@ -428,14 +444,14 @@ do \
* the largest to the smallest. * the largest to the smallest.
*/ */
COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo,
NSEraCalendarUnit, setEra:, UCAL_ERA, err); NSCalendarUnitEra, setEra:, UCAL_ERA, err);
COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo,
NSYearCalendarUnit, setYear:, UCAL_YEAR, err); NSCalendarUnitYear, setYear:, UCAL_YEAR, err);
COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo,
NSMonthCalendarUnit, setMonth:, UCAL_MONTH, err); NSCalendarUnitMonth, setMonth:, UCAL_MONTH, err);
COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo,
NSWeekOfYearCalendarUnit, setWeek:, UCAL_WEEK_OF_YEAR, err); NSCalendarUnitWeekOfYear, setWeek:, UCAL_WEEK_OF_YEAR, err);
if (!(unitFlags & NSWeekOfYearCalendarUnit)) if (!(unitFlags & NSCalendarUnitWeekOfYear))
{ {
/* We must avoid setting the same unit twice (it would be zero because /* We must avoid setting the same unit twice (it would be zero because
* of the automatic advancement. * of the automatic advancement.
@ -444,20 +460,20 @@ do \
NSWeekCalendarUnit, setWeek:, UCAL_WEEK_OF_YEAR, err); NSWeekCalendarUnit, setWeek:, UCAL_WEEK_OF_YEAR, err);
} }
COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo,
NSWeekOfMonthCalendarUnit, setWeekOfMonth:, UCAL_WEEK_OF_MONTH, err); NSCalendarUnitWeekOfMonth, setWeekOfMonth:, UCAL_WEEK_OF_MONTH, err);
COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo,
NSDayCalendarUnit, setDay:, UCAL_DAY_OF_MONTH, err); NSCalendarUnitDay, setDay:, UCAL_DAY_OF_MONTH, err);
COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo,
NSWeekdayOrdinalCalendarUnit, setWeekdayOrdinal:, NSCalendarUnitWeekdayOrdinal, setWeekdayOrdinal:,
UCAL_DAY_OF_WEEK_IN_MONTH, err); UCAL_DAY_OF_WEEK_IN_MONTH, err);
COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo,
NSWeekdayCalendarUnit, setWeekday:, UCAL_DAY_OF_WEEK, err); NSCalendarUnitWeekday, setWeekday:, UCAL_DAY_OF_WEEK, err);
COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo,
NSHourCalendarUnit, setHour:, UCAL_HOUR_OF_DAY, err); NSCalendarUnitHour, setHour:, UCAL_HOUR_OF_DAY, err);
COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo,
NSMinuteCalendarUnit, setMinute:, UCAL_MINUTE, err); NSCalendarUnitMinute, setMinute:, UCAL_MINUTE, err);
COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo,
NSSecondCalendarUnit, setSecond:, UCAL_SECOND, err); NSCalendarUnitSecond, setSecond:, UCAL_SECOND, err);
# if 0 # if 0
if (unitFlags & NSCalendarUnitNanosecond) if (unitFlags & NSCalendarUnitNanosecond)
{ {
@ -600,6 +616,10 @@ do \
{ {
ucal_set (my->cal, UCAL_DAY_OF_WEEK, (int32_t)amount); ucal_set (my->cal, UCAL_DAY_OF_WEEK, (int32_t)amount);
} }
if ((amount = [comps weekdayOrdinal]) != NSDateComponentUndefined)
{
ucal_set (my->cal, UCAL_DAY_OF_WEEK_IN_MONTH, (int32_t)amount);
}
if ((amount = [comps weekOfMonth]) != NSDateComponentUndefined) if ((amount = [comps weekOfMonth]) != NSDateComponentUndefined)
{ {
ucal_set (my->cal, UCAL_WEEK_OF_MONTH, (int32_t)amount); ucal_set (my->cal, UCAL_WEEK_OF_MONTH, (int32_t)amount);
@ -608,6 +628,10 @@ do \
{ {
ucal_set (my->cal, UCAL_YEAR_WOY, (int32_t)amount); ucal_set (my->cal, UCAL_YEAR_WOY, (int32_t)amount);
} }
if ((amount = [comps nanosecond]) != NSDateComponentUndefined)
{
ucal_set (my->cal, UCAL_MILLISECOND, (int32_t)(amount / 1000));
}
udate = ucal_getMillis (my->cal, &err); udate = ucal_getMillis (my->cal, &err);
if (U_FAILURE(err)) if (U_FAILURE(err))
@ -664,62 +688,67 @@ do \
- (void) setTimeZone: (NSTimeZone *) tz - (void) setTimeZone: (NSTimeZone *) tz
{ {
if ([tz isEqual: my->tz]) if ([tz isEqual: my->tz])
return; {
return;
}
RELEASE(my->tz); ASSIGN(my->tz, tz);
my->tz = RETAIN(tz);
[self _resetCalendar]; [self _resetCalendar];
} }
- (NSRange) maximumRangeOfUnit: (NSCalendarUnit) unit - (NSRange) maximumRangeOfUnit: (NSCalendarUnit) unit
{ {
NSRange result = NSMakeRange (0, 0);
#if GS_USE_ICU == 1 #if GS_USE_ICU == 1
UCalendarDateFields dateField; UCalendarDateFields dateField;
NSRange result;
UErrorCode err = U_ZERO_ERROR; UErrorCode err = U_ZERO_ERROR;
[self _resetCalendar]; [self _resetCalendar];
dateField = _NSCalendarUnitToDateField (unit); dateField = _NSCalendarUnitToDateField (unit);
// We really don't care if there are any errors... if (dateField != -1)
result.location = {
(NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_MINIMUM, &err); // We really don't care if there are any errors...
result.length = result.location =
(NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_MAXIMUM, &err) (NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_MINIMUM, &err);
- result.location + 1; result.length =
// ICU's month is 0-based, while NSCalendar is 1-based (NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_MAXIMUM, &err)
if (dateField == UCAL_MONTH) - result.location + 1;
result.location += 1; // ICU's month is 0-based, while NSCalendar is 1-based
if (dateField == UCAL_MONTH)
return result; {
#else result.location += 1;
return NSMakeRange (0, 0); }
}
#endif #endif
return result;
} }
- (NSRange) minimumRangeofUnit: (NSCalendarUnit) unit - (NSRange) minimumRangeofUnit: (NSCalendarUnit) unit
{ {
NSRange result = NSMakeRange (0, 0);
#if GS_USE_ICU == 1 #if GS_USE_ICU == 1
UCalendarDateFields dateField; UCalendarDateFields dateField;
NSRange result;
UErrorCode err = U_ZERO_ERROR; UErrorCode err = U_ZERO_ERROR;
[self _resetCalendar]; [self _resetCalendar];
dateField = _NSCalendarUnitToDateField (unit); dateField = _NSCalendarUnitToDateField (unit);
// We really don't care if there are any errors... if (dateField != -1)
result.location = {
(NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_GREATEST_MINIMUM, &err); // We really don't care if there are any errors...
result.length = result.location =
(NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_LEAST_MAXIMUM, &err) (NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_GREATEST_MINIMUM, &err);
- result.location + 1; result.length =
// ICU's month is 0-based, while NSCalendar is 1-based (NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_LEAST_MAXIMUM, &err)
if (dateField == UCAL_MONTH) - result.location + 1;
result.location += 1; // ICU's month is 0-based, while NSCalendar is 1-based
if (dateField == UCAL_MONTH)
return result; {
#else result.location += 1;
return NSMakeRange (0, 0); }
}
#endif #endif
return result;
} }
- (NSUInteger) ordinalityOfUnit: (NSCalendarUnit) smaller - (NSUInteger) ordinalityOfUnit: (NSCalendarUnit) smaller