]> git.saurik.com Git - apple/cf.git/blob - CFDate.c
CF-550.tar.gz
[apple/cf.git] / CFDate.c
1 /*
2 * Copyright (c) 2009 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23 /* CFDate.c
24 Copyright (c) 1998-2009, Apple Inc. All rights reserved.
25 Responsibility: Christopher Kane
26 */
27
28 #include <CoreFoundation/CFDate.h>
29 #include <CoreFoundation/CFTimeZone.h>
30 #include <CoreFoundation/CFDictionary.h>
31 #include <CoreFoundation/CFArray.h>
32 #include <CoreFoundation/CFString.h>
33 #include <CoreFoundation/CFNumber.h>
34 #include "CFInternal.h"
35 #include <math.h>
36 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
37 #include <sys/time.h>
38 #elif DEPLOYMENT_TARGET_WINDOWS
39 #else
40 #error Unknown or unspecified DEPLOYMENT_TARGET
41 #endif
42
43
44 /* cjk: The Julian Date for the reference date is 2451910.5,
45 I think, in case that's ever useful. */
46
47
48 const CFTimeInterval kCFAbsoluteTimeIntervalSince1970 = 978307200.0L;
49 const CFTimeInterval kCFAbsoluteTimeIntervalSince1904 = 3061152000.0L;
50
51 __private_extern__ double __CFTSRRate = 0.0;
52 static double __CF1_TSRRate = 0.0;
53
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);
57 }
58
59 __private_extern__ CFTimeInterval __CFTSRToTimeInterval(int64_t tsr) {
60 return (CFTimeInterval)((double)tsr * __CF1_TSRRate);
61 }
62
63 CFAbsoluteTime CFAbsoluteTimeGetCurrent(void) {
64 CFAbsoluteTime ret;
65 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_IPHONE
66 struct timeval tv;
67 gettimeofday(&tv, NULL);
68 ret = (CFTimeInterval)tv.tv_sec - kCFAbsoluteTimeIntervalSince1970;
69 ret += (1.0E-6 * (CFTimeInterval)tv.tv_usec);
70 #elif DEPLOYMENT_TARGET_WINDOWS_SYNC || DEPLOYMENT_TARGET_CODE011
71 FILETIME ft;
72 GetSystemTimeAsFileTime(&ft);
73 ret = _CFAbsoluteTimeFromFileTime(&ft);
74 #else
75 #error Unknown or unspecified DEPLOYMENT_TARGET
76 #endif
77 return ret;
78 }
79
80 __private_extern__ void __CFDateInitialize(void) {
81 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_IPHONE
82 struct mach_timebase_info info;
83 mach_timebase_info(&info);
84 __CFTSRRate = (1.0E9 / (double)info.numer) * (double)info.denom;
85 __CF1_TSRRate = 1.0 / __CFTSRRate;
86 #elif DEPLOYMENT_TARGET_WINDOWS_SYNC || DEPLOYMENT_TARGET_CODE011
87 LARGE_INTEGER freq;
88 if (!QueryPerformanceFrequency(&freq)) {
89 HALT;
90 }
91 __CFTSRRate = (double)freq.QuadPart;
92 __CF1_TSRRate = 1.0 / __CFTSRRate;
93 #else
94 #error Unknown or unspecified DEPLOYMENT_TARGET
95 #endif
96 CFDateGetTypeID(); // cause side-effects
97 }
98
99 #if 1
100 struct __CFDate {
101 CFRuntimeBase _base;
102 CFAbsoluteTime _time; /* immutable */
103 };
104
105 static Boolean __CFDateEqual(CFTypeRef cf1, CFTypeRef cf2) {
106 CFDateRef date1 = (CFDateRef)cf1;
107 CFDateRef date2 = (CFDateRef)cf2;
108 if (date1->_time != date2->_time) return false;
109 return true;
110 }
111
112 static CFHashCode __CFDateHash(CFTypeRef cf) {
113 CFDateRef date = (CFDateRef)cf;
114 return (CFHashCode)(float)floor(date->_time);
115 }
116
117 static CFStringRef __CFDateCopyDescription(CFTypeRef cf) {
118 CFDateRef date = (CFDateRef)cf;
119 return CFStringCreateWithFormat(CFGetAllocator(date), NULL, CFSTR("<CFDate %p [%p]>{time = %0.09g}"), cf, CFGetAllocator(date), date->_time);
120 }
121
122 static CFTypeID __kCFDateTypeID = _kCFRuntimeNotATypeID;
123
124 static const CFRuntimeClass __CFDateClass = {
125 0,
126 "CFDate",
127 NULL, // init
128 NULL, // copy
129 NULL, // dealloc
130 __CFDateEqual,
131 __CFDateHash,
132 NULL, //
133 __CFDateCopyDescription
134 };
135
136 CFTypeID CFDateGetTypeID(void) {
137 if (_kCFRuntimeNotATypeID == __kCFDateTypeID) __kCFDateTypeID = _CFRuntimeRegisterClass(&__CFDateClass);
138 return __kCFDateTypeID;
139 }
140
141 CFDateRef CFDateCreate(CFAllocatorRef allocator, CFAbsoluteTime at) {
142 CFDateRef memory;
143 uint32_t size;
144 size = sizeof(struct __CFDate) - sizeof(CFRuntimeBase);
145 memory = (CFDateRef)_CFRuntimeCreateInstance(allocator, CFDateGetTypeID(), size, NULL);
146 if (NULL == memory) {
147 return NULL;
148 }
149 ((struct __CFDate *)memory)->_time = at;
150 return memory;
151 }
152
153 CFTimeInterval CFDateGetAbsoluteTime(CFDateRef date) {
154 CF_OBJC_FUNCDISPATCH0(CFDateGetTypeID(), CFTimeInterval, date, "timeIntervalSinceReferenceDate");
155 __CFGenericValidateType(date, CFDateGetTypeID());
156 return date->_time;
157 }
158
159 CFTimeInterval CFDateGetTimeIntervalSinceDate(CFDateRef date, CFDateRef otherDate) {
160 CF_OBJC_FUNCDISPATCH1(CFDateGetTypeID(), CFTimeInterval, date, "timeIntervalSinceDate:", otherDate);
161 __CFGenericValidateType(date, CFDateGetTypeID());
162 __CFGenericValidateType(otherDate, CFDateGetTypeID());
163 return date->_time - otherDate->_time;
164 }
165
166 CFComparisonResult CFDateCompare(CFDateRef date, CFDateRef otherDate, void *context) {
167 CF_OBJC_FUNCDISPATCH1(CFDateGetTypeID(), CFComparisonResult, date, "compare:", otherDate);
168 __CFGenericValidateType(date, CFDateGetTypeID());
169 __CFGenericValidateType(otherDate, CFDateGetTypeID());
170 if (date->_time < otherDate->_time) return kCFCompareLessThan;
171 if (date->_time > otherDate->_time) return kCFCompareGreaterThan;
172 return kCFCompareEqualTo;
173 }
174 #endif
175
176
177 CF_INLINE int32_t __CFDoubleModToInt(double d, int32_t modulus) {
178 int32_t result = (int32_t)(float)floor(d - floor(d / modulus) * modulus);
179 if (result < 0) result += modulus;
180 return result;
181 }
182
183 CF_INLINE double __CFDoubleMod(double d, int32_t modulus) {
184 double result = d - floor(d / modulus) * modulus;
185 if (result < 0.0) result += (double)modulus;
186 return result;
187 }
188
189 static const uint8_t daysInMonth[16] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0, 0, 0};
190 static const uint16_t daysBeforeMonth[16] = {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365, 0, 0};
191 static const uint16_t daysAfterMonth[16] = {365, 334, 306, 275, 245, 214, 184, 153, 122, 92, 61, 31, 0, 0, 0, 0};
192
193 CF_INLINE bool isleap(int64_t year) {
194 int64_t y = (year + 1) % 400; /* correct to nearest multiple-of-400 year, then find the remainder */
195 if (y < 0) y = -y;
196 return (0 == (y & 3) && 100 != y && 200 != y && 300 != y);
197 }
198
199 /* year arg is absolute year; Gregorian 2001 == year 0; 2001/1/1 = absolute date 0 */
200 CF_INLINE uint8_t __CFDaysInMonth(int8_t month, int64_t year, bool leap) {
201 return daysInMonth[month] + (2 == month && leap);
202 }
203
204 /* year arg is absolute year; Gregorian 2001 == year 0; 2001/1/1 = absolute date 0 */
205 CF_INLINE uint16_t __CFDaysBeforeMonth(int8_t month, int64_t year, bool leap) {
206 return daysBeforeMonth[month] + (2 < month && leap);
207 }
208
209 /* year arg is absolute year; Gregorian 2001 == year 0; 2001/1/1 = absolute date 0 */
210 CF_INLINE uint16_t __CFDaysAfterMonth(int8_t month, int64_t year, bool leap) {
211 return daysAfterMonth[month] + (month < 2 && leap);
212 }
213
214 /* year arg is absolute year; Gregorian 2001 == year 0; 2001/1/1 = absolute date 0 */
215 static void __CFYMDFromAbsolute(int64_t absolute, int64_t *year, int8_t *month, int8_t *day) {
216 int64_t b = absolute / 146097; // take care of as many multiples of 400 years as possible
217 int64_t y = b * 400;
218 uint16_t ydays;
219 absolute -= b * 146097;
220 while (absolute < 0) {
221 y -= 1;
222 absolute += __CFDaysAfterMonth(0, y, isleap(y));
223 }
224 /* Now absolute is non-negative days to add to year */
225 ydays = __CFDaysAfterMonth(0, y, isleap(y));
226 while (ydays <= absolute) {
227 y += 1;
228 absolute -= ydays;
229 ydays = __CFDaysAfterMonth(0, y, isleap(y));
230 }
231 /* Now we have year and days-into-year */
232 if (year) *year = y;
233 if (month || day) {
234 int8_t m = absolute / 33 + 1; /* search from the approximation */
235 bool leap = isleap(y);
236 while (__CFDaysBeforeMonth(m + 1, y, leap) <= absolute) m++;
237 if (month) *month = m;
238 if (day) *day = absolute - __CFDaysBeforeMonth(m, y, leap) + 1;
239 }
240 }
241
242 /* year arg is absolute year; Gregorian 2001 == year 0; 2001/1/1 = absolute date 0 */
243 static double __CFAbsoluteFromYMD(int64_t year, int8_t month, int8_t day) {
244 double absolute = 0.0;
245 int64_t idx;
246 int64_t b = year / 400; // take care of as many multiples of 400 years as possible
247 absolute += b * 146097.0;
248 year -= b * 400;
249 if (year < 0) {
250 for (idx = year; idx < 0; idx++)
251 absolute -= __CFDaysAfterMonth(0, idx, isleap(idx));
252 } else {
253 for (idx = 0; idx < year; idx++)
254 absolute += __CFDaysAfterMonth(0, idx, isleap(idx));
255 }
256 /* Now add the days into the original year */
257 absolute += __CFDaysBeforeMonth(month, year, isleap(year)) + day - 1;
258 return absolute;
259 }
260
261 Boolean CFGregorianDateIsValid(CFGregorianDate gdate, CFOptionFlags unitFlags) {
262 if ((unitFlags & kCFGregorianUnitsYears) && (gdate.year <= 0)) return false;
263 if ((unitFlags & kCFGregorianUnitsMonths) && (gdate.month < 1 || 12 < gdate.month)) return false;
264 if ((unitFlags & kCFGregorianUnitsDays) && (gdate.day < 1 || 31 < gdate.day)) return false;
265 if ((unitFlags & kCFGregorianUnitsHours) && (gdate.hour < 0 || 23 < gdate.hour)) return false;
266 if ((unitFlags & kCFGregorianUnitsMinutes) && (gdate.minute < 0 || 59 < gdate.minute)) return false;
267 if ((unitFlags & kCFGregorianUnitsSeconds) && (gdate.second < 0.0 || 60.0 <= gdate.second)) return false;
268 if ((unitFlags & kCFGregorianUnitsDays) && (unitFlags & kCFGregorianUnitsMonths) && (unitFlags & kCFGregorianUnitsYears) && (__CFDaysInMonth(gdate.month, gdate.year - 2001, isleap(gdate.year - 2001)) < gdate.day)) return false;
269 return true;
270 }
271
272 CFAbsoluteTime CFGregorianDateGetAbsoluteTime(CFGregorianDate gdate, CFTimeZoneRef tz) {
273 CFAbsoluteTime at;
274 CFTimeInterval offset0, offset1;
275 if (NULL != tz) {
276 __CFGenericValidateType(tz, CFTimeZoneGetTypeID());
277 }
278 at = 86400.0 * __CFAbsoluteFromYMD(gdate.year - 2001, gdate.month, gdate.day);
279 at += 3600.0 * gdate.hour + 60.0 * gdate.minute + gdate.second;
280 if (NULL != tz) {
281 offset0 = CFTimeZoneGetSecondsFromGMT(tz, at);
282 offset1 = CFTimeZoneGetSecondsFromGMT(tz, at - offset0);
283 at -= offset1;
284 }
285 return at;
286 }
287
288 CFGregorianDate CFAbsoluteTimeGetGregorianDate(CFAbsoluteTime at, CFTimeZoneRef tz) {
289 CFGregorianDate gdate;
290 int64_t absolute, year;
291 int8_t month, day;
292 CFAbsoluteTime fixedat;
293 if (NULL != tz) {
294 __CFGenericValidateType(tz, CFTimeZoneGetTypeID());
295 }
296 fixedat = at + (NULL != tz ? CFTimeZoneGetSecondsFromGMT(tz, at) : 0.0);
297 absolute = (int64_t)floor(fixedat / 86400.0);
298 __CFYMDFromAbsolute(absolute, &year, &month, &day);
299 if (INT32_MAX - 2001 < year) year = INT32_MAX - 2001;
300 gdate.year = year + 2001;
301 gdate.month = month;
302 gdate.day = day;
303 gdate.hour = __CFDoubleModToInt(floor(fixedat / 3600.0), 24);
304 gdate.minute = __CFDoubleModToInt(floor(fixedat / 60.0), 60);
305 gdate.second = __CFDoubleMod(fixedat, 60);
306 if (0.0 == gdate.second) gdate.second = 0.0; // stomp out possible -0.0
307 return gdate;
308 }
309
310 /* Note that the units of years and months are not equal length, but are treated as such. */
311 CFAbsoluteTime CFAbsoluteTimeAddGregorianUnits(CFAbsoluteTime at, CFTimeZoneRef tz, CFGregorianUnits units) {
312 CFGregorianDate gdate;
313 CFGregorianUnits working;
314 CFAbsoluteTime candidate_at0, candidate_at1;
315 uint8_t monthdays;
316
317 if (NULL != tz) {
318 __CFGenericValidateType(tz, CFTimeZoneGetTypeID());
319 }
320
321 /* Most people seem to expect years, then months, then days, etc.
322 to be added in that order. Thus, 27 April + (4 days, 1 month)
323 = 31 May, and not 1 June. This is also relatively predictable.
324
325 On another issue, months not being equal length, people also
326 seem to expect late day-of-month clamping (don't clamp as you
327 go through months), but clamp before adding in the days. Late
328 clamping is also more predictable given random starting points
329 and random numbers of months added (ie Jan 31 + 2 months could
330 be March 28 or March 29 in different years with aggressive
331 clamping). Proportionality (28 Feb + 1 month = 31 March) is
332 also not expected.
333
334 Also, people don't expect time zone transitions to have any
335 effect when adding years and/or months and/or days, only.
336 Hours, minutes, and seconds, though, are added in as humans
337 would experience the passing of that time. What this means
338 is that if the date, after adding years, months, and days
339 lands on some date, and then adding hours, minutes, and
340 seconds crosses a time zone transition, the time zone
341 transition is accounted for. If adding years, months, and
342 days gets the date into a different time zone offset period,
343 that transition is not taken into account.
344 */
345 gdate = CFAbsoluteTimeGetGregorianDate(at, tz);
346 /* We must work in a CFGregorianUnits, because the fields in the CFGregorianDate can easily overflow */
347 working.years = gdate.year;
348 working.months = gdate.month;
349 working.days = gdate.day;
350 working.years += units.years;
351 working.months += units.months;
352 while (12 < working.months) {
353 working.months -= 12;
354 working.years += 1;
355 }
356 while (working.months < 1) {
357 working.months += 12;
358 working.years -= 1;
359 }
360 monthdays = __CFDaysInMonth(working.months, working.years - 2001, isleap(working.years - 2001));
361 if (monthdays < working.days) { /* Clamp day to new month */
362 working.days = monthdays;
363 }
364 working.days += units.days;
365 while (monthdays < working.days) {
366 working.months += 1;
367 if (12 < working.months) {
368 working.months -= 12;
369 working.years += 1;
370 }
371 working.days -= monthdays;
372 monthdays = __CFDaysInMonth(working.months, working.years - 2001, isleap(working.years - 2001));
373 }
374 while (working.days < 1) {
375 working.months -= 1;
376 if (working.months < 1) {
377 working.months += 12;
378 working.years -= 1;
379 }
380 monthdays = __CFDaysInMonth(working.months, working.years - 2001, isleap(working.years - 2001));
381 working.days += monthdays;
382 }
383 gdate.year = working.years;
384 gdate.month = working.months;
385 gdate.day = working.days;
386 /* Roll in hours, minutes, and seconds */
387 candidate_at0 = CFGregorianDateGetAbsoluteTime(gdate, tz);
388 candidate_at1 = candidate_at0 + 3600.0 * units.hours + 60.0 * units.minutes + units.seconds;
389 /* If summing in the hours, minutes, and seconds delta pushes us
390 * into a new time zone offset, that will automatically be taken
391 * care of by the fact that we just add the raw time above. To
392 * undo that effect, we'd have to get the time zone offsets for
393 * candidate_at0 and candidate_at1 here, and subtract the
394 * difference (offset1 - offset0) from candidate_at1. */
395 return candidate_at1;
396 }
397
398 /* at1 - at2. The only constraint here is that this needs to be the inverse
399 of CFAbsoluteTimeByAddingGregorianUnits(), but that's a very rigid constraint.
400 Unfortunately, due to the nonuniformity of the year and month units, this
401 inversion essentially has to approximate until it finds the answer. */
402 CFGregorianUnits CFAbsoluteTimeGetDifferenceAsGregorianUnits(CFAbsoluteTime at1, CFAbsoluteTime at2, CFTimeZoneRef tz, CFOptionFlags unitFlags) {
403 const int32_t seconds[5] = {366 * 24 * 3600, 31 * 24 * 3600, 24 * 3600, 3600, 60};
404 CFGregorianUnits units = {0, 0, 0, 0, 0, 0.0};
405 CFAbsoluteTime atold, atnew = at2;
406 int32_t idx, incr;
407 incr = (at2 < at1) ? 1 : -1;
408 /* Successive approximation: years, then months, then days, then hours, then minutes. */
409 for (idx = 0; idx < 5; idx++) {
410 if (unitFlags & (1 << idx)) {
411 ((int32_t *)&units)[idx] = -3 * incr + (int32_t)((at1 - atnew) / seconds[idx]);
412 do {
413 atold = atnew;
414 ((int32_t *)&units)[idx] += incr;
415 atnew = CFAbsoluteTimeAddGregorianUnits(at2, tz, units);
416 } while ((1 == incr && atnew <= at1) || (-1 == incr && at1 <= atnew));
417 ((int32_t *)&units)[idx] -= incr;
418 atnew = atold;
419 }
420 }
421 if (unitFlags & kCFGregorianUnitsSeconds) {
422 units.seconds = at1 - atnew;
423 }
424 if (0.0 == units.seconds) units.seconds = 0.0; // stomp out possible -0.0
425 return units;
426 }
427
428 SInt32 CFAbsoluteTimeGetDayOfWeek(CFAbsoluteTime at, CFTimeZoneRef tz) {
429 int64_t absolute;
430 CFAbsoluteTime fixedat;
431 if (NULL != tz) {
432 __CFGenericValidateType(tz, CFTimeZoneGetTypeID());
433 }
434 fixedat = at + (NULL != tz ? CFTimeZoneGetSecondsFromGMT(tz, at) : 0.0);
435 absolute = (int64_t)floor(fixedat / 86400.0);
436 return (absolute < 0) ? ((absolute + 1) % 7 + 7) : (absolute % 7 + 1); /* Monday = 1, etc. */
437 }
438
439 SInt32 CFAbsoluteTimeGetDayOfYear(CFAbsoluteTime at, CFTimeZoneRef tz) {
440 CFAbsoluteTime fixedat;
441 int64_t absolute, year;
442 int8_t month, day;
443 if (NULL != tz) {
444 __CFGenericValidateType(tz, CFTimeZoneGetTypeID());
445 }
446 fixedat = at + (NULL != tz ? CFTimeZoneGetSecondsFromGMT(tz, at) : 0.0);
447 absolute = (int64_t)floor(fixedat / 86400.0);
448 __CFYMDFromAbsolute(absolute, &year, &month, &day);
449 return __CFDaysBeforeMonth(month, year, isleap(year)) + day;
450 }
451
452 /* "the first week of a year is the one which includes the first Thursday" (ISO 8601) */
453 SInt32 CFAbsoluteTimeGetWeekOfYear(CFAbsoluteTime at, CFTimeZoneRef tz) {
454 int64_t absolute, year;
455 int8_t month, day;
456 CFAbsoluteTime fixedat;
457 if (NULL != tz) {
458 __CFGenericValidateType(tz, CFTimeZoneGetTypeID());
459 }
460 fixedat = at + (NULL != tz ? CFTimeZoneGetSecondsFromGMT(tz, at) : 0.0);
461 absolute = (int64_t)floor(fixedat / 86400.0);
462 __CFYMDFromAbsolute(absolute, &year, &month, &day);
463 double absolute0101 = __CFAbsoluteFromYMD(year, 1, 1);
464 int64_t dow0101 = __CFDoubleModToInt(absolute0101, 7) + 1;
465 /* First three and last three days of a year can end up in a week of a different year */
466 if (1 == month && day < 4) {
467 if ((day < 4 && 5 == dow0101) || (day < 3 && 6 == dow0101) || (day < 2 && 7 == dow0101)) {
468 return 53;
469 }
470 }
471 if (12 == month && 28 < day) {
472 double absolute20101 = __CFAbsoluteFromYMD(year + 1, 1, 1);
473 int64_t dow20101 = __CFDoubleModToInt(absolute20101, 7) + 1;
474 if ((28 < day && 4 == dow20101) || (29 < day && 3 == dow20101) || (30 < day && 2 == dow20101)) {
475 return 1;
476 }
477 }
478 /* Days into year, plus a week-shifting correction, divided by 7. First week is 1. */
479 return (__CFDaysBeforeMonth(month, year, isleap(year)) + day + (dow0101 - 11) % 7 + 2) / 7 + 1;
480 }
481
482