/* * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 * Jonathan Schleifer <js@heap.zone> * * 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 <stdint.h> #include <limits.h> #include <time.h> #include <math.h> #include <float.h> #include <sys/time.h> #import "OFDate.h" #import "OFString.h" #import "OFDictionary.h" #import "OFXMLElement.h" #ifdef OF_HAVE_THREADS # import "OFMutex.h" #endif #import "OFSystemInfo.h" #import "OFInitializationFailedException.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFOutOfRangeException.h" #import "of_strptime.h" #if (!defined(HAVE_GMTIME_R) || !defined(HAVE_LOCALTIME_R)) && \ defined(OF_HAVE_THREADS) static OFMutex *mutex; #endif #ifdef HAVE_GMTIME_R # define GMTIME_RET(field) \ time_t seconds = (time_t)_seconds; \ struct tm tm; \ \ if (seconds != floor(_seconds)) \ @throw [OFOutOfRangeException exception]; \ \ if (gmtime_r(&seconds, &tm) == NULL) \ @throw [OFOutOfRangeException exception]; \ \ return tm.field; # define LOCALTIME_RET(field) \ time_t seconds = (time_t)_seconds; \ struct tm tm; \ \ if (seconds != floor(_seconds)) \ @throw [OFOutOfRangeException exception]; \ \ if (localtime_r(&seconds, &tm) == NULL) \ @throw [OFOutOfRangeException exception]; \ \ return tm.field; #else # ifdef OF_HAVE_THREADS # define GMTIME_RET(field) \ time_t seconds = (time_t)_seconds; \ struct tm *tm; \ \ if (seconds != floor(_seconds)) \ @throw [OFOutOfRangeException exception]; \ \ [mutex lock]; \ \ @try { \ if ((tm = gmtime(&seconds)) == NULL) \ @throw [OFOutOfRangeException exception]; \ \ return tm->field; \ } @finally { \ [mutex unlock]; \ } # define LOCALTIME_RET(field) \ time_t seconds = (time_t)_seconds; \ struct tm *tm; \ \ if (seconds != floor(_seconds)) \ @throw [OFOutOfRangeException exception]; \ \ [mutex lock]; \ \ @try { \ if ((tm = localtime(&seconds)) == NULL) \ @throw [OFOutOfRangeException exception]; \ \ return tm->field; \ } @finally { \ [mutex unlock]; \ } # else # define GMTIME_RET(field) \ time_t seconds = (time_t)_seconds; \ struct tm *tm; \ \ if (seconds != floor(_seconds)) \ @throw [OFOutOfRangeException exception]; \ \ if ((tm = gmtime(&seconds)) == NULL) \ @throw [OFOutOfRangeException exception]; \ \ return tm->field; # define LOCALTIME_RET(field) \ time_t seconds = (time_t)_seconds; \ struct tm *tm; \ \ if (seconds != floor(_seconds)) \ @throw [OFOutOfRangeException exception]; \ \ if ((tm = localtime(&seconds)) == NULL) \ @throw [OFOutOfRangeException exception]; \ \ return tm->field; # endif #endif static int monthToDayOfYear[12] = { 0, 31, 31 + 28, 31 + 28 + 31, 31 + 28 + 31 + 30, 31 + 28 + 31 + 30 + 31, 31 + 28 + 31 + 30 + 31 + 30, 31 + 28 + 31 + 30 + 31 + 30 + 31, 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, }; @implementation OFDate #if (!defined(HAVE_GMTIME_R) || !defined(HAVE_LOCALTIME_R)) && \ defined(OF_HAVE_THREADS) + (void)initialize { if (self == [OFDate class]) mutex = [[OFMutex alloc] init]; } #endif + (instancetype)date { return [[[self alloc] init] autorelease]; } + (instancetype)dateWithTimeIntervalSince1970: (of_time_interval_t)seconds { return [[[self alloc] initWithTimeIntervalSince1970: seconds] autorelease]; } + (instancetype)dateWithTimeIntervalSinceNow: (of_time_interval_t)seconds { return [[[self alloc] initWithTimeIntervalSinceNow: seconds] autorelease]; } + (instancetype)dateWithDateString: (OFString*)string format: (OFString*)format { return [[[self alloc] initWithDateString: string format: format] autorelease]; } + (instancetype)dateWithLocalDateString: (OFString*)string format: (OFString*)format { return [[[self alloc] initWithLocalDateString: string format: format] autorelease]; } + (instancetype)distantFuture { return [[[self alloc] initWithTimeIntervalSince1970: INFINITY] autorelease]; } + (instancetype)distantPast { return [[[self alloc] initWithTimeIntervalSince1970: -INFINITY] autorelease]; } - init { struct timeval t; self = [super init]; OF_ENSURE(gettimeofday(&t, NULL) == 0); _seconds = t.tv_sec; _seconds += (of_time_interval_t)t.tv_usec / 1000000; return self; } - initWithTimeIntervalSince1970: (of_time_interval_t)seconds { self = [super init]; _seconds = seconds; return self; } - initWithTimeIntervalSinceNow: (of_time_interval_t)seconds { self = [self init]; _seconds += seconds; return self; } - initWithDateString: (OFString*)string format: (OFString*)format { self = [super init]; @try { struct tm tm = { 0 }; tm.tm_isdst = -1; if (of_strptime([string UTF8String], [format UTF8String], &tm) == NULL) @throw [OFInvalidFormatException exception]; /* Years */ _seconds = (int64_t)(tm.tm_year - 70) * 31536000; /* Days of leap years, excluding the year to look at */ _seconds += (((tm.tm_year + 1899) / 4) - 492) * 86400; _seconds -= (((tm.tm_year + 1899) / 100) - 19) * 86400; _seconds += (((tm.tm_year + 1899) / 400) - 4) * 86400; /* Leap day */ if (tm.tm_mon >= 2 && (((tm.tm_year + 1900) % 4 == 0 && (tm.tm_year + 1900) % 100 != 0) || (tm.tm_year + 1900) % 400 == 0)) _seconds += 86400; /* Months */ if (tm.tm_mon < 0 || tm.tm_mon > 12) @throw [OFInvalidFormatException exception]; _seconds += monthToDayOfYear[tm.tm_mon] * 86400; /* Days */ _seconds += (tm.tm_mday - 1) * 86400; /* Hours */ _seconds += tm.tm_hour * 3600; /* Minutes */ _seconds += tm.tm_min * 60; /* Seconds */ _seconds += tm.tm_sec; } @catch (id e) { [self release]; @throw e; } return self; } - initWithLocalDateString: (OFString*)string format: (OFString*)format { self = [super init]; @try { struct tm tm = { 0 }; tm.tm_isdst = -1; if (of_strptime([string UTF8String], [format UTF8String], &tm) == NULL) @throw [OFInvalidFormatException exception]; if ((_seconds = mktime(&tm)) == -1) @throw [OFInvalidFormatException exception]; } @catch (id e) { [self release]; @throw e; } return self; } - initWithSerialization: (OFXMLElement*)element { self = [super init]; @try { void *pool = objc_autoreleasePoolPush(); OFString *stringValue; union { double d; uint64_t u; } d; if (![[element name] isEqual: [self className]] || ![[element namespace] isEqual: OF_SERIALIZATION_NS]) @throw [OFInvalidArgumentException exception]; stringValue = [element stringValue]; if ([stringValue isEqual: @"DISTANT_PAST"]) _seconds = -INFINITY; else if ([stringValue isEqual: @"DISTANT_FUTURE"]) _seconds = INFINITY; else { d.u = (uint64_t)[element hexadecimalValue]; _seconds = d.d; } objc_autoreleasePoolPop(pool); } @catch (id e) { [self release]; @throw e; } return self; } - (bool)isEqual: (id)object { OFDate *otherDate; if (![object isKindOfClass: [OFDate class]]) return false; otherDate = object; if (otherDate->_seconds != _seconds) return false; return true; } - (uint32_t)hash { uint32_t hash; union { double d; uint8_t b[sizeof(double)]; } d; d.d = OF_BSWAP_DOUBLE_IF_BE(_seconds); OF_HASH_INIT(hash); for (size_t i = 0; i < sizeof(double); i++) OF_HASH_ADD(hash, d.b[i]); OF_HASH_FINALIZE(hash); return hash; } - copy { return [self retain]; } - (of_comparison_result_t)compare: (id <OFComparing>)object { OFDate *otherDate; if (![object isKindOfClass: [OFDate class]]) @throw [OFInvalidArgumentException exception]; otherDate = (OFDate*)object; if (_seconds < otherDate->_seconds) return OF_ORDERED_ASCENDING; if (_seconds > otherDate->_seconds) return OF_ORDERED_DESCENDING; return OF_ORDERED_SAME; } - (OFString*)description { if (isinf(_seconds)) return (_seconds > 0 ? @"Distant Future" : @"Distant Past"); else return [self dateStringWithFormat: @"%Y-%m-%dT%H:%M:%SZ"]; } - (OFXMLElement*)XMLElementBySerializing { void *pool = objc_autoreleasePoolPush(); OFXMLElement *element; union { double d; uint64_t u; } d; element = [OFXMLElement elementWithName: [self className] namespace: OF_SERIALIZATION_NS]; if (isinf(_seconds)) [element setStringValue: (_seconds > 0 ? @"DISTANT_FUTURE" : @"DISTANT_PAST")]; else { d.d = _seconds; [element setStringValue: [OFString stringWithFormat: @"%016" PRIx64, d.u]]; } [element retain]; objc_autoreleasePoolPop(pool); return [element autorelease]; } - (uint32_t)microsecond { return (uint32_t)rint((_seconds - floor(_seconds)) * 1000000); } - (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; size_t pageSize; #ifndef OF_WINDOWS char *buffer; #else wchar_t *buffer; #endif if (seconds != floor(_seconds)) @throw [OFOutOfRangeException exception]; #ifdef HAVE_GMTIME_R if (gmtime_r(&seconds, &tm) == NULL) @throw [OFOutOfRangeException exception]; #else # ifdef OF_HAVE_THREADS [mutex lock]; @try { # endif struct tm *tmp; if ((tmp = gmtime(&seconds)) == NULL) @throw [OFOutOfRangeException exception]; tm = *tmp; # ifdef OF_HAVE_THREADS } @finally { [mutex unlock]; } # endif #endif pageSize = [OFSystemInfo pageSize]; buffer = [self allocMemoryWithSize: pageSize]; @try { #ifndef OF_WINDOWS if (strftime(buffer, pageSize, [format UTF8String], &tm) == 0) @throw [OFOutOfRangeException exception]; ret = [OFString stringWithUTF8String: buffer]; #else if (wcsftime(buffer, pageSize / sizeof(wchar_t), [format UTF16String], &tm) == 0) @throw [OFOutOfRangeException exception]; ret = [OFString stringWithUTF16String: buffer]; #endif } @finally { [self freeMemory: buffer]; } return ret; } - (OFString*)localDateStringWithFormat: (OFConstantString*)format { OFString *ret; time_t seconds = (time_t)_seconds; struct tm tm; size_t pageSize; #ifndef OF_WINDOWS char *buffer; #else wchar_t *buffer; #endif if (seconds != floor(_seconds)) @throw [OFOutOfRangeException exception]; #ifdef HAVE_LOCALTIME_R if (localtime_r(&seconds, &tm) == NULL) @throw [OFOutOfRangeException exception]; #else # ifdef OF_HAVE_THREADS [mutex lock]; @try { # endif struct tm *tmp; if ((tmp = localtime(&seconds)) == NULL) @throw [OFOutOfRangeException exception]; tm = *tmp; # ifdef OF_HAVE_THREADS } @finally { [mutex unlock]; } # endif #endif pageSize = [OFSystemInfo pageSize]; buffer = [self allocMemoryWithSize: pageSize]; @try { #ifndef OF_WINDOWS if (strftime(buffer, pageSize, [format UTF8String], &tm) == 0) @throw [OFOutOfRangeException exception]; ret = [OFString stringWithUTF8String: buffer]; #else if (wcsftime(buffer, pageSize / sizeof(wchar_t), [format UTF16String], &tm) == 0) @throw [OFOutOfRangeException exception]; ret = [OFString stringWithUTF16String: buffer]; #endif } @finally { [self freeMemory: buffer]; } return ret; } - (OFDate*)earlierDate: (OFDate*)otherDate { if (otherDate == nil) return [[self retain] autorelease]; if ([self compare: otherDate] == OF_ORDERED_DESCENDING) return [[otherDate retain] autorelease]; return [[self retain] autorelease]; } - (OFDate*)laterDate: (OFDate*)otherDate { if (otherDate == nil) return [[self retain] autorelease]; if ([self compare: otherDate] == OF_ORDERED_ASCENDING) return [[otherDate retain] autorelease]; return [[self retain] autorelease]; } - (of_time_interval_t)timeIntervalSince1970 { return _seconds; } - (of_time_interval_t)timeIntervalSinceDate: (OFDate*)otherDate { return _seconds - otherDate->_seconds; } - (of_time_interval_t)timeIntervalSinceNow { struct timeval t; of_time_interval_t seconds; OF_ENSURE(gettimeofday(&t, NULL) == 0); seconds = t.tv_sec; seconds += (of_time_interval_t)t.tv_usec / 1000000; return _seconds - seconds; } - (OFDate*)dateByAddingTimeInterval: (of_time_interval_t)seconds { return [OFDate dateWithTimeIntervalSince1970: _seconds + seconds]; } @end