/* * Copyright (c) 2008, 2009, 2010, 2011 * Jonathan Schleifer * * All rights reserved. * * This file is part of ObjFW. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE.QPL included in * the packaging of this file. * * Alternatively, it may be distributed under the terms of the GNU General * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #include "config.h" #include #include #include #include #import "OFDate.h" #import "OFString.h" #import "OFAutoreleasePool.h" #ifdef OF_THREADS # import "OFThread.h" #endif #import "OFInitializationFailedException.h" #import "OFInvalidArgumentException.h" #import "OFOutOfRangeException.h" #if (!defined(HAVE_GMTIME_R) || !defined(HAVE_LOCALTIME_R)) && \ defined(OF_THREADS) static OFMutex *mutex; #endif #ifdef HAVE_GMTIME_R # define GMTIME_RET(field) \ time_t sec_ = (time_t)sec; \ struct tm tm; \ \ if (sec != sec_) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ if (gmtime_r(&sec_, &tm) == NULL) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ return tm.field; # define LOCALTIME_RET(field) \ time_t sec_ = (time_t)sec; \ struct tm tm; \ \ if (sec != sec_) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ if (localtime_r(&sec_, &tm) == NULL) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ return tm.field; #else # ifdef OF_THREADS # define GMTIME_RET(field) \ time_t sec_ = (time_t)sec; \ struct tm *tm; \ \ if (sec != sec_) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ [mutex lock]; \ \ @try { \ if ((tm = gmtime(&sec_)) == NULL) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ return tm->field; \ } @finally { \ [mutex unlock]; \ } # define LOCALTIME_RET(field) \ time_t sec_ = (time_t)sec; \ struct tm *tm; \ \ if (sec != sec_) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ [mutex lock]; \ \ @try { \ if ((tm = localtime(&sec_)) == NULL) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ return tm->field; \ } @finally { \ [mutex unlock]; \ } # else # define GMTIME_RET(field) \ time_t sec_ = (time_t)sec; \ struct tm *tm; \ \ if (sec != sec_) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ if ((tm = gmtime(&sec_)) == NULL) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ return tm->field; # define LOCALTIME_RET(field) \ time_t sec_ = (time_t)sec; \ struct tm *tm; \ \ if (sec != sec_) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ if ((tm = localtime(&sec_)) == NULL) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ return tm->field; # endif #endif @implementation OFDate #if (!defined(HAVE_GMTIME_R) || !defined(HAVE_LOCALTIME_R)) && \ defined(OF_THREADS) + (void)initialize { if (self == [OFDate class]) mutex = [[OFMutex alloc] init]; } #endif + date { return [[[self alloc] init] autorelease]; } + dateWithTimeIntervalSince1970: (int64_t)sec { return [[[self alloc] initWithTimeIntervalSince1970: sec] autorelease]; } + dateWithTimeIntervalSince1970: (int64_t)sec microseconds: (uint32_t)usec { return [[[self alloc] initWithTimeIntervalSince1970: sec microseconds: usec] autorelease]; } + dateWithTimeIntervalSinceNow: (int64_t)sec { return [[[self alloc] initWithTimeIntervalSinceNow: sec] autorelease]; } + dateWithTimeIntervalSinceNow: (int64_t)sec microseconds: (uint32_t)usec { return [[[self alloc] initWithTimeIntervalSinceNow: sec microseconds: usec] autorelease]; } + distantFuture { if (sizeof(time_t) == sizeof(int64_t)) return [[[self alloc] initWithTimeIntervalSince1970: INT64_MAX microseconds: 999999] autorelease]; if (sizeof(time_t) == sizeof(int32_t)) return [[[self alloc] initWithTimeIntervalSince1970: INT32_MAX microseconds: 999999] autorelease]; /* Neither 64 nor 32 bit. But it's guaranteed to be at least an int */ return [[[self alloc] initWithTimeIntervalSince1970: INT_MAX microseconds: 999999] autorelease]; } + distantPast { /* We don't know if time_t is signed or unsigned. Use 0 to be safe */ return [[[self alloc] initWithTimeIntervalSince1970: 0] autorelease]; } - init { struct timeval t; if (gettimeofday(&t, NULL)) { Class c = isa; [self release]; @throw [OFInitializationFailedException newWithClass: c]; } return [self initWithTimeIntervalSince1970: t.tv_sec microseconds: (uint32_t)t.tv_usec]; } - initWithTimeIntervalSince1970: (int64_t)sec_ { return [self initWithTimeIntervalSince1970: sec_ microseconds: 0]; } - initWithTimeIntervalSince1970: (int64_t)sec_ microseconds: (uint32_t)usec_ { self = [super init]; sec = sec_; usec = usec_; return self; } - initWithTimeIntervalSinceNow: (int64_t)sec_ { return [self initWithTimeIntervalSinceNow: sec_ microseconds: 0]; } - initWithTimeIntervalSinceNow: (int64_t)sec_ microseconds: (uint32_t)usec_ { self = [self init]; sec += sec_; usec += usec_; while (usec > 999999) { usec -= 10000000; sec++; } return self; } - (BOOL)isEqual: (id)obj { if (![obj isKindOfClass: [OFDate class]]) return NO; if (((OFDate*)obj)->sec != sec || ((OFDate*)obj)->usec != usec) return NO; return YES; } - copy { return [self retain]; } - (of_comparison_result_t)compare: (id)obj { if (![obj isKindOfClass: [OFDate class]]) @throw [OFInvalidArgumentException newWithClass: isa selector: _cmd]; if (sec < ((OFDate*)obj)->sec) return OF_ORDERED_ASCENDING; if (sec > ((OFDate*)obj)->sec) return OF_ORDERED_DESCENDING; if (usec < ((OFDate*)obj)->usec) return OF_ORDERED_ASCENDING; if (usec > ((OFDate*)obj)->usec) return OF_ORDERED_DESCENDING; return OF_ORDERED_SAME; } - (OFString*)description { return [self dateStringWithFormat: @"%Y-%m-%dT%H:%M:%SZ"]; } - (uint32_t)microsecond { return usec; } - (uint8_t)second { GMTIME_RET(tm_sec) } - (uint8_t)minute { GMTIME_RET(tm_min) } - (uint8_t)hour { GMTIME_RET(tm_hour) } - (uint8_t)localHour { LOCALTIME_RET(tm_hour) } - (uint8_t)dayOfMonth { GMTIME_RET(tm_mday) } - (uint8_t)localDayOfMonth { LOCALTIME_RET(tm_mday) } - (uint8_t)monthOfYear { GMTIME_RET(tm_mon + 1) } - (uint8_t)localMonthOfYear { LOCALTIME_RET(tm_mon + 1) } - (uint16_t)year { GMTIME_RET(tm_year + 1900) } - (uint8_t)dayOfWeek { GMTIME_RET(tm_wday) } - (uint8_t)localDayOfWeek { LOCALTIME_RET(tm_wday) } - (uint16_t)dayOfYear { GMTIME_RET(tm_yday + 1) } - (uint16_t)localDayOfYear { LOCALTIME_RET(tm_yday + 1) } - (OFString*)dateStringWithFormat: (OFString*)fmt { OFString *ret; time_t sec_ = (time_t)sec; struct tm tm; char *buf; if (sec != sec_) @throw [OFOutOfRangeException newWithClass: isa]; #ifdef HAVE_GMTIME_R if (gmtime_r(&sec_, &tm) == NULL) @throw [OFOutOfRangeException newWithClass: isa]; #else # ifdef OF_THREADS [mutex lock]; @try { # endif struct tm *tmp; if ((tmp = gmtime(&sec_)) == NULL) @throw [OFOutOfRangeException newWithClass: isa]; tm = *tmp; # ifdef OF_THREADS } @finally { [mutex unlock]; } # endif #endif buf = [self allocMemoryWithSize: of_pagesize]; @try { if (!strftime(buf, of_pagesize, [fmt cString], &tm)) @throw [OFOutOfRangeException newWithClass: isa]; ret = [OFString stringWithCString: buf]; } @finally { [self freeMemory: buf]; } return ret; } - (OFString*)localDateStringWithFormat: (OFString*)fmt { OFString *ret; time_t sec_ = (time_t)sec; struct tm tm; char *buf; if (sec != sec_) @throw [OFOutOfRangeException newWithClass: isa]; #ifdef HAVE_LOCALTIME_R if (localtime_r(&sec_, &tm) == NULL) @throw [OFOutOfRangeException newWithClass: isa]; #else # ifdef OF_THREADS [mutex lock]; @try { # endif struct tm *tmp; if ((tmp = localtime(&sec_)) == NULL) @throw [OFOutOfRangeException newWithClass: isa]; tm = *tmp; # ifdef OF_THREADS } @finally { [mutex unlock]; } # endif #endif buf = [self allocMemoryWithSize: of_pagesize]; @try { if (!strftime(buf, of_pagesize, [fmt cString], &tm)) @throw [OFOutOfRangeException newWithClass: isa]; ret = [OFString stringWithCString: buf]; } @finally { [self freeMemory: buf]; } return ret; } - (OFDate*)earlierDate: (OFDate*)date { if ([self compare: date] == OF_ORDERED_DESCENDING) return [[date retain] autorelease]; return [[self retain] autorelease]; } - (OFDate*)laterDate: (OFDate*)date { if ([self compare: date] == OF_ORDERED_ASCENDING) return [[date retain] autorelease]; return [[self retain] autorelease]; } - (int64_t)timeIntervalSince1970 { return sec; } - (uint32_t)microsecondsOfTimeIntervalSince1970 { return usec; } - (int64_t)timeIntervalSinceDate: (OFDate*)date { int64_t sec_ = sec - date->sec; int32_t usec_ = (int32_t)usec - date->usec; while (usec_ > 999999) { usec_ -= 1000000; sec_++; } while (usec_ < 0) { usec_ += 1000000; sec_--; } return sec_; } - (uint32_t)microsecondsOfTimeIntervalSinceDate: (OFDate*)date { int32_t usec_ = (int32_t)usec - date->usec; while (usec_ > 999999) usec_ -= 1000000; while (usec_ < 0) usec_ += 1000000; return usec_; } - (OFDate*)dateByAddingTimeInterval: (int64_t)sec_ { return [self dateByAddingTimeInterval: sec_ withMicroseconds: 0]; } - (OFDate*)dateByAddingTimeInterval: (int64_t)sec_ withMicroseconds: (uint32_t)usec_ { sec_ += sec; usec_ += usec; while (usec_ > 999999) { usec_ -= 1000000; sec_++; } return [OFDate dateWithTimeIntervalSince1970: sec_ microseconds: usec_]; } @end