/** Implementation for NSCalendarDate for GNUstep Copyright (C) 1996, 1998, 1999, 2000, 2002 Free Software Foundation, Inc. Author: Scott Christley Date: October 1996 Author: Richard Frith-Macdonald Date: September 2002 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., 31 Milk Street #960789 Boston, MA 02196 USA. NSCalendarDate class reference $Date$ $Revision$ */ #import "common.h" #define EXPOSE_NSCalendarDate_IVARS 1 #include #import "Foundation/NSArray.h" #import "Foundation/NSAutoreleasePool.h" #import "Foundation/NSCalendarDate.h" #import "Foundation/NSCoder.h" #import "Foundation/NSData.h" #import "Foundation/NSDate.h" #import "Foundation/NSDictionary.h" #import "Foundation/NSException.h" #import "Foundation/NSTimeZone.h" #import "Foundation/NSUserDefaults.h" #import "GNUstepBase/GSObjCRuntime.h" #import "GSPrivate.h" #ifdef HAVE_SYS_TIME_H #include #endif #include #include #include @class GSTimeZone; @interface GSTimeZone : NSObject // Help the compiler @end @class GSAbsTimeZone; @interface GSAbsTimeZone : NSObject // Help the compiler @end #define DISTANT_FUTURE 63113990400.0 #define DISTANT_PAST -63113817600.0 static NSString *cformat = @"%Y-%m-%d %H:%M:%S %z"; static NSTimeZone *localTZ = nil; static Class NSCalendarDateClass; static Class absClass; static Class dstClass; static SEL offSEL; static int (*offIMP)(id, SEL, id); static int (*absOffIMP)(id, SEL, id); static int (*dstOffIMP)(id, SEL, id); static SEL abrSEL; static NSString* (*abrIMP)(id, SEL, id); static NSString* (*absAbrIMP)(id, SEL, id); static NSString* (*dstAbrIMP)(id, SEL, id); /* Do not fetch the default locale unless we actually need it. * Tries to avoid recursion when loading NSUserDefaults containing dates. * This is also a little more efficient with many date formats. */ #define LOCALE (nil == locale ? (locale = GSPrivateDefaultLocale()) : locale) /* * Return the offset from GMT for a date in a timezone ... * Optimize for the local timezone, and less so for the other * base library time zone classes. */ static inline int offset(NSTimeZone *tz, NSDate *d) { if (tz == nil) { return 0; } if (tz == localTZ && offIMP != 0) { return (*offIMP)(tz, offSEL, d); } else { Class c = object_getClass(tz); if (c == dstClass && dstOffIMP != 0) { return (*dstOffIMP)(tz, offSEL, d); } if (c == absClass && absOffIMP != 0) { return (*absOffIMP)(tz, offSEL, d); } return [tz secondsFromGMTForDate: d]; } } /* * Return the offset from GMT for a date in a timezone ... * Optimize for the local timezone, and less so for the other * base library time zone classes. */ static inline NSString* abbrev(NSTimeZone *tz, NSDate *d) { if (tz == nil) { return @"GMT"; } if (tz == localTZ && abrIMP != 0) { return (*abrIMP)(tz, abrSEL, d); } else { Class c = object_getClass(tz); if (c == dstClass && dstAbrIMP != 0) { return (*dstAbrIMP)(tz, abrSEL, d); } if (c == absClass && absAbrIMP != 0) { return (*absAbrIMP)(tz, abrSEL, d); } return [tz abbreviationForDate: d]; } } static inline NSUInteger lastDayOfGregorianMonth(NSUInteger month, NSUInteger year) { switch (month) { case 2: if ((((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0)) return 29; else return 28; case 4: case 6: case 9: case 11: return 30; default: return 31; } } static inline NSUInteger absoluteGregorianDay(NSUInteger day, NSUInteger month, NSUInteger year) { if (month > 1) { while (--month > 0) { day = day + lastDayOfGregorianMonth(month, year); } } if (year > 0) { year--; } return (day // days this year + 365 * year // days in previous years ignoring leap days + year/4 // Julian leap days before this year... - year/100 // ...minus prior century years... + year/400); // ...plus prior years divisible by 400 } static inline NSInteger dayOfCommonEra(NSTimeInterval when) { NSInteger r; // Get reference date in terms of days when /= 86400.0; // Offset by Gregorian reference when += GREGORIAN_REFERENCE; r = (NSInteger)when; return r; } static void gregorianDateFromAbsolute(NSInteger abs, NSInteger *day, NSInteger *month, NSInteger *year) { NSInteger y; NSInteger m; // Search forward year by year from approximate year y = abs/366; while (abs >= absoluteGregorianDay(1, 1, y+1)) { y++; } // Search forward month by month from January m = 1; while (abs > absoluteGregorianDay(lastDayOfGregorianMonth(m, y), m, y)) { m++; } *year = y; *month = m; *day = abs - absoluteGregorianDay(1, m, y) + 1; } /** * Convert a broken out time specification into a time interval * since the reference date. */ static NSTimeInterval GSTime(unsigned day, unsigned month, unsigned year, unsigned hour, unsigned minute, unsigned second, unsigned mil) { NSTimeInterval a; a = (NSTimeInterval)absoluteGregorianDay(day, month, year); // Calculate date as GMT a -= GREGORIAN_REFERENCE; a = (NSTimeInterval)a * 86400; a += hour * 3600; a += minute * 60; a += second; a += ((NSTimeInterval)mil)/1000.0; return a; } /** * Convert a time interval since the reference date into broken out * elements.
* External - so NSTimeZone can use it ... but should really be static. */ void GSBreakTime(NSTimeInterval when, NSInteger *year, NSInteger *month, NSInteger *day, NSInteger *hour, NSInteger *minute, NSInteger *second, NSInteger *mil) { NSInteger h, m, dayOfEra; double a, b, c, d; /* The 0.1 constant was experimentally derived to cause our behavior * to match Mac OS X 10.9.1. */ when = floor(when * 1000.0 + 0.1) / 1000.0; // Get reference date in terms of days a = when / 86400.0; // Offset by Gregorian reference a += GREGORIAN_REFERENCE; // result is the day of common era. dayOfEra = (NSInteger)a; // Calculate year, month, and day gregorianDateFromAbsolute(dayOfEra, day, month, year); // Calculate hour, minute, and seconds d = dayOfEra - GREGORIAN_REFERENCE; d *= 86400; a = fabs(d - when); b = a / 3600; *hour = (NSInteger)b; h = *hour; h = h * 3600; b = a - h; b = b / 60; *minute = (NSInteger)b; m = *minute; m = m * 60; c = a - h - m; *second = (NSInteger)c; *mil = (NSInteger)rint((a - h - m - *second) * 1000.0); } /** * Returns the current time (seconds since reference date) as an NSTimeInterval. */ NSTimeInterval GSPrivateTimeNow(void) { NSTimeInterval t; #if !defined(_WIN32) struct timeval tp; gettimeofday (&tp, NULL); t = (NSTimeInterval)tp.tv_sec - NSTimeIntervalSince1970; t += (NSTimeInterval)tp.tv_usec / (NSTimeInterval)1000000.0; #if 1 /* This is a workaround for a bug on some SMP intel systems where the TSC * clock information from the processors gets out of sync and causes a * leap of 4398 seconds into the future for an instant, and then back. * If we detect a time jump back by more than the sort of small interval * that ntpd might do (or forwards by a very large amount) we refetch the * system time to make sure we don't have a temporary glitch. */ { static int old = 0; if (old == 0) { old = tp.tv_sec; } else { int diff = tp.tv_sec - old; old = tp.tv_sec; if (diff < -1 || diff > 3000) { time_t now = (time_t)tp.tv_sec; fprintf(stderr, "WARNING: system time changed by %d seconds: %s\n", diff, ctime(&now)); /* Get time again ... should be OK now. */ t = GSPrivateTimeNow(); } } } #endif #else SYSTEMTIME sys_time; /* * Get current GMT time, convert to NSTimeInterval since reference date, */ GetSystemTime(&sys_time); t = GSTime(sys_time.wDay, sys_time.wMonth, sys_time.wYear, sys_time.wHour, sys_time.wMinute, sys_time.wSecond, sys_time.wMilliseconds); #endif /* _WIN32 */ return t; } /** * An [NSDate] subclass which understands about timezones and provides * methods for dealing with date and time information by calendar and * with hours minutes and seconds. */ @implementation NSCalendarDate + (void) initialize { if (self == [NSCalendarDate class] && nil == NSCalendarDateClass) { NSAutoreleasePool *pool = [NSAutoreleasePool new]; NSCalendarDateClass = self; [self setVersion: 1]; localTZ = RETAIN([NSTimeZone localTimeZone]); dstClass = [GSTimeZone class]; absClass = [GSAbsTimeZone class]; offSEL = @selector(secondsFromGMTForDate:); offIMP = (int (*)(id,SEL,id)) [localTZ methodForSelector: offSEL]; dstOffIMP = (int (*)(id,SEL,id)) [dstClass instanceMethodForSelector: offSEL]; absOffIMP = (int (*)(id,SEL,id)) [absClass instanceMethodForSelector: offSEL]; abrSEL = @selector(abbreviationForDate:); abrIMP = (NSString* (*)(id,SEL,id)) [localTZ methodForSelector: abrSEL]; dstAbrIMP = (NSString* (*)(id,SEL,id)) [dstClass instanceMethodForSelector: abrSEL]; absAbrIMP = (NSString* (*)(id,SEL,id)) [absClass instanceMethodForSelector: abrSEL]; [pool release]; } } /** * Return an NSCalendarDate for the current date and time using the * default timezone. */ + (id) calendarDate { id d = [[self alloc] init]; return AUTORELEASE(d); } /** * Return an NSCalendarDate generated from the supplied description * using the format specified for parsing that string.
* Calls -initWithString:calendarFormat: to create the date. */ + (id) dateWithString: (NSString *)description calendarFormat: (NSString *)format { NSCalendarDate *d = [[self alloc] initWithString: description calendarFormat: format]; return AUTORELEASE(d); } /** * Return an NSCalendarDate generated from the supplied description * using the format specified for parsing that string and interpreting * it according to the dictionary specified.
* Calls -initWithString:calendarFormat:locale: to create the date. */ + (id) dateWithString: (NSString *)description calendarFormat: (NSString *)format locale: (NSDictionary *)dictionary { NSCalendarDate *d = [[self alloc] initWithString: description calendarFormat: format locale: dictionary]; return AUTORELEASE(d); } /** * Creates and returns an NSCalendarDate from the specified values * by calling -initWithYear:month:day:hour:minute:second:timeZone: */ + (id) dateWithYear: (NSInteger)year month: (NSUInteger)month day: (NSUInteger)day hour: (NSUInteger)hour minute: (NSUInteger)minute second: (NSUInteger)second timeZone: (NSTimeZone *)aTimeZone { NSCalendarDate *d = [[self alloc] initWithYear: year month: month day: day hour: hour minute: minute second: second timeZone: aTimeZone]; 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. */ - (id) addTimeInterval: (NSTimeInterval)seconds { return [self dateByAddingTimeInterval: seconds]; } - (Class) classForCoder { return [self class]; } /** * Creates and returns a new NSCalendarDate object by taking the * value of the receiver and adding the interval specified. */ - (id) dateByAddingTimeInterval: (NSTimeInterval)seconds { id newObj = [[self class] dateWithTimeIntervalSinceReferenceDate: [self timeIntervalSinceReferenceDate] + seconds]; [newObj setTimeZone: [self timeZoneDetail]]; [newObj setCalendarFormat: [self calendarFormat]]; return newObj; } - (id) replacementObjectForPortCoder: (NSPortCoder*)aRmc { return self; } - (void) encodeWithCoder: (NSCoder*)coder { [coder encodeValueOfObjCType: @encode(NSTimeInterval) at: &_seconds_since_ref]; [coder encodeObject: _calendar_format]; [coder encodeObject: _time_zone]; } - (id) initWithCoder: (NSCoder*)coder { [coder decodeValueOfObjCType: @encode(NSTimeInterval) at: &_seconds_since_ref]; [coder decodeValueOfObjCType: @encode(id) at: &_calendar_format]; [coder decodeValueOfObjCType: @encode(id) at: &_time_zone]; return self; } - (void) dealloc { RELEASE(_calendar_format); RELEASE(_time_zone); [super dealloc]; } /** * Initializes an NSCalendarDate using the specified description and the * default calendar format and locale.
* Calls -initWithString:calendarFormat:locale: */ - (id) initWithString: (NSString *)description { return [self initWithString: description calendarFormat: cformat locale: nil]; } /** * Initializes an NSCalendarDate using the specified description and format * string interpreted in the default locale.
* Calls -initWithString:calendarFormat:locale: */ - (id) initWithString: (NSString *)description calendarFormat: (NSString *)format { return [self initWithString: description calendarFormat: format locale: nil]; } /* * read up to the specified number of characters, terminating at a non-digit * except for leading whitespace characters. */ static inline int getDigits(const char *from, char *to, int limit, BOOL *error) { int i = 0; int j = 0; BOOL foundDigit = NO; while (i < limit) { if (isdigit(from[i])) { to[j++] = from[i]; foundDigit = YES; } else if (isspace(from[i])) { if (foundDigit == YES) { break; } } else { break; } i++; } to[j] = '\0'; if (j == 0) { *error = YES; // No digits read } return i; } #define hadY 1 #define hadM 2 #define hadD 4 #define hadh 8 #define hadm 16 #define hads 32 #define hadw 64 /** * Initializes an NSCalendarDate using the specified description and format * string interpreted in the given locale.
* If description does not match fmt exactly, this method returns nil.
* Excess characters in the description (after the format is matched) * are ignored.
* Format specifiers are - * * * %% literal % character * * * %a abbreviated weekday name according to locale * * * %A full weekday name according to locale * * * %b abbreviated month name according to locale * * * %B full month name according to locale * * * %c same as '%X %x' * * * %d day of month as a two digit decimal number * * * %e same as %d without leading zero * * * %F milliseconds as a decimal number * * * %H hour as a decimal number using 24-hour clock * * * %I hour as a decimal number using 12-hour clock * * * %j day of year as a decimal number * * * %k same as %H without leading zero (leading space is used instead) * * * %m month as decimal number * * * %M minute as decimal number * * * %p 'am' or 'pm' * * * %S second as decimal number * * * %U week of the current year as decimal number (Sunday first day) * * * %W week of the current year as decimal number (Monday first day) * * * %w day of the week as decimal number (Sunday = 0) * * * %x date with date representation for locale * * * %X time with time representation for locale * * * %y year as a decimal number without century * * * %Y year as a decimal number with century * * * %z time zone offset in hours and minutes from GMT (HHMM) * * * %Z time zone abbreviation * * * If no year is specified in the format, the current year is assumed.
* If no month is specified in the format, January is assumed.
* If no day is specified in the format, 1 is assumed.
* If no hour is specified in the format, 0 is assumed.
* If no minute is specified in the format, 0 is assumed.
* If no second is specified in the format, 0 is assumed.
* If no millisecond is specified in the format, 0 is assumed.
* If no timezone is specified in the format, the local timezone is assumed. *

If GSMacOSXCompatible is YES, the %k specifier is not recognized.

*

NB. Where the format calls for a numeric value and the string contains * fewer digits than expected, the value will be accepted and left padded * with zeros to the expected size.
* For instance, the '%z' format implies four digits (two for the hour * offset and two for the digit offset) and if the string contains '01' * it will be treated as '0001' ie. a timezone offset of 1 minute.
* Similarly, the '%F' format implies three digits, so a value of '1' * would be treated as '001' or 1 millisecond, not a tenth of a second * (as you might assume as '%F' is usually used after a decimal separator). *

*/ - (id) initWithString: (NSString*)description calendarFormat: (NSString*)fmt locale: (NSDictionary*)locale { int milliseconds = 0; int year = 1; int month = 1; int day = 1; int hour = 0; int min = 0; int sec = 0; NSTimeZone *tz = nil; BOOL ampm = NO; BOOL isPM = NO; BOOL twelveHrClock = NO; int julianWeeks = -1, weekStartsMonday = 0, dayOfWeek = -1; const char *source; unsigned sourceLen; unichar *format; unsigned formatLen; unsigned formatIdx = 0; unsigned sourceIdx = 0; char tmpStr[120]; unsigned int tmpIdx; unsigned int tmpEnd; unsigned had = 0; unsigned int pos; BOOL hadPercent = NO; NSMutableData *fd; BOOL changedFormat = NO; BOOL error = NO; if (description == nil) { description = @""; } source = [description cString]; sourceLen = strlen(source); if (fmt == nil) { fmt = [LOCALE objectForKey: NSTimeDateFormatString]; if (fmt == nil) { fmt = @""; } } /* * Get format into a buffer, leaving room for expansion in case it has * escapes that need to be converted. */ formatLen = [fmt length]; fd = [[NSMutableData alloc] initWithLength: (formatLen + 32) * sizeof(unichar)]; format = (unichar*)[fd mutableBytes]; [fmt getCharacters: format]; /* * Expand any sequences to their basic components. */ for (pos = 0; pos < formatLen; pos++) { unichar c = format[pos]; if (c == '%') { if (hadPercent == YES) { hadPercent = NO; } else { hadPercent = YES; } } else { if (hadPercent == YES) { NSString *sub = nil; if (c == 'c') { sub = [LOCALE objectForKey: NSTimeDateFormatString]; if (sub == nil) { sub = @"%X %x"; } } else if (c == 'R') { sub = @"%H:%M"; } else if (c == 'r') { sub = @"%I:%M:%S %p"; } else if (c == 'T') { sub = @"%H:%M:%S"; } else if (c == 't') { sub = @"\t"; } else if (c == 'X') { sub = [LOCALE objectForKey: NSTimeFormatString]; if (sub == nil) { sub = @"%H-%M-%S"; } } else if (c == 'x') { sub = [LOCALE objectForKey: NSShortDateFormatString]; if (sub == nil) { sub = @"%y-%m-%d"; } } if (sub != nil) { unsigned sLen = [sub length]; int i; if (sLen > 2) { [fd setLength: (formatLen + sLen - 2) * sizeof(unichar)]; format = (unichar*)[fd mutableBytes]; for (i = formatLen-1; i > (NSInteger)pos; i--) { format[i+sLen-2] = format[i]; } } else { for (i = pos+1; i < (NSInteger)formatLen; i++) { format[i+sLen-2] = format[i]; } [fd setLength: (formatLen + sLen - 2) * sizeof(unichar)]; format = (unichar*)[fd mutableBytes]; } [sub getCharacters: &format[pos-1]]; formatLen += sLen - 2; changedFormat = YES; pos -= 2; // Re-parse the newly substituted data. } } hadPercent = NO; } } /* * Set up calendar format. */ if (changedFormat == YES) { fmt = [NSString stringWithCharacters: format length: formatLen]; } ASSIGN(_calendar_format, fmt); // // WARNING: // -Most locale stuff is dubious at best. // -Long day and month names depend on a non-alpha character after the // last digit to work. // while (error == NO && formatIdx < formatLen) { if (format[formatIdx] != '%') { // If it's not a format specifier, ignore it. if (isspace(format[formatIdx])) { // Skip any amount of white space. while (source[sourceIdx] != 0 && isspace(source[sourceIdx])) { sourceIdx++; } } else { if (sourceIdx < sourceLen) { if (source[sourceIdx] != format[formatIdx]) { error = YES; NSDebugMLog( @"Expected literal '%c' but got '%c' parsing" @"'%@' using '%@'", format[formatIdx], source[sourceIdx], description, fmt); } sourceIdx++; } } } else { // Skip '%' formatIdx++; while (formatIdx < formatLen && isdigit(format[formatIdx])) { formatIdx++; // skip field width } if (formatIdx < formatLen) { switch (format[formatIdx]) { case '%': // skip literal % if (sourceIdx < sourceLen) { if (source[sourceIdx] != '%') { error = YES; NSDebugMLog( @"Expected literal '%%' but got '%c' parsing" @"'%@' using '%@'", source[sourceIdx], description, fmt); } sourceIdx++; } else { error = YES; NSDebugMLog( @"Expected literal '%%' but got end of string parsing" @"'%@' using '%@'", description, fmt); } break; case 'a': /* FIXME ... Should look for all values from the locale, * matching for longest values first, rather than (wrongly) * assuming a fixed length of three characters. */ tmpStr[0] = toupper(source[sourceIdx]); if (sourceIdx < sourceLen) sourceIdx++; tmpStr[1] = tolower(source[sourceIdx]); if (sourceIdx < sourceLen) sourceIdx++; tmpStr[2] = tolower(source[sourceIdx]); if (sourceIdx < sourceLen) sourceIdx++; tmpStr[3] = '\0'; { NSString *currDay; NSArray *dayNames; currDay = [[NSString alloc] initWithCString: tmpStr]; dayNames = [LOCALE objectForKey: NSShortWeekDayNameArray]; for (tmpIdx = 0; tmpIdx < 7; tmpIdx++) { if ([[dayNames objectAtIndex: tmpIdx] isEqual: currDay] == YES) { break; } } if (tmpIdx == 7) { error = YES; NSDebugMLog(@"Day of week '%@' not found in locale", currDay); } else { dayOfWeek = tmpIdx; had |= hadw; } RELEASE(currDay); } break; case 'A': /* FIXME ... Should look for all values from the locale, * matching for longest values first, rather than (wrongly) * assuming the name contains only western letters. */ tmpEnd = sizeof(tmpStr) - 1; if (sourceLen - sourceIdx < tmpEnd) { tmpEnd = sourceLen - sourceIdx; } for (tmpIdx = 0; tmpIdx < tmpEnd; tmpIdx++) { if (isalpha(source[sourceIdx + tmpIdx])) { tmpStr[tmpIdx] = source[sourceIdx + tmpIdx]; } else { break; } } tmpStr[tmpIdx] = '\0'; sourceIdx += tmpIdx; { NSString *currDay; NSArray *dayNames; currDay = [[NSString alloc] initWithCString: tmpStr]; dayNames = [LOCALE objectForKey: NSWeekDayNameArray]; for (tmpIdx = 0; tmpIdx < 7; tmpIdx++) { if ([[dayNames objectAtIndex: tmpIdx] isEqual: currDay] == YES) { break; } } if (tmpIdx == 7) { error = YES; NSDebugMLog(@"Day of week '%@' not found in locale", currDay); } else { dayOfWeek = tmpIdx; had |= hadw; } RELEASE(currDay); } break; case 'b': /* FIXME ... Should look for all values from the locale, * matching for longest values first, rather than (wrongly) * assuming a fixed length of three characters. */ tmpStr[0] = toupper(source[sourceIdx]); if (sourceIdx < sourceLen) sourceIdx++; tmpStr[1] = tolower(source[sourceIdx]); if (sourceIdx < sourceLen) sourceIdx++; tmpStr[2] = tolower(source[sourceIdx]); if (sourceIdx < sourceLen) sourceIdx++; tmpStr[3] = '\0'; { NSString *currMonth; NSArray *monthNames; currMonth = [[NSString alloc] initWithCString: tmpStr]; monthNames = [LOCALE objectForKey: NSShortMonthNameArray]; for (tmpIdx = 0; tmpIdx < 12; tmpIdx++) { if ([[monthNames objectAtIndex: tmpIdx] isEqual: currMonth] == YES) { break; } } if (tmpIdx == 12) { error = YES; NSDebugMLog(@"Month of year '%@' not found in locale", currMonth); } else { month = tmpIdx+1; had |= hadM; } RELEASE(currMonth); } break; case 'B': /* FIXME ... Should look for all values from the locale, * matching for longest values first, rather than (wrongly) * assuming the name contains only western letters. */ tmpEnd = sizeof(tmpStr) - 1; if (sourceLen - sourceIdx < tmpEnd) { tmpEnd = sourceLen - sourceIdx; } for (tmpIdx = 0; tmpIdx < tmpEnd; tmpIdx++) { if (isalpha(source[sourceIdx + tmpIdx])) { tmpStr[tmpIdx] = source[sourceIdx + tmpIdx]; } else { break; } } tmpStr[tmpIdx] = '\0'; sourceIdx += tmpIdx; { NSString *currMonth; NSArray *monthNames; currMonth = [[NSString alloc] initWithCString: tmpStr]; monthNames = [LOCALE objectForKey: NSMonthNameArray]; for (tmpIdx = 0; tmpIdx < 12; tmpIdx++) { if ([[monthNames objectAtIndex: tmpIdx] isEqual: currMonth] == YES) { break; } } if (tmpIdx == 12) { error = YES; NSDebugMLog(@"Month of year '%@' not found in locale", currMonth); } else { month = tmpIdx+1; had |= hadM; } RELEASE(currMonth); } break; case 'd': // fall through case 'e': sourceIdx += getDigits(&source[sourceIdx], tmpStr, 2, &error); day = atoi(tmpStr); had |= hadD; if (error == NO && day < 1) { error = YES; NSDebugMLog(@"Day of month is zero"); } break; case 'F': sourceIdx += getDigits(&source[sourceIdx], tmpStr, 3, &error); milliseconds = atoi(tmpStr); break; case 'k': // GNUstep extension, not available in Cocoa if (GSPrivateDefaultsFlag(GSMacOSXCompatible)) { error = YES; NSLog(@"Invalid NSCalendar date, " @"specifier %c not recognized in format %@", format[formatIdx], fmt); } case 'I': // fall through twelveHrClock = YES; case 'H': sourceIdx += getDigits(&source[sourceIdx], tmpStr, 2, &error); hour = atoi(tmpStr); had |= hadh; break; case 'j': sourceIdx += getDigits(&source[sourceIdx], tmpStr, 3, &error); day = atoi(tmpStr); had |= hadD; break; case 'm': sourceIdx += getDigits(&source[sourceIdx], tmpStr, 2, &error); month = atoi(tmpStr); had |= hadM; if (error == NO && month < 1) { error = YES; NSDebugMLog(@"Month of year is zero"); } break; case 'M': sourceIdx += getDigits(&source[sourceIdx], tmpStr, 2, &error); min = atoi(tmpStr); had |= hadm; break; case 'p': /* FIXME ... Should look for all values from the locale, * matching for longest values first, rather than (wrongly) * assuming the name is always two uppercase letters. */ twelveHrClock = YES; tmpStr[0] = toupper(source[sourceIdx]); if (sourceIdx < sourceLen) sourceIdx++; tmpStr[1] = toupper(source[sourceIdx]); if (sourceIdx < sourceLen) sourceIdx++; tmpStr[2] = '\0'; { NSString *currAMPM; NSArray *amPMNames; currAMPM = [NSString stringWithUTF8String: tmpStr]; amPMNames = [LOCALE objectForKey: NSAMPMDesignation]; /* * The time addition is handled below because this * indicator only modifies the time on a 12hour clock. */ if ([currAMPM caseInsensitiveCompare: [amPMNames objectAtIndex: 0]] == NSOrderedSame) { ampm = YES; isPM = NO; } else if ([currAMPM caseInsensitiveCompare: [amPMNames objectAtIndex: 1]] == NSOrderedSame) { ampm = YES; isPM = YES; } } break; case 'S': sourceIdx += getDigits(&source[sourceIdx], tmpStr, 2, &error); sec = atoi(tmpStr); had |= hads; break; case 'w': sourceIdx += getDigits(&source[sourceIdx], tmpStr, 1, &error); dayOfWeek = atoi(tmpStr); had |= hadw; break; case 'W': // Fall through weekStartsMonday = 1; case 'U': sourceIdx += getDigits(&source[sourceIdx], tmpStr, 1, &error); julianWeeks = atoi(tmpStr); break; // case 'x': // break; // case 'X': // break; case 'y': sourceIdx += getDigits(&source[sourceIdx], tmpStr, 2, &error); year = atoi(tmpStr); if (year >= 70) { year += 1900; } else { year += 2000; } had |= hadY; break; case 'Y': sourceIdx += getDigits(&source[sourceIdx], tmpStr, 4, &error); year = atoi(tmpStr); had |= hadY; break; case 'z': { int sign = 1; int zone; int found; if (source[sourceIdx] == '+') { sourceIdx++; } else if (source[sourceIdx] == '-') { sign = -1; sourceIdx++; } found = getDigits(&source[sourceIdx], tmpStr, 4, &error); if (found > 0) { sourceIdx += found; zone = atoi(tmpStr); if (found == 2) { zone *= 100; // Convert 2 digits to 4 } tz = [NSTimeZone timeZoneForSecondsFromGMT: sign * ((zone / 100) * 60 + (zone % 100)) * 60]; } } break; case 'Z': /* Can we assume a timezone name is always space terminated? */ tmpEnd = sizeof(tmpStr) - 1; if (sourceLen - sourceIdx < tmpEnd) { tmpEnd = sourceLen - sourceIdx; } for (tmpIdx = 0; tmpIdx < tmpEnd; tmpIdx++) { if (!isspace(source[sourceIdx + tmpIdx])) { tmpStr[tmpIdx] = source[sourceIdx + tmpIdx]; } else { break; } } tmpStr[tmpIdx] = '\0'; sourceIdx += tmpIdx; { NSString *z = [NSString stringWithUTF8String: tmpStr]; /* Abbreviations aren't one-to-one with time zone names so just look for the zone named after the abbreviation, then look up the abbreviation as a last resort */ if ([z length] > 0) { tz = [NSTimeZone timeZoneWithName: z]; if (tz == nil) { tz = [NSTimeZone timeZoneWithAbbreviation: z]; if (tz == nil) { error = YES; NSDebugMLog(@"Time zone '%@' not found", z); } } } else { error = YES; NSDebugMLog(@"Time zone not given"); } } break; default: error = YES; NSLog(@"Invalid NSCalendar date, " @"specifier %c not recognized in format %@", format[formatIdx], fmt); break; } } } formatIdx++; } RELEASE(fd); if (error == NO) { if (tz == nil) { tz = localTZ; } if (twelveHrClock == YES) { if (ampm == YES && isPM == YES && hour != 12) { hour += 12; } else if (ampm == YES && isPM == NO && hour == 12) { hour = 0; // 12 AM } } if (julianWeeks != -1) { NSTimeZone *gmtZone; NSCalendarDate *d; int currDay; gmtZone = [NSTimeZone timeZoneForSecondsFromGMT: 0]; if ((had & (hadY|hadw)) != (hadY|hadw)) { NSCalendarDate *now = [[NSCalendarDateClass alloc] init]; [now setTimeZone: gmtZone]; if ((had & hadY) == 0) { year = [now yearOfCommonEra]; had |= hadY; } if ((had & hadw) == 0) { dayOfWeek = [now dayOfWeek]; had |= hadw; } RELEASE(now); } d = [[NSCalendarDateClass alloc] initWithYear: year month: 1 day: 1 hour: 0 minute: 0 second: 0 timeZone: gmtZone]; currDay = [d dayOfWeek]; RELEASE(d); /* * The julian weeks are either sunday relative or monday relative * but all of the day of week specifiers are sunday relative. * This means that if no day of week specifier was used the week * starts on monday. */ if (dayOfWeek == -1) { if (weekStartsMonday) { dayOfWeek = 1; } else { dayOfWeek = 0; } } day = dayOfWeek + (julianWeeks * 7 - (currDay - 1)); had |= hadD; } /* * If the year has not been set ... use this year ... as on MacOS-X */ if ((had & hadY) == 0) { NSCalendarDate *now = [[NSCalendarDateClass alloc] init]; year = [now yearOfCommonEra]; RELEASE(now); } self = [self initWithYear: year month: month day: day hour: hour minute: min second: sec timeZone: tz]; if (self != nil) { _seconds_since_ref += ((NSTimeInterval)milliseconds) / 1000.0; } } if (error == YES) { DESTROY(self); } return self; } /** * Returns an NSCalendarDate instance with the given year, month, day, * hour, minute, and second, using aTimeZone.
* The year includes the century (ie you can't just say '02' when you * mean '2002').
* The month is in the range 1 to 12,
* The day is in the range 1 to 31,
* The hour is in the range 0 to 23,
* The minute is in the range 0 to 59,
* The second is in the range 0 to 59.
* If aTimeZone is nil, the [NSTimeZone+localTimeZone] value is used. *

* GNUstep checks the validity of the method arguments, and unless * the base library was built with 'warn=no' it generates a warning * for bad values. It tries to use those bad values to generate a * date anyway though, rather than failing (this also appears to be * the behavior of MacOS-X). *

* The algorithm GNUstep uses to create the date is this ...
* * * Convert the broken out date values into a time interval since * the reference date, as if those values represent a GMT date/time. * * * Ask the time zone for the offset from GMT at the resulting date, * and apply that offset to the time interval ... so get the value * for the specified timezone. * * * Ask the time zone for the offset from GMT at the new date ... * in case the new date is in a different daylight savings time * band from the original date. If this offset differs from the * previous one, apply the difference so that the result is * corrected for daylight savings. This is the final result used. * * * After establishing the time interval we will use and completing * initialisation, we ask the time zone for the offset from GMT again. * If it is not the same as the last time, then the time specified by * the broken out date does not really exist ... since it's in the * period lost by the transition to daylight savings. The resulting * date is therefore not the date that was actually asked for, but is * the best approximation we can do. If the base library was not * built with 'warn=no' then a warning message is logged for this * condition. * * */ - (id) initWithYear: (NSInteger)year month: (NSUInteger)month day: (NSUInteger)day hour: (NSUInteger)hour minute: (NSUInteger)minute second: (NSUInteger)second timeZone: (NSTimeZone *)aTimeZone { unsigned int c; NSTimeInterval s; NSTimeInterval oldOffset; NSTimeInterval newOffset; if (month < 1 || month > 12) { NSWarnMLog(@"invalid month given - %"PRIuPTR, month); } c = lastDayOfGregorianMonth(month, year); if (day < 1 || day > c) { NSWarnMLog(@"invalid day given - %"PRIuPTR, day); } if (hour > 23) { NSWarnMLog(@"invalid hour given - %"PRIuPTR, hour); } if (minute > 59) { NSWarnMLog(@"invalid minute given - %"PRIuPTR, minute); } if (second > 59) { NSWarnMLog(@"invalid second given - %"PRIuPTR, second); } // Calculate date as GMT s = GSTime(day, month, year, hour, minute, second, 0); // Assign time zone detail if (aTimeZone == nil) { _time_zone = localTZ; // retain is a no-op for the local timezone. } else { _time_zone = RETAIN(aTimeZone); } if (_calendar_format == nil) { _calendar_format = cformat; } _seconds_since_ref = s; /* * Adjust date so it is correct for time zone. */ oldOffset = offset(_time_zone, self); s -= oldOffset; _seconds_since_ref = s; /* * See if we need to adjust for daylight savings time */ newOffset = offset(_time_zone, self); if (oldOffset != newOffset) { s -= (newOffset - oldOffset); _seconds_since_ref = s; oldOffset = offset(_time_zone, self); /* * If the adjustment puts us in another offset, we must be in the * non-existent period at the start of daylight savings time. */ if (oldOffset != newOffset) { NSWarnMLog(@"init non-existent time at start of daylight savings"); } } return self; } /** * Initialises the receiver with the specified interval since the * reference date. Uses th standard format string "%Y-%m-%d %H:%M:%S %z" * and the default time zone. */ - (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)seconds { if (isnan(seconds)) { [NSException raise: NSInvalidArgumentException format: @"[%@-%@] interval is not a number", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } #if GS_SIZEOF_VOIDP == 4 if (seconds <= DISTANT_PAST) { seconds = DISTANT_PAST; } else if (seconds >= DISTANT_FUTURE) { seconds = DISTANT_FUTURE; } #endif _seconds_since_ref = seconds; if (_calendar_format == nil) { _calendar_format = cformat; } if (_time_zone == nil) { _time_zone = localTZ; // retain is a no-op for the local timezone. } return self; } /** * Return the day number (ie number of days since the start of) in the * 'common' era of the receiving date. The era starts at 1 A.D. */ - (NSInteger) dayOfCommonEra { NSTimeInterval when; when = _seconds_since_ref + offset(_time_zone, self); return dayOfCommonEra(when); } /** * Return the month (1 to 31) of the receiving date. */ - (NSInteger) dayOfMonth { NSInteger m, d, y; NSTimeInterval when; when = _seconds_since_ref + offset(_time_zone, self); gregorianDateFromAbsolute(dayOfCommonEra(when), &d, &m, &y); return d; } /** * Return the day of the week (0 to 6) of the receiving date. * * 0 is sunday * 1 is monday * 2 is tuesday * 3 is wednesday * 4 is thursday * 5 is friday * 6 is saturday * */ - (NSInteger) dayOfWeek { NSInteger d; NSTimeInterval when; when = _seconds_since_ref + offset(_time_zone, self); d = dayOfCommonEra(when); /* The era started on a sunday. Did we always have a seven day week? Did we lose week days changing from Julian to Gregorian? AFAIK seven days a week is ok for all reasonable dates. */ d = d % 7; if (d < 0) d += 7; return d; } /** * Return the day of the year (1 to 366) of the receiving date. */ - (NSInteger) dayOfYear { NSInteger m, d, y, days, i; NSTimeInterval when; when = _seconds_since_ref + offset(_time_zone, self); gregorianDateFromAbsolute(dayOfCommonEra(when), &d, &m, &y); days = d; for (i = m - 1; i > 0; i--) // days in prior months this year days = days + lastDayOfGregorianMonth(i, y); return days; } /** * Return the hour of the day (0 to 23) of the receiving date. */ - (NSInteger) hourOfDay { NSInteger h; double a, d; NSTimeInterval when; when = _seconds_since_ref + offset(_time_zone, self); d = dayOfCommonEra(when); d -= GREGORIAN_REFERENCE; d *= 86400; a = fabs(d - (_seconds_since_ref + offset(_time_zone, self))); a = a / 3600; h = (NSInteger)a; // There is a small chance of getting // it right at the stroke of midnight if (h == 24) h = 0; return h; } /** * Return the minute of the hour (0 to 59) of the receiving date. */ - (NSInteger) minuteOfHour { NSInteger h, m; double a, b, d; NSTimeInterval when; when = _seconds_since_ref + offset(_time_zone, self); d = dayOfCommonEra(when); d -= GREGORIAN_REFERENCE; d *= 86400; a = fabs(d - (_seconds_since_ref + offset(_time_zone, self))); b = a / 3600; h = (NSInteger)b; h = h * 3600; b = a - h; b = b / 60; m = (NSInteger)b; return m; } /** * Return the month of the year (1 to 12) of the receiving date. */ - (NSInteger) monthOfYear { NSInteger m, d, y; NSTimeInterval when; when = _seconds_since_ref + offset(_time_zone, self); gregorianDateFromAbsolute(dayOfCommonEra(when), &d, &m, &y); return m; } /** * Return the second of the minute (0 to 59) of the receiving date. */ - (NSInteger) secondOfMinute { NSInteger h, m, s; double a, b, c, d; NSTimeInterval when; when = _seconds_since_ref + offset(_time_zone, self); d = dayOfCommonEra(when); d -= GREGORIAN_REFERENCE; d *= 86400; a = fabs(d - (_seconds_since_ref + offset(_time_zone, self))); b = a / 3600; h = (NSInteger)b; h = h * 3600; b = a - h; b = b / 60; m = (NSInteger)b; m = m * 60; c = a - h - m; s = (NSInteger)c; return s; } /** * Return the year of the 'common' era of the receiving date. * The era starts at 1 A.D. */ - (NSInteger) yearOfCommonEra { NSInteger m, d, y; NSTimeInterval when; when = _seconds_since_ref + offset(_time_zone, self); gregorianDateFromAbsolute(dayOfCommonEra(when), &d, &m, &y); return y; } /** * This method exists solely for conformance to the OpenStep spec. * Its use is deprecated ... it simply calls * -dateByAddingYears:months:days:hours:minutes:seconds: */ - (NSCalendarDate*) addYear: (NSInteger)year month: (NSInteger)month day: (NSInteger)day hour: (NSInteger)hour minute: (NSInteger)minute second: (NSInteger)second { return [self dateByAddingYears: year months: month days: day hours: hour minutes: minute seconds: second]; } /** * Calls -descriptionWithCalendarFormat:locale: passing the receiver's * calendar format and a nil locale. */ - (NSString*) description { return [self descriptionWithCalendarFormat: _calendar_format locale: nil]; } /** * Returns a string representation of the receiver using the specified * format string.
* Calls -descriptionWithCalendarFormat:locale: with a nil locale. */ - (NSString*) descriptionWithCalendarFormat: (NSString *)format { return [self descriptionWithCalendarFormat: format locale: nil]; } #define UNIX_REFERENCE_INTERVAL -978307200.0 typedef struct { unichar *base; unichar *t; unsigned length; unsigned offset; NSInteger yd; NSInteger md; NSInteger dom; NSInteger hd; NSInteger mnd; NSInteger sd; NSInteger mil; } DescriptionInfo; static void Grow(DescriptionInfo *info, unsigned size) { if (info->offset + size >= info->length) { if (info->t == info->base) { unichar *old = info->t; info->t = NSZoneMalloc(NSDefaultMallocZone(), (info->length + 512) * sizeof(unichar)); memcpy(info->t, old, info->length*sizeof(unichar)); } else { info->t = NSZoneRealloc(NSDefaultMallocZone(), info->t, (info->length + 512) * sizeof(unichar)); } info->length += 512; } } #define MAX_FLD_WIDTH 99 static void outputValueWithFormat(int v, char *fldfmt, DescriptionInfo *info) { char cbuf[MAX_FLD_WIDTH + 1]; int idx = 0; snprintf((char*)cbuf, sizeof(cbuf), fldfmt, v); Grow(info, strlen((char*)cbuf)); while (cbuf[idx] != '\0') { info->t[info->offset++] = cbuf[idx++]; } } - (void) _format: (NSString*)fmt locale: (NSDictionary*)locale info: (DescriptionInfo*)info { unichar fbuf[512]; unichar *f = fbuf; unsigned lf = [fmt length]; unsigned i = 0; int v; if (lf == 0) { return; // Nothing to do. } if (lf >= sizeof(fbuf)/sizeof(unichar)) { /* * Make temporary buffer to hold format string as unicode. */ f = (unichar*)NSZoneMalloc(NSDefaultMallocZone(), lf*sizeof(unichar)); } [fmt getCharacters: f]; while (i < lf) { NSString *str; BOOL mtag = NO; BOOL dtag = NO; BOOL ycent = NO; BOOL mname = NO; BOOL dname = NO; BOOL twelve = NO; BOOL hspc = NO; char fldfmt[8]; int fmtlen = 0; int width = 0; // Only care about a format specifier if (f[i] == '%') { i++; fldfmt[fmtlen++] = '%'; // field width specified while (fmtlen < 5 && f[i] >= '0' && f[i] <= '9') { fldfmt[fmtlen++] = f[i]; width = 10 * width + f[i] - '0'; i++; } if (fmtlen >= 5 || width > MAX_FLD_WIDTH) { /* ignore formats that specify field width * greater than the max allowed. * set i back so all ignored characters will * be copied */ i -= fmtlen; } // check the character that comes after switch (f[i++]) { // literal % case '%': Grow(info, 1); info->t[info->offset++] = f[i-1]; break; case 'R': [self _format: @"%H:%M" locale: locale info: info]; break; case 'r': [self _format: @"%I:%M:%S %p" locale: locale info: info]; break; case 'T': [self _format: @"%H:%M:%S" locale: locale info: info]; break; case 't': Grow(info, 1); info->t[info->offset++] = '\t'; break; case 'c': str = (NSString*)[LOCALE objectForKey: NSTimeFormatString]; [self _format: str locale: locale info: info]; Grow(info, 1); info->t[info->offset++] = ' '; str = (NSString*)[LOCALE objectForKey: NSDateFormatString]; [self _format: str locale: locale info: info]; break; case 'X': str = (NSString*)[LOCALE objectForKey: NSTimeFormatString]; [self _format: str locale: locale info: info]; break; case 'x': str = (NSString*)[LOCALE objectForKey: NSDateFormatString]; [self _format: str locale: locale info: info]; break; // is it the year case 'Y': ycent = YES; case 'y': v = info->yd; if (ycent) { if (fmtlen == 1) { // no format width specified; supply default fldfmt[fmtlen++] = '0'; fldfmt[fmtlen++] = '4'; } } else { if (v < 0) v = -v; v = v % 100; if (fmtlen == 1) { // no format width specified; supply default fldfmt[fmtlen++] = '0'; fldfmt[fmtlen++] = '2'; } } fldfmt[fmtlen++] = 'd'; fldfmt[fmtlen] = 0; outputValueWithFormat(v, fldfmt, info); break; // is it the month case 'b': mname = YES; case 'B': mtag = YES; // Month is character string case 'm': if (mtag == YES) { NSArray *months; if (mname) months = [LOCALE objectForKey: NSShortMonthNameArray]; else months = [LOCALE objectForKey: NSMonthNameArray]; if (info->md > [months count]) { mtag = NO; } else { NSString *name; name = [months objectAtIndex: info->md-1]; v = [name length]; Grow(info, v); [name getCharacters: info->t + info->offset]; info->offset += v; } } if (mtag == NO) { v = info->md; v = v % 100; if (fmtlen == 1) { // no format width specified; supply default fldfmt[fmtlen++] = '0'; fldfmt[fmtlen++] = '2'; } fldfmt[fmtlen++] = 'd'; fldfmt[fmtlen] = 0; outputValueWithFormat(v, fldfmt, info); } break; case 'd': // day of month with leading zero v = info->dom; v = v % 100; if (fmtlen == 1) // no format width specified; supply default { fldfmt[fmtlen++] = '0'; fldfmt[fmtlen++] = '2'; } fldfmt[fmtlen++] = 'd'; fldfmt[fmtlen] = 0; outputValueWithFormat(v, fldfmt, info); break; case 'e': // day of month with leading space v = info->dom; v = v % 100; if (fmtlen == 1) // no format width specified; supply default { fldfmt[fmtlen++] = '1'; // no leading space, just like Cocoa } fldfmt[fmtlen++] = 'd'; fldfmt[fmtlen] = 0; outputValueWithFormat(v, fldfmt, info); break; case 'F': // milliseconds v = info->mil; if (fmtlen == 1) // no format width specified; supply default { fldfmt[fmtlen++] = '0'; fldfmt[fmtlen++] = '3'; } fldfmt[fmtlen++] = 'd'; fldfmt[fmtlen] = 0; outputValueWithFormat(v, fldfmt, info); break; case 'j': // day of year v = [self dayOfYear]; if (fmtlen == 1) // no format width specified; supply default { fldfmt[fmtlen++] = '0'; fldfmt[fmtlen++] = '3'; } fldfmt[fmtlen++] = 'd'; fldfmt[fmtlen] = 0; outputValueWithFormat(v, fldfmt, info); break; // is it the week-day case 'a': dname = YES; case 'A': dtag = YES; // Day is character string case 'w': { v = [self dayOfWeek]; if (dtag == YES) { NSArray *days; if (dname) days = [LOCALE objectForKey: NSShortWeekDayNameArray]; else days = [LOCALE objectForKey: NSWeekDayNameArray]; if (v < [days count]) { NSString *name; name = [days objectAtIndex: v]; v = [name length]; Grow(info, v); [name getCharacters: info->t + info->offset]; info->offset += v; } else { dtag = NO; } } if (dtag == NO) { if (fmtlen == 1) { // no format width specified; supply default fldfmt[fmtlen++] = '1'; } fldfmt[fmtlen++] = 'd'; fldfmt[fmtlen] = 0; outputValueWithFormat(v, fldfmt, info); } } break; // is it the hour case 'I': twelve = YES; case 'k': if (twelve == NO) hspc = YES; case 'H': v = info->hd; if (twelve == YES) { if (info->hd == 12 || info->hd == 0) { v = 12; } else { v = v % 12; } } if (fmtlen == 1) // no format width specified; supply default { if (hspc == YES) fldfmt[fmtlen++] = '2'; // ensure a leading space else { fldfmt[fmtlen++] = '0'; fldfmt[fmtlen++] = '2'; } } fldfmt[fmtlen++] = 'd'; fldfmt[fmtlen] = 0; if (GSPrivateDefaultsFlag(GSMacOSXCompatible) && hspc == YES) { Grow(info, 2); info->t[info->offset++] = '%'; info->t[info->offset++] = f[i-1]; break; } else outputValueWithFormat(v, fldfmt, info); break; // is it the minute case 'M': v = info->mnd; if (fmtlen == 1) // no format width specified; supply default { fldfmt[fmtlen++] = '0'; fldfmt[fmtlen++] = '2'; } fldfmt[fmtlen++] = 'd'; fldfmt[fmtlen] = 0; outputValueWithFormat(v, fldfmt, info); break; // is it the second case 'S': v = info->sd; if (fmtlen == 1) // no format width specified; supply default { fldfmt[fmtlen++] = '0'; fldfmt[fmtlen++] = '2'; } fldfmt[fmtlen++] = 'd'; fldfmt[fmtlen] = 0; outputValueWithFormat(v, fldfmt, info); break; // Is it the am/pm indicator case 'p': { NSArray *a = [LOCALE objectForKey: NSAMPMDesignation]; NSString *ampm; if (info->hd >= 12) { if ([a count] > 1) ampm = [a objectAtIndex: 1]; else ampm = @"pm"; } else { if ([a count] > 0) ampm = [a objectAtIndex: 0]; else ampm = @"am"; } v = [ampm length]; Grow(info, v); [ampm getCharacters: info->t + info->offset]; info->offset += v; } break; // is it the zone name case 'Z': { NSString *s; s = abbrev(_time_zone, self); v = [s length]; Grow(info, v); [s getCharacters: info->t + info->offset]; info->offset += v; } break; case 'z': { int z; Grow(info, 5); z = offset(_time_zone, self); if (z < 0) { z = -z; info->t[info->offset++] = '-'; } else { info->t[info->offset++] = '+'; } z /= 60; // Convert seconds to minutes. v = z / 60; info->t[info->offset+1] = (v%10) + '0'; v /= 10; info->t[info->offset+0] = (v%10) + '0'; info->offset += 2; v = z % 60; info->t[info->offset+1] = (v%10) + '0'; v /= 10; info->t[info->offset+0] = (v%10) + '0'; info->offset += 2; } break; // Anything else is unknown so just copy default: Grow(info, 2); info->t[info->offset++] = '%'; info->t[info->offset++] = f[i-1]; break; } } else { Grow(info, 1); info->t[info->offset++] = f[i++]; } } if (f != fbuf) { NSZoneFree(NSDefaultMallocZone(), f); } } /** * Returns a string representation of the receiver using the specified * format string and locale dictionary.
* Format specifiers are - * * * %a abbreviated weekday name according to locale * * * %A full weekday name according to locale * * * %b abbreviated month name according to locale * * * %c this is the same as %X %x * * * %B full month name according to locale * * * %d day of month as two digit decimal number (leading zero) * * * %e day of month as decimal number (without leading zero) * * * %F milliseconds (000 to 999) * * * %H hour as a decimal number using 24-hour clock * * * %I hour as a decimal number using 12-hour clock * * * %j day of year as a decimal number * * * %k same as %H with leading space instead of zero * * * %m month as decimal number * * * %M minute as decimal number * * * %p 'am' or 'pm' * * * %S second as decimal number * * * %U week of the current year as decimal number (Sunday first day) * * * %W week of the current year as decimal number (Monday first day) * * * %w day of the week as decimal number (Sunday = 0) * * * %x date formatted according to the locale * * * %X time formatted according to the locale * * * %y year as a decimal number without century (minimum 0) * * * %Y year as a decimal number with century, minimum 0, maximum 9999 * * * %z time zone offset (HHMM) * * * %Z time zone * * * %% literal % character * * * *

NB. If GSMacOSCompatible is set to YES, the %k specifier is not * recognized.

*/ - (NSString*) descriptionWithCalendarFormat: (NSString*)format locale: (NSDictionary*)locale { unichar tbuf[512]; NSString *result; DescriptionInfo info; if (format == nil) format = [LOCALE objectForKey: NSTimeDateFormatString]; GSBreakTime(_seconds_since_ref + offset(_time_zone, self), &info.yd, &info.md, &info.dom, &info.hd, &info.mnd, &info.sd, &info.mil); info.base = tbuf; info.t = tbuf; info.length = sizeof(tbuf)/sizeof(unichar); info.offset = 0; [self _format: format locale: locale info: &info]; result = [NSString stringWithCharacters: info.t length: info.offset]; if (info.t != tbuf) { NSZoneFree(NSDefaultMallocZone(), info.t); } return result; } - (id) copyWithZone: (NSZone*)zone { NSCalendarDate *newDate; if (NSShouldRetainWithZone(self, zone)) { newDate = RETAIN(self); } else { newDate = (NSCalendarDate*)NSCopyObject(self, 0, zone); if (newDate != nil) { if (_calendar_format != cformat) { newDate->_calendar_format = [_calendar_format copyWithZone: zone]; } if (_time_zone != localTZ) { newDate->_time_zone = RETAIN(_time_zone); } } } return newDate; } /** * Returns a description of the receiver using its normal format but with * the specified locale dictionary.
* Calls -descriptionWithCalendarFormat:locale: to do this. */ - (NSString*) descriptionWithLocale: (id)locale { return [self descriptionWithCalendarFormat: _calendar_format locale: locale]; } /** * Returns the format string associated with the receiver.
* See -descriptionWithCalendarFormat:locale: for details. */ - (NSString*) calendarFormat { return _calendar_format; } /** * Sets the format string associated with the receiver.
* Providing a nil argument sets the default calendar format.
* See -descriptionWithCalendarFormat:locale: for details. */ - (void) setCalendarFormat: (NSString *)format { if (format == nil) { format = cformat; } ASSIGNCOPY(_calendar_format, format); } /** * Sets the time zone associated with the receiver.
* Providing a nil argument sets the local time zone. */ - (void) setTimeZone: (NSTimeZone *)aTimeZone { if (aTimeZone == nil) { aTimeZone = localTZ; } ASSIGN(_time_zone, aTimeZone); } /** * Returns the time zone associated with the receiver. */ - (NSTimeZone*) timeZone { return _time_zone; } /** * Returns the time zone detail associated with the receiver. */ - (NSTimeZoneDetail*) timeZoneDetail { NSTimeZoneDetail *detail = [_time_zone timeZoneDetailForDate: self]; return detail; } @end /** * Routines for manipulating Gregorian dates. */ // The following code is based upon the source code in // ``Calendrical Calculations'' by Nachum Dershowitz and Edward M. Reingold, // Software---Practice & Experience, vol. 20, no. 9 (September, 1990), // pp. 899--928. @implementation NSCalendarDate (GregorianDate) /** * Returns the number of the last day of the month in the specified year. */ - (NSInteger) lastDayOfGregorianMonth: (NSInteger)month year: (NSInteger)year { return lastDayOfGregorianMonth(month, year); } /** * Returns the number of days since the start of the era for the specified * day, month, and year. */ - (NSInteger) absoluteGregorianDay: (NSInteger)day month: (NSInteger)month year: (NSInteger)year { return absoluteGregorianDay(day, month, year); } /** * Given a day number since the start of the era, returns the date as a * day, month, and year. */ - (void) gregorianDateFromAbsolute: (NSInteger)d day: (NSInteger *)day month: (NSInteger *)month year: (NSInteger *)year { gregorianDateFromAbsolute(d, day, month, year); } @end /** * Methods present in OpenStep but later removed from MacOS-X. */ @implementation NSCalendarDate (OPENSTEP) - (NSCalendarDate*) dateByAddingYears: (NSInteger)years months: (NSInteger)months days: (NSInteger)days hours: (NSInteger)hours minutes: (NSInteger)minutes seconds: (NSInteger)seconds { NSCalendarDate *c; NSTimeInterval s; NSTimeInterval oldOffset; NSTimeInterval newOffset; NSInteger i, year, month, day, hour, minute, second, mil; /* Apply timezone offset to _seconds_since_ref from GMT to local time, * then break into components in local time zone. */ oldOffset = offset(_time_zone, self); s = _seconds_since_ref + oldOffset; GSBreakTime(s, &year, &month, &day, &hour, &minute, &second, &mil); /* Apply required offsets to get new local time. */ while (years != 0 || months != 0 || days != 0 || hours != 0 || minutes != 0 || seconds != 0) { year += years; years = 0; month += months; months = 0; while (month > 12) { year++; month -= 12; } while (month < 1) { year--; month += 12; } day += days; days = 0; if (day > 28) { i = lastDayOfGregorianMonth(month, year); while (day > i) { day -= i; if (month < 12) { month++; } else { month = 1; year++; } i = lastDayOfGregorianMonth(month, year); } } else { while (day < 1) { if (month == 1) { year--; month = 12; } else { month--; } day += lastDayOfGregorianMonth(month, year); } } hour += hours; hours = 0; days += hour/24; hour %= 24; if (hour < 0) { days--; hour += 24; } minute += minutes; minutes = 0; hours += minute/60; minute %= 60; if (minute < 0) { hours--; minute += 60; } second += seconds; seconds = 0; minutes += second/60; second %= 60; if (second < 0) { minutes--; second += 60; } } /* * Reassemble and apply original timezone offset to get * _seconds_since_ref back to GMT. */ s = GSTime(day, month, year, hour, minute, second, mil); s -= oldOffset; c = [NSCalendarDateClass alloc]; c->_calendar_format = [_calendar_format copy]; c->_time_zone = [_time_zone copy]; c->_seconds_since_ref = s; /* * Adjust date to try to maintain the time of day over * a daylight savings time boundary if necessary. */ newOffset = offset(_time_zone, c); if (newOffset != oldOffset) { NSTimeInterval tmpOffset = newOffset; s -= (newOffset - oldOffset); c->_seconds_since_ref = s; /* * If the date we have lies within a missing hour at a * daylight savings time transition, we use the original * date rather than the adjusted one. */ newOffset = offset(_time_zone, c); if (newOffset == oldOffset) { s += (tmpOffset - oldOffset); c->_seconds_since_ref = s; } } return AUTORELEASE(c); } /** * Returns the number of years, months, days, hours, minutes, and seconds * between the receiver and the given date.
* If date is in the future of the receiver, the returned values will * be negative (or zero), otherwise they are all positive.
* If any of the pointers to return value in is null, the corresponding * value will not be returned, and other return values will be adjusted * accordingly. eg. If a difference of 1 hour was to be returned but * hours is null, then the value returned in minutes will be increased * by 60. */ - (void) years: (NSInteger*)years months: (NSInteger*)months days: (NSInteger*)days hours: (NSInteger*)hours minutes: (NSInteger*)minutes seconds: (NSInteger*)seconds sinceDate: (NSDate*)date { NSCalendarDate *start; NSCalendarDate *end; NSCalendarDate *tmp; int diff; int extra; int sign; NSInteger mil; NSInteger syear, smonth, sday, shour, sminute, ssecond; NSInteger eyear, emonth, eday, ehour, eminute, esecond; /* FIXME What if the two dates are in different time zones? How about daylight savings time? */ if ([date isKindOfClass: NSCalendarDateClass]) { tmp = (NSCalendarDate*)RETAIN(date); } else if ([date isKindOfClass: [NSDate class]]) { tmp = [[NSCalendarDateClass alloc] initWithTimeIntervalSinceReferenceDate: [date timeIntervalSinceReferenceDate]]; } else { tmp = nil; // Avoid compiler warning [NSException raise: NSInvalidArgumentException format: @"%@ invalid date given - %@", NSStringFromSelector(_cmd), date]; } end = (NSCalendarDate*)[self laterDate: tmp]; if (end == self) { start = tmp; sign = 1; } else { start = self; sign = -1; } GSBreakTime(start->_seconds_since_ref + offset(start->_time_zone, start), &syear, &smonth, &sday, &shour, &sminute, &ssecond, &mil); GSBreakTime(end->_seconds_since_ref + offset(end->_time_zone, end), &eyear, &emonth, &eday, &ehour, &eminute, &esecond, &mil); if (esecond < ssecond) { eminute -= 1; esecond += 60; } if (eminute < sminute) { ehour -= 1; eminute += 60; } if (ehour < shour) { eday -= 1; ehour += 24; } if (eday < sday) { emonth -= 1; if (emonth >= 0) { eday += [end lastDayOfGregorianMonth: emonth year: eyear]; } else { eday += 31; } } if (emonth < smonth || (emonth == smonth && eday < sday)) { eyear -= 1; emonth += 12; } /* Calculate year difference and leave any remaining months in 'extra' */ diff = eyear - syear; extra = 0; if (years != 0) { *years = sign*diff; } else { extra += diff*12; } /* Calculate month difference and leave any remaining days in 'extra' */ diff = emonth - smonth + extra; extra = 0; if (months != 0) { *months = sign*diff; } else { while (diff-- > 0) { int tmpmonth = emonth - diff - 1; int tmpyear = eyear; while (tmpmonth < 1) { tmpmonth += 12; tmpyear--; } extra += lastDayOfGregorianMonth(tmpmonth, tmpyear); } } /* Calculate day difference and leave any remaining hours in 'extra' */ diff = eday - sday + extra; extra = 0; if (days != 0) { *days = sign*diff; } else { extra += diff*24; } /* Calculate hour difference and leave any remaining minutes in 'extra' */ diff = ehour - shour + extra; extra = 0; if (hours != 0) { *hours = sign*diff; } else { extra += diff*60; } /* Calculate minute difference and leave any remaining seconds in 'extra' */ diff = eminute - sminute + extra; extra = 0; if (minutes != 0) { *minutes = sign*diff; } else { extra += diff*60; } diff = esecond - ssecond + extra; if (seconds != 0) { *seconds = sign*diff; } RELEASE(tmp); } @end