From 82e9ddc21f7185787e99447bcbbed7b2e8708674 Mon Sep 17 00:00:00 2001 From: fredkiefer Date: Sat, 25 Apr 2020 22:19:20 +0200 Subject: [PATCH 01/10] Add missing NSDateComponents methods. --- Headers/Foundation/NSCalendar.h | 17 ++++- Source/NSCalendar.m | 112 ++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) diff --git a/Headers/Foundation/NSCalendar.h b/Headers/Foundation/NSCalendar.h index dfbc618ff..28fea7548 100644 --- a/Headers/Foundation/NSCalendar.h +++ b/Headers/Foundation/NSCalendar.h @@ -129,7 +129,7 @@ enum NSCalendarUnitWeekOfMonth = (1UL << 12), NSCalendarUnitWeekOfYear = (1UL << 13), NSCalendarUnitYearForWeekOfYear = (1UL << 14), - NSCalendarUnitNanosecond = (1 << 15), // FIXME: unimplemented + NSCalendarUnitNanosecond = (1 << 15), NSCalendarUnitCalendar = (1 << 20), // FIXME: unimplemented NSCalendarUnitTimeZone = (1 << 21) // FIXME: unimplemented #endif @@ -227,6 +227,7 @@ enum * yearForWeekOfYear is 2013, since it's already week 1 in 2013. */ - (NSInteger) yearForWeekOfYear; +- (NSInteger) nanosecond; /** Sets the number of the week in this month. */ - (void) setWeekOfMonth: (NSInteger) v; @@ -241,8 +242,22 @@ enum * See the explanation at -yearForWeekOfYear. */ - (void) setYearForWeekOfYear: (NSInteger) v; +- (void) setNanosecond: (NSInteger) v; #endif + +#if OS_API_VERSION(MAC_OS_X_VERSION_10_8, GS_API_LATEST) +- (BOOL) leapMonth; +- (void) setLeapMonth: (BOOL) v; +#endif + +#if OS_API_VERSION(MAC_OS_X_VERSION_10_9, GS_API_LATEST) +- (BOOL) isValidDate; +- (BOOL) isValidDateInCalendar: (NSCalendar *) calendar; +- (NSInteger) valueForComponent: (NSCalendarUnit) unit; +- (void) setValue: (NSInteger) value + forComponent: (NSCalendarUnit) unit; +#endif @end diff --git a/Source/NSCalendar.m b/Source/NSCalendar.m index 11ee265d9..d33ddcf42 100644 --- a/Source/NSCalendar.m +++ b/Source/NSCalendar.m @@ -843,6 +843,8 @@ typedef struct { NSInteger quarter; NSInteger weekOfMonth; NSInteger yearForWeekOfYear; + BOOL leapMonth; + NSInteger nanosecond; NSCalendar *cal; NSTimeZone *tz; } DateComp; @@ -880,6 +882,8 @@ typedef struct { my->quarter = NSDateComponentUndefined; my->weekOfMonth = NSDateComponentUndefined; my->yearForWeekOfYear = NSDateComponentUndefined; + my->leapMonth = NO; + my->nanosecond = NSDateComponentUndefined; my->cal = NULL; my->tz = NULL; } @@ -921,6 +925,11 @@ typedef struct { return my->second; } +- (NSInteger) nanosecond +{ + return my->nanosecond; +} + - (NSInteger) week { return my->week; @@ -956,6 +965,11 @@ typedef struct { return my->yearForWeekOfYear; } +- (BOOL) leapMonth +{ + return my->leapMonth; +} + - (NSCalendar *) calendar { return my->cal; @@ -1013,6 +1027,11 @@ typedef struct { my->second = v; } +- (void) setNanosecond: (NSInteger) v +{ + my->nanosecond = v; +} + - (void) setWeek: (NSInteger) v { my->week = v; @@ -1048,6 +1067,11 @@ typedef struct { my->yearForWeekOfYear = v; } +- (void) setLeapMonth: (BOOL) v +{ + my->leapMonth = v; +} + - (void) setCalendar: (NSCalendar *) cal { ASSIGN(my->cal, cal); @@ -1058,6 +1082,94 @@ typedef struct { ASSIGN(my->tz, tz); } +- (BOOL) isValidDate +{ + if (my->cal == nil) + { + return NO; + } + return [self isValidDateInCalendar: my->cal]; +} + +- (BOOL) isValidDateInCalendar: (NSCalendar *) calendar +{ + return [calendar dateFromComponents: self] != nil; +} + +- (NSInteger) valueForComponent: (NSCalendarUnit) unit +{ + switch (unit) + { + case NSCalendarUnitEra: return my->era; + case NSCalendarUnitYear: return my->year; + case NSCalendarUnitMonth: return my->month; + case NSCalendarUnitDay: return my->day; + case NSCalendarUnitHour: return my->hour; + case NSCalendarUnitMinute: return my->minute; + case NSCalendarUnitSecond: return my->second; + case NSCalendarUnitWeekday: return my->weekday; + case NSCalendarUnitWeekdayOrdinal: return my->weekdayOrdinal; + case NSCalendarUnitQuarter: return my->quarter; + case NSCalendarUnitWeekOfMonth: return my->weekOfMonth; + case NSCalendarUnitWeekOfYear: return my->week; + case NSCalendarUnitYearForWeekOfYear: return my->yearForWeekOfYear; + case NSCalendarUnitNanosecond: return my->nanosecond; + default: return 0; + } +} + +- (void) setValue: (NSInteger) value + forComponent: (NSCalendarUnit) unit +{ + switch (unit) + { + case NSCalendarUnitEra: + my->era = value; + break; + case NSCalendarUnitYear: + my->year = value; + break; + case NSCalendarUnitMonth: + my->month = value; + break; + case NSCalendarUnitDay: + my->day = value; + break; + case NSCalendarUnitHour: + my->hour = value; + break; + case NSCalendarUnitMinute: + my->minute = value; + break; + case NSCalendarUnitSecond: + my->second = value; + break; + case NSCalendarUnitWeekday: + my->weekday = value; + break; + case NSCalendarUnitWeekdayOrdinal: + my->weekdayOrdinal = value; + break; + case NSCalendarUnitQuarter: + my->quarter = value; + break; + case NSCalendarUnitWeekOfMonth: + my->weekOfMonth = value; + break; + case NSCalendarUnitWeekOfYear: + my->week = value; + break; + case NSCalendarUnitYearForWeekOfYear: + my->yearForWeekOfYear = value; + break; + case NSCalendarUnitNanosecond: + my->nanosecond = value; + break; + default: + break; + } +} + - (id) copyWithZone: (NSZone*)zone { if (NSShouldRetainWithZone(self, zone)) From 4fe6cb20a15cadd536b06313f7bc5d9abbba4905 Mon Sep 17 00:00:00 2001 From: fredkiefer Date: Sun, 26 Apr 2020 00:32:49 +0200 Subject: [PATCH 02/10] Started to rewrite NSCalendar to handle more components correctly. --- Source/NSCalendar.m | 313 ++++++++++++++++++++++++-------------------- 1 file changed, 171 insertions(+), 142 deletions(-) diff --git a/Source/NSCalendar.m b/Source/NSCalendar.m index d33ddcf42..22954423d 100644 --- a/Source/NSCalendar.m +++ b/Source/NSCalendar.m @@ -46,31 +46,26 @@ #if GS_USE_ICU == 1 static UCalendarDateFields _NSCalendarUnitToDateField (NSCalendarUnit unit) { - // I'm just going to go in the order they appear in Apple's documentation - if (unit & NSEraCalendarUnit) + if (unit & NSCalendarUnitEra) return UCAL_ERA; - if (unit & NSYearCalendarUnit) + if (unit & NSCalendarUnitYear) return UCAL_YEAR; - if (unit & NSMonthCalendarUnit) + if (unit & NSCalendarUnitMonth) return UCAL_MONTH; - if (unit & NSDayCalendarUnit) + if (unit & NSCalendarUnitDay) return UCAL_DAY_OF_MONTH; - if (unit & NSHourCalendarUnit) + if (unit & NSCalendarUnitHour) return UCAL_HOUR_OF_DAY; - if (unit & NSMinuteCalendarUnit) + if (unit & NSCalendarUnitMinute) return UCAL_MINUTE; - if (unit & NSSecondCalendarUnit) + if (unit & NSCalendarUnitSecond) return UCAL_SECOND; - if (unit & NSWeekCalendarUnit) + if (unit & NSCalendarUnitWeekOfYear) return UCAL_WEEK_OF_YEAR; - if (unit & NSWeekdayCalendarUnit) + if (unit & NSCalendarUnitWeekday) return UCAL_DAY_OF_WEEK; - if (unit & NSWeekdayOrdinalCalendarUnit) - // FIXME: Is this right??? + if (unit & NSCalendarUnitWeekdayOrdinal) return UCAL_DAY_OF_WEEK_IN_MONTH; - // ICU doesn't include a quarter DateField... - if (unit & NSQuarterCalendarUnit) - return (UCAL_MONTH + 2) / 3; return -1; } #endif /* GS_USE_ICU */ @@ -97,25 +92,32 @@ typedef struct { #define TZ_NAME_LENGTH 1024 @implementation NSCalendar (PrivateMethods) -- (void) _resetCalendar -{ + #if GS_USE_ICU == 1 +- (void *) _openCalendarFor: (NSTimeZone *)timeZone +{ NSString *tzName; NSUInteger tzLen; unichar cTzId[TZ_NAME_LENGTH]; const char *cLocaleId; UErrorCode err = U_ZERO_ERROR; - - if (my->cal != NULL) - ucal_close (my->cal); - + UCalendarType type; + cLocaleId = [my->localeID UTF8String]; - tzName = [my->tz name]; + tzName = [timeZone name]; tzLen = [tzName length]; if (tzLen > TZ_NAME_LENGTH) - tzLen = TZ_NAME_LENGTH; + { + tzLen = TZ_NAME_LENGTH; + } [tzName getCharacters: cTzId range: NSMakeRange(0, tzLen)]; + if ([NSGregorianCalendar isEqualToString: my->identifier]) + { + type = UCAL_GREGORIAN; + } + else + { #ifndef UCAL_DEFAULT /* * Older versions of ICU used UCAL_TRADITIONAL rather than UCAL_DEFAULT @@ -123,8 +125,24 @@ typedef struct { */ #define UCAL_DEFAULT UCAL_TRADITIONAL #endif - my->cal = - ucal_open ((const UChar *)cTzId, tzLen, cLocaleId, UCAL_DEFAULT, &err); + type = UCAL_DEFAULT; + // 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) { @@ -195,8 +213,10 @@ static NSRecursiveLock *classLock = nil; + (void) initialize { - if (self == [NSLocale class]) - classLock = [NSRecursiveLock new]; + if (self == [NSCalendar class]) + { + classLock = [NSRecursiveLock new]; + } } + (void) defaultsDidChange: (NSNotification*)n @@ -246,15 +266,14 @@ static NSRecursiveLock *classLock = nil; return AUTORELEASE(result); } -- (id) init -{ - self = [self initWithCalendarIdentifier: nil]; - return self; -} - + (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 @@ -265,44 +284,7 @@ static NSRecursiveLock *classLock = nil; my->firstWeekday = NSNotFound; my->minimumDaysInFirstWeek = NSNotFound; - - 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; - } + ASSIGN(my->identifier, string); // 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. @@ -345,34 +327,68 @@ static NSRecursiveLock *classLock = nil; udate = (UDate)floor([date timeIntervalSince1970] * 1000.0); ucal_setMillis (my->cal, udate, &err); if (U_FAILURE(err)) - return nil; + { + return nil; + } comps = [[NSDateComponents alloc] init]; - if (unitFlags & NSEraCalendarUnit) - [comps setEra: ucal_get(my->cal, UCAL_ERA, &err)]; - if (unitFlags & NSYearCalendarUnit) - [comps setYear: ucal_get(my->cal, UCAL_YEAR, &err)]; - if (unitFlags & NSMonthCalendarUnit) - [comps setMonth: ucal_get(my->cal, UCAL_MONTH, &err)+1]; - if (unitFlags & NSDayCalendarUnit) - [comps setDay: ucal_get(my->cal, UCAL_DAY_OF_MONTH, &err)]; - if (unitFlags & NSHourCalendarUnit) - [comps setHour: ucal_get(my->cal, UCAL_HOUR_OF_DAY, &err)]; - if (unitFlags & NSMinuteCalendarUnit) - [comps setMinute: ucal_get(my->cal, UCAL_MINUTE, &err)]; - if (unitFlags & NSSecondCalendarUnit) - [comps setSecond: ucal_get(my->cal, UCAL_SECOND, &err)]; - if (unitFlags & (NSWeekCalendarUnit|NSWeekOfYearCalendarUnit)) - [comps setWeek: ucal_get(my->cal, UCAL_WEEK_OF_YEAR, &err)]; - if (unitFlags & NSWeekdayCalendarUnit) - [comps setWeekday: ucal_get(my->cal, UCAL_DAY_OF_WEEK, &err)]; - if (unitFlags & NSWeekdayOrdinalCalendarUnit) - [comps setWeekdayOrdinal: - ucal_get(my->cal, UCAL_DAY_OF_WEEK_IN_MONTH, &err)]; - if (unitFlags & NSWeekOfMonthCalendarUnit) - [comps setWeekOfMonth: ucal_get(my->cal, UCAL_WEEK_OF_MONTH, &err)]; - if (unitFlags & NSYearForWeekOfYearCalendarUnit) - [comps setYearForWeekOfYear: ucal_get(my->cal, UCAL_YEAR_WOY, &err)]; + if (unitFlags & NSCalendarUnitEra) + { + [comps setEra: ucal_get(my->cal, UCAL_ERA, &err)]; + } + if (unitFlags & NSCalendarUnitYear) + { + [comps setYear: ucal_get(my->cal, UCAL_YEAR, &err)]; + } + if (unitFlags & NSCalendarUnitMonth) + { + [comps setMonth: ucal_get(my->cal, UCAL_MONTH, &err)+1]; + } + if (unitFlags & NSCalendarUnitDay) + { + [comps setDay: ucal_get(my->cal, UCAL_DAY_OF_MONTH, &err)]; + } + if (unitFlags & NSCalendarUnitHour) + { + [comps setHour: ucal_get(my->cal, UCAL_HOUR_OF_DAY, &err)]; + } + if (unitFlags & NSCalendarUnitMinute) + { + [comps setMinute: ucal_get(my->cal, UCAL_MINUTE, &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); #else @@ -428,14 +444,14 @@ do \ * the largest to the smallest. */ COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, - NSEraCalendarUnit, setEra:, UCAL_ERA, err); + NSCalendarUnitEra, setEra:, UCAL_ERA, err); COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, - NSYearCalendarUnit, setYear:, UCAL_YEAR, err); + NSCalendarUnitYear, setYear:, UCAL_YEAR, err); COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, - NSMonthCalendarUnit, setMonth:, UCAL_MONTH, err); + NSCalendarUnitMonth, setMonth:, UCAL_MONTH, err); COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, - NSWeekOfYearCalendarUnit, setWeek:, UCAL_WEEK_OF_YEAR, err); - if (!(unitFlags & NSWeekOfYearCalendarUnit)) + NSCalendarUnitWeekOfYear, setWeek:, UCAL_WEEK_OF_YEAR, err); + if (!(unitFlags & NSCalendarUnitWeekOfYear)) { /* We must avoid setting the same unit twice (it would be zero because * of the automatic advancement. @@ -444,20 +460,20 @@ do \ NSWeekCalendarUnit, setWeek:, UCAL_WEEK_OF_YEAR, err); } 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, - NSDayCalendarUnit, setDay:, UCAL_DAY_OF_MONTH, err); + NSCalendarUnitDay, setDay:, UCAL_DAY_OF_MONTH, err); COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, - NSWeekdayOrdinalCalendarUnit, setWeekdayOrdinal:, + NSCalendarUnitWeekdayOrdinal, setWeekdayOrdinal:, UCAL_DAY_OF_WEEK_IN_MONTH, err); 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, - NSHourCalendarUnit, setHour:, UCAL_HOUR_OF_DAY, err); + NSCalendarUnitHour, setHour:, UCAL_HOUR_OF_DAY, err); COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, - NSMinuteCalendarUnit, setMinute:, UCAL_MINUTE, err); + NSCalendarUnitMinute, setMinute:, UCAL_MINUTE, err); COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, - NSSecondCalendarUnit, setSecond:, UCAL_SECOND, err); + NSCalendarUnitSecond, setSecond:, UCAL_SECOND, err); # if 0 if (unitFlags & NSCalendarUnitNanosecond) { @@ -600,6 +616,10 @@ do \ { 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) { 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); } + if ((amount = [comps nanosecond]) != NSDateComponentUndefined) + { + ucal_set (my->cal, UCAL_MILLISECOND, (int32_t)(amount / 1000)); + } udate = ucal_getMillis (my->cal, &err); if (U_FAILURE(err)) @@ -664,62 +688,67 @@ do \ - (void) setTimeZone: (NSTimeZone *) tz { if ([tz isEqual: my->tz]) - return; + { + return; + } - RELEASE(my->tz); - my->tz = RETAIN(tz); + ASSIGN(my->tz, tz); [self _resetCalendar]; } - - (NSRange) maximumRangeOfUnit: (NSCalendarUnit) unit { + NSRange result = NSMakeRange (0, 0); #if GS_USE_ICU == 1 UCalendarDateFields dateField; - NSRange result; UErrorCode err = U_ZERO_ERROR; [self _resetCalendar]; dateField = _NSCalendarUnitToDateField (unit); - // We really don't care if there are any errors... - result.location = - (NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_MINIMUM, &err); - result.length = - (NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_MAXIMUM, &err) - - result.location + 1; - // ICU's month is 0-based, while NSCalendar is 1-based - if (dateField == UCAL_MONTH) - result.location += 1; - - return result; -#else - return NSMakeRange (0, 0); + if (dateField != -1) + { + // We really don't care if there are any errors... + result.location = + (NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_MINIMUM, &err); + result.length = + (NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_MAXIMUM, &err) + - result.location + 1; + // ICU's month is 0-based, while NSCalendar is 1-based + if (dateField == UCAL_MONTH) + { + result.location += 1; + } + } #endif + + return result; } - (NSRange) minimumRangeofUnit: (NSCalendarUnit) unit { + NSRange result = NSMakeRange (0, 0); #if GS_USE_ICU == 1 UCalendarDateFields dateField; - NSRange result; UErrorCode err = U_ZERO_ERROR; [self _resetCalendar]; dateField = _NSCalendarUnitToDateField (unit); - // We really don't care if there are any errors... - result.location = - (NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_GREATEST_MINIMUM, &err); - result.length = - (NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_LEAST_MAXIMUM, &err) - - result.location + 1; - // ICU's month is 0-based, while NSCalendar is 1-based - if (dateField == UCAL_MONTH) - result.location += 1; - - return result; -#else - return NSMakeRange (0, 0); + if (dateField != -1) + { + // We really don't care if there are any errors... + result.location = + (NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_GREATEST_MINIMUM, &err); + result.length = + (NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_LEAST_MAXIMUM, &err) + - result.location + 1; + // ICU's month is 0-based, while NSCalendar is 1-based + if (dateField == UCAL_MONTH) + { + result.location += 1; + } + } #endif + return result; } - (NSUInteger) ordinalityOfUnit: (NSCalendarUnit) smaller From a48694941c1cade19dd0ecfe63713cf03f50e831 Mon Sep 17 00:00:00 2001 From: fredkiefer Date: Sun, 26 Apr 2020 21:13:22 +0200 Subject: [PATCH 03/10] Add newer features for NSDateComponents and started to clean up NSCalendar implementation. --- ChangeLog | 10 + Source/NSCalendar.m | 379 ++++++++++++++------------ Source/NSLocale.m | 5 + Tests/base/NSCalendar/features-10-7.m | 10 +- 4 files changed, 229 insertions(+), 175 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3e7f738c2..29786fcf7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2020-04-26 Fred Kiefer + + * Source/NSLocale.m: Respect NSLocaleCalendarIdentifier if + NSLocaleCalendar isn't set. + * Headers/Foundation/NSCalendar.h, + * Source/NSCalendar.m: Add newer features for NSDateComponents and + started to clean up NSCalendar implementation. + * Tests/base/NSCalendar/features-10-7.m: Add test for different + time zone. + 2020-04-21 Richard Frith-Macdonald * Source/NSNotification.m: diff --git a/Source/NSCalendar.m b/Source/NSCalendar.m index 22954423d..07801fa93 100644 --- a/Source/NSCalendar.m +++ b/Source/NSCalendar.m @@ -73,23 +73,28 @@ static UCalendarDateFields _NSCalendarUnitToDateField (NSCalendarUnit unit) typedef struct { NSString *identifier; NSString *localeID; - NSTimeZone *tz; + NSTimeZone *tz; void *cal; NSInteger firstWeekday; NSInteger minimumDaysInFirstWeek; -} Calendar; +} Calendar; #define my ((Calendar*)_NSCalendarInternal) -#define aac ((Calendar*)(autoupdatingCalendar->_NSCalendarInternal)) @interface NSCalendar (PrivateMethods) +- (void *) _openCalendarFor: (NSTimeZone *)timeZone; - (void) _resetCalendar; - (void *) _UCalendar; - (NSString *) _localeIDWithLocale: (NSLocale*)locale; -- (NSString *) _localeIDentifier; +- (NSString *) _localeIdentifier; - (void) _setLocaleIdentifier: (NSString*)identifier; @end +static NSCalendar *autoupdatingCalendar = nil; +static NSRecursiveLock *classLock = nil; + #define TZ_NAME_LENGTH 1024 +#define SECOND_TO_MILLI 1000.0 +#define MILLI_TO_NANO 1000000 @implementation NSCalendar (PrivateMethods) @@ -122,24 +127,25 @@ typedef struct { /* * Older versions of ICU used UCAL_TRADITIONAL rather than UCAL_DEFAULT * so if one is not available we use the other. - */ -#define UCAL_DEFAULT UCAL_TRADITIONAL -#endif + */ + type = UCAL_TRADITIONAL; +#else type = UCAL_DEFAULT; - // FIXME: Should use uloc_setKeywordValue() here +#endif + // We do not need to call uloc_setKeywordValue() here to set the calendar on the locale + // as the calendar is already encoded in the locale id by _localeIDWithLocale:. } - return ucal_open ((const UChar *)cTzId, tzLen, cLocaleId, type, &err); + 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); + ucal_close(my->cal); } my->cal = [self _openCalendarFor: my->tz]; @@ -165,7 +171,6 @@ typedef struct { ucal_setAttribute(my->cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK, (int32_t)my->minimumDaysInFirstWeek); } - #endif } @@ -179,18 +184,26 @@ typedef struct { NSString *result; NSString *localeId; NSMutableDictionary *tmpDict; - + localeId = [locale localeIdentifier]; - tmpDict = [[NSLocale componentsFromLocaleIdentifier: localeId] - mutableCopyWithZone: NULL]; - [tmpDict setObject: my->identifier forKey: NSLocaleCalendarIdentifier]; - result = [NSLocale localeIdentifierFromComponents: tmpDict]; - RELEASE(tmpDict); - + if (my->identifier) + { + tmpDict = [[NSLocale componentsFromLocaleIdentifier: localeId] + mutableCopyWithZone: NULL]; + [tmpDict removeObjectForKey: NSLocaleCalendar]; + [tmpDict setObject: my->identifier forKey: NSLocaleCalendarIdentifier]; + result = [NSLocale localeIdentifierFromComponents: tmpDict]; + RELEASE(tmpDict); + } + else + { + result = localeId; + } + return result; } -- (NSString*) _localeIDentifier +- (NSString*) _localeIdentifier { return my->localeID; } @@ -198,28 +211,15 @@ typedef struct { - (void) _setLocaleIdentifier: (NSString *) identifier { if ([identifier isEqualToString: my->localeID]) - return; - - RELEASE(my->localeID); - my->localeID = RETAIN(identifier); + { + return; + } + + ASSIGN(my->localeID, identifier); [self _resetCalendar]; } -@end -@implementation NSCalendar - -static NSCalendar *autoupdatingCalendar = nil; -static NSRecursiveLock *classLock = nil; - -+ (void) initialize -{ - if (self == [NSCalendar class]) - { - classLock = [NSRecursiveLock new]; - } -} - -+ (void) defaultsDidChange: (NSNotification*)n +- (void) _defaultsDidChange: (NSNotification*)n { NSUserDefaults *defs; NSString *locale; @@ -230,45 +230,70 @@ static NSRecursiveLock *classLock = nil; locale = [defs stringForKey: @"Locale"]; calendar = [defs stringForKey: @"Calendar"]; tz = [defs stringForKey: @"Local Time Zone"]; - - if ([locale isEqual: aac->localeID] == NO - || [calendar isEqual: aac->identifier] == NO - || [tz isEqual: [(aac->tz) name]] == NO) + + [classLock lock]; + if ([locale isEqual: my->localeID] == NO + || [calendar isEqual: my->identifier] == NO + || [tz isEqual: [my->tz name]] == NO) { - [classLock lock]; - RELEASE(aac->localeID); - RELEASE(aac->identifier); - RELEASE(aac->tz); #if GS_USE_ICU == 1 - ucal_close(aac->cal); + ucal_close(my->cal); #endif - - aac->localeID = RETAIN(locale); - aac->identifier = RETAIN(calendar); - aac->tz = [[NSTimeZone alloc] initWithName: tz]; - - [autoupdatingCalendar _resetCalendar]; - [classLock unlock]; + + ASSIGN(my->localeID, locale); + ASSIGN(my->identifier, calendar); + RELEASE(my->tz); + my->tz = [[NSTimeZone alloc] initWithName: tz]; + + [self _resetCalendar]; + } + [classLock unlock]; +} +@end + +@implementation NSCalendar + ++ (void) initialize +{ + if (self == [NSCalendar class]) + { + classLock = [NSRecursiveLock new]; } } + (id) currentCalendar { NSCalendar *result; - NSLocale *locale; - NSCalendar *cal; - - locale = [NSLocale currentLocale]; - cal = [locale objectForKey: NSLocaleCalendar]; - result = - [[NSCalendar alloc] initWithCalendarIdentifier: [cal calendarIdentifier]]; - + NSString *identifier; + + // This identifier may be nil + identifier = [[NSLocale currentLocale] objectForKey: NSLocaleCalendarIdentifier]; + result = [[NSCalendar alloc] initWithCalendarIdentifier: identifier]; + return AUTORELEASE(result); } -+ (id) calendarWithIdentifier: (NSString *) string ++ (id) autoupdatingCurrentCalendar { - return AUTORELEASE([[self alloc] initWithCalendarIdentifier: string]); + [classLock lock]; + if (nil == autoupdatingCalendar) + { + autoupdatingCalendar = [[self currentCalendar] copy]; + [[NSNotificationCenter defaultCenter] + addObserver: autoupdatingCalendar + selector: @selector(_defaultsDidChange:) + name: NSUserDefaultsDidChangeNotification + object: nil]; + } + + [classLock unlock]; + return autoupdatingCalendar; +} + + ++ (id) calendarWithIdentifier: (NSString *) identifier +{ + return AUTORELEASE([[self alloc] initWithCalendarIdentifier: identifier]); } - (id) init @@ -276,22 +301,17 @@ static NSRecursiveLock *classLock = nil; return [self initWithCalendarIdentifier: nil]; } -- (id) initWithCalendarIdentifier: (NSString *) string +- (id) initWithCalendarIdentifier: (NSString *) identifier { NSAssert(0 == _NSCalendarInternal, NSInvalidArgumentException); - _NSCalendarInternal = - NSZoneCalloc([self zone], sizeof(Calendar), 1); + _NSCalendarInternal = NSZoneCalloc([self zone], sizeof(Calendar), 1); my->firstWeekday = NSNotFound; my->minimumDaysInFirstWeek = NSNotFound; - ASSIGN(my->identifier, string); - - // 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. - my->localeID = RETAIN([self _localeIDWithLocale: [NSLocale currentLocale]]); - my->tz = RETAIN([NSTimeZone defaultTimeZone]); - - [self _resetCalendar]; + ASSIGN(my->identifier, identifier); + ASSIGN(my->tz, [NSTimeZone defaultTimeZone]); + [self setLocale: [NSLocale currentLocale]]; + return self; } @@ -323,14 +343,14 @@ static NSRecursiveLock *classLock = nil; NSDateComponents *comps; UErrorCode err = U_ZERO_ERROR; UDate udate; - - udate = (UDate)floor([date timeIntervalSince1970] * 1000.0); - ucal_setMillis (my->cal, udate, &err); + + udate = (UDate)floor([date timeIntervalSince1970] * SECOND_TO_MILLI); + ucal_setMillis(my->cal, udate, &err); if (U_FAILURE(err)) { return nil; } - + comps = [[NSDateComponents alloc] init]; if (unitFlags & NSCalendarUnitEra) { @@ -342,7 +362,7 @@ static NSRecursiveLock *classLock = nil; } if (unitFlags & NSCalendarUnitMonth) { - [comps setMonth: ucal_get(my->cal, UCAL_MONTH, &err)+1]; + [comps setMonth: ucal_get(my->cal, UCAL_MONTH, &err) + 1]; } if (unitFlags & NSCalendarUnitDay) { @@ -375,7 +395,7 @@ static NSRecursiveLock *classLock = nil; } if (unitFlags & NSCalendarUnitQuarter) { - [comps setQuarter: (ucal_get(my->cal, UCAL_MONTH, &err) + 2) / 3]; + [comps setQuarter: (ucal_get(my->cal, UCAL_MONTH, &err) + 3) / 3]; } if (unitFlags & NSCalendarUnitWeekOfMonth) { @@ -387,9 +407,9 @@ static NSRecursiveLock *classLock = nil; } if (unitFlags & NSCalendarUnitNanosecond) { - [comps setNanosecond: ucal_get(my->cal, UCAL_MILLISECOND, &err) * 1000]; + [comps setNanosecond: ucal_get(my->cal, UCAL_MILLISECOND, &err) * MILLI_TO_NANO]; } - + return AUTORELEASE(comps); #else return nil; @@ -429,10 +449,10 @@ do \ NSDateComponents *comps = nil; UErrorCode err = U_ZERO_ERROR; - UDate udateFrom = (UDate)floor([startingDate timeIntervalSince1970] * 1000.0); - UDate udateTo = (UDate)floor([resultDate timeIntervalSince1970] * 1000.0); + UDate udateFrom = (UDate)floor([startingDate timeIntervalSince1970] * SECOND_TO_MILLI); + UDate udateTo = (UDate)floor([resultDate timeIntervalSince1970] * SECOND_TO_MILLI); - ucal_setMillis (my->cal, udateFrom, &err); + ucal_setMillis(my->cal, udateFrom, &err); if (U_FAILURE(err)) { return nil; @@ -474,21 +494,19 @@ do \ NSCalendarUnitMinute, setMinute:, UCAL_MINUTE, err); COMPONENT_DIFF(my->cal, unitFlags, comps, udateTo, NSCalendarUnitSecond, setSecond:, UCAL_SECOND, err); -# if 0 if (unitFlags & NSCalendarUnitNanosecond) { - int32_t ns; + int32_t ms; - ns = ucal_getFieldDifference(my->cal, udateTo, UCAL_MILLISECOND, &err) - * 1000000; + ms = ucal_getFieldDifference(my->cal, udateTo, UCAL_MILLISECOND, &err); if (U_FAILURE(err)) { RELEASE(comps); return nil; } - [comps setNanosecond: ns]; + [comps setNanosecond: ms * MILLI_TO_NANO]; } -# endif + return AUTORELEASE(comps); #else @@ -496,6 +514,18 @@ do \ #endif } +#undef COMPONENT_DIFF + +#define _ADD_COMPONENT(c, n) \ + if (opts & NSWrapCalendarComponents) \ + ucal_roll(my->cal, c, n, &err); \ + else \ + ucal_add(my->cal, c, n, &err); \ + if (U_FAILURE(err)) \ + { \ + return nil; \ + } + - (NSDate *) dateByAddingComponents: (NSDateComponents *) comps toDate: (NSDate *) date options: (NSUInteger) opts @@ -504,16 +534,11 @@ do \ NSInteger amount; UErrorCode err = U_ZERO_ERROR; UDate udate; - + [self _resetCalendar]; - udate = (UDate)([date timeIntervalSince1970] * 1000.0); - ucal_setMillis (my->cal, udate, &err); - -#define _ADD_COMPONENT(c, n) \ - if (opts & NSWrapCalendarComponents) \ - ucal_roll (my->cal, c, n, &err); \ - else \ - ucal_add (my->cal, c, n, &err); + udate = (UDate)([date timeIntervalSince1970] * SECOND_TO_MILLI); + ucal_setMillis(my->cal, udate, &err); + if ((amount = [comps era]) != NSDateComponentUndefined) { _ADD_COMPONENT(UCAL_ERA, (int32_t)amount); @@ -558,86 +583,107 @@ do \ { _ADD_COMPONENT(UCAL_YEAR_WOY, (int32_t)amount); } -#undef _ADD_COMPONENT - - udate = ucal_getMillis (my->cal, &err); + if ((amount = [comps nanosecond]) != NSDateComponentUndefined) + { + _ADD_COMPONENT(UCAL_MILLISECOND, (int32_t)(amount / MILLI_TO_NANO)); + } + + udate = ucal_getMillis(my->cal, &err); if (U_FAILURE(err)) - return nil; - - return [NSDate dateWithTimeIntervalSince1970: (udate / 1000.0)]; + { + return nil; + } + + return [NSDate dateWithTimeIntervalSince1970: (udate / SECOND_TO_MILLI)]; #else return nil; #endif } +#undef _ADD_COMPONENT + - (NSDate *) dateFromComponents: (NSDateComponents *) comps { #if GS_USE_ICU == 1 NSInteger amount; UDate udate; UErrorCode err = U_ZERO_ERROR; - - [self _resetCalendar]; - ucal_clear (my->cal); - + void *cal; + NSTimeZone *timeZone; + + timeZone = [comps timeZone]; + if (timeZone == nil) + { + timeZone = [self timeZone]; + } + cal = [self _openCalendarFor: timeZone]; + if (!cal) + { + return nil; + } + ucal_clear(cal); + if ((amount = [comps era]) != NSDateComponentUndefined) { - ucal_set (my->cal, UCAL_ERA, (int32_t)amount); + ucal_set(cal, UCAL_ERA, (int32_t)amount); } if ((amount = [comps year]) != NSDateComponentUndefined) { - ucal_set (my->cal, UCAL_YEAR, (int32_t)amount); + ucal_set (cal, UCAL_YEAR, (int32_t)amount); } if ((amount = [comps month]) != NSDateComponentUndefined) { - ucal_set (my->cal, UCAL_MONTH, amount-1); + ucal_set (cal, UCAL_MONTH, amount - 1); } if ((amount = [comps day]) != NSDateComponentUndefined) { - ucal_set (my->cal, UCAL_DAY_OF_MONTH, (int32_t)amount); + ucal_set (cal, UCAL_DAY_OF_MONTH, (int32_t)amount); } if ((amount = [comps hour]) != NSDateComponentUndefined) { - ucal_set (my->cal, UCAL_HOUR_OF_DAY, (int32_t)amount); + ucal_set (cal, UCAL_HOUR_OF_DAY, (int32_t)amount); } if ((amount = [comps minute]) != NSDateComponentUndefined) { - ucal_set (my->cal, UCAL_MINUTE, (int32_t)amount); + ucal_set (cal, UCAL_MINUTE, (int32_t)amount); } if ((amount = [comps second]) != NSDateComponentUndefined) { - ucal_set (my->cal, UCAL_SECOND, (int32_t)amount); + ucal_set (cal, UCAL_SECOND, (int32_t)amount); } if ((amount = [comps week]) != NSDateComponentUndefined) { - ucal_set (my->cal, UCAL_WEEK_OF_YEAR, (int32_t)amount); + ucal_set (cal, UCAL_WEEK_OF_YEAR, (int32_t)amount); } if ((amount = [comps weekday]) != NSDateComponentUndefined) { - ucal_set (my->cal, UCAL_DAY_OF_WEEK, (int32_t)amount); + ucal_set (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); + ucal_set (cal, UCAL_DAY_OF_WEEK_IN_MONTH, (int32_t)amount); } if ((amount = [comps weekOfMonth]) != NSDateComponentUndefined) { - ucal_set (my->cal, UCAL_WEEK_OF_MONTH, (int32_t)amount); + ucal_set (cal, UCAL_WEEK_OF_MONTH, (int32_t)amount); } if ((amount = [comps yearForWeekOfYear]) != NSDateComponentUndefined) { - ucal_set (my->cal, UCAL_YEAR_WOY, (int32_t)amount); + ucal_set (cal, UCAL_YEAR_WOY, (int32_t)amount); } if ((amount = [comps nanosecond]) != NSDateComponentUndefined) { - ucal_set (my->cal, UCAL_MILLISECOND, (int32_t)(amount / 1000)); + ucal_set (cal, UCAL_MILLISECOND, (int32_t)(amount / MILLI_TO_NANO)); } - - udate = ucal_getMillis (my->cal, &err); + + udate = ucal_getMillis(cal, &err); + ucal_close(cal); if (U_FAILURE(err)) - return nil; - - return [NSDate dateWithTimeIntervalSince1970: (udate / 1000.0)]; + { + return nil; + } + + return [NSDate dateWithTimeIntervalSince1970: (udate / SECOND_TO_MILLI)]; #else return nil; #endif @@ -650,6 +696,8 @@ do \ - (void) setLocale: (NSLocale *) locale { + // 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. [self _setLocaleIdentifier: [self _localeIDWithLocale: locale]]; } @@ -662,7 +710,7 @@ do \ { my->firstWeekday = weekday; #if GS_USE_ICU == 1 - ucal_setAttribute (my->cal, UCAL_FIRST_DAY_OF_WEEK, my->firstWeekday); + ucal_setAttribute(my->cal, UCAL_FIRST_DAY_OF_WEEK, my->firstWeekday); #endif } @@ -675,7 +723,7 @@ do \ { my->minimumDaysInFirstWeek = (int32_t)mdw; #if GS_USE_ICU == 1 - ucal_setAttribute (my->cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK, + ucal_setAttribute(my->cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK, my->minimumDaysInFirstWeek); #endif } @@ -691,7 +739,7 @@ do \ { return; } - + ASSIGN(my->tz, tz); [self _resetCalendar]; } @@ -702,16 +750,16 @@ do \ #if GS_USE_ICU == 1 UCalendarDateFields dateField; UErrorCode err = U_ZERO_ERROR; - + [self _resetCalendar]; - dateField = _NSCalendarUnitToDateField (unit); + dateField = _NSCalendarUnitToDateField(unit); if (dateField != -1) { // We really don't care if there are any errors... result.location = - (NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_MINIMUM, &err); + (NSUInteger)ucal_getLimit(my->cal, dateField, UCAL_MINIMUM, &err); result.length = - (NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_MAXIMUM, &err) + (NSUInteger)ucal_getLimit(my->cal, dateField, UCAL_MAXIMUM, &err) - result.location + 1; // ICU's month is 0-based, while NSCalendar is 1-based if (dateField == UCAL_MONTH) @@ -730,16 +778,16 @@ do \ #if GS_USE_ICU == 1 UCalendarDateFields dateField; UErrorCode err = U_ZERO_ERROR; - + [self _resetCalendar]; - dateField = _NSCalendarUnitToDateField (unit); + dateField = _NSCalendarUnitToDateField(unit); if (dateField != -1) { // We really don't care if there are any errors... result.location = - (NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_GREATEST_MINIMUM, &err); + (NSUInteger)ucal_getLimit(my->cal, dateField, UCAL_GREATEST_MINIMUM, &err); result.length = - (NSUInteger)ucal_getLimit (my->cal, dateField, UCAL_LEAST_MAXIMUM, &err) + (NSUInteger)ucal_getLimit(my->cal, dateField, UCAL_LEAST_MAXIMUM, &err) - result.location + 1; // ICU's month is 0-based, while NSCalendar is 1-based if (dateField == UCAL_MONTH) @@ -765,27 +813,6 @@ do \ return NSMakeRange (0, 0); } -+ (id) autoupdatingCurrentCalendar -{ - NSCalendar *result; - - [classLock lock]; - if (nil == autoupdatingCalendar) - { - autoupdatingCalendar = [[self currentCalendar] copy]; - [[NSNotificationCenter defaultCenter] - addObserver: self - selector: @selector(defaultsDidChange:) - name: NSUserDefaultsDidChangeNotification - object: nil]; - } - - result = RETAIN(autoupdatingCalendar); - [classLock unlock]; - return AUTORELEASE(result); -} - - - (BOOL) rangeOfUnit: (NSCalendarUnit) unit startDate: (NSDate **) datep interval: (NSTimeInterval *)tip @@ -797,7 +824,7 @@ do \ - (BOOL) isEqual: (id) obj { #if GS_USE_ICU == 1 - return (BOOL)ucal_equivalentTo (my->cal, [obj _UCalendar]); + return (BOOL)ucal_equivalentTo(my->cal, [obj _UCalendar]); #else if ([obj isKindOfClass: [self class]]) { @@ -807,9 +834,13 @@ do \ return NO; if (![my->tz isEqual: [obj timeZone]]) return NO; + if (my->firstWeekday != [obj firstWeekday]) + return NO; + if (my->minimumDaysInFirstWeek != [obj minimumDaysInFirstWeek]) + return NO; return YES; } - + return NO; #endif } @@ -828,14 +859,14 @@ do \ [self initWithCalendarIdentifier: s]; [self _setLocaleIdentifier: [decoder decodeObject]]; [self setTimeZone: [decoder decodeObject]]; - + return self; } - (id) copyWithZone: (NSZone*)zone { NSCalendar *result; - + if (NSShouldRetainWithZone(self, zone)) { return RETAIN(self); @@ -847,7 +878,7 @@ do \ [result _setLocaleIdentifier: my->localeID]; [result setTimeZone: my->tz]; } - + return result; } @@ -895,7 +926,7 @@ typedef struct { { if (nil != (self = [super init])) { - _NSDateComponentsInternal = + _NSDateComponentsInternal = NSZoneCalloc([self zone], sizeof(DateComp), 1); my->era = NSDateComponentUndefined; @@ -915,7 +946,7 @@ typedef struct { my->nanosecond = NSDateComponentUndefined; my->cal = NULL; my->tz = NULL; - } + } return self; } @@ -1012,10 +1043,6 @@ typedef struct { - (NSDate *) date { NSCalendar* cal = [self calendar]; - NSTimeZone* zone = [self timeZone]; - - if (zone != NULL) - [cal setTimeZone: zone]; return [cal dateFromComponents: self]; } @@ -1141,6 +1168,7 @@ typedef struct { case NSCalendarUnitQuarter: return my->quarter; case NSCalendarUnitWeekOfMonth: return my->weekOfMonth; case NSCalendarUnitWeekOfYear: return my->week; + case NSWeekCalendarUnit: return my->week; case NSCalendarUnitYearForWeekOfYear: return my->yearForWeekOfYear; case NSCalendarUnitNanosecond: return my->nanosecond; default: return 0; @@ -1188,6 +1216,9 @@ typedef struct { case NSCalendarUnitWeekOfYear: my->week = value; break; + case NSWeekCalendarUnit: + my->week = value; + break; case NSCalendarUnitYearForWeekOfYear: my->yearForWeekOfYear = value; break; @@ -1208,7 +1239,7 @@ typedef struct { else { NSDateComponents *c = [[NSDateComponents allocWithZone: zone] init]; - + memcpy(c->_NSDateComponentsInternal, _NSDateComponentsInternal, sizeof(DateComp)); /* We gave objects to the copy, so we need to retain them too. diff --git a/Source/NSLocale.m b/Source/NSLocale.m index 6f5321b50..479db6dd9 100644 --- a/Source/NSLocale.m +++ b/Source/NSLocale.m @@ -627,6 +627,11 @@ static NSRecursiveLock *classLock = nil; [[dict objectForKey: NSLocaleCollationIdentifier] UTF8String]; const char *currency = [[dict objectForKey: NSLocaleCurrencyCode] UTF8String]; + if (!calendar) + { + calendar = [[dict objectForKey: NSLocaleCalendarIdentifier] UTF8String]; + } + // A locale cannot be constructed without a language. if (language == NULL) return nil; diff --git a/Tests/base/NSCalendar/features-10-7.m b/Tests/base/NSCalendar/features-10-7.m index 0f83763b8..3dd873cba 100644 --- a/Tests/base/NSCalendar/features-10-7.m +++ b/Tests/base/NSCalendar/features-10-7.m @@ -17,6 +17,8 @@ int main() NSCalendar *cal; NSDate *date; NSDate *date2; + NSDate *date3; + NSDate *date4; START_SET("NSCalendar 10.7 features") if (!NSCALENDAR_SUPPORTED) @@ -48,7 +50,13 @@ int main() date2 = [comps date]; PASS_EQUAL(date, date2, "-[NSDateComponents date] returns the correct date"); - + + date3 = [NSDate dateWithString: @"2012-12-31 13:57:00 +0200"]; + [comps setTimeZone: [NSTimeZone timeZoneForSecondsFromGMT: 7200]]; + date4 = [cal dateFromComponents: comps]; + + PASS_EQUAL(date3, date4, "-[NSCalendar dateFromComponents:] respects the time zone"); + RELEASE(comps); RELEASE(cal); From 9eb0fd4e1ab73753ac1ea5b00e71164b6f9458c0 Mon Sep 17 00:00:00 2001 From: Richard Frith-Macdonald Date: Mon, 27 Apr 2020 09:14:36 +0100 Subject: [PATCH 04/10] Fix typo spotted by 'alotrev' --- Source/NSString.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/NSString.m b/Source/NSString.m index 0b3b777e2..5e8f5352a 100644 --- a/Source/NSString.m +++ b/Source/NSString.m @@ -1963,7 +1963,7 @@ GSICUCollatorOpen(NSStringCompareOptions mask, NSLocale *locale) uint8_t hi = s[index+1]; uint8_t lo = s[index+2]; - if (isdigit(hi) && isxdigit(lo)) + if (isxdigit(hi) && isxdigit(lo)) { index += 2; if (hi <= '9') From 4b53530b5c334bea235718cf0775593b9dd97fd6 Mon Sep 17 00:00:00 2001 From: Artem Lotorev Date: Mon, 27 Apr 2020 19:57:26 +0300 Subject: [PATCH 05/10] Added test for [NSString stringByRemovingPercentEncoding], updated percent-encoding tests with PASS_EQUAL --- Tests/base/NSString/test09.m | 82 ++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 27 deletions(-) diff --git a/Tests/base/NSString/test09.m b/Tests/base/NSString/test09.m index ea1ee04af..cfd746689 100644 --- a/Tests/base/NSString/test09.m +++ b/Tests/base/NSString/test09.m @@ -3,55 +3,83 @@ #import #import -BOOL testUrlCharacterSetEncoding( - NSString* decodedString, - NSString* encodedString, - NSCharacterSet* allowedCharacterSet) -{ - NSString *testString - = [decodedString stringByAddingPercentEncodingWithAllowedCharacters: - allowedCharacterSet]; -// NSLog(@"String by adding percent, done. test=%@ decoded=%@", testString, decodedString); - return [encodedString isEqualToString: testString]; -} - int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + NSString *urlDecodedString = @"Only alphabetic characters should be allowed and not encoded. !@#$%^&*()_+-="; - NSString *urlEncodedString = - @"Only%20alphabetic%20characters%20should%20be%20allowed%20and%20not%20encoded%2E%20%21%40%23%24%25%5E%26%2A%28%29%5F%2B%2D%3D"; + NSString *urlEncodedString = @"Only%20alphabetic%20characters%20should%20be%20allowed%20and%20not%20encoded%2E%20%21%40%23%24%25%5E%26%2A%28%29%5F%2B%2D%3D"; NSCharacterSet *allowedCharacterSet = [NSCharacterSet alphanumericCharacterSet]; - PASS(testUrlCharacterSetEncoding(urlDecodedString, urlEncodedString, allowedCharacterSet), "alphanumericCharacterSet"); - + NSString *testEncodingString = [urlDecodedString stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; + PASS_EQUAL(testEncodingString, urlEncodedString, "Percent-encoding, skip alphanumericCharacterSet"); + PASS_EQUAL([urlEncodedString stringByRemovingPercentEncoding], urlDecodedString, "Percent-encoded string decoding 01"); + + urlDecodedString = @"https://www.microsoft.com/en-us/!@#$%^&*()_"; urlEncodedString = @"https://www.microsoft.com/en-us/!@%23$%25%5E&*()_"; allowedCharacterSet = [NSCharacterSet URLFragmentAllowedCharacterSet]; - PASS(testUrlCharacterSetEncoding(urlDecodedString, urlEncodedString, allowedCharacterSet), "fragmentCharacterSet"); - + testEncodingString = [urlDecodedString stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; + PASS_EQUAL(testEncodingString, urlEncodedString, "Percent-encoding, skip URLFragmentAllowedCharacterSet "); + PASS_EQUAL([urlEncodedString stringByRemovingPercentEncoding], urlDecodedString,"Percent-encoded string decoding 02"); + + urlDecodedString = @"All alphabetic characters should be encoded. Symbols should not be: !@#$%^&*()_+-="; urlEncodedString = @"%41%6C%6C %61%6C%70%68%61%62%65%74%69%63 %63%68%61%72%61%63%74%65%72%73 %73%68%6F%75%6C%64 %62%65 " @"%65%6E%63%6F%64%65%64. %53%79%6D%62%6F%6C%73 %73%68%6F%75%6C%64 %6E%6F%74 %62%65: !@#$%^&*()_+-="; allowedCharacterSet = [[NSCharacterSet alphanumericCharacterSet] invertedSet]; - PASS(testUrlCharacterSetEncoding(urlDecodedString, urlEncodedString, allowedCharacterSet), "inverted"); + testEncodingString = [urlDecodedString stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; + PASS_EQUAL(testEncodingString, urlEncodedString, "Percent-encoding, skip not alphanumeric character "); + //In Apple's Foundation implementation -stringByRemovingPercentEncoding always returns nil if a decoded string contains % not followed by two hex digit. + PASS_EQUAL([urlEncodedString stringByRemovingPercentEncoding], urlDecodedString, "Percent-encoded string decoding 03"); + urlDecodedString = @"Here are some Emojis: \U0001F601 \U0001F602 \U0001F638 Emojis done."; // Multibyte encoded characters urlEncodedString = @"Here%20are%20some%20Emojis:%20%F0%9F%98%81%20%F0%9F%98%82%20%F0%9F%98%B8%20Emojis%20done."; allowedCharacterSet = [NSCharacterSet URLFragmentAllowedCharacterSet]; - PASS(testUrlCharacterSetEncoding(urlDecodedString, urlEncodedString, allowedCharacterSet), "fragmentCharacterSet emojis"); + testEncodingString = [urlDecodedString stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; + PASS_EQUAL(testEncodingString, urlEncodedString, "Percent-encoding, multibyte string skip alphanumeric characters"); + PASS_EQUAL([urlEncodedString stringByRemovingPercentEncoding], urlDecodedString, "Percent-encoded string decoding 04"); + urlDecodedString = @"\1"; urlEncodedString = @"%01"; allowedCharacterSet = [NSCharacterSet alphanumericCharacterSet]; - PASS(testUrlCharacterSetEncoding(urlDecodedString, urlEncodedString, allowedCharacterSet), "alphanumericCharacterSet"); + testEncodingString = [urlDecodedString stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; + PASS_EQUAL(testEncodingString, urlEncodedString, "Percent-encoding one byte string"); + PASS_EQUAL( [urlEncodedString stringByRemovingPercentEncoding], urlDecodedString, "Percent-encoded string decoding 05"); + + + urlDecodedString = @"£"; + urlEncodedString = @"%C2%A3"; + allowedCharacterSet = [NSCharacterSet alphanumericCharacterSet]; + testEncodingString = [urlDecodedString stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; + PASS_EQUAL( testEncodingString, urlEncodedString, "Percent-encoding two byte character"); + PASS_EQUAL([urlEncodedString stringByRemovingPercentEncoding], urlDecodedString, "Two-byte character decoding 06"); + + + //check full string encoding and decoding + NSString* asIsString = @"0123456789 AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz _-~`!#$&'()*+,/:;=?@[]™…©®£ƒ‰¥§"; + urlDecodedString = @"0123456789 AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz _-~`!#$%&'()*+,/:;=?@[]™…©®£ƒ‰¥§"; + urlEncodedString = @"%30%31%32%33%34%35%36%37%38%39%20%41%61%42%62%43%63%44%64%45%65%46%66%47%67%48%68%49%69%4A%6A%4B%6B%4C%6C%4D%6D%4E%6E%4F%6F%50%70%51%71%52%72%53%73%54%74%55%75%56%76%57%77%58%78%59%79%5A%7A%20%5F%2D%7E%60%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D%E2%84%A2%E2%80%A6%C2%A9%C2%AE%C2%A3%C6%92%E2%80%B0%C2%A5%C2%A7"; + allowedCharacterSet = [[NSCharacterSet characterSetWithCharactersInString:urlDecodedString] invertedSet]; + testEncodingString = [urlDecodedString stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; + PASS_EQUAL(testEncodingString, urlEncodedString, "Percent-encoding all characters in the string"); + PASS_EQUAL([urlEncodedString stringByRemovingPercentEncoding], urlDecodedString, "Percent-encoded string decoding 07"); + + + //check decoding of string with an unencoded part at the beginning + urlDecodedString = [asIsString stringByAppendingString:urlDecodedString]; + urlEncodedString = [asIsString stringByAppendingString:urlEncodedString]; + PASS_EQUAL([urlEncodedString stringByRemovingPercentEncoding], urlDecodedString, "Percent-encoded string decoding 08"); + + + //check decoding of string with the encoded part in the middle + urlDecodedString = [urlDecodedString stringByAppendingString:asIsString]; + urlEncodedString = [urlEncodedString stringByAppendingString:asIsString]; + PASS_EQUAL([urlEncodedString stringByRemovingPercentEncoding], urlDecodedString, "Percent-encoded string decoding 09"); + - urlDecodedString = @"All alphabetic characters should be encoded. Symbols should not be: !@#$%^&*()_+-="; - urlEncodedString = @"%41%6C%6C %61%6C%70%68%61%62%65%74%69%63 %63%68%61%72%61%63%74%65%72%73 %73%68%6F%75%6C%64 %62%65 " - @"%65%6E%63%6F%64%65%64. %53%79%6D%62%6F%6C%73 %73%68%6F%75%6C%64 %6E%6F%74 %62%65: !@#$%^&*()_+-="; - NSString *result = [urlEncodedString stringByRemovingPercentEncoding]; - PASS([urlDecodedString isEqualToString: result], "stringByRemovingPercentEncoding"); - // NSLog(@"Result = \"%@\",\ndecodedString = \"%@\",\nencodedString = \"%@\"", result, urlDecodedString, urlEncodedString); [pool drain]; return 0; } From 0aeaee68dad55e2d4eaf59ce5ff1829039287a22 Mon Sep 17 00:00:00 2001 From: Richard Frith-Macdonald Date: Mon, 27 Apr 2020 20:25:24 +0100 Subject: [PATCH 06/10] Fixup -stringByAddingPercentEncodingWithAllowedCharacters: to ignore membership of the supplied set if the character is not in the ASCII range. --- Source/NSString.m | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Source/NSString.m b/Source/NSString.m index 5e8f5352a..7f00bfde3 100644 --- a/Source/NSString.m +++ b/Source/NSString.m @@ -1901,7 +1901,8 @@ GSICUCollatorOpen(NSStringCompareOptions mask, NSLocale *locale) } } -- (NSString *) stringByAddingPercentEncodingWithAllowedCharacters: (NSCharacterSet *)aSet +- (NSString *) stringByAddingPercentEncodingWithAllowedCharacters: + (NSCharacterSet *)aSet { NSData *data = [self dataUsingEncoding: NSUTF8StringEncoding]; NSString *s = nil; @@ -1921,7 +1922,10 @@ GSICUCollatorOpen(NSStringCompareOptions mask, NSLocale *locale) unsigned int hi; unsigned int lo; - if([aSet characterIsMember: c]) // if the character is in the allowed set, put it in + /* If the character is in the allowed set *and* is in the + * 7-bit ASCII range, it can be added unchanged. + */ + if (c < 128 && [aSet characterIsMember: c]) { dst[dpos++] = c; } @@ -1936,7 +1940,7 @@ GSICUCollatorOpen(NSStringCompareOptions mask, NSLocale *locale) } s = [[NSString alloc] initWithBytes: dst length: dpos - encoding: NSUTF8StringEncoding]; + encoding: NSASCIIStringEncoding]; NSZoneFree(NSDefaultMallocZone(), dst); IF_NO_GC([s autorelease];) } From 2e22bf344d99808c68579cbe1f1d9cb0225e3cb7 Mon Sep 17 00:00:00 2001 From: Artem Lotorev Date: Tue, 28 Apr 2020 02:27:18 +0300 Subject: [PATCH 07/10] Added helper method for percent decoding/encoding tests --- Tests/base/NSString/test09.m | 71 ++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/Tests/base/NSString/test09.m b/Tests/base/NSString/test09.m index cfd746689..2f8f97353 100644 --- a/Tests/base/NSString/test09.m +++ b/Tests/base/NSString/test09.m @@ -3,81 +3,88 @@ #import #import +void testEncodeDecode(NSString* encoded, NSString* decoded, NSCharacterSet* charset, NSString* description){ + + NSString* encodeTest = [decoded stringByAddingPercentEncodingWithAllowedCharacters:charset]; + NSString* decodeTest = [encoded stringByRemovingPercentEncoding]; + + PASS_EQUAL(encodeTest, encoded, ([[NSString stringWithFormat:@"Percent-Encode: %@", description] UTF8String])); + PASS_EQUAL(decodeTest, decoded, ([[NSString stringWithFormat:@"Percent-Decode: %@", description] UTF8String])); +} + int main (int argc, const char * argv[]) { + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSString *urlDecodedString = @"Only alphabetic characters should be allowed and not encoded. !@#$%^&*()_+-="; NSString *urlEncodedString = @"Only%20alphabetic%20characters%20should%20be%20allowed%20and%20not%20encoded%2E%20%21%40%23%24%25%5E%26%2A%28%29%5F%2B%2D%3D"; - NSCharacterSet *allowedCharacterSet = [NSCharacterSet alphanumericCharacterSet]; - NSString *testEncodingString = [urlDecodedString stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; - PASS_EQUAL(testEncodingString, urlEncodedString, "Percent-encoding, skip alphanumericCharacterSet"); - PASS_EQUAL([urlEncodedString stringByRemovingPercentEncoding], urlDecodedString, "Percent-encoded string decoding 01"); + testEncodeDecode(urlEncodedString, urlDecodedString, [NSCharacterSet alphanumericCharacterSet], + @"Skip alphanumericCharacterSet 01"); urlDecodedString = @"https://www.microsoft.com/en-us/!@#$%^&*()_"; urlEncodedString = @"https://www.microsoft.com/en-us/!@%23$%25%5E&*()_"; - allowedCharacterSet = [NSCharacterSet URLFragmentAllowedCharacterSet]; - testEncodingString = [urlDecodedString stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; - PASS_EQUAL(testEncodingString, urlEncodedString, "Percent-encoding, skip URLFragmentAllowedCharacterSet "); - PASS_EQUAL([urlEncodedString stringByRemovingPercentEncoding], urlDecodedString,"Percent-encoded string decoding 02"); + testEncodeDecode(urlEncodedString, urlDecodedString, [NSCharacterSet URLFragmentAllowedCharacterSet], + @"Skip URLFragmentAllowedCharacterSet 02"); urlDecodedString = @"All alphabetic characters should be encoded. Symbols should not be: !@#$%^&*()_+-="; urlEncodedString = @"%41%6C%6C %61%6C%70%68%61%62%65%74%69%63 %63%68%61%72%61%63%74%65%72%73 %73%68%6F%75%6C%64 %62%65 " @"%65%6E%63%6F%64%65%64. %53%79%6D%62%6F%6C%73 %73%68%6F%75%6C%64 %6E%6F%74 %62%65: !@#$%^&*()_+-="; - allowedCharacterSet = [[NSCharacterSet alphanumericCharacterSet] invertedSet]; - testEncodingString = [urlDecodedString stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; - PASS_EQUAL(testEncodingString, urlEncodedString, "Percent-encoding, skip not alphanumeric character "); - //In Apple's Foundation implementation -stringByRemovingPercentEncoding always returns nil if a decoded string contains % not followed by two hex digit. - PASS_EQUAL([urlEncodedString stringByRemovingPercentEncoding], urlDecodedString, "Percent-encoded string decoding 03"); + testEncodeDecode(urlEncodedString, urlDecodedString, [[NSCharacterSet alphanumericCharacterSet] invertedSet], + @"Skip not alphanumericCharacterSet 03"); urlDecodedString = @"Here are some Emojis: \U0001F601 \U0001F602 \U0001F638 Emojis done."; // Multibyte encoded characters urlEncodedString = @"Here%20are%20some%20Emojis:%20%F0%9F%98%81%20%F0%9F%98%82%20%F0%9F%98%B8%20Emojis%20done."; - allowedCharacterSet = [NSCharacterSet URLFragmentAllowedCharacterSet]; - testEncodingString = [urlDecodedString stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; - PASS_EQUAL(testEncodingString, urlEncodedString, "Percent-encoding, multibyte string skip alphanumeric characters"); - PASS_EQUAL([urlEncodedString stringByRemovingPercentEncoding], urlDecodedString, "Percent-encoded string decoding 04"); + testEncodeDecode(urlEncodedString, urlDecodedString, [NSCharacterSet URLFragmentAllowedCharacterSet], + @"Skip URLFragmentAllowedCharacterSet 04"); urlDecodedString = @"\1"; urlEncodedString = @"%01"; - allowedCharacterSet = [NSCharacterSet alphanumericCharacterSet]; - testEncodingString = [urlDecodedString stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; - PASS_EQUAL(testEncodingString, urlEncodedString, "Percent-encoding one byte string"); - PASS_EQUAL( [urlEncodedString stringByRemovingPercentEncoding], urlDecodedString, "Percent-encoded string decoding 05"); + testEncodeDecode(urlEncodedString, urlDecodedString, [NSCharacterSet alphanumericCharacterSet], + @"Skip URLFragmentAllowedCharacterSet 05"); urlDecodedString = @"£"; urlEncodedString = @"%C2%A3"; - allowedCharacterSet = [NSCharacterSet alphanumericCharacterSet]; - testEncodingString = [urlDecodedString stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; - PASS_EQUAL( testEncodingString, urlEncodedString, "Percent-encoding two byte character"); - PASS_EQUAL([urlEncodedString stringByRemovingPercentEncoding], urlDecodedString, "Two-byte character decoding 06"); + testEncodeDecode(urlEncodedString, urlDecodedString, [NSCharacterSet alphanumericCharacterSet], + @"Two-byte character 06"); + + + urlDecodedString = @"€"; + urlEncodedString = @"%E2%82%AC"; + testEncodeDecode(urlEncodedString, urlDecodedString, [NSCharacterSet alphanumericCharacterSet], + @"Three-byte character 07"); + + + urlDecodedString = @"𐍈"; + urlEncodedString = @"%F0%90%8D%88"; + testEncodeDecode(urlEncodedString, urlDecodedString, [NSCharacterSet alphanumericCharacterSet], + @"Four-byte character 08"); //check full string encoding and decoding - NSString* asIsString = @"0123456789 AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz _-~`!#$&'()*+,/:;=?@[]™…©®£ƒ‰¥§"; urlDecodedString = @"0123456789 AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz _-~`!#$%&'()*+,/:;=?@[]™…©®£ƒ‰¥§"; urlEncodedString = @"%30%31%32%33%34%35%36%37%38%39%20%41%61%42%62%43%63%44%64%45%65%46%66%47%67%48%68%49%69%4A%6A%4B%6B%4C%6C%4D%6D%4E%6E%4F%6F%50%70%51%71%52%72%53%73%54%74%55%75%56%76%57%77%58%78%59%79%5A%7A%20%5F%2D%7E%60%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D%E2%84%A2%E2%80%A6%C2%A9%C2%AE%C2%A3%C6%92%E2%80%B0%C2%A5%C2%A7"; - allowedCharacterSet = [[NSCharacterSet characterSetWithCharactersInString:urlDecodedString] invertedSet]; - testEncodingString = [urlDecodedString stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; - PASS_EQUAL(testEncodingString, urlEncodedString, "Percent-encoding all characters in the string"); - PASS_EQUAL([urlEncodedString stringByRemovingPercentEncoding], urlDecodedString, "Percent-encoded string decoding 07"); + testEncodeDecode(urlEncodedString, urlDecodedString, [[NSCharacterSet characterSetWithCharactersInString:urlDecodedString] invertedSet], + @"All characters int string 09"); //check decoding of string with an unencoded part at the beginning + NSString* asIsString = @"0123456789 AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz _-~`!#$&'()*+,/:;=?@[]™…©®£ƒ‰¥§"; urlDecodedString = [asIsString stringByAppendingString:urlDecodedString]; urlEncodedString = [asIsString stringByAppendingString:urlEncodedString]; - PASS_EQUAL([urlEncodedString stringByRemovingPercentEncoding], urlDecodedString, "Percent-encoded string decoding 08"); + PASS_EQUAL([urlEncodedString stringByRemovingPercentEncoding], urlDecodedString, "Percent-encoded string decoding 10"); //check decoding of string with the encoded part in the middle urlDecodedString = [urlDecodedString stringByAppendingString:asIsString]; urlEncodedString = [urlEncodedString stringByAppendingString:asIsString]; - PASS_EQUAL([urlEncodedString stringByRemovingPercentEncoding], urlDecodedString, "Percent-encoded string decoding 09"); + PASS_EQUAL([urlEncodedString stringByRemovingPercentEncoding], urlDecodedString, "Percent-encoded string decoding 11"); [pool drain]; From 56be28aa114fc27056ab0a8184615fb40bf24a48 Mon Sep 17 00:00:00 2001 From: Artem Lotorev Date: Tue, 28 Apr 2020 03:07:09 +0300 Subject: [PATCH 08/10] Fixed build issue due to wrong argument in the macro --- Tests/base/NSString/test09.m | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Tests/base/NSString/test09.m b/Tests/base/NSString/test09.m index 2f8f97353..a54d1081b 100644 --- a/Tests/base/NSString/test09.m +++ b/Tests/base/NSString/test09.m @@ -7,9 +7,12 @@ void testEncodeDecode(NSString* encoded, NSString* decoded, NSCharacterSet* char NSString* encodeTest = [decoded stringByAddingPercentEncodingWithAllowedCharacters:charset]; NSString* decodeTest = [encoded stringByRemovingPercentEncoding]; + + const char* encodeMsg = [[NSString stringWithFormat:@"Percent-Encode: %@", description] UTF8String]; + const char* decodeMsg = [[NSString stringWithFormat:@"Percent-Decode: %@", description] UTF8String]; - PASS_EQUAL(encodeTest, encoded, ([[NSString stringWithFormat:@"Percent-Encode: %@", description] UTF8String])); - PASS_EQUAL(decodeTest, decoded, ([[NSString stringWithFormat:@"Percent-Decode: %@", description] UTF8String])); + PASS_EQUAL(encodeTest, encoded, "%s", encodeMsg); + PASS_EQUAL(decodeTest, decoded, "%s", decodeMsg); } int main (int argc, const char * argv[]) @@ -71,7 +74,7 @@ int main (int argc, const char * argv[]) urlDecodedString = @"0123456789 AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz _-~`!#$%&'()*+,/:;=?@[]™…©®£ƒ‰¥§"; urlEncodedString = @"%30%31%32%33%34%35%36%37%38%39%20%41%61%42%62%43%63%44%64%45%65%46%66%47%67%48%68%49%69%4A%6A%4B%6B%4C%6C%4D%6D%4E%6E%4F%6F%50%70%51%71%52%72%53%73%54%74%55%75%56%76%57%77%58%78%59%79%5A%7A%20%5F%2D%7E%60%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D%E2%84%A2%E2%80%A6%C2%A9%C2%AE%C2%A3%C6%92%E2%80%B0%C2%A5%C2%A7"; testEncodeDecode(urlEncodedString, urlDecodedString, [[NSCharacterSet characterSetWithCharactersInString:urlDecodedString] invertedSet], - @"All characters int string 09"); + @"All characters in string 09"); //check decoding of string with an unencoded part at the beginning From 58e1b0ed48fe671ebb6468ccd32180e7e075f0a1 Mon Sep 17 00:00:00 2001 From: Artem Lotorev Date: Thu, 30 Apr 2020 14:18:05 +0300 Subject: [PATCH 09/10] Added param to run a test on a separate object or test file. Added .DS_Store to .gitignore --- .gitignore | 3 +++ Tests/GNUmakefile | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 6ca0a81ff..17e71c7ff 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,9 @@ Tests/base/coding/ulong-8.type *.orig *.swp +# MacOS Desktop Services Store +.DS_Store + # Created by https://www.gitignore.io/api/xcode # Edit at https://www.gitignore.io/?templates=xcode diff --git a/Tests/GNUmakefile b/Tests/GNUmakefile index fcc7d3722..b2d65052f 100644 --- a/Tests/GNUmakefile +++ b/Tests/GNUmakefile @@ -51,6 +51,9 @@ TOP_DIR := $(shell dirname $(CURDIR)) all:: @(echo If you want to run the gnustep-base testsuite, please type \'${MAKE} check\') +# To run tests for a separate group please, use 'make check testobj=NSString' +# or 'make check testobj=NSDate/general.m' to run an individual test file +# # # 'make check' runs the testsuite (Objective-C tests only) # @@ -72,9 +75,9 @@ check:: export LD_LIBRARY_PATH;\ export PATH;\ if [ "$(debug)" = "yes" ]; then \ - gnustep-tests --debug base;\ + gnustep-tests --debug 'base/$(testobj)';\ else \ - gnustep-tests base;\ + gnustep-tests 'base/$(testobj)';\ fi; \ ) From 6869df9a505e39ab28346635722facb88d28af26 Mon Sep 17 00:00:00 2001 From: niwatako <1546218+niwatako@users.noreply.github.com> Date: Fri, 1 May 2020 23:51:55 +0900 Subject: [PATCH 10/10] Fix loss of buffer in NSJSONSerialization. The first time bufferSize is extended, number loses the previously parsed characters. After malloc the number, copy the numberBuffer to the number. --- Source/NSJSONSerialization.m | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Source/NSJSONSerialization.m b/Source/NSJSONSerialization.m index ffed5b1ec..5bbc4715d 100644 --- a/Source/NSJSONSerialization.m +++ b/Source/NSJSONSerialization.m @@ -468,11 +468,16 @@ parseNumber(ParserState *state) {\ bufferSize *= 2;\ if (number == numberBuffer)\ - number = malloc(bufferSize);\ + {\ + number = malloc(bufferSize);\ + memcpy(number, numberBuffer, sizeof(numberBuffer));\ + }\ else\ - number = realloc(number, bufferSize);\ + {\ + number = realloc(number, bufferSize);\ + }\ }\ - number[parsedSize++] = (char)x; } while (0) + number[parsedSize++] = (char)x; } while (0) // JSON numbers must start with a - or a digit if (!(c == '-' || isdigit(c))) {