/* * Copyright (c) 2008, 2009, 2010, 2011 * Jonathan Schleifer <js@webkeks.org> * * 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" #define _GNU_SOURCE #include <stdint.h> #include <limits.h> #include <time.h> #include <sys/time.h> #import "OFDate.h" #import "OFString.h" #import "OFDictionary.h" #import "OFXMLElement.h" #import "OFAutoreleasePool.h" #ifdef OF_THREADS # import "OFThread.h" #endif #import "OFInitializationFailedException.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFOutOfRangeException.h" #import "macros.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 seconds_ = (time_t)seconds; \ struct tm tm; \ \ if (seconds != seconds_) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ if (gmtime_r(&seconds_, &tm) == NULL) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ return tm.field; # define LOCALTIME_RET(field) \ time_t seconds_ = (time_t)seconds; \ struct tm tm; \ \ if (seconds != seconds_) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ if (localtime_r(&seconds_, &tm) == NULL) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ return tm.field; #else # ifdef OF_THREADS # define GMTIME_RET(field) \ time_t seconds_ = (time_t)seconds; \ struct tm *tm; \ \ if (seconds != seconds_) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ [mutex lock]; \ \ @try { \ if ((tm = gmtime(&seconds_)) == NULL) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ return tm->field; \ } @finally { \ [mutex unlock]; \ } # define LOCALTIME_RET(field) \ time_t seconds_ = (time_t)seconds; \ struct tm *tm; \ \ if (seconds != seconds_) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ [mutex lock]; \ \ @try { \ if ((tm = localtime(&seconds_)) == NULL) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ return tm->field; \ } @finally { \ [mutex unlock]; \ } # else # define GMTIME_RET(field) \ time_t seconds_ = (time_t)seconds; \ struct tm *tm; \ \ if (seconds != seconds_) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ if ((tm = gmtime(&seconds_)) == NULL) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ return tm->field; # define LOCALTIME_RET(field) \ time_t seconds_ = (time_t)seconds; \ struct tm *tm; \ \ if (seconds != seconds_) \ @throw [OFOutOfRangeException newWithClass: isa]; \ \ if ((tm = localtime(&seconds_)) == 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)seconds { return [[[self alloc] initWithTimeIntervalSince1970: seconds] autorelease]; } + dateWithTimeIntervalSince1970: (int64_t)seconds microseconds: (uint32_t)microseconds { return [[[self alloc] initWithTimeIntervalSince1970: seconds microseconds: microseconds] autorelease]; } + dateWithTimeIntervalSinceNow: (int64_t)seconds { return [[[self alloc] initWithTimeIntervalSinceNow: seconds] autorelease]; } + dateWithTimeIntervalSinceNow: (int64_t)seconds microseconds: (uint32_t)microseconds { return [[[self alloc] initWithTimeIntervalSinceNow: seconds microseconds: microseconds] autorelease]; } + dateWithDateString: (OFString*)string format: (OFString*)format { return [[[self alloc] initWithDateString: string format: format] autorelease]; } + dateWithLocalDateString: (OFString*)string format: (OFString*)format { return [[[self alloc] initWithLocalDateString: string format: format] 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)seconds_ { return [self initWithTimeIntervalSince1970: seconds_ microseconds: 0]; } - initWithTimeIntervalSince1970: (int64_t)seconds_ microseconds: (uint32_t)microseconds_ { self = [super init]; seconds = seconds_; microseconds = microseconds_; return self; } - initWithTimeIntervalSinceNow: (int64_t)seconds_ { return [self initWithTimeIntervalSinceNow: seconds_ microseconds: 0]; } - initWithTimeIntervalSinceNow: (int64_t)seconds_ microseconds: (uint32_t)microseconds_ { self = [self init]; seconds += seconds_; microseconds += microseconds_; seconds += microseconds / 1000000; microseconds %= 1000000; return self; } - initWithDateString: (OFString*)string format: (OFString*)format { self = [super init]; @try { struct tm tm = {}; tm.tm_isdst = -1; if (strptime([string UTF8String], [format UTF8String], &tm) == NULL) @throw [OFInvalidFormatException newWithClass: isa]; if (tm.tm_gmtoff) @throw [OFInvalidFormatException newWithClass: isa]; if ((seconds = mktime(&tm)) == -1) @throw [OFInvalidFormatException newWithClass: isa]; seconds += tm.tm_gmtoff; } @catch (id e) { [self release]; @throw e; } return self; } - initWithLocalDateString: (OFString*)string format: (OFString*)format { self = [super init]; @try { struct tm tm = {}; tm.tm_isdst = -1; if (strptime([string UTF8String], [format UTF8String], &tm) == NULL) @throw [OFInvalidFormatException newWithClass: isa]; if ((seconds = mktime(&tm)) == -1) @throw [OFInvalidFormatException newWithClass: isa]; } @catch (id e) { [self release]; @throw e; } return self; } - initWithSerialization: (OFXMLElement*)element { self = [super init]; @try { OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; OFXMLAttribute *secondsAttribute, *microsecondsAttribute; if (![[element name] isEqual: [self className]] || ![[element namespace] isEqual: OF_SERIALIZATION_NS]) @throw [OFInvalidArgumentException newWithClass: isa selector: _cmd]; secondsAttribute = [element attributeForName: @"seconds"]; microsecondsAttribute = [element attributeForName: @"microseconds"]; if (secondsAttribute == nil || microsecondsAttribute == nil) @throw [OFInvalidArgumentException newWithClass: isa selector: _cmd]; seconds = (int64_t)[secondsAttribute decimalValue]; microseconds = (uint32_t)[microsecondsAttribute decimalValue]; [pool release]; } @catch (id e) { [self release]; @throw e; } return self; } - (BOOL)isEqual: (id)object { OFDate *otherDate; if (![object isKindOfClass: [OFDate class]]) return NO; otherDate = object; if (otherDate->seconds != seconds || otherDate->microseconds != microseconds) return NO; return YES; } - (uint32_t)hash { uint32_t hash; OF_HASH_INIT(hash); OF_HASH_ADD_INT64(hash, seconds); OF_HASH_ADD_INT32(hash, microseconds); OF_HASH_FINALIZE(hash); return hash; } - copy { return [self retain]; } - (of_comparison_result_t)compare: (id)object { OFDate *otherDate; if (![object isKindOfClass: [OFDate class]]) @throw [OFInvalidArgumentException newWithClass: isa selector: _cmd]; otherDate = object; if (seconds < otherDate->seconds) return OF_ORDERED_ASCENDING; if (seconds > otherDate->seconds) return OF_ORDERED_DESCENDING; if (microseconds < otherDate->microseconds) return OF_ORDERED_ASCENDING; if (microseconds > otherDate->microseconds) return OF_ORDERED_DESCENDING; return OF_ORDERED_SAME; } - (OFString*)description { return [self dateStringWithFormat: @"%Y-%m-%dT%H:%M:%SZ"]; } - (OFXMLElement*)XMLElementBySerializing { OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; OFXMLElement *element; OFString *secondsString, *microsecondsString; element = [OFXMLElement elementWithName: [self className] namespace: OF_SERIALIZATION_NS]; secondsString = [OFString stringWithFormat: @"%" @PRId64, seconds]; microsecondsString = [OFString stringWithFormat: @"%" @PRIu32, microseconds]; [element addAttributeWithName: @"seconds" stringValue: secondsString]; [element addAttributeWithName: @"microseconds" stringValue: microsecondsString]; [element retain]; [pool release]; [element autorelease]; return element; } - (uint32_t)microsecond { return microseconds; } - (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) } - (uint16_t)localYear { LOCALTIME_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: (OFConstantString*)format { OFString *ret; time_t seconds_ = (time_t)seconds; struct tm tm; char *buffer; if (seconds != seconds_) @throw [OFOutOfRangeException newWithClass: isa]; #ifdef HAVE_GMTIME_R if (gmtime_r(&seconds_, &tm) == NULL) @throw [OFOutOfRangeException newWithClass: isa]; #else # ifdef OF_THREADS [mutex lock]; @try { # endif struct tm *tmp; if ((tmp = gmtime(&seconds_)) == NULL) @throw [OFOutOfRangeException newWithClass: isa]; tm = *tmp; # ifdef OF_THREADS } @finally { [mutex unlock]; } # endif #endif buffer = [self allocMemoryWithSize: of_pagesize]; @try { if (!strftime(buffer, of_pagesize, [format UTF8String], &tm)) @throw [OFOutOfRangeException newWithClass: isa]; ret = [OFString stringWithUTF8String: buffer]; } @finally { [self freeMemory: buffer]; } return ret; } - (OFString*)localDateStringWithFormat: (OFConstantString*)format { OFString *ret; time_t seconds_ = (time_t)seconds; struct tm tm; char *buffer; if (seconds != seconds_) @throw [OFOutOfRangeException newWithClass: isa]; #ifdef HAVE_LOCALTIME_R if (localtime_r(&seconds_, &tm) == NULL) @throw [OFOutOfRangeException newWithClass: isa]; #else # ifdef OF_THREADS [mutex lock]; @try { # endif struct tm *tmp; if ((tmp = localtime(&seconds_)) == NULL) @throw [OFOutOfRangeException newWithClass: isa]; tm = *tmp; # ifdef OF_THREADS } @finally { [mutex unlock]; } # endif #endif buffer = [self allocMemoryWithSize: of_pagesize]; @try { if (!strftime(buffer, of_pagesize, [format UTF8String], &tm)) @throw [OFOutOfRangeException newWithClass: isa]; ret = [OFString stringWithUTF8String: buffer]; } @finally { [self freeMemory: buffer]; } return ret; } - (OFDate*)earlierDate: (OFDate*)otherDate { if ([self compare: otherDate] == OF_ORDERED_DESCENDING) return [[otherDate retain] autorelease]; return [[self retain] autorelease]; } - (OFDate*)laterDate: (OFDate*)otherDate { if ([self compare: otherDate] == OF_ORDERED_ASCENDING) return [[otherDate retain] autorelease]; return [[self retain] autorelease]; } - (int64_t)timeIntervalSince1970 { return seconds; } - (uint32_t)microsecondsOfTimeIntervalSince1970 { return microseconds; } - (int64_t)timeIntervalSinceDate: (OFDate*)otherDate { int64_t seconds_ = seconds - otherDate->seconds; int32_t microseconds_ = (int32_t)microseconds - otherDate->microseconds; seconds_ += microseconds_ / 1000000; microseconds_ %= 1000000; while (microseconds_ < 0) { microseconds_ += 1000000; seconds_--; } return seconds_; } - (uint32_t)microsecondsOfTimeIntervalSinceDate: (OFDate*)otherDate { int32_t microseconds_ = (int32_t)microseconds - otherDate->microseconds; microseconds_ %= 1000000; while (microseconds_ < 0) microseconds_ += 1000000; return microseconds_; } - (OFDate*)dateByAddingTimeInterval: (int64_t)sec_ { return [self dateByAddingTimeInterval: sec_ withMicroseconds: 0]; } - (OFDate*)dateByAddingTimeInterval: (int64_t)seconds_ withMicroseconds: (uint32_t)microseconds_ { seconds_ += seconds; microseconds_ += microseconds; seconds_ += microseconds_ / 1000000; microseconds_ %= 1000000; return [OFDate dateWithTimeIntervalSince1970: seconds_ microseconds: microseconds_]; } @end