2 * Copyright (c) 2012 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 Copyright (c) 1998-2012, Apple Inc. All rights reserved.
26 Responsibility: Christopher Kane
29 #include <CoreFoundation/CFDate.h>
30 #include <CoreFoundation/CFTimeZone.h>
31 #include <CoreFoundation/CFDictionary.h>
32 #include <CoreFoundation/CFArray.h>
33 #include <CoreFoundation/CFString.h>
34 #include <CoreFoundation/CFNumber.h>
35 #include "CFInternal.h"
37 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI || DEPLOYMENT_TARGET_LINUX
41 #define DEFINE_CFDATE_FUNCTIONS 1
43 /* cjk: The Julian Date for the reference date is 2451910.5,
44 I think, in case that's ever useful. */
46 #if DEFINE_CFDATE_FUNCTIONS
48 const CFTimeInterval kCFAbsoluteTimeIntervalSince1970
= 978307200.0L;
49 const CFTimeInterval kCFAbsoluteTimeIntervalSince1904
= 3061152000.0L;
51 __private_extern__
double __CFTSRRate
= 0.0;
52 static double __CF1_TSRRate
= 0.0;
54 __private_extern__
int64_t __CFTimeIntervalToTSR(CFTimeInterval ti
) {
55 if ((ti
* __CFTSRRate
) > INT64_MAX
/ 2) return (INT64_MAX
/ 2);
56 return (int64_t)(ti
* __CFTSRRate
);
59 __private_extern__ CFTimeInterval
__CFTSRToTimeInterval(int64_t tsr
) {
60 return (CFTimeInterval
)((double)tsr
* __CF1_TSRRate
);
63 CFAbsoluteTime
CFAbsoluteTimeGetCurrent(void) {
66 gettimeofday(&tv
, NULL
);
67 ret
= (CFTimeInterval
)tv
.tv_sec
- kCFAbsoluteTimeIntervalSince1970
;
68 ret
+= (1.0E-6 * (CFTimeInterval
)tv
.tv_usec
);
72 __private_extern__
void __CFDateInitialize(void) {
73 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
74 struct mach_timebase_info info
;
75 mach_timebase_info(&info
);
76 __CFTSRRate
= (1.0E9
/ (double)info
.numer
) * (double)info
.denom
;
77 __CF1_TSRRate
= 1.0 / __CFTSRRate
;
78 #elif DEPLOYMENT_TARGET_WINDOWS
80 if (!QueryPerformanceFrequency(&freq
)) {
83 __CFTSRRate
= (double)freq
.QuadPart
;
84 __CF1_TSRRate
= 1.0 / __CFTSRRate
;
85 #elif DEPLOYMENT_TARGET_LINUX
87 if (!clock_getres(CLOCK_MONOTONIC
, &res
)) {
90 __CFTSRRate
= res
.tv_sec
+ (1000000000 * res
.tv_nsec
);
91 __CF1_TSRRate
= 1.0 / __CFTSRRate
;
93 #error Unable to initialize date
95 CFDateGetTypeID(); // cause side-effects
100 CFAbsoluteTime _time
; /* immutable */
103 static Boolean
__CFDateEqual(CFTypeRef cf1
, CFTypeRef cf2
) {
104 CFDateRef date1
= (CFDateRef
)cf1
;
105 CFDateRef date2
= (CFDateRef
)cf2
;
106 if (date1
->_time
!= date2
->_time
) return false;
110 static CFHashCode
__CFDateHash(CFTypeRef cf
) {
111 CFDateRef date
= (CFDateRef
)cf
;
112 return (CFHashCode
)(float)floor(date
->_time
);
115 static CFStringRef
__CFDateCopyDescription(CFTypeRef cf
) {
116 CFDateRef date
= (CFDateRef
)cf
;
117 return CFStringCreateWithFormat(CFGetAllocator(date
), NULL
, CFSTR("<CFDate %p [%p]>{time = %0.09g}"), cf
, CFGetAllocator(date
), date
->_time
);
120 static CFTypeID __kCFDateTypeID
= _kCFRuntimeNotATypeID
;
122 static const CFRuntimeClass __CFDateClass
= {
131 __CFDateCopyDescription
134 CFTypeID
CFDateGetTypeID(void) {
135 if (_kCFRuntimeNotATypeID
== __kCFDateTypeID
) __kCFDateTypeID
= _CFRuntimeRegisterClass(&__CFDateClass
);
136 return __kCFDateTypeID
;
139 CFDateRef
CFDateCreate(CFAllocatorRef allocator
, CFAbsoluteTime at
) {
142 size
= sizeof(struct __CFDate
) - sizeof(CFRuntimeBase
);
143 memory
= (CFDateRef
)_CFRuntimeCreateInstance(allocator
, CFDateGetTypeID(), size
, NULL
);
144 if (NULL
== memory
) {
147 ((struct __CFDate
*)memory
)->_time
= at
;
151 CFTimeInterval
CFDateGetAbsoluteTime(CFDateRef date
) {
152 CF_OBJC_FUNCDISPATCHV(CFDateGetTypeID(), CFTimeInterval
, (NSDate
*)date
, timeIntervalSinceReferenceDate
);
153 __CFGenericValidateType(date
, CFDateGetTypeID());
157 CFTimeInterval
CFDateGetTimeIntervalSinceDate(CFDateRef date
, CFDateRef otherDate
) {
158 CF_OBJC_FUNCDISPATCHV(CFDateGetTypeID(), CFTimeInterval
, (NSDate
*)date
, timeIntervalSinceDate
:(NSDate
*)otherDate
);
159 __CFGenericValidateType(date
, CFDateGetTypeID());
160 __CFGenericValidateType(otherDate
, CFDateGetTypeID());
161 return date
->_time
- otherDate
->_time
;
164 CFComparisonResult
CFDateCompare(CFDateRef date
, CFDateRef otherDate
, void *context
) {
165 CF_OBJC_FUNCDISPATCHV(CFDateGetTypeID(), CFComparisonResult
, (NSDate
*)date
, compare
:(NSDate
*)otherDate
);
166 __CFGenericValidateType(date
, CFDateGetTypeID());
167 __CFGenericValidateType(otherDate
, CFDateGetTypeID());
168 if (date
->_time
< otherDate
->_time
) return kCFCompareLessThan
;
169 if (date
->_time
> otherDate
->_time
) return kCFCompareGreaterThan
;
170 return kCFCompareEqualTo
;
175 CF_INLINE
int32_t __CFDoubleModToInt(double d
, int32_t modulus
) {
176 int32_t result
= (int32_t)(float)floor(d
- floor(d
/ modulus
) * modulus
);
177 if (result
< 0) result
+= modulus
;
181 CF_INLINE
double __CFDoubleMod(double d
, int32_t modulus
) {
182 double result
= d
- floor(d
/ modulus
) * modulus
;
183 if (result
< 0.0) result
+= (double)modulus
;
187 static const uint8_t daysInMonth
[16] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0, 0, 0};
188 static const uint16_t daysBeforeMonth
[16] = {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365, 0, 0};
189 static const uint16_t daysAfterMonth
[16] = {365, 334, 306, 275, 245, 214, 184, 153, 122, 92, 61, 31, 0, 0, 0, 0};
191 CF_INLINE
bool isleap(int64_t year
) {
192 int64_t y
= (year
+ 1) % 400; /* correct to nearest multiple-of-400 year, then find the remainder */
194 return (0 == (y
& 3) && 100 != y
&& 200 != y
&& 300 != y
);
197 /* year arg is absolute year; Gregorian 2001 == year 0; 2001/1/1 = absolute date 0 */
198 CF_INLINE
uint8_t __CFDaysInMonth(int8_t month
, int64_t year
, bool leap
) {
199 return daysInMonth
[month
] + (2 == month
&& leap
);
202 /* year arg is absolute year; Gregorian 2001 == year 0; 2001/1/1 = absolute date 0 */
203 CF_INLINE
uint16_t __CFDaysBeforeMonth(int8_t month
, int64_t year
, bool leap
) {
204 return daysBeforeMonth
[month
] + (2 < month
&& leap
);
207 /* year arg is absolute year; Gregorian 2001 == year 0; 2001/1/1 = absolute date 0 */
208 CF_INLINE
uint16_t __CFDaysAfterMonth(int8_t month
, int64_t year
, bool leap
) {
209 return daysAfterMonth
[month
] + (month
< 2 && leap
);
212 /* year arg is absolute year; Gregorian 2001 == year 0; 2001/1/1 = absolute date 0 */
213 static void __CFYMDFromAbsolute(int64_t absolute
, int64_t *year
, int8_t *month
, int8_t *day
) {
214 int64_t b
= absolute
/ 146097; // take care of as many multiples of 400 years as possible
217 absolute
-= b
* 146097;
218 while (absolute
< 0) {
220 absolute
+= __CFDaysAfterMonth(0, y
, isleap(y
));
222 /* Now absolute is non-negative days to add to year */
223 ydays
= __CFDaysAfterMonth(0, y
, isleap(y
));
224 while (ydays
<= absolute
) {
227 ydays
= __CFDaysAfterMonth(0, y
, isleap(y
));
229 /* Now we have year and days-into-year */
232 int8_t m
= absolute
/ 33 + 1; /* search from the approximation */
233 bool leap
= isleap(y
);
234 while (__CFDaysBeforeMonth(m
+ 1, y
, leap
) <= absolute
) m
++;
235 if (month
) *month
= m
;
236 if (day
) *day
= absolute
- __CFDaysBeforeMonth(m
, y
, leap
) + 1;
240 /* year arg is absolute year; Gregorian 2001 == year 0; 2001/1/1 = absolute date 0 */
241 static double __CFAbsoluteFromYMD(int64_t year
, int8_t month
, int8_t day
) {
242 double absolute
= 0.0;
244 int64_t b
= year
/ 400; // take care of as many multiples of 400 years as possible
245 absolute
+= b
* 146097.0;
248 for (idx
= year
; idx
< 0; idx
++)
249 absolute
-= __CFDaysAfterMonth(0, idx
, isleap(idx
));
251 for (idx
= 0; idx
< year
; idx
++)
252 absolute
+= __CFDaysAfterMonth(0, idx
, isleap(idx
));
254 /* Now add the days into the original year */
255 absolute
+= __CFDaysBeforeMonth(month
, year
, isleap(year
)) + day
- 1;
259 Boolean
CFGregorianDateIsValid(CFGregorianDate gdate
, CFOptionFlags unitFlags
) {
260 if ((unitFlags
& kCFGregorianUnitsYears
) && (gdate
.year
<= 0)) return false;
261 if ((unitFlags
& kCFGregorianUnitsMonths
) && (gdate
.month
< 1 || 12 < gdate
.month
)) return false;
262 if ((unitFlags
& kCFGregorianUnitsDays
) && (gdate
.day
< 1 || 31 < gdate
.day
)) return false;
263 if ((unitFlags
& kCFGregorianUnitsHours
) && (gdate
.hour
< 0 || 23 < gdate
.hour
)) return false;
264 if ((unitFlags
& kCFGregorianUnitsMinutes
) && (gdate
.minute
< 0 || 59 < gdate
.minute
)) return false;
265 if ((unitFlags
& kCFGregorianUnitsSeconds
) && (gdate
.second
< 0.0 || 60.0 <= gdate
.second
)) return false;
266 if ((unitFlags
& kCFGregorianUnitsDays
) && (unitFlags
& kCFGregorianUnitsMonths
) && (unitFlags
& kCFGregorianUnitsYears
) && (__CFDaysInMonth(gdate
.month
, gdate
.year
- 2001, isleap(gdate
.year
- 2001)) < gdate
.day
)) return false;
270 CFAbsoluteTime
CFGregorianDateGetAbsoluteTime(CFGregorianDate gdate
, CFTimeZoneRef tz
) {
272 at
= 86400.0 * __CFAbsoluteFromYMD(gdate
.year
- 2001, gdate
.month
, gdate
.day
);
273 at
+= 3600.0 * gdate
.hour
+ 60.0 * gdate
.minute
+ gdate
.second
;
274 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_WINDOWS || DEPLOYMENT_TARGET_LINUX
276 __CFGenericValidateType(tz
, CFTimeZoneGetTypeID());
278 CFTimeInterval offset0
, offset1
;
280 offset0
= CFTimeZoneGetSecondsFromGMT(tz
, at
);
281 offset1
= CFTimeZoneGetSecondsFromGMT(tz
, at
- offset0
);
288 CFGregorianDate
CFAbsoluteTimeGetGregorianDate(CFAbsoluteTime at
, CFTimeZoneRef tz
) {
289 CFGregorianDate gdate
;
290 int64_t absolute
, year
;
292 CFAbsoluteTime fixedat
;
293 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_WINDOWS || DEPLOYMENT_TARGET_LINUX
295 __CFGenericValidateType(tz
, CFTimeZoneGetTypeID());
297 fixedat
= at
+ (NULL
!= tz
? CFTimeZoneGetSecondsFromGMT(tz
, at
) : 0.0);
301 absolute
= (int64_t)floor(fixedat
/ 86400.0);
302 __CFYMDFromAbsolute(absolute
, &year
, &month
, &day
);
303 if (INT32_MAX
- 2001 < year
) year
= INT32_MAX
- 2001;
304 gdate
.year
= year
+ 2001;
307 gdate
.hour
= __CFDoubleModToInt(floor(fixedat
/ 3600.0), 24);
308 gdate
.minute
= __CFDoubleModToInt(floor(fixedat
/ 60.0), 60);
309 gdate
.second
= __CFDoubleMod(fixedat
, 60);
310 if (0.0 == gdate
.second
) gdate
.second
= 0.0; // stomp out possible -0.0
314 /* Note that the units of years and months are not equal length, but are treated as such. */
315 CFAbsoluteTime
CFAbsoluteTimeAddGregorianUnits(CFAbsoluteTime at
, CFTimeZoneRef tz
, CFGregorianUnits units
) {
316 CFGregorianDate gdate
;
317 CFGregorianUnits working
;
318 CFAbsoluteTime candidate_at0
, candidate_at1
;
321 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_WINDOWS || DEPLOYMENT_TARGET_LINUX
323 __CFGenericValidateType(tz
, CFTimeZoneGetTypeID());
327 /* Most people seem to expect years, then months, then days, etc.
328 to be added in that order. Thus, 27 April + (4 days, 1 month)
329 = 31 May, and not 1 June. This is also relatively predictable.
331 On another issue, months not being equal length, people also
332 seem to expect late day-of-month clamping (don't clamp as you
333 go through months), but clamp before adding in the days. Late
334 clamping is also more predictable given random starting points
335 and random numbers of months added (ie Jan 31 + 2 months could
336 be March 28 or March 29 in different years with aggressive
337 clamping). Proportionality (28 Feb + 1 month = 31 March) is
340 Also, people don't expect time zone transitions to have any
341 effect when adding years and/or months and/or days, only.
342 Hours, minutes, and seconds, though, are added in as humans
343 would experience the passing of that time. What this means
344 is that if the date, after adding years, months, and days
345 lands on some date, and then adding hours, minutes, and
346 seconds crosses a time zone transition, the time zone
347 transition is accounted for. If adding years, months, and
348 days gets the date into a different time zone offset period,
349 that transition is not taken into account.
351 gdate
= CFAbsoluteTimeGetGregorianDate(at
, tz
);
352 /* We must work in a CFGregorianUnits, because the fields in the CFGregorianDate can easily overflow */
353 working
.years
= gdate
.year
;
354 working
.months
= gdate
.month
;
355 working
.days
= gdate
.day
;
356 working
.years
+= units
.years
;
357 working
.months
+= units
.months
;
358 while (12 < working
.months
) {
359 working
.months
-= 12;
362 while (working
.months
< 1) {
363 working
.months
+= 12;
366 monthdays
= __CFDaysInMonth(working
.months
, working
.years
- 2001, isleap(working
.years
- 2001));
367 if (monthdays
< working
.days
) { /* Clamp day to new month */
368 working
.days
= monthdays
;
370 working
.days
+= units
.days
;
371 while (monthdays
< working
.days
) {
373 if (12 < working
.months
) {
374 working
.months
-= 12;
377 working
.days
-= monthdays
;
378 monthdays
= __CFDaysInMonth(working
.months
, working
.years
- 2001, isleap(working
.years
- 2001));
380 while (working
.days
< 1) {
382 if (working
.months
< 1) {
383 working
.months
+= 12;
386 monthdays
= __CFDaysInMonth(working
.months
, working
.years
- 2001, isleap(working
.years
- 2001));
387 working
.days
+= monthdays
;
389 gdate
.year
= working
.years
;
390 gdate
.month
= working
.months
;
391 gdate
.day
= working
.days
;
392 /* Roll in hours, minutes, and seconds */
393 candidate_at0
= CFGregorianDateGetAbsoluteTime(gdate
, tz
);
394 candidate_at1
= candidate_at0
+ 3600.0 * units
.hours
+ 60.0 * units
.minutes
+ units
.seconds
;
395 /* If summing in the hours, minutes, and seconds delta pushes us
396 * into a new time zone offset, that will automatically be taken
397 * care of by the fact that we just add the raw time above. To
398 * undo that effect, we'd have to get the time zone offsets for
399 * candidate_at0 and candidate_at1 here, and subtract the
400 * difference (offset1 - offset0) from candidate_at1. */
401 return candidate_at1
;
404 /* at1 - at2. The only constraint here is that this needs to be the inverse
405 of CFAbsoluteTimeByAddingGregorianUnits(), but that's a very rigid constraint.
406 Unfortunately, due to the nonuniformity of the year and month units, this
407 inversion essentially has to approximate until it finds the answer. */
408 CFGregorianUnits
CFAbsoluteTimeGetDifferenceAsGregorianUnits(CFAbsoluteTime at1
, CFAbsoluteTime at2
, CFTimeZoneRef tz
, CFOptionFlags unitFlags
) {
409 const int32_t seconds
[5] = {366 * 24 * 3600, 31 * 24 * 3600, 24 * 3600, 3600, 60};
410 CFGregorianUnits units
= {0, 0, 0, 0, 0, 0.0};
411 CFAbsoluteTime atold
, atnew
= at2
;
413 incr
= (at2
< at1
) ? 1 : -1;
414 /* Successive approximation: years, then months, then days, then hours, then minutes. */
415 for (idx
= 0; idx
< 5; idx
++) {
416 if (unitFlags
& (1 << idx
)) {
417 ((int32_t *)&units
)[idx
] = -3 * incr
+ (int32_t)((at1
- atnew
) / seconds
[idx
]);
420 ((int32_t *)&units
)[idx
] += incr
;
421 atnew
= CFAbsoluteTimeAddGregorianUnits(at2
, tz
, units
);
422 } while ((1 == incr
&& atnew
<= at1
) || (-1 == incr
&& at1
<= atnew
));
423 ((int32_t *)&units
)[idx
] -= incr
;
427 if (unitFlags
& kCFGregorianUnitsSeconds
) {
428 units
.seconds
= at1
- atnew
;
430 if (0.0 == units
.seconds
) units
.seconds
= 0.0; // stomp out possible -0.0
434 SInt32
CFAbsoluteTimeGetDayOfWeek(CFAbsoluteTime at
, CFTimeZoneRef tz
) {
436 CFAbsoluteTime fixedat
;
437 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_WINDOWS || DEPLOYMENT_TARGET_LINUX
439 __CFGenericValidateType(tz
, CFTimeZoneGetTypeID());
441 fixedat
= at
+ (NULL
!= tz
? CFTimeZoneGetSecondsFromGMT(tz
, at
) : 0.0);
445 absolute
= (int64_t)floor(fixedat
/ 86400.0);
446 return (absolute
< 0) ? ((absolute
+ 1) % 7 + 7) : (absolute
% 7 + 1); /* Monday = 1, etc. */
449 SInt32
CFAbsoluteTimeGetDayOfYear(CFAbsoluteTime at
, CFTimeZoneRef tz
) {
450 CFAbsoluteTime fixedat
;
451 int64_t absolute
, year
;
453 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_WINDOWS || DEPLOYMENT_TARGET_LINUX
455 __CFGenericValidateType(tz
, CFTimeZoneGetTypeID());
457 fixedat
= at
+ (NULL
!= tz
? CFTimeZoneGetSecondsFromGMT(tz
, at
) : 0.0);
461 absolute
= (int64_t)floor(fixedat
/ 86400.0);
462 __CFYMDFromAbsolute(absolute
, &year
, &month
, &day
);
463 return __CFDaysBeforeMonth(month
, year
, isleap(year
)) + day
;
466 /* "the first week of a year is the one which includes the first Thursday" (ISO 8601) */
467 SInt32
CFAbsoluteTimeGetWeekOfYear(CFAbsoluteTime at
, CFTimeZoneRef tz
) {
468 int64_t absolute
, year
;
470 CFAbsoluteTime fixedat
;
471 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_WINDOWS || DEPLOYMENT_TARGET_LINUX
473 __CFGenericValidateType(tz
, CFTimeZoneGetTypeID());
475 fixedat
= at
+ (NULL
!= tz
? CFTimeZoneGetSecondsFromGMT(tz
, at
) : 0.0);
479 absolute
= (int64_t)floor(fixedat
/ 86400.0);
480 __CFYMDFromAbsolute(absolute
, &year
, &month
, &day
);
481 double absolute0101
= __CFAbsoluteFromYMD(year
, 1, 1);
482 int64_t dow0101
= __CFDoubleModToInt(absolute0101
, 7) + 1;
483 /* First three and last three days of a year can end up in a week of a different year */
484 if (1 == month
&& day
< 4) {
485 if ((day
< 4 && 5 == dow0101
) || (day
< 3 && 6 == dow0101
) || (day
< 2 && 7 == dow0101
)) {
489 if (12 == month
&& 28 < day
) {
490 double absolute20101
= __CFAbsoluteFromYMD(year
+ 1, 1, 1);
491 int64_t dow20101
= __CFDoubleModToInt(absolute20101
, 7) + 1;
492 if ((28 < day
&& 4 == dow20101
) || (29 < day
&& 3 == dow20101
) || (30 < day
&& 2 == dow20101
)) {
496 /* Days into year, plus a week-shifting correction, divided by 7. First week is 1. */
497 return (__CFDaysBeforeMonth(month
, year
, isleap(year
)) + day
+ (dow0101
- 11) % 7 + 2) / 7 + 1;