/* Implementation for NSCalendarDate for GNUstep Copyright (C) 1996, 1998 Free Software Foundation, Inc. Author: Scott Christley Date: October 1996 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 Library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #ifndef __WIN32__ #include #endif /* !__WIN32__ */ #include #include #ifndef __WIN32__ #include #endif /* !__WIN32__ */ // Absolute Gregorian date for NSDate reference date Jan 01 2001 // // N = 1; // day of month // N = N + 0; // days in prior months for year // N = N + // days this year // + 365 * (year - 1) // days in previous years ignoring leap days // + (year - 1)/4 // Julian leap days before this year... // - (year - 1)/100 // ...minus prior century years... // + (year - 1)/400 // ...plus prior years divisible by 400 #define GREGORIAN_REFERENCE 730486 @interface NSCalendarDate (Private) - (void)getYear:(int *)year month:(int *)month day:(int *)day hour:(int *)hour minute:(int *)minute second:(int *)second; @end @implementation NSCalendarDate // // Getting an NSCalendar Date // + (id)calendarDate { return [[[self alloc] init] autorelease]; } + (id)dateWithString:(NSString *)description calendarFormat:(NSString *)format { NSCalendarDate *d = [[NSCalendarDate alloc] initWithString: description calendarFormat: format]; return [d autorelease]; } + (id)dateWithString:(NSString *)description calendarFormat:(NSString *)format locale:(NSDictionary *)dictionary { NSCalendarDate *d = [[NSCalendarDate alloc] initWithString: description calendarFormat: format locale: dictionary]; return [d autorelease]; } + (id)dateWithYear:(int)year month:(unsigned int)month day:(unsigned int)day hour:(unsigned int)hour minute:(unsigned int)minute second:(unsigned int)second timeZone:(NSTimeZone *)aTimeZone { NSCalendarDate *d = [[NSCalendarDate alloc] initWithYear: year month: month day: day hour: hour minute: minute second: second timeZone: aTimeZone]; return [d autorelease]; } - (void) encodeWithCoder: (NSCoder*)aCoder { [super encodeWithCoder: aCoder]; [aCoder encodeObject: calendar_format]; [aCoder encodeObject: time_zone]; } - (id) initWithCoder: (NSCoder*)aCoder { self = [super initWithCoder: aCoder]; [aCoder decodeValueOfObjCType: @encode(id) at: &calendar_format]; [aCoder decodeValueOfObjCType: @encode(id) at: &time_zone]; return self; } - (void) dealloc { [calendar_format release]; [super dealloc]; } // Initializing an NSCalendar Date - (id)initWithString:(NSString *)description { // +++ What is the locale? return [self initWithString: description calendarFormat: @"%Y-%m-%d %H:%M:%S %z" locale: nil]; } - (id)initWithString:(NSString *)description calendarFormat:(NSString *)format { // ++ What is the locale? return [self initWithString: description calendarFormat: format locale: nil]; } // // This function could possibly be written better // but it works ok; currently ignores locale // information and some specifiers. // - (id)initWithString:(NSString *)description calendarFormat:(NSString *)format locale:(NSDictionary *)locale { const char *d = [description cString]; const char *f = [format cString]; char *newf; int lf = strlen(f); BOOL mtag = NO, dtag = NO, ycent = NO; BOOL fullm = NO; char ms[80] = "", ds[80] = "", timez[80] = "", ampm[80] = ""; int tznum = 0; int yd = 0, md = 0, dd = 0, hd = 0, mnd = 0, sd = 0; void *pntr[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; int order; int yord = 0, mord = 0, dord = 0, hord = 0, mnord = 0, sord = 0, tzord = 0; int ampmord = 0; int i; NSTimeZone *tz; BOOL zoneByAbbreviation = YES; // If either the string or format is nil then raise exception if (!description) [NSException raise: NSInvalidArgumentException format: @"NSCalendar date description is nil"]; if (!format) [NSException raise: NSInvalidArgumentException format: @"NSCalendar date format is nil"]; // The strftime specifiers // %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 // %d day of month as 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 // %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) // %y year as a decimal number without century // %Y year as a decimal number with century // %Z time zone // %% literal % character // Find the order of date elements // and translate format string into scanf ready string order = 1; newf = objc_malloc(lf+1); for (i = 0;i < lf; ++i) { newf[i] = f[i]; // Only care about a format specifier if (f[i] == '%') { // check the character that comes after switch (f[i+1]) { // skip literal % case '%': ++i; newf[i] = f[i]; break; // is it the year case 'Y': ycent = YES; case 'y': yord = order; ++order; ++i; newf[i] = 'd'; pntr[yord] = (void *)&yd; break; // is it the month case 'B': fullm = YES; // Full month name case 'b': mtag = YES; // Month is character string case 'm': mord = order; ++order; ++i; if (mtag) { newf[i] = 's'; pntr[mord] = (void *)ms; } else { newf[i] = 'd'; pntr[mord] = (void *)&md; } break; // is it the day case 'a': case 'A': dtag = YES; // Day is character string case 'd': case 'j': case 'w': dord = order; ++order; ++i; if (dtag) { newf[i] = 's'; pntr[dord] = (void *)ds; } else { newf[i] = 'd'; pntr[dord] = (void *)ⅆ } break; // is it the hour case 'H': case 'I': hord = order; ++order; ++i; newf[i] = 'd'; pntr[hord] = (void *)&hd; break; // is it the minute case 'M': mnord = order; ++order; ++i; newf[i] = 'd'; pntr[mnord] = (void *)&mnd; break; // is it the second case 'S': sord = order; ++order; ++i; newf[i] = 'd'; pntr[sord] = (void *)&sd; break; // the time zone abbreviation case 'Z': tzord = order; ++order; ++i; newf[i] = 's'; pntr[tzord] = (void *)timez; break; // the time zone in numeric format case 'z': tzord = order; ++order; ++i; newf[i] = 'd'; pntr[tzord] = (void *)&tznum; zoneByAbbreviation = NO; break; // AM PM indicator case 'p': ampmord = order; ++order; ++i; newf[i] = 's'; pntr[ampmord] = (void *)ampm; break; // Anything else is an invalid format default: free(newf); [NSException raise: NSInvalidArgumentException format: @"Invalid NSCalendar date, specifier %c not recognized in format %s", f[i+1], f]; } } } newf[lf] = '\0'; // Have sscanf parse and retrieve the values for us if (order != 1) sscanf(d, newf, pntr[1], pntr[2], pntr[3], pntr[4], pntr[5], pntr[6], pntr[7], pntr[8], pntr[9]); else // nothing in the string? ; // Put century on year if need be // +++ How do we be year 2000 compliant? if (!ycent) yd += 1900; // Possibly convert month from string to decimal number if (mtag) { int i; NSString *m = [NSString stringWithCString: ms]; if (fullm) { NSArray *names = [locale objectForKey: NSMonthNameArray]; for (i = 0;i < 12; ++i) if ([[names objectAtIndex: i] isEqual: m] == YES) break; } else { NSArray *names = [locale objectForKey: NSShortMonthNameArray]; for (i = 0;i < 12; ++i) if ([[names objectAtIndex: i] isEqual: m] == YES) break; } md = i + 1; } // Possibly convert day from string to decimal number // +++ how do we take locale into account? if (dtag) { } // +++ We need to take 'am' and 'pm' into account if (ampmord) { // If its PM then we shift if ((ampm[0] == 'p') || (ampm[0] == 'P')) { // 12pm is 12pm not 24pm if (hd != 12) hd += 12; } } // +++ then there is the time zone if (tzord) if (zoneByAbbreviation) { tz = [NSTimeZone timeZoneWithAbbreviation: [NSString stringWithCString: timez]]; if (!tz) tz = [NSTimeZone localTimeZone]; } else { int tzm, tzh, sign; if (tznum < 0) { sign = -1; tznum = -tznum; } else sign = 1; tzm = tznum % 100; tzh = tznum / 100; tz = [NSTimeZone timeZoneForSecondsFromGMT: (tzh * 60 + tzm) * 60 * sign]; if (!tz) tz = [NSTimeZone localTimeZone]; } else tz = [NSTimeZone localTimeZone]; free(newf); return [self initWithYear: yd month: md day: dd hour: hd minute: mnd second: sd timeZone: tz]; } - (id)initWithYear:(int)year month:(unsigned int)month day:(unsigned int)day hour:(unsigned int)hour minute:(unsigned int)minute second:(unsigned int)second timeZone:(NSTimeZone *)aTimeZone { int a; int c; NSTimeInterval s; a = [self absoluteGregorianDay: day month: month year: year]; // Calculate date as GMT a -= GREGORIAN_REFERENCE; s = (double)a * 86400; s += hour * 3600; s += minute * 60; s += second; // Assign time zone detail time_zone = [aTimeZone timeZoneDetailForDate: [NSDate dateWithTimeIntervalSinceReferenceDate: s]]; // Adjust date so it is correct for time zone. s -= [time_zone timeZoneSecondsFromGMT]; self = [self initWithTimeIntervalSinceReferenceDate: s]; /* Now permit up to five cycles of adjustment to allow for daylight savings. NB. this depends on it being OK to call the [-initWithTimeIntervalSinceReferenceDate:] method repeatedly! */ for (c = 0; c < 5 && self != nil; c++) { int y, m, d, h, mm, ss; NSTimeZoneDetail *z; [self getYear: &y month: &m day: &d hour: &h minute: &mm second: &ss]; if (y==year && m==month && d==day && h==hour && mm==minute && ss==second) return self; /* Has the time-zone detail changed? If so - adjust time for it, other wise - try to adjust to the correct time. */ z = [aTimeZone timeZoneDetailForDate: [NSDate dateWithTimeIntervalSinceReferenceDate: s]]; if (z != time_zone) { NSTimeInterval oldOffset; NSTimeInterval newOffset; oldOffset = [time_zone timeZoneSecondsFromGMT]; time_zone = z; newOffset = [time_zone timeZoneSecondsFromGMT]; s += newOffset - oldOffset; } else { NSTimeInterval move; /* Do we need to go back or forwards in time? Shift at most two hours - we know of no daylight savings time which is an offset of more than two hourts */ if (y > year) move = -7200.0; else if (y < year) move = +7200.0; else if (m > month) move = -7200.0; else if (m < month) move = +7200.0; else if (d > day) move = -7200.0; else if (d < day) move = +7200.0; else if (h > hour || h < hour) move = (hour - h)*3600.0; else if (mm > minute || mm < minute) move = (minute - mm)*60.0; else move = (second - ss); s += move; } self = [self initWithTimeIntervalSinceReferenceDate: s]; } return self; } // Default initializer - (id)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)seconds { [super initWithTimeIntervalSinceReferenceDate: seconds]; if (!calendar_format) calendar_format = @"%Y-%m-%d %H:%M:%S %z"; if (!time_zone) time_zone = [[NSTimeZone localTimeZone] timeZoneDetailForDate: self]; return self; } // Retreiving Date Elements - (void)getYear:(int *)year month:(int *)month day:(int *)day hour:(int *)hour minute:(int *)minute second:(int *)second { int h, m; double a, b, c, d = [self dayOfCommonEra]; // Calculate year, month, and day [self gregorianDateFromAbsolute: d day: day month: month year: year]; // Calculate hour, minute, and seconds d -= GREGORIAN_REFERENCE; d *= 86400; a = abs(d - (seconds_since_ref+[time_zone timeZoneSecondsFromGMT])); b = a / 3600; *hour = (int)b; h = *hour; h = h * 3600; b = a - h; b = b / 60; *minute = (int)b; m = *minute; m = m * 60; c = a - h - m; *second = (int)c; } - (int)dayOfCommonEra { double a; int r; // Get reference date in terms of days a = (seconds_since_ref+[time_zone timeZoneSecondsFromGMT]) / 86400.0; // Offset by Gregorian reference a += GREGORIAN_REFERENCE; r = (int)a; return r; } - (int)dayOfMonth { int m, d, y; [self gregorianDateFromAbsolute: [self dayOfCommonEra] day: &d month: &m year: &y]; return d; } - (int)dayOfWeek { int d = [self dayOfCommonEra]; /* 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; } - (int)dayOfYear { int m, d, y, days, i; [self gregorianDateFromAbsolute: [self dayOfCommonEra] day: &d month: &m year: &y]; days = d; for (i = m - 1; i > 0; i--) // days in prior months this year days = days + [self lastDayOfGregorianMonth: i year: y]; return days; } - (int)hourOfDay { int h; double a, d = [self dayOfCommonEra]; d -= GREGORIAN_REFERENCE; d *= 86400; a = abs(d - (seconds_since_ref+[time_zone timeZoneSecondsFromGMT])); a = a / 3600; h = (int)a; // There is a small chance of getting // it right at the stroke of midnight if (h == 24) h = 0; return h; } - (int)minuteOfHour { int h, m; double a, b, d = [self dayOfCommonEra]; d -= GREGORIAN_REFERENCE; d *= 86400; a = abs(d - (seconds_since_ref+[time_zone timeZoneSecondsFromGMT])); b = a / 3600; h = (int)b; h = h * 3600; b = a - h; b = b / 60; m = (int)b; return m; } - (int)monthOfYear { int m, d, y; [self gregorianDateFromAbsolute: [self dayOfCommonEra] day: &d month: &m year: &y]; return m; } - (int)secondOfMinute { int h, m, s; double a, b, c, d = [self dayOfCommonEra]; d -= GREGORIAN_REFERENCE; d *= 86400; a = abs(d - (seconds_since_ref+[time_zone timeZoneSecondsFromGMT])); b = a / 3600; h = (int)b; h = h * 3600; b = a - h; b = b / 60; m = (int)b; m = m * 60; c = a - h - m; s = (int)c; return s; } - (int)yearOfCommonEra { int m, d, y; int a; // Get reference date in terms of days a = (seconds_since_ref+[time_zone timeZoneSecondsFromGMT]) / 86400; // Offset by Gregorian reference a += GREGORIAN_REFERENCE; [self gregorianDateFromAbsolute: a day: &d month: &m year: &y]; return y; } // Providing Adjusted Dates - (NSCalendarDate *)addYear:(int)year month:(unsigned int)month day:(unsigned int)day hour:(unsigned int)hour minute:(unsigned int)minute second:(unsigned int)second { return [self dateByAddingYears: year months: month days: day hours: hour minutes: minute seconds: second]; } // Getting String Descriptions of Dates - (NSString *)description { return [self descriptionWithCalendarFormat: calendar_format locale: nil]; } - (NSString *)descriptionWithCalendarFormat:(NSString *)format { return [self descriptionWithCalendarFormat: format locale: nil]; } #define UNIX_REFERENCE_INTERVAL -978307200.0 - (NSString *)descriptionWithCalendarFormat:(NSString *)format locale:(NSDictionary *)locale { char buf[1024]; const char *f; int lf; BOOL mtag = NO, dtag = NO, ycent = NO; BOOL mname = NO, dname = NO; double s; int yd = 0, md = 0, dd = 0, mnd = 0, sd = 0, dom = -1, dow = -1, doy = -1; int hd = 0, nhd; int i, j, k, z; if (locale == nil) locale = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]; if (format == nil) format = [locale objectForKey: NSTimeDateFormatString]; // If the format is nil then return an empty string if (!format) return @""; f = [format cString]; lf = strlen(f); [self getYear: &yd month: &md day: &dom hour: &hd minute: &mnd second: &sd]; nhd = hd; // The strftime specifiers // %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 // %d day of month as decimal number (leading zero) // %e day of month as decimal number (leading space) // %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 // %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) // %y year as a decimal number without century // %Y year as a decimal number with century // %z time zone offset (HHMM) // %Z time zone // %% literal % character // Find the order of date elements // and translate format string into printf ready string j = 0; for (i = 0;i < lf; ++i) { // Only care about a format specifier if (f[i] == '%') { // check the character that comes after switch (f[i+1]) { // literal % case '%': ++i; buf[j] = f[i]; ++j; break; // is it the year case 'Y': ycent = YES; case 'y': ++i; if (ycent) k = VSPRINTF_LENGTH(sprintf(&(buf[j]), "%04d", yd)); else k = VSPRINTF_LENGTH(sprintf(&(buf[j]), "%02d", (yd - 1900))); j += k; break; // is it the month case 'b': mname = YES; case 'B': mtag = YES; // Month is character string case 'm': ++i; if (mtag) { NSArray *months; NSString *name; if (mname) months = [locale objectForKey: NSShortMonthNameArray]; else months = [locale objectForKey: NSMonthNameArray]; name = [months objectAtIndex: md-1]; if (name) k = VSPRINTF_LENGTH(sprintf(&(buf[j]), "%s", [name cString])); else k = VSPRINTF_LENGTH(sprintf(&(buf[j]), "%02d", md)); } else k = VSPRINTF_LENGTH(sprintf(&(buf[j]), "%02d", md)); j += k; break; case 'd': // day of month ++i; k = VSPRINTF_LENGTH(sprintf(&(buf[j]), "%02d", dom)); j += k; break; case 'e': // day of month ++i; k = VSPRINTF_LENGTH(sprintf(&(buf[j]), "%2d", dom)); j += k; break; case 'F': // milliseconds s = ([self dayOfCommonEra] - GREGORIAN_REFERENCE) * 86400.0; s -= (seconds_since_ref+[time_zone timeZoneSecondsFromGMT]); s = abs(s); s -= floor(s); ++i; k = VSPRINTF_LENGTH(sprintf(&(buf[j]), "%03d", (int)s*1000)); j += k; break; case 'j': // day of year if (doy < 0) doy = [self dayOfYear]; ++i; k = VSPRINTF_LENGTH(sprintf(&(buf[j]), "%02d", doy)); j += k; break; // is it the week-day case 'a': dname = YES; case 'A': dtag = YES; // Day is character string case 'w': { ++i; if (dow < 0) dow = [self dayOfWeek]; if (dtag) { NSArray *days; NSString *name; if (dname) days = [locale objectForKey: NSShortWeekDayNameArray]; else days = [locale objectForKey: NSWeekDayNameArray]; name = [days objectAtIndex: dow]; if (name) k = VSPRINTF_LENGTH(sprintf(&(buf[j]), "%s", [name cString])); else k = VSPRINTF_LENGTH(sprintf(&(buf[j]), "%02d", dow)); } else k = VSPRINTF_LENGTH(sprintf(&(buf[j]), "%02d", dow)); j += k; } break; // is it the hour case 'I': nhd = hd % 12; // 12 hour clock if (hd == 12) nhd = 12; // 12pm not 0pm case 'H': ++i; k = VSPRINTF_LENGTH(sprintf(&(buf[j]), "%02d", nhd)); j += k; break; // is it the minute case 'M': ++i; k = VSPRINTF_LENGTH(sprintf(&(buf[j]), "%02d", mnd)); j += k; break; // is it the second case 'S': ++i; k = VSPRINTF_LENGTH(sprintf(&(buf[j]), "%02d", sd)); j += k; break; // Is it the am/pm indicator case 'p': { NSArray *a = [locale objectForKey: NSAMPMDesignation]; NSString *ampm; ++i; if (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"; k = VSPRINTF_LENGTH(sprintf(&(buf[j]), [ampm cString])); j += k; } break; // is it the zone name case 'Z': ++i; k = VSPRINTF_LENGTH(sprintf(&(buf[j]), "%s", [[time_zone timeZoneAbbreviation] cString])); j += k; break; case 'z': ++i; z = [time_zone timeZoneSecondsFromGMT]; if (z < 0) { z = -z; z /= 60; k = VSPRINTF_LENGTH(sprintf(&(buf[j]),"-%02d%02d",z/60,z%60)); } else { z /= 60; k = VSPRINTF_LENGTH(sprintf(&(buf[j]),"+%02d%02d",z/60,z%60)); } j += k; break; // Anything else is unknown so just copy default: buf[j] = f[i]; ++i; ++j; buf[j] = f[i]; ++i; ++j; break; } } else { buf[j] = f[i]; ++j; } } buf[j] = '\0'; return [NSString stringWithCString: buf]; } - (id) copyWithZone:(NSZone*)zone { NSCalendarDate *newDate; if (NSShouldRetainWithZone(self, zone)) { newDate = [self retain]; } else { newDate = (NSCalendarDate*)NSCopyObject(self, 0, zone); if (newDate) { newDate->calendar_format = [calendar_format copyWithZone: zone]; } } return newDate; } - (NSString *)descriptionWithLocale:(NSDictionary *)locale { return [self descriptionWithCalendarFormat: calendar_format locale: locale]; } // Getting and Setting Calendar Formats - (NSString *)calendarFormat { return calendar_format; } - (void)setCalendarFormat:(NSString *)format { [calendar_format release]; calendar_format = [format copyWithZone: [self zone]]; } // Getting and Setting Time Zones - (void)setTimeZone:(NSTimeZone *)aTimeZone { time_zone = [aTimeZone timeZoneDetailForDate: self]; } - (NSTimeZoneDetail *)timeZoneDetail { return time_zone; } @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) - (int)lastDayOfGregorianMonth:(int)month year:(int)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; } } - (int)absoluteGregorianDay:(int)day month:(int)month year:(int)year { int m, N; N = day; // day of month for (m = month - 1; m > 0; m--) // days in prior months this year N = N + [self lastDayOfGregorianMonth: m year: year]; return (N // days this year + 365 * (year - 1) // days in previous years ignoring leap days + (year - 1)/4 // Julian leap days before this year... - (year - 1)/100 // ...minus prior century years... + (year - 1)/400); // ...plus prior years divisible by 400 } - (void)gregorianDateFromAbsolute:(int)d day:(int *)day month:(int *)month year:(int *)year { // Search forward year by year from approximate year *year = d/366; while (d >= [self absoluteGregorianDay: 1 month: 1 year: (*year)+1]) (*year)++; // Search forward month by month from January (*month) = 1; while (d > [self absoluteGregorianDay: [self lastDayOfGregorianMonth: *month year: *year] month: *month year: *year]) (*month)++; *day = d - [self absoluteGregorianDay: 1 month: *month year: *year] + 1; } @end @implementation NSCalendarDate (OPENSTEP) - (NSCalendarDate *)dateByAddingYears:(int)years months:(int)months days:(int)days hours:(int)hours minutes:(int)minutes seconds:(int)seconds { int i, year, month, day, hour, minute, second; [self getYear: &year month: &month day: &day hour: &hour minute: &minute second: &second]; second += seconds; minute += second/60; second %= 60; if (second < 0) { minute--; second += 60; } minute += minutes; hour += minute/60; minute %= 60; if (minute < 0) { hour--; minute += 60; } hour += hours; day += hour/24; hour %= 24; if (hour < 0) { day--; hour += 24; } day += days; if (day > 28) { i = [self lastDayOfGregorianMonth: month year: year]; while (day > i) { day -= i; if (month < 12) month++; else { month = 1; year++; } } } else while (day < 0) { if (month == 1) { year--; month = 12; } else month--; day += [self lastDayOfGregorianMonth: month year: year]; } month += months; while (month > 12) { year++; month -= 12; } while (month < 1) { year--; month += 12; } year += years; return [NSCalendarDate dateWithYear:year month:month day:day hour:hour minute:minute second:second timeZone:nil]; } - (void) years: (int*)years months: (int*)months days: (int*)days hours: (int*)hours minutes: (int*)minutes seconds: (int*)seconds sinceDate: (NSDate*)date { NSCalendarDate *start; NSCalendarDate *end; NSCalendarDate *tmp; int diff; int extra; int sign; int syear, smonth, sday, shour, sminute, ssecond; int 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: [NSCalendarDate class]]) tmp = (NSCalendarDate*)[date retain]; else tmp = [[NSCalendarDate alloc] initWithTimeIntervalSinceReferenceDate: [date timeIntervalSinceReferenceDate]]; end = (NSCalendarDate*)[self laterDate: tmp]; if (end == self) { start = tmp; sign = 1; } else { start = self; sign = -1; } [start getYear: &syear month: &smonth day: &sday hour: &shour minute: &sminute second: &ssecond]; [end getYear: &eyear month: &emonth day: &eday hour: &ehour minute: &eminute second: &esecond]; /* Calculate year difference and leave any remaining months in 'extra' */ diff = eyear - syear; extra = 0; if (emonth < smonth) { diff--; extra += 12; } if (years) *years = sign*diff; else extra += diff*12; /* Calculate month difference and leave any remaining days in 'extra' */ diff = emonth - smonth + extra; extra = 0; if (eday < sday) { diff--; if (emonth > 1) extra = [end lastDayOfGregorianMonth: emonth-1 year: eyear]; else extra = 31; } if (months) *months = sign*diff; else { while (diff--) { if (emonth - diff >= 1) extra += [end lastDayOfGregorianMonth: emonth-diff year: eyear]; else extra += [end lastDayOfGregorianMonth: emonth-diff+12 year: eyear-1]; } } /* Calculate day difference and leave any remaining hours in 'extra' */ diff = eday - sday + extra; extra = 0; if (ehour < shour) { diff--; extra = 24; } if (days) *days = sign*diff; else extra += diff*24; /* Calculate hour difference and leave any remaining minutes in 'extra' */ diff = ehour - shour + extra; extra = 0; if (eminute < sminute) { diff--; extra = 60; } if (hours) *hours = sign*diff; else extra += diff*60; /* Calculate minute difference and leave any remaining seconds in 'extra' */ diff = eminute - sminute + extra; extra = 0; if (esecond < ssecond) { diff--; extra = 60; } if (minutes) *minutes = sign*diff; else extra += diff*60; diff = esecond - ssecond + extra; if (seconds) *seconds = sign*diff; [tmp release]; } @end