diff --git a/ChangeLog b/ChangeLog index 7ec6b9fac..a0a6c1902 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,22 +15,7 @@ * Source/NSFileManager.m: Create an NSError object when we fail to copy because the destination item already exists. -2024-10-10: Hugo Melder - - * Source/NSDate.m: - * Source/NSDatePrivate.h: - NSDate is now a small object in slot 6, when configuring GNUstep with the - clang/libobjc2 toolchain. This is done by compressing the binary64 fp - holding the seconds since reference date (because someone at Apple thought - it would be a good idea to represent a time interval as a fp). Note that - this assumes that IEEE 754 is used. - * Source/NSCalendarDate.m: - Remove access to the NSDate private concrete Class - and implement -timeIntervalSinceReferenceDate instead. - Previously, all methods of the concrete date class were - added to NSCalendarDate via GSObjCAddClassBehavior. - -2024-23-09: Hugo Melder +2024-09-23: Hugo Melder * Headers/Foundation/NSThread.h: * Source/NSString.m: diff --git a/Source/NSCalendarDate.m b/Source/NSCalendarDate.m index 5f8a52abc..895248e37 100644 --- a/Source/NSCalendarDate.m +++ b/Source/NSCalendarDate.m @@ -58,6 +58,9 @@ @interface GSTimeZone : NSObject // Help the compiler @class GSAbsTimeZone; @interface GSAbsTimeZone : NSObject // Help the compiler @end +@class NSGDate; +@interface NSGDate : NSObject // Help the compiler +@end #define DISTANT_FUTURE 63113990400.0 @@ -393,6 +396,7 @@ + (void) initialize absAbrIMP = (NSString* (*)(id,SEL,id)) [absClass instanceMethodForSelector: abrSEL]; + GSObjCAddClassBehavior(self, [NSGDate class]); [pool release]; } } @@ -459,11 +463,6 @@ + (id) dateWithYear: (NSInteger)year return AUTORELEASE(d); } -- (NSTimeInterval) timeIntervalSinceReferenceDate -{ - return _seconds_since_ref; -} - /** * Creates and returns a new NSCalendarDate object by taking the * value of the receiver and adding the interval in seconds specified. diff --git a/Source/NSDate.m b/Source/NSDate.m index b71bbb65f..5f632f0ca 100644 --- a/Source/NSDate.m +++ b/Source/NSDate.m @@ -1,11 +1,10 @@ /** Implementation for NSDate for GNUStep - Copyright (C) 2024 Free Software Foundation, Inc. + Copyright (C) 1995, 1996, 1997, 1998 Free Software Foundation, Inc. Written by: Jeremy Bettis Rewritten by: Scott Christley + Date: March 1995 Modifications by: Richard Frith-Macdonald - Small Object Optimization by: Hugo Melder - Date: September 2024 This file is part of the GNUstep Base Library. @@ -40,13 +39,9 @@ #import "Foundation/NSScanner.h" #import "Foundation/NSTimeZone.h" #import "Foundation/NSUserDefaults.h" -#import "Foundation/NSHashTable.h" #import "GNUstepBase/GSObjCRuntime.h" #import "GSPrivate.h" -#import "GSPThread.h" - -#import "NSDatePrivate.h" #include @@ -56,598 +51,41 @@ /* On older Solaris we don't have NAN nor nan() */ #if defined(__sun) && defined(__SVR4) && !defined(NAN) -#define NAN 0x7fffffffffffffff -#endif - -GS_DECLARE const NSTimeInterval NSTimeIntervalSince1970 = 978307200.0; - -static BOOL debug = NO; -static Class abstractClass = nil; -static Class concreteClass = nil; -static Class calendarClass = nil; - -static gs_mutex_t classLock = GS_MUTEX_INIT_STATIC; - -// Singleton instances for distantPast and distantFuture -static id _distantPast = nil; -static id _distantFuture = nil; - -/** - * Compression of IEEE 754 double-precision floating-point numbers - * - * libobjc2 just like Apple's Objective-C runtime implement small - * object classes, or tagged pointers in the case of Apple's runtime, - * to store a 60-bit payload and 4-bit metadata in a 64-bit pointer. - * This avoids constructing a full object on the heap. - * - * NSDate stores the time as a double-precision floating-point number - * representing the number of seconds since the reference date, the - * Cocoa epoch (2001-01-01 00:00:00 UTC). This is a 64-bit value. - * This poses a problem for small object classes, as the time value - * is too large to fit in the 60-bit payload. - * - * To solve this problem, we look at the range of values that we - * need to acurately represent. Idealy, this would include dates - * before distant past and beyond distant future. - * - * After poking around with __NSTaggedDate, here is the algorithm - * for constructing its payload: - * - * Sign and mantissa are not touched. The exponent is compressed. - * Compression: - * 1. Take the 11-bit unsigned exponent and sign-extend it to a 64-bit signed integer. - * 2. Subtract a new secondary bias of 0x3EF from the exponent. - * 3. Truncate the result to a 7-bit signed integer. - * - * The order of operations is important. The biased exponent of a - * double-precision floating-point number is in range [0, 2047] (including - * special values). Sign-extending and subtracting the secondary bias results - * in a value in range [-1007, 1040]. Truncating this to a 7-bit signed integer - * further reduces the range to [-64, 63]. - * - * When unbiasing the compressed 7-bit signed exponent with 0x3EF, we - * get a biased exponent in range [943, 1070]. We have effectively shifted - * the value range in order to represent values from - * (-1)^0 * 2^(943 - 1023) * 1.048576 = 8.673617379884035e-25 - * to (-1)^0 * 2^(1070 - 1023) * 1.048576 = 147573952589676.4 - * - * This encodes all dates for a few million years beyond distantPast and - * distantFuture, except within about 1e-25 second of the reference date. - * - * So how does decompression work? - * 1. Sign extend the 7-bit signed exponent to a 64-bit signed integer. - * 2. Add the secondary bias of 0x3EF to the exponent. - * 3. Cast the result to an unsigned 11-bit integer. - * - * Note that we only use the least-significant 3-bits for the tag in - * libobjc2, contrary to Apple's runtime which uses the most-significant - * 4-bits. - * - * We'll thus use 8-bits for the exponent. - */ - -#if USE_SMALL_DATE - -// 1-5 are already used by NSNumber and GSString -#define SMALL_DATE_MASK 6 -#define EXPONENT_BIAS 0x3EF - -#define GET_INTERVAL(obj) decompressTimeInterval((uintptr_t)obj) -#define SET_INTERVAL(obj, interval) (obj = (id)(compressTimeInterval(interval) | SMALL_DATE_MASK)) - -#define IS_CONCRETE_CLASS(obj) isSmallDate(obj) - -#define CREATE_SMALL_DATE(interval) (id)(compressTimeInterval(interval) | SMALL_DATE_MASK) - -union CompressedDouble { - uintptr_t data; - struct { - uintptr_t tag : 3; // placeholder for tag bits - uintptr_t fraction : 52; - intptr_t exponent : 8; // signed! - uintptr_t sign : 1; - }; -}; - -union DoubleBits { - double val; - struct { - uintptr_t fraction : 52; - uintptr_t exponent : 11; - uintptr_t sign : 1; - }; -}; - -static __attribute__((always_inline)) uintptr_t compressTimeInterval(NSTimeInterval interval) { - union CompressedDouble c; - union DoubleBits db; - intptr_t exponent; - - db.val = interval; - c.fraction = db.fraction; - c.sign = db.sign; - - // 1. Cast 11-bit unsigned exponent to 64-bit signed - exponent = db.exponent; - // 2. Subtract secondary Bias first - exponent -= EXPONENT_BIAS; - // 3. Truncate to 8-bit signed - c.exponent = exponent; - c.tag = 0; - - return c.data; -} - -static __attribute__((always_inline)) NSTimeInterval decompressTimeInterval(uintptr_t compressed) { - union CompressedDouble c; - union DoubleBits d; - intptr_t biased_exponent; - - c.data = compressed; - d.fraction = c.fraction; - d.sign = c.sign; - - // 1. Sign Extend 8-bit to 64-bit - biased_exponent = c.exponent; - // 2. Add secondary Bias - biased_exponent += 0x3EF; - // Cast to 11-bit unsigned exponent - d.exponent = biased_exponent; - - return d.val; -} - -static __attribute__((always_inline)) BOOL isSmallDate(id obj) { - // Do a fast check if the object is also a small date. - // libobjc2 guarantees that the classes are 16-byte (word) aligned. - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-objc-pointer-introspection" - return !!((uintptr_t)obj & SMALL_DATE_MASK); - #pragma clang diagnostic pop -} - -// Populated in +[GSSmallDate load] -static BOOL useSmallDate; - - -#else -#define GET_INTERVAL(obj) ((NSGDate*)obj)->_seconds_since_ref -#define SET_INTERVAL(obj, interval) (((NSGDate*)obj)->_seconds_since_ref = interval) - -#define IS_CONCRETE_CLASS(obj) ([obj isKindOfClass: concreteClass]) - -@interface GSDateSingle : NSGDate -@end - -@interface GSDatePast : GSDateSingle -@end - -@interface GSDateFuture : GSDateSingle -@end - -#endif - -@implementation DATE_CONCRETE_CLASS_NAME - -#if USE_SMALL_DATE - -+ (void) load -{ - useSmallDate = objc_registerSmallObjectClass_np(self, SMALL_DATE_MASK); - // If this fails, someone else has already registered a small object class for this slot. - if (unlikely(useSmallDate == NO)) - { - [NSException raise: NSInternalInconsistencyException format: @"Failed to register GSSmallDate small object class"]; - } -} - -// Overwrite default memory management methods - -+ (id) alloc -{ - return (id)SMALL_DATE_MASK; -} - -+ (id) allocWithZone: (NSZone*)aZone -{ - return (id)SMALL_DATE_MASK; -} - -- (id) copy -{ - return self; -} - -- (id) copyWithZone: (NSZone*)aZone -{ - return self; -} - -- (id) retain -{ - return self; -} - -- (NSUInteger) retainCount -{ - return UINT_MAX; -} - -- (id) autorelease -{ - return self; -} - -- (oneway void) release -{ - return; -} - -// NSObject(MemoryFootprint) informal protocol - -- (NSUInteger) sizeInBytesExcluding: (NSHashTable*)exclude -{ - if (0 == NSHashGet(exclude, self)) - { - return 0; - } - return 8; -} - -- (NSUInteger) sizeOfContentExcluding: (NSHashTable*)exclude -{ - return 0; -} - -- (NSUInteger) sizeOfInstance -{ - return 0; -} - -#else - -+ (void) initialize -{ - if (self == [NSDate class]) - { - [self setVersion: 1]; - } -} - -#endif - -// NSDate initialization - -- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs -{ - if (isnan(secs)) - { - [NSException raise: NSInvalidArgumentException - format: @"[%@-%@] interval is not a number", - NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; - } - -#if USE_SMALL_DATE == 0 && GS_SIZEOF_VOIDP == 4 - if (secs <= DISTANT_PAST) - { - secs = DISTANT_PAST; - } - else if (secs >= DISTANT_FUTURE) - { - secs = DISTANT_FUTURE; - } -#endif - -#if USE_SMALL_DATE == 0 - _seconds_since_ref = secs; - return self; -#else - return CREATE_SMALL_DATE(secs); -#endif -} - -- (id) initWithCoder: (NSCoder*)coder -{ - double secondsSinceRef; - if ([coder allowsKeyedCoding]) - { - secondsSinceRef = [coder decodeDoubleForKey: @"NS.time"]; - } - else - { - [coder decodeValueOfObjCType: @encode(NSTimeInterval) - at: &secondsSinceRef]; - } - -#if USE_SMALL_DATE == 0 - _seconds_since_ref = secondsSinceRef; - return self; -#else - return CREATE_SMALL_DATE(secondsSinceRef); -#endif -} - -// NSDate Hashing, Comparison and Equality - -- (NSUInteger) hash -{ - #if USE_SMALL_DATE - return (NSUInteger)self; - #else - return (NSUInteger)GET_INTERVAL(self); - #endif -} - -- (NSComparisonResult) compare: (NSDate*)otherDate -{ - double selfTime = GET_INTERVAL(self); - double otherTime; - - if (otherDate == self) - { - return NSOrderedSame; - } - if (unlikely(otherDate == nil)) - { - [NSException raise: NSInvalidArgumentException - format: @"nil argument for compare:"]; - } - - if (IS_CONCRETE_CLASS(otherDate)) - { - otherTime = GET_INTERVAL(otherDate); - } else { - otherTime = [otherDate timeIntervalSinceReferenceDate]; - } - - if (selfTime > otherTime) - { - return NSOrderedDescending; - } - if (selfTime < otherTime) - { - return NSOrderedAscending; - } - return NSOrderedSame; -} - -- (BOOL) isEqual: (id)other -{ - double selfTime = GET_INTERVAL(self); - double otherTime; - - if (other == self) - { - return YES; - } - - if (IS_CONCRETE_CLASS(other)) - { - otherTime = GET_INTERVAL(other); - } else if ([other isKindOfClass: abstractClass]) - { - otherTime = [other timeIntervalSinceReferenceDate]; - } else { - return NO; - } - - return selfTime == otherTime; -} - -- (BOOL) isEqualToDate: (NSDate*)other -{ - return [self isEqual: other]; -} - -- (NSDate*) laterDate: (NSDate*)otherDate -{ - double selfTime; - double otherTime; - - if (unlikely(otherDate == nil)) - { - [NSException raise: NSInvalidArgumentException - format: @"nil argument for laterDate:"]; - } - - selfTime = GET_INTERVAL(self); - if (IS_CONCRETE_CLASS(otherDate)) - { - otherTime = GET_INTERVAL(otherDate); - } else { - otherTime = [otherDate timeIntervalSinceReferenceDate]; - } - - // If the receiver and anotherDate represent the same date, returns the receiver. - if (selfTime <= otherTime) - { - return otherDate; - } - - return self; -} - -- (NSDate*) earlierDate: (NSDate*)otherDate -{ - double selfTime; - double otherTime; - - if (unlikely(otherDate == nil)) - { - [NSException raise: NSInvalidArgumentException - format: @"nil argument for earlierDate:"]; - } - - selfTime = GET_INTERVAL(self); - if (IS_CONCRETE_CLASS(otherDate)) - { - otherTime = GET_INTERVAL(otherDate); - } else { - otherTime = [otherDate timeIntervalSinceReferenceDate]; - } - - // If the receiver and anotherDate represent the same date, returns the receiver. - if (selfTime >= otherTime) - { - return otherDate; - } - - return self; -} - -- (void) encodeWithCoder: (NSCoder*)coder -{ - double time = GET_INTERVAL(self); - if ([coder allowsKeyedCoding]) - { - [coder encodeDouble:time forKey:@"NS.time"]; - } - else - { - [coder encodeValueOfObjCType: @encode(NSTimeInterval) - at: &time]; - } -} - -// NSDate Accessors - -- (NSTimeInterval) timeIntervalSince1970 -{ - return GET_INTERVAL(self) + NSTimeIntervalSince1970; -} - -- (NSTimeInterval) timeIntervalSinceDate: (NSDate*)otherDate -{ - double otherTime; - if (unlikely(otherDate == nil)) - { - [NSException raise: NSInvalidArgumentException - format: @"nil argument for timeIntervalSinceDate:"]; - } - - if (IS_CONCRETE_CLASS(otherDate)) - { - otherTime = GET_INTERVAL(otherDate); - } else { - otherTime = [otherDate timeIntervalSinceReferenceDate]; - } - - return GET_INTERVAL(self) - otherTime; -} - -- (NSTimeInterval) timeIntervalSinceNow -{ - return GET_INTERVAL(self) - GSPrivateTimeNow(); -} - -- (NSTimeInterval) timeIntervalSinceReferenceDate -{ - return GET_INTERVAL(self); -} - -@end - -#if USE_SMALL_DATE == 0 -/* - * This abstract class represents a date of which there can be only - * one instance. - */ -@implementation GSDateSingle - -+ (void) initialize -{ - if (self == [GSDateSingle class]) - { - [self setVersion: 1]; - GSObjCAddClassBehavior(self, [NSGDate class]); - } -} - -- (id) autorelease -{ - return self; -} - -- (oneway void) release -{ -} - -- (id) retain -{ - return self; -} - -+ (id) allocWithZone: (NSZone*)z -{ - [NSException raise: NSInternalInconsistencyException - format: @"Attempt to allocate fixed date"]; - return nil; -} - -- (id) copyWithZone: (NSZone*)z -{ - return self; -} - -- (void) dealloc -{ - [NSException raise: NSInternalInconsistencyException - format: @"Attempt to deallocate fixed date"]; - GSNOSUPERDEALLOC; -} - -- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs -{ - return self; -} - -@end - -@implementation GSDatePast - -+ (id) allocWithZone: (NSZone*)z -{ - if (_distantPast == nil) - { - id obj = NSAllocateObject(self, 0, NSDefaultMallocZone()); - - _distantPast = [obj init]; - } - return _distantPast; -} - -- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs -{ - SET_INTERVAL(self, DISTANT_PAST); - return self; -} +#define NAN 0x7fffffffffffffff +#endif -@end +GS_DECLARE const NSTimeInterval NSTimeIntervalSince1970 = 978307200.0; + -@implementation GSDateFuture +static BOOL debug = NO; +static Class abstractClass = nil; +static Class concreteClass = nil; +static Class calendarClass = nil; -+ (id) allocWithZone: (NSZone*)z +/** + * Our concrete base class - NSCalendar date must share the ivar layout. + */ +@interface NSGDate : NSDate { - if (_distantFuture == nil) - { - id obj = NSAllocateObject(self, 0, NSDefaultMallocZone()); - - _distantFuture = [obj init]; - } - return _distantFuture; +@public + NSTimeInterval _seconds_since_ref; } +@end -- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs -{ - SET_INTERVAL(self, DISTANT_FUTURE); - return self; -} +@interface GSDateSingle : NSGDate +@end + +@interface GSDatePast : GSDateSingle +@end +@interface GSDateFuture : GSDateSingle @end -#endif // USE_SMALL_DATE == 0 +static id _distantPast = nil; +static id _distantFuture = nil; + static NSString* findInArray(NSArray *array, unsigned pos, NSString *str) { @@ -668,10 +106,17 @@ - (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs static inline NSTimeInterval otherTime(NSDate* other) { - if (unlikely(other == nil)) + Class c; + + if (other == nil) [NSException raise: NSInvalidArgumentException format: @"other time nil"]; - - return [other timeIntervalSinceReferenceDate]; + if (GSObjCIsInstance(other) == NO) + [NSException raise: NSInvalidArgumentException format: @"other time bad"]; + c = object_getClass(other); + if (c == concreteClass || c == calendarClass) + return ((NSGDate*)other)->_seconds_since_ref; + else + return [other timeIntervalSinceReferenceDate]; } /** @@ -690,7 +135,7 @@ + (void) initialize { [self setVersion: 1]; abstractClass = self; - concreteClass = [DATE_CONCRETE_CLASS_NAME class]; + concreteClass = [NSGDate class]; calendarClass = [NSCalendarDate class]; } } @@ -699,11 +144,7 @@ + (id) alloc { if (self == abstractClass) { - #if USE_SMALL_DATE - return [DATE_CONCRETE_CLASS_NAME alloc]; // alloc is overridden to return a small object - #else return NSAllocateObject(concreteClass, 0, NSDefaultMallocZone()); - #endif } return NSAllocateObject(self, 0, NSDefaultMallocZone()); } @@ -712,11 +153,7 @@ + (id) allocWithZone: (NSZone*)z { if (self == abstractClass) { - #if USE_SMALL_DATE - return [DATE_CONCRETE_CLASS_NAME alloc]; // alloc is overridden to return a small object - #else return NSAllocateObject(concreteClass, 0, z); - #endif } return NSAllocateObject(self, 0, z); } @@ -1448,7 +885,8 @@ + (instancetype) dateWithNaturalLanguageString: (NSString*)string } else { - return [self dateWithTimeIntervalSinceReferenceDate: otherTime(theDate)]; + return [self dateWithTimeIntervalSinceReferenceDate: + otherTime(theDate)]; } } @@ -1485,16 +923,7 @@ + (instancetype) distantPast { if (_distantPast == nil) { - GS_MUTEX_LOCK(classLock); - if (_distantPast == nil) - { - #if USE_SMALL_DATE - _distantPast = CREATE_SMALL_DATE(DISTANT_PAST); - #else - _distantPast = [GSDatePast allocWithZone: 0]; - #endif - } - GS_MUTEX_UNLOCK(classLock); + _distantPast = [GSDatePast allocWithZone: 0]; } return _distantPast; } @@ -1503,16 +932,7 @@ + (instancetype) distantFuture { if (_distantFuture == nil) { - GS_MUTEX_LOCK(classLock); - if (_distantFuture == nil) - { - #if USE_SMALL_DATE - _distantFuture = CREATE_SMALL_DATE(DISTANT_FUTURE); - #else - _distantFuture = [GSDateFuture allocWithZone: 0]; - #endif - } - GS_MUTEX_UNLOCK(classLock); + _distantFuture = [GSDateFuture allocWithZone: 0]; } return _distantFuture; } @@ -1591,48 +1011,267 @@ - (NSString*) description return s; } -- (NSString*) descriptionWithCalendarFormat: (NSString*)format - timeZone: (NSTimeZone*)aTimeZone - locale: (NSDictionary*)l +- (NSString*) descriptionWithCalendarFormat: (NSString*)format + timeZone: (NSTimeZone*)aTimeZone + locale: (NSDictionary*)l +{ + // Easiest to just have NSCalendarDate do the work for us + NSString *s; + NSCalendarDate *d = [calendarClass alloc]; + id f; + + d = [d initWithTimeIntervalSinceReferenceDate: otherTime(self)]; + if (!format) + { + f = [d calendarFormat]; + } + else + { + f = format; + } + if (aTimeZone) + { + [d setTimeZone: aTimeZone]; + } + s = [d descriptionWithCalendarFormat: f locale: l]; + RELEASE(d); + return s; +} + +- (NSString *) descriptionWithLocale: (id)locale +{ + // Easiest to just have NSCalendarDate do the work for us + NSString *s; + NSCalendarDate *d = [calendarClass alloc]; + + d = [d initWithTimeIntervalSinceReferenceDate: otherTime(self)]; + s = [d descriptionWithLocale: locale]; + RELEASE(d); + return s; +} + +- (NSDate*) earlierDate: (NSDate*)otherDate +{ + if (otherTime(self) > otherTime(otherDate)) + { + return otherDate; + } + return self; +} + +- (void) encodeWithCoder: (NSCoder*)coder +{ + NSTimeInterval interval = [self timeIntervalSinceReferenceDate]; + + if ([coder allowsKeyedCoding]) + { + [coder encodeDouble: interval forKey: @"NS.time"]; + } + [coder encodeValueOfObjCType: @encode(NSTimeInterval) at: &interval]; +} + +- (NSUInteger) hash +{ + return (NSUInteger)[self timeIntervalSinceReferenceDate]; +} + +- (instancetype) initWithCoder: (NSCoder*)coder +{ + NSTimeInterval interval; + id o; + + if ([coder allowsKeyedCoding]) + { + interval = [coder decodeDoubleForKey: @"NS.time"]; + } + else + { + [coder decodeValueOfObjCType: @encode(NSTimeInterval) at: &interval]; + } + if (interval == DISTANT_PAST) + { + o = RETAIN([abstractClass distantPast]); + } + else if (interval == DISTANT_FUTURE) + { + o = RETAIN([abstractClass distantFuture]); + } + else + { + o = [concreteClass allocWithZone: NSDefaultMallocZone()]; + o = [o initWithTimeIntervalSinceReferenceDate: interval]; + } + DESTROY(self); + return o; +} + +- (instancetype) init +{ + return [self initWithTimeIntervalSinceReferenceDate: GSPrivateTimeNow()]; +} + +- (instancetype) initWithString: (NSString*)description +{ + // Easiest to just have NSCalendarDate do the work for us + NSCalendarDate *d = [calendarClass alloc]; + + d = [d initWithString: description]; + if (nil == d) + { + DESTROY(self); + return nil; + } + else + { + self = [self initWithTimeIntervalSinceReferenceDate: otherTime(d)]; + RELEASE(d); + return self; + } +} + +- (instancetype) initWithTimeInterval: (NSTimeInterval)secsToBeAdded + sinceDate: (NSDate*)anotherDate +{ + if (anotherDate == nil) + { + NSLog(@"initWithTimeInterval:sinceDate: given nil date"); + DESTROY(self); + return nil; + } + // Get the other date's time, add the secs and init thyself + return [self initWithTimeIntervalSinceReferenceDate: + otherTime(anotherDate) + secsToBeAdded]; +} + +- (instancetype) initWithTimeIntervalSince1970: (NSTimeInterval)seconds +{ + return [self initWithTimeIntervalSinceReferenceDate: + seconds - NSTimeIntervalSince1970]; +} + +- (instancetype) initWithTimeIntervalSinceNow: (NSTimeInterval)secsToBeAdded +{ + // Get the current time, add the secs and init thyself + return [self initWithTimeIntervalSinceReferenceDate: + GSPrivateTimeNow() + secsToBeAdded]; +} + +- (instancetype) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs +{ + [self subclassResponsibility: _cmd]; + return self; +} + +- (BOOL) isEqual: (id)other +{ + if (other != nil + && [other isKindOfClass: abstractClass] + && otherTime(self) == otherTime(other)) + { + return YES; + } + return NO; +} + +- (BOOL) isEqualToDate: (NSDate*)other +{ + if (other != nil + && otherTime(self) == otherTime(other)) + { + return YES; + } + return NO; +} + +- (NSDate*) laterDate: (NSDate*)otherDate +{ + if (otherTime(self) < otherTime(otherDate)) + { + return otherDate; + } + return self; +} + +- (id) replacementObjectForPortCoder: (NSPortCoder*)aCoder +{ + if ([aCoder isByref] == NO) + { + return self; + } + return [super replacementObjectForPortCoder: aCoder]; +} + +- (NSTimeInterval) timeIntervalSince1970 +{ + return otherTime(self) + NSTimeIntervalSince1970; +} + +- (NSTimeInterval) timeIntervalSinceDate: (NSDate*)otherDate +{ + if (nil == otherDate) + { +#ifndef NAN + return nan(""); +#else + return NAN; +#endif + } + return otherTime(self) - otherTime(otherDate); +} + +- (NSTimeInterval) timeIntervalSinceNow +{ + return otherTime(self) - GSPrivateTimeNow(); +} + +- (NSTimeInterval) timeIntervalSinceReferenceDate +{ + [self subclassResponsibility: _cmd]; + return 0; +} + +@end + +@implementation NSGDate + ++ (void) initialize +{ + if (self == [NSDate class]) + { + [self setVersion: 1]; + } +} + +- (NSComparisonResult) compare: (NSDate*)otherDate { - // Easiest to just have NSCalendarDate do the work for us - NSString *s; - NSCalendarDate *d = [calendarClass alloc]; - id f; - - d = [d initWithTimeIntervalSinceReferenceDate: otherTime(self)]; - if (!format) + if (otherDate == self) { - f = [d calendarFormat]; + return NSOrderedSame; } - else + if (otherDate == nil) { - f = format; + [NSException raise: NSInvalidArgumentException + format: @"nil argument for compare:"]; } - if (aTimeZone) + if (_seconds_since_ref > otherTime(otherDate)) { - [d setTimeZone: aTimeZone]; + return NSOrderedDescending; } - s = [d descriptionWithCalendarFormat: f locale: l]; - RELEASE(d); - return s; -} - -- (NSString *) descriptionWithLocale: (id)locale -{ - // Easiest to just have NSCalendarDate do the work for us - NSString *s; - NSCalendarDate *d = [calendarClass alloc]; - - d = [d initWithTimeIntervalSinceReferenceDate: otherTime(self)]; - s = [d descriptionWithLocale: locale]; - RELEASE(d); - return s; + if (_seconds_since_ref < otherTime(otherDate)) + { + return NSOrderedAscending; + } + return NSOrderedSame; } - (NSDate*) earlierDate: (NSDate*)otherDate { - if (otherTime(self) > otherTime(otherDate)) + if (otherDate == nil) + { + [NSException raise: NSInvalidArgumentException + format: @"nil argument for earlierDate:"]; + } + if (_seconds_since_ref > otherTime(otherDate)) { return otherDate; } @@ -1641,198 +1280,221 @@ - (NSDate*) earlierDate: (NSDate*)otherDate - (void) encodeWithCoder: (NSCoder*)coder { - NSTimeInterval interval = [self timeIntervalSinceReferenceDate]; - if ([coder allowsKeyedCoding]) { - [coder encodeDouble: interval forKey: @"NS.time"]; + [coder encodeDouble:_seconds_since_ref forKey:@"NS.time"]; + } + else + { + [coder encodeValueOfObjCType: @encode(NSTimeInterval) + at: &_seconds_since_ref]; } - [coder encodeValueOfObjCType: @encode(NSTimeInterval) at: &interval]; } - (NSUInteger) hash { - return (NSUInteger)[self timeIntervalSinceReferenceDate]; + return (unsigned)_seconds_since_ref; } -- (instancetype) initWithCoder: (NSCoder*)coder +- (id) initWithCoder: (NSCoder*)coder { - NSTimeInterval interval; - id o; - if ([coder allowsKeyedCoding]) { - interval = [coder decodeDoubleForKey: @"NS.time"]; + _seconds_since_ref = [coder decodeDoubleForKey: @"NS.time"]; } else { - [coder decodeValueOfObjCType: @encode(NSTimeInterval) at: &interval]; + [coder decodeValueOfObjCType: @encode(NSTimeInterval) + at: &_seconds_since_ref]; } - if (interval == DISTANT_PAST) + return self; +} + +- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs +{ + if (isnan(secs)) { - o = RETAIN([abstractClass distantPast]); + [NSException raise: NSInvalidArgumentException + format: @"[%@-%@] interval is not a number", + NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } - else if (interval == DISTANT_FUTURE) + +#if GS_SIZEOF_VOIDP == 4 + if (secs <= DISTANT_PAST) { - o = RETAIN([abstractClass distantFuture]); + secs = DISTANT_PAST; } - else + else if (secs >= DISTANT_FUTURE) { - o = [concreteClass allocWithZone: NSDefaultMallocZone()]; - o = [o initWithTimeIntervalSinceReferenceDate: interval]; + secs = DISTANT_FUTURE; } - DESTROY(self); - return o; +#endif + _seconds_since_ref = secs; + return self; } -- (instancetype) init +- (BOOL) isEqual: (id)other { - return [self initWithTimeIntervalSinceReferenceDate: GSPrivateTimeNow()]; + if (other != nil + && [other isKindOfClass: abstractClass] + && _seconds_since_ref == otherTime(other)) + { + return YES; + } + return NO; } -- (instancetype) initWithString: (NSString*)description +- (BOOL) isEqualToDate: (NSDate*)other { - // Easiest to just have NSCalendarDate do the work for us - NSCalendarDate *d = [calendarClass alloc]; - - d = [d initWithString: description]; - if (nil == d) - { - DESTROY(self); - return nil; - } - else + if (other != nil + && _seconds_since_ref == otherTime(other)) { - self = [self initWithTimeIntervalSinceReferenceDate: otherTime(d)]; - RELEASE(d); - return self; + return YES; } + return NO; } -- (instancetype) initWithTimeInterval: (NSTimeInterval)secsToBeAdded - sinceDate: (NSDate*)anotherDate +- (NSDate*) laterDate: (NSDate*)otherDate { - if (anotherDate == nil) + if (otherDate == nil) { - NSLog(@"initWithTimeInterval:sinceDate: given nil date"); - DESTROY(self); - return nil; + [NSException raise: NSInvalidArgumentException + format: @"nil argument for laterDate:"]; } - // Get the other date's time, add the secs and init thyself - return [self initWithTimeIntervalSinceReferenceDate: otherTime(anotherDate) + secsToBeAdded]; + if (_seconds_since_ref < otherTime(otherDate)) + { + return otherDate; + } + return self; } -- (instancetype) initWithTimeIntervalSince1970: (NSTimeInterval)seconds +- (NSTimeInterval) timeIntervalSince1970 { - return [self initWithTimeIntervalSinceReferenceDate: - seconds - NSTimeIntervalSince1970]; + return _seconds_since_ref + NSTimeIntervalSince1970; } -- (instancetype) initWithTimeIntervalSinceNow: (NSTimeInterval)secsToBeAdded +- (NSTimeInterval) timeIntervalSinceDate: (NSDate*)otherDate { - // Get the current time, add the secs and init thyself - return [self initWithTimeIntervalSinceReferenceDate: - GSPrivateTimeNow() + secsToBeAdded]; + if (otherDate == nil) + { + [NSException raise: NSInvalidArgumentException + format: @"nil argument for timeIntervalSinceDate:"]; + } + return _seconds_since_ref - otherTime(otherDate); } -- (instancetype) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs +- (NSTimeInterval) timeIntervalSinceNow { - [self subclassResponsibility: _cmd]; - return self; + return _seconds_since_ref - GSPrivateTimeNow(); } -- (BOOL) isEqual: (id)other +- (NSTimeInterval) timeIntervalSinceReferenceDate { - if (other == nil) - { - return NO; - } + return _seconds_since_ref; +} - if (self == other) - { - return YES; - } +@end + + + +/* + * This abstract class represents a date of which there can be only + * one instance. + */ +@implementation GSDateSingle - if ([other isKindOfClass: abstractClass]) ++ (void) initialize +{ + if (self == [GSDateSingle class]) { - double selfTime = [self timeIntervalSinceReferenceDate]; - return selfTime == otherTime(other); + [self setVersion: 1]; + GSObjCAddClassBehavior(self, [NSGDate class]); } +} - return NO; +- (id) autorelease +{ + return self; } -- (BOOL) isEqualToDate: (NSDate*)other +- (oneway void) release { - double selfTime; - double otherTime; - if (other == nil) - { - return NO; - } +} - selfTime = [self timeIntervalSinceReferenceDate]; - otherTime = [other timeIntervalSinceReferenceDate]; - if (selfTime == otherTime) - { - return YES; - } +- (id) retain +{ + return self; +} - return NO; ++ (id) allocWithZone: (NSZone*)z +{ + [NSException raise: NSInternalInconsistencyException + format: @"Attempt to allocate fixed date"]; + return nil; } -- (NSDate*) laterDate: (NSDate*)otherDate +- (id) copyWithZone: (NSZone*)z { - double selfTime; - if (otherDate == nil) - { - return nil; - } - - selfTime = [self timeIntervalSinceReferenceDate]; - if (selfTime < otherTime(otherDate)) - { - return otherDate; - } return self; } -- (id) replacementObjectForPortCoder: (NSPortCoder*)aCoder +- (void) dealloc { - if ([aCoder isByref] == NO) - { - return self; - } - return [super replacementObjectForPortCoder: aCoder]; + [NSException raise: NSInternalInconsistencyException + format: @"Attempt to deallocate fixed date"]; + GSNOSUPERDEALLOC; } -- (NSTimeInterval) timeIntervalSince1970 +- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs { - return otherTime(self) + NSTimeIntervalSince1970; + return self; } -- (NSTimeInterval) timeIntervalSinceDate: (NSDate*)otherDate +@end + + + +@implementation GSDatePast + ++ (id) allocWithZone: (NSZone*)z { - if (nil == otherDate) + if (_distantPast == nil) { -#ifndef NAN - return nan(""); -#else - return NAN; -#endif + id obj = NSAllocateObject(self, 0, NSDefaultMallocZone()); + + _distantPast = [obj init]; } - return [self timeIntervalSinceReferenceDate] - otherTime(otherDate); + return _distantPast; } -- (NSTimeInterval) timeIntervalSinceNow +- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs { - return [self timeIntervalSinceReferenceDate] - GSPrivateTimeNow(); + _seconds_since_ref = DISTANT_PAST; + return self; } -- (NSTimeInterval) timeIntervalSinceReferenceDate +@end + + +@implementation GSDateFuture + ++ (id) allocWithZone: (NSZone*)z { - [self subclassResponsibility: _cmd]; - return 0; + if (_distantFuture == nil) + { + id obj = NSAllocateObject(self, 0, NSDefaultMallocZone()); + + _distantFuture = [obj init]; + } + return _distantFuture; +} + +- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs +{ + _seconds_since_ref = DISTANT_FUTURE; + return self; } @end + + diff --git a/Source/NSDatePrivate.h b/Source/NSDatePrivate.h deleted file mode 100644 index 07075d748..000000000 --- a/Source/NSDatePrivate.h +++ /dev/null @@ -1,41 +0,0 @@ -/** NSDate Private Interface - Copyright (C) 2024 Free Software Foundation, Inc. - - Written by: Hugo Melder - - This file is part of the GNUstep Base Library. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free - Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110 USA. -*/ - -#import - -#if defined(OBJC_SMALL_OBJECT_SHIFT) && (OBJC_SMALL_OBJECT_SHIFT == 3) -#define USE_SMALL_DATE 1 -#define DATE_CONCRETE_CLASS_NAME GSSmallDate -#else -#define USE_SMALL_DATE 0 -#define DATE_CONCRETE_CLASS_NAME NSGDate -#endif - -@interface DATE_CONCRETE_CLASS_NAME : NSDate -#if USE_SMALL_DATE == 0 -{ -@public - NSTimeInterval _seconds_since_ref; -} -#endif -@end \ No newline at end of file diff --git a/Source/NSTimer.m b/Source/NSTimer.m index 1646c124c..1444f46c2 100644 --- a/Source/NSTimer.m +++ b/Source/NSTimer.m @@ -35,8 +35,9 @@ #import "Foundation/NSRunLoop.h" #import "Foundation/NSInvocation.h" -#import "NSDatePrivate.h" - +@class NSGDate; +@interface NSGDate : NSObject // Help the compiler +@end static Class NSDate_class; /** @@ -57,7 +58,7 @@ + (void) initialize { if (self == [NSTimer class]) { - NSDate_class = [DATE_CONCRETE_CLASS_NAME class]; + NSDate_class = [NSGDate class]; } } diff --git a/Tests/base/NSFileManager/general.m b/Tests/base/NSFileManager/general.m index fb2ca6ee4..6acebd310 100644 --- a/Tests/base/NSFileManager/general.m +++ b/Tests/base/NSFileManager/general.m @@ -12,7 +12,7 @@ #ifdef EQ #undef EQ #endif -#define EPSILON (DBL_EPSILON*100) +#define EPSILON (FLT_EPSILON*100) #define EQ(x,y) ((x >= y - EPSILON) && (x <= y + EPSILON)) @interface MyHandler : NSObject @@ -204,7 +204,6 @@ int main() NSTimeInterval ot, nt; ot = [[oa fileCreationDate] timeIntervalSinceReferenceDate]; nt = [[na fileCreationDate] timeIntervalSinceReferenceDate]; - NSLog(@"ot = %f, nt = %f", ot, nt); PASS(EQ(ot, nt), "copy creation date equals original") ot = [[oa fileModificationDate] timeIntervalSinceReferenceDate]; nt = [[na fileModificationDate] timeIntervalSinceReferenceDate];