]> git.saurik.com Git - apple/cf.git/blob - CFCalendar.c
CF-744.12.tar.gz
[apple/cf.git] / CFCalendar.c
1 /*
2 * Copyright (c) 2012 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
24 /* CFCalendar.c
25 Copyright (c) 2004-2012, Apple Inc. All rights reserved.
26 Responsibility: Christopher Kane
27 */
28
29
30 #include <CoreFoundation/CFCalendar.h>
31 #include <CoreFoundation/CFRuntime.h>
32 #include "CFInternal.h"
33 #include "CFPriv.h"
34 #include <unicode/ucal.h>
35
36 #define BUFFER_SIZE 512
37
38 struct __CFCalendar {
39 CFRuntimeBase _base;
40 CFStringRef _identifier; // canonical identifier, never NULL
41 CFLocaleRef _locale;
42 CFStringRef _localeID;
43 CFTimeZoneRef _tz;
44 UCalendar *_cal;
45 };
46
47 static Boolean __CFCalendarEqual(CFTypeRef cf1, CFTypeRef cf2) {
48 CFCalendarRef calendar1 = (CFCalendarRef)cf1;
49 CFCalendarRef calendar2 = (CFCalendarRef)cf2;
50 return CFEqual(calendar1->_identifier, calendar2->_identifier);
51 }
52
53 static CFHashCode __CFCalendarHash(CFTypeRef cf) {
54 CFCalendarRef calendar = (CFCalendarRef)cf;
55 return CFHash(calendar->_identifier);
56 }
57
58 static CFStringRef __CFCalendarCopyDescription(CFTypeRef cf) {
59 CFCalendarRef calendar = (CFCalendarRef)cf;
60 return CFStringCreateWithFormat(CFGetAllocator(calendar), NULL, CFSTR("<CFCalendar %p [%p]>{identifier = '%@'}"), cf, CFGetAllocator(calendar), calendar->_identifier);
61 }
62
63 static void __CFCalendarDeallocate(CFTypeRef cf) {
64 CFCalendarRef calendar = (CFCalendarRef)cf;
65 CFRelease(calendar->_identifier);
66 if (calendar->_locale) CFRelease(calendar->_locale);
67 if (calendar->_localeID) CFRelease(calendar->_localeID);
68 CFRelease(calendar->_tz);
69 if (calendar->_cal) ucal_close(calendar->_cal);
70 }
71
72 static CFTypeID __kCFCalendarTypeID = _kCFRuntimeNotATypeID;
73
74 static const CFRuntimeClass __CFCalendarClass = {
75 0,
76 "CFCalendar",
77 NULL, // init
78 NULL, // copy
79 __CFCalendarDeallocate,
80 __CFCalendarEqual,
81 __CFCalendarHash,
82 NULL, //
83 __CFCalendarCopyDescription
84 };
85
86 __private_extern__ void __CFCalendarInitialize(void) {
87 __kCFCalendarTypeID = _CFRuntimeRegisterClass(&__CFCalendarClass);
88 }
89
90 CFTypeID CFCalendarGetTypeID(void) {
91 if (_kCFRuntimeNotATypeID == __kCFCalendarTypeID) __CFCalendarInitialize();
92 return __kCFCalendarTypeID;
93 }
94
95 __private_extern__ UCalendar *__CFCalendarCreateUCalendar(CFStringRef calendarID, CFStringRef localeID, CFTimeZoneRef tz) {
96 if (calendarID) {
97 CFDictionaryRef components = CFLocaleCreateComponentsFromLocaleIdentifier(kCFAllocatorSystemDefault, localeID);
98 CFMutableDictionaryRef mcomponents = CFDictionaryCreateMutableCopy(kCFAllocatorSystemDefault, 0, components);
99 CFDictionarySetValue(mcomponents, kCFLocaleCalendarIdentifier, calendarID);
100 localeID = CFLocaleCreateLocaleIdentifierFromComponents(kCFAllocatorSystemDefault, mcomponents);
101 CFRelease(mcomponents);
102 CFRelease(components);
103 }
104
105 char buffer[BUFFER_SIZE];
106 const char *cstr = CFStringGetCStringPtr(localeID, kCFStringEncodingASCII);
107 if (NULL == cstr) {
108 if (CFStringGetCString(localeID, buffer, BUFFER_SIZE, kCFStringEncodingASCII)) cstr = buffer;
109 }
110 if (NULL == cstr) {
111 if (calendarID) CFRelease(localeID);
112 return NULL;
113 }
114
115 UChar ubuffer[BUFFER_SIZE];
116 CFStringRef tznam = CFTimeZoneGetName(tz);
117 CFIndex cnt = CFStringGetLength(tznam);
118 if (BUFFER_SIZE < cnt) cnt = BUFFER_SIZE;
119 CFStringGetCharacters(tznam, CFRangeMake(0, cnt), (UniChar *)ubuffer);
120
121 UErrorCode status = U_ZERO_ERROR;
122 UCalendar *cal = ucal_open(ubuffer, cnt, cstr, UCAL_DEFAULT, &status);
123 if (calendarID) CFRelease(localeID);
124 return cal;
125 }
126
127 static void __CFCalendarSetupCal(CFCalendarRef calendar) {
128 calendar->_cal = __CFCalendarCreateUCalendar(calendar->_identifier, calendar->_localeID, calendar->_tz);
129 }
130
131 static void __CFCalendarZapCal(CFCalendarRef calendar) {
132 ucal_close(calendar->_cal);
133 calendar->_cal = NULL;
134 }
135
136 CFCalendarRef CFCalendarCopyCurrent(void) {
137 CFLocaleRef locale = CFLocaleCopyCurrent();
138 CFCalendarRef calID = (CFCalendarRef)CFLocaleGetValue(locale, kCFLocaleCalendarIdentifier);
139 if (calID) {
140 CFCalendarRef calendar = CFCalendarCreateWithIdentifier(kCFAllocatorSystemDefault, (CFStringRef)calID);
141 CFCalendarSetLocale(calendar, locale);
142 CFRelease(locale);
143 return calendar;
144 }
145 return NULL;
146 }
147
148 CFCalendarRef CFCalendarCreateWithIdentifier(CFAllocatorRef allocator, CFStringRef identifier) {
149 if (allocator == NULL) allocator = __CFGetDefaultAllocator();
150 __CFGenericValidateType(allocator, CFAllocatorGetTypeID());
151 __CFGenericValidateType(identifier, CFStringGetTypeID());
152 // return NULL until Chinese calendar is available
153 if (identifier != kCFGregorianCalendar && identifier != kCFBuddhistCalendar && identifier != kCFJapaneseCalendar && identifier != kCFIslamicCalendar && identifier != kCFIslamicCivilCalendar && identifier != kCFHebrewCalendar) {
154 // if (identifier != kCFGregorianCalendar && identifier != kCFBuddhistCalendar && identifier != kCFJapaneseCalendar && identifier != kCFIslamicCalendar && identifier != kCFIslamicCivilCalendar && identifier != kCFHebrewCalendar && identifier != kCFChineseCalendar) {
155 if (CFEqual(kCFGregorianCalendar, identifier)) identifier = kCFGregorianCalendar;
156 else if (CFEqual(kCFBuddhistCalendar, identifier)) identifier = kCFBuddhistCalendar;
157 else if (CFEqual(kCFJapaneseCalendar, identifier)) identifier = kCFJapaneseCalendar;
158 else if (CFEqual(kCFIslamicCalendar, identifier)) identifier = kCFIslamicCalendar;
159 else if (CFEqual(kCFIslamicCivilCalendar, identifier)) identifier = kCFIslamicCivilCalendar;
160 else if (CFEqual(kCFHebrewCalendar, identifier)) identifier = kCFHebrewCalendar;
161 // else if (CFEqual(kCFChineseCalendar, identifier)) identifier = kCFChineseCalendar;
162 else return NULL;
163 }
164 struct __CFCalendar *calendar = NULL;
165 uint32_t size = sizeof(struct __CFCalendar) - sizeof(CFRuntimeBase);
166 calendar = (struct __CFCalendar *)_CFRuntimeCreateInstance(allocator, CFCalendarGetTypeID(), size, NULL);
167 if (NULL == calendar) {
168 return NULL;
169 }
170 calendar->_identifier = (CFStringRef)CFRetain(identifier);
171 calendar->_locale = NULL;
172 calendar->_localeID = CFLocaleGetIdentifier(CFLocaleGetSystem());
173 calendar->_tz = CFTimeZoneCopyDefault();
174 calendar->_cal = NULL;
175 return (CFCalendarRef)calendar;
176 }
177
178 CFStringRef CFCalendarGetIdentifier(CFCalendarRef calendar) {
179 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFStringRef, calendar, calendarIdentifier);
180 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
181 return calendar->_identifier;
182 }
183
184 CFLocaleRef CFCalendarCopyLocale(CFCalendarRef calendar) {
185 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFLocaleRef, calendar, _copyLocale);
186 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
187 return (CFLocaleRef)CFLocaleCreate(kCFAllocatorSystemDefault, calendar->_localeID);
188 }
189
190 void CFCalendarSetLocale(CFCalendarRef calendar, CFLocaleRef locale) {
191 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), void, calendar, setLocale:locale);
192 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
193 __CFGenericValidateType(locale, CFLocaleGetTypeID());
194 CFStringRef localeID = CFLocaleGetIdentifier(locale);
195 if (localeID != calendar->_localeID) {
196 CFRelease(calendar->_localeID);
197 CFRetain(localeID);
198 calendar->_localeID = localeID;
199 if (calendar->_cal) __CFCalendarZapCal(calendar);
200 }
201 }
202
203 CFTimeZoneRef CFCalendarCopyTimeZone(CFCalendarRef calendar) {
204 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFTimeZoneRef, calendar_copyTimeZone);
205 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
206 return (CFTimeZoneRef)CFRetain(calendar->_tz);
207 }
208
209 void CFCalendarSetTimeZone(CFCalendarRef calendar, CFTimeZoneRef tz) {
210 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), void, calendar, setTimeZone:tz);
211 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
212 if (tz) __CFGenericValidateType(tz, CFTimeZoneGetTypeID());
213 if (tz != calendar->_tz) {
214 CFRelease(calendar->_tz);
215 calendar->_tz = tz ? (CFTimeZoneRef)CFRetain(tz) : CFTimeZoneCopyDefault();
216 if (calendar->_cal) __CFCalendarZapCal(calendar);
217 }
218 }
219
220 CFIndex CFCalendarGetFirstWeekday(CFCalendarRef calendar) {
221 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFIndex, calendar, firstWeekday);
222 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
223 if (!calendar->_cal) __CFCalendarSetupCal(calendar);
224 if (calendar->_cal) {
225 return ucal_getAttribute(calendar->_cal, UCAL_FIRST_DAY_OF_WEEK);
226 }
227 return -1;
228 }
229
230 void CFCalendarSetFirstWeekday(CFCalendarRef calendar, CFIndex wkdy) {
231 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), void, calendar, setFirstWeekday:wkdy);
232 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
233 if (!calendar->_cal) __CFCalendarSetupCal(calendar);
234 if (calendar->_cal) {
235 ucal_setAttribute(calendar->_cal, UCAL_FIRST_DAY_OF_WEEK, wkdy);
236 }
237 }
238
239 CFIndex CFCalendarGetMinimumDaysInFirstWeek(CFCalendarRef calendar) {
240 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFIndex, calendar, minimumDaysInFirstWeek);
241 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
242 if (!calendar->_cal) __CFCalendarSetupCal(calendar);
243 return calendar->_cal ? ucal_getAttribute(calendar->_cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK) : -1;
244 }
245
246 void CFCalendarSetMinimumDaysInFirstWeek(CFCalendarRef calendar, CFIndex mwd) {
247 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), void, calendar, setMinimumDaysInFirstWeek:mwd);
248 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
249 if (!calendar->_cal) __CFCalendarSetupCal(calendar);
250 if (calendar->_cal) ucal_setAttribute(calendar->_cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK, mwd);
251 }
252
253 CFDateRef CFCalendarCopyGregorianStartDate(CFCalendarRef calendar) {
254 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFDateRef, calendar, _gregorianStartDate);
255 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
256 if (!calendar->_cal) __CFCalendarSetupCal(calendar);
257 UErrorCode status = U_ZERO_ERROR;
258 UDate udate = calendar->_cal ? ucal_getGregorianChange(calendar->_cal, &status) : 0;
259 if (calendar->_cal && U_SUCCESS(status)) {
260 CFAbsoluteTime at = (double)udate / 1000.0 - kCFAbsoluteTimeIntervalSince1970;
261 return CFDateCreate(CFGetAllocator(calendar), at);
262 }
263 return NULL;
264 }
265
266 void CFCalendarSetGregorianStartDate(CFCalendarRef calendar, CFDateRef date) {
267 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), void, calendar, _setGregorianStartDate:date);
268 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
269 if (date) __CFGenericValidateType(date, CFDateGetTypeID());
270 if (!calendar->_cal) __CFCalendarSetupCal(calendar);
271 if (!calendar->_cal) return;
272 if (!date) {
273 UErrorCode status = U_ZERO_ERROR;
274 UCalendar *cal = __CFCalendarCreateUCalendar(calendar->_identifier, calendar->_localeID, calendar->_tz);
275 UDate udate = cal ? ucal_getGregorianChange(cal, &status) : 0;
276 if (cal && U_SUCCESS(status)) {
277 status = U_ZERO_ERROR;
278 if (calendar->_cal) ucal_setGregorianChange(calendar->_cal, udate, &status);
279 }
280 if (cal) ucal_close(cal);
281 } else {
282 CFAbsoluteTime at = CFDateGetAbsoluteTime(date);
283 UDate udate = (at + kCFAbsoluteTimeIntervalSince1970) * 1000.0;
284 UErrorCode status = U_ZERO_ERROR;
285 if (calendar->_cal) ucal_setGregorianChange(calendar->_cal, udate, &status);
286 }
287 }
288
289
290 static UCalendarDateFields __CFCalendarGetICUFieldCode(CFCalendarUnit unit) {
291 switch (unit) {
292 case kCFCalendarUnitEra: return UCAL_ERA;
293 case kCFCalendarUnitYear: return UCAL_YEAR;
294 case kCFCalendarUnitMonth: return UCAL_MONTH;
295 case kCFCalendarUnitDay: return UCAL_DAY_OF_MONTH;
296 case kCFCalendarUnitHour: return UCAL_HOUR_OF_DAY;
297 case kCFCalendarUnitMinute: return UCAL_MINUTE;
298 case kCFCalendarUnitSecond: return UCAL_SECOND;
299 case kCFCalendarUnitWeek: return UCAL_WEEK_OF_YEAR;
300 case kCFCalendarUnitWeekOfYear: return UCAL_WEEK_OF_YEAR;
301 case kCFCalendarUnitWeekOfMonth: return UCAL_WEEK_OF_MONTH;
302 case kCFCalendarUnitYearForWeekOfYear: return UCAL_YEAR_WOY;
303 case kCFCalendarUnitWeekday: return UCAL_DAY_OF_WEEK;
304 case kCFCalendarUnitWeekdayOrdinal: return UCAL_DAY_OF_WEEK_IN_MONTH;
305 }
306 return (UCalendarDateFields)-1;
307 }
308
309 static UCalendarDateFields __CFCalendarGetICUFieldCodeFromChar(char ch) {
310 switch (ch) {
311 case 'G': return UCAL_ERA;
312 case 'y': return UCAL_YEAR;
313 case 'M': return UCAL_MONTH;
314 case 'd': return UCAL_DAY_OF_MONTH;
315 case 'h': return UCAL_HOUR;
316 case 'H': return UCAL_HOUR_OF_DAY;
317 case 'm': return UCAL_MINUTE;
318 case 's': return UCAL_SECOND;
319 case 'S': return UCAL_MILLISECOND;
320 case 'w': return UCAL_WEEK_OF_YEAR;
321 case 'W': return UCAL_WEEK_OF_MONTH;
322 case 'Y': return UCAL_YEAR_WOY;
323 case 'E': return UCAL_DAY_OF_WEEK;
324 case 'D': return UCAL_DAY_OF_YEAR;
325 case 'F': return UCAL_DAY_OF_WEEK_IN_MONTH;
326 case 'a': return UCAL_AM_PM;
327 case 'g': return UCAL_JULIAN_DAY;
328 }
329 return (UCalendarDateFields)-1;
330 }
331
332 static CFCalendarUnit __CFCalendarGetCalendarUnitFromChar(char ch) {
333 switch (ch) {
334 case 'G': return kCFCalendarUnitEra;
335 case 'y': return kCFCalendarUnitYear;
336 case 'M': return kCFCalendarUnitMonth;
337 case 'd': return kCFCalendarUnitDay;
338 case 'H': return kCFCalendarUnitHour;
339 case 'm': return kCFCalendarUnitMinute;
340 case 's': return kCFCalendarUnitSecond;
341 case 'w': return kCFCalendarUnitWeekOfYear;
342 case 'W': return kCFCalendarUnitWeekOfMonth;
343 case 'Y': return kCFCalendarUnitYearForWeekOfYear;
344 case 'E': return kCFCalendarUnitWeekday;
345 case 'F': return kCFCalendarUnitWeekdayOrdinal;
346 }
347 return (UCalendarDateFields)-1;
348 }
349
350 CFRange CFCalendarGetMinimumRangeOfUnit(CFCalendarRef calendar, CFCalendarUnit unit) {
351 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFRange, calendar, _minimumRangeOfUnit:unit);
352 CFRange range = {kCFNotFound, kCFNotFound};
353 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
354 if (!calendar->_cal) __CFCalendarSetupCal(calendar);
355 if (calendar->_cal) {
356 ucal_clear(calendar->_cal);
357 UCalendarDateFields field = __CFCalendarGetICUFieldCode(unit);
358 UErrorCode status = U_ZERO_ERROR;
359 range.location = ucal_getLimit(calendar->_cal, field, UCAL_GREATEST_MINIMUM, &status);
360 range.length = ucal_getLimit(calendar->_cal, field, UCAL_LEAST_MAXIMUM, &status) - range.location + 1;
361 if (UCAL_MONTH == field) range.location++;
362 if (100000 < range.length) range.length = 100000;
363 }
364 return range;
365 }
366
367 CFRange CFCalendarGetMaximumRangeOfUnit(CFCalendarRef calendar, CFCalendarUnit unit) {
368 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFRange, calendar, _maximumRangeOfUnit:unit);
369 CFRange range = {kCFNotFound, kCFNotFound};
370 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
371 if (!calendar->_cal) __CFCalendarSetupCal(calendar);
372 if (calendar->_cal) {
373 ucal_clear(calendar->_cal);
374 UCalendarDateFields field = __CFCalendarGetICUFieldCode(unit);
375 UErrorCode status = U_ZERO_ERROR;
376 range.location = ucal_getLimit(calendar->_cal, field, UCAL_MINIMUM, &status);
377 range.length = ucal_getLimit(calendar->_cal, field, UCAL_MAXIMUM, &status) - range.location + 1;
378 if (UCAL_MONTH == field) range.location++;
379 if (100000 < range.length) range.length = 100000;
380 }
381 return range;
382 }
383
384 static void __CFCalendarSetToFirstInstant(CFCalendarRef calendar, CFCalendarUnit unit, CFAbsoluteTime at) {
385 // Set UCalendar to first instant of unit prior to 'at'
386 UErrorCode status = U_ZERO_ERROR;
387 UDate udate = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
388 ucal_setMillis(calendar->_cal, udate, &status);
389 int target_era = INT_MIN;
390 switch (unit) { // largest to smallest, we set the fields to their minimum value
391 case kCFCalendarUnitYearForWeekOfYear:;
392 ucal_set(calendar->_cal, UCAL_WEEK_OF_YEAR, ucal_getLimit(calendar->_cal, UCAL_WEEK_OF_YEAR, UCAL_ACTUAL_MINIMUM, &status));
393 case kCFCalendarUnitWeek:
394 case kCFCalendarUnitWeekOfMonth:;
395 case kCFCalendarUnitWeekOfYear:;
396 {
397 // reduce to first day of week, then reduce the rest of the day
398 int32_t goal = ucal_getAttribute(calendar->_cal, UCAL_FIRST_DAY_OF_WEEK);
399 int32_t dow = ucal_get(calendar->_cal, UCAL_DAY_OF_WEEK, &status);
400 while (dow != goal) {
401 ucal_add(calendar->_cal, UCAL_DAY_OF_MONTH, -1, &status);
402 dow = ucal_get(calendar->_cal, UCAL_DAY_OF_WEEK, &status);
403 }
404 goto day;
405 }
406 case kCFCalendarUnitEra:
407 {
408 target_era = ucal_get(calendar->_cal, UCAL_ERA, &status);
409 ucal_set(calendar->_cal, UCAL_YEAR, ucal_getLimit(calendar->_cal, UCAL_YEAR, UCAL_ACTUAL_MINIMUM, &status));
410 }
411 case kCFCalendarUnitYear:
412 ucal_set(calendar->_cal, UCAL_MONTH, ucal_getLimit(calendar->_cal, UCAL_MONTH, UCAL_ACTUAL_MINIMUM, &status));
413 case kCFCalendarUnitMonth:
414 ucal_set(calendar->_cal, UCAL_DAY_OF_MONTH, ucal_getLimit(calendar->_cal, UCAL_DAY_OF_MONTH, UCAL_ACTUAL_MINIMUM, &status));
415 case kCFCalendarUnitWeekday:
416 case kCFCalendarUnitDay:
417 day:;
418 ucal_set(calendar->_cal, UCAL_HOUR_OF_DAY, ucal_getLimit(calendar->_cal, UCAL_HOUR_OF_DAY, UCAL_ACTUAL_MINIMUM, &status));
419 case kCFCalendarUnitHour:
420 ucal_set(calendar->_cal, UCAL_MINUTE, ucal_getLimit(calendar->_cal, UCAL_MINUTE, UCAL_ACTUAL_MINIMUM, &status));
421 case kCFCalendarUnitMinute:
422 ucal_set(calendar->_cal, UCAL_SECOND, ucal_getLimit(calendar->_cal, UCAL_SECOND, UCAL_ACTUAL_MINIMUM, &status));
423 case kCFCalendarUnitSecond:
424 ucal_set(calendar->_cal, UCAL_MILLISECOND, 0);
425 }
426 if (INT_MIN != target_era && ucal_get(calendar->_cal, UCAL_ERA, &status) < target_era) {
427 // In the Japanese calendar, and possibly others, eras don't necessarily
428 // start on the first day of a year, so the previous code may have backed
429 // up into the previous era, and we have to correct forward.
430 UDate bad_udate = ucal_getMillis(calendar->_cal, &status);
431 ucal_add(calendar->_cal, UCAL_MONTH, 1, &status);
432 while (ucal_get(calendar->_cal, UCAL_ERA, &status) < target_era) {
433 bad_udate = ucal_getMillis(calendar->_cal, &status);
434 ucal_add(calendar->_cal, UCAL_MONTH, 1, &status);
435 }
436 udate = ucal_getMillis(calendar->_cal, &status);
437 // target date is between bad_udate and udate
438 for (;;) {
439 UDate test_udate = (udate + bad_udate) / 2;
440 ucal_setMillis(calendar->_cal, test_udate, &status);
441 if (ucal_get(calendar->_cal, UCAL_ERA, &status) < target_era) {
442 bad_udate = test_udate;
443 } else {
444 udate = test_udate;
445 }
446 if (fabs(udate - bad_udate) < 1000) break;
447 }
448 do {
449 bad_udate = floor((bad_udate + 1000) / 1000) * 1000;
450 ucal_setMillis(calendar->_cal, bad_udate, &status);
451 } while (ucal_get(calendar->_cal, UCAL_ERA, &status) < target_era);
452 }
453 }
454
455 static Boolean __validUnits(CFCalendarUnit smaller, CFCalendarUnit bigger) {
456 switch (bigger) {
457 case kCFCalendarUnitEra:
458 if (kCFCalendarUnitEra == smaller) return false;
459 if (kCFCalendarUnitWeekday == smaller) return false;
460 if (kCFCalendarUnitMinute == smaller) return false; // this causes CFIndex overflow in range.length
461 if (kCFCalendarUnitSecond == smaller) return false; // this causes CFIndex overflow in range.length
462 return true;
463 case kCFCalendarUnitYearForWeekOfYear:
464 case kCFCalendarUnitYear:
465 if (kCFCalendarUnitEra == smaller) return false;
466 if (kCFCalendarUnitYear == smaller) return false;
467 if (kCFCalendarUnitYearForWeekOfYear == smaller) return false;
468 if (kCFCalendarUnitWeekday == smaller) return false;
469 return true;
470 case kCFCalendarUnitMonth:
471 if (kCFCalendarUnitEra == smaller) return false;
472 if (kCFCalendarUnitYear == smaller) return false;
473 if (kCFCalendarUnitMonth == smaller) return false;
474 if (kCFCalendarUnitWeekday == smaller) return false;
475 return true;
476 case kCFCalendarUnitDay:
477 if (kCFCalendarUnitHour == smaller) return true;
478 if (kCFCalendarUnitMinute == smaller) return true;
479 if (kCFCalendarUnitSecond == smaller) return true;
480 return false;
481 case kCFCalendarUnitHour:
482 if (kCFCalendarUnitMinute == smaller) return true;
483 if (kCFCalendarUnitSecond == smaller) return true;
484 return false;
485 case kCFCalendarUnitMinute:
486 if (kCFCalendarUnitSecond == smaller) return true;
487 return false;
488 case kCFCalendarUnitWeek:
489 case kCFCalendarUnitWeekOfMonth:
490 case kCFCalendarUnitWeekOfYear:
491 if (kCFCalendarUnitWeekday == smaller) return true;
492 if (kCFCalendarUnitDay == smaller) return true;
493 if (kCFCalendarUnitHour == smaller) return true;
494 if (kCFCalendarUnitMinute == smaller) return true;
495 if (kCFCalendarUnitSecond == smaller) return true;
496 return false;
497 case kCFCalendarUnitSecond:
498 case kCFCalendarUnitWeekday:
499 case kCFCalendarUnitWeekdayOrdinal:
500 return false;
501 }
502 return false;
503 };
504
505 static CFRange __CFCalendarGetRangeOfUnit1(CFCalendarRef calendar, CFCalendarUnit smallerUnit, CFCalendarUnit biggerUnit, CFAbsoluteTime at) {
506 CFRange range = {kCFNotFound, kCFNotFound};
507 if (!__validUnits(smallerUnit, biggerUnit)) return range;
508 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFRange, calendar, _rangeOfUnit:smallerUnit inUnit:biggerUnit forAT:at);
509 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
510 if (!calendar->_cal) __CFCalendarSetupCal(calendar);
511 if (calendar->_cal) {
512 int32_t dow = -1;
513 ucal_clear(calendar->_cal);
514 UCalendarDateFields smallField = __CFCalendarGetICUFieldCode(smallerUnit);
515 UCalendarDateFields bigField = __CFCalendarGetICUFieldCode(biggerUnit);
516 if (kCFCalendarUnitWeekdayOrdinal == smallerUnit) {
517 UErrorCode status = U_ZERO_ERROR;
518 UDate udate = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
519 ucal_setMillis(calendar->_cal, udate, &status);
520 dow = ucal_get(calendar->_cal, UCAL_DAY_OF_WEEK, &status);
521 }
522 // Set calendar to first instant of big unit
523 __CFCalendarSetToFirstInstant(calendar, biggerUnit, at);
524 UErrorCode status = U_ZERO_ERROR;
525 UDate start = ucal_getMillis(calendar->_cal, &status);
526 if (kCFCalendarUnitWeek == biggerUnit) {
527 range.location = ucal_get(calendar->_cal, smallField, &status);
528 if (kCFCalendarUnitMonth == smallerUnit) range.location++;
529 } else {
530 range.location = (kCFCalendarUnitHour == smallerUnit || kCFCalendarUnitMinute == smallerUnit || kCFCalendarUnitSecond == smallerUnit) ? 0 : 1;
531 }
532 // Set calendar to first instant of next value of big unit
533 if (UCAL_ERA == bigField) {
534 // ICU refuses to do the addition, probably because we are
535 // at the limit of UCAL_ERA. Use alternate strategy.
536 CFIndex limit = ucal_getLimit(calendar->_cal, UCAL_YEAR, UCAL_MAXIMUM, &status);
537 if (100000 < limit) limit = 100000;
538 ucal_add(calendar->_cal, UCAL_YEAR, limit, &status);
539 } else {
540 ucal_add(calendar->_cal, bigField, 1, &status);
541 }
542 if (kCFCalendarUnitWeek == smallerUnit && kCFCalendarUnitYear == biggerUnit) {
543 ucal_add(calendar->_cal, UCAL_SECOND, -1, &status);
544 range.length = ucal_get(calendar->_cal, UCAL_WEEK_OF_YEAR, &status);
545 while (1 == range.length) {
546 ucal_add(calendar->_cal, UCAL_DAY_OF_MONTH, -1, &status);
547 range.length = ucal_get(calendar->_cal, UCAL_WEEK_OF_YEAR, &status);
548 }
549 range.location = 1;
550 return range;
551 } else if (kCFCalendarUnitWeek == smallerUnit && kCFCalendarUnitMonth == biggerUnit) {
552 ucal_add(calendar->_cal, UCAL_SECOND, -1, &status);
553 range.length = ucal_get(calendar->_cal, UCAL_WEEK_OF_YEAR, &status);
554 range.location = 1;
555 return range;
556 }
557 UDate goal = ucal_getMillis(calendar->_cal, &status);
558 // Set calendar back to first instant of big unit
559 ucal_setMillis(calendar->_cal, start, &status);
560 if (kCFCalendarUnitWeekdayOrdinal == smallerUnit) {
561 // roll day forward to first 'dow'
562 while (ucal_get(calendar->_cal, (kCFCalendarUnitMonth == biggerUnit) ? UCAL_WEEK_OF_MONTH : UCAL_WEEK_OF_YEAR, &status) != 1) {
563 ucal_add(calendar->_cal, UCAL_DAY_OF_MONTH, 1, &status);
564 }
565 while (ucal_get(calendar->_cal, UCAL_DAY_OF_WEEK, &status) != dow) {
566 ucal_add(calendar->_cal, UCAL_DAY_OF_MONTH, 1, &status);
567 }
568 start = ucal_getMillis(calendar->_cal, &status);
569 goal -= 1000;
570 range.location = 1; // constant here works around ICU -- see 3948293
571 }
572 UDate curr = start;
573 range.length = (kCFCalendarUnitWeekdayOrdinal == smallerUnit) ? 1 : 0;
574 const int multiple_table[] = {0, 0, 16, 19, 24, 26, 24, 28, 14, 14, 14};
575 int multiple = (1 << multiple_table[flsl(smallerUnit) - 1]);
576 Boolean divide = false, alwaysDivide = false;
577 while (curr < goal) {
578 ucal_add(calendar->_cal, smallField, multiple, &status);
579 UDate newcurr = ucal_getMillis(calendar->_cal, &status);
580 if (curr < newcurr && newcurr <= goal) {
581 range.length += multiple;
582 curr = newcurr;
583 } else {
584 // Either newcurr is going backwards, or not making
585 // progress, or has overshot the goal; reset date
586 // and try smaller multiples.
587 ucal_setMillis(calendar->_cal, curr, &status);
588 divide = true;
589 // once we start overshooting the goal, the add at
590 // smaller multiples will succeed at most once for
591 // each multiple, so we reduce it every time through
592 // the loop.
593 if (goal < newcurr) alwaysDivide = true;
594 }
595 if (divide) {
596 multiple = multiple / 2;
597 if (0 == multiple) break;
598 divide = alwaysDivide;
599 }
600 }
601 }
602 return range;
603 }
604
605 static CFRange __CFCalendarGetRangeOfUnit2(CFCalendarRef calendar, CFCalendarUnit smallerUnit, CFCalendarUnit biggerUnit, CFAbsoluteTime at) __attribute__((noinline));
606 static CFRange __CFCalendarGetRangeOfUnit2(CFCalendarRef calendar, CFCalendarUnit smallerUnit, CFCalendarUnit biggerUnit, CFAbsoluteTime at) {
607 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFRange, calendar, _rangeOfUnit:smallerUnit inUnit:biggerUnit forAT:at);
608 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
609 CFRange range = {kCFNotFound, kCFNotFound};
610 if (!calendar->_cal) __CFCalendarSetupCal(calendar);
611 if (calendar->_cal) {
612 switch (smallerUnit) {
613 case kCFCalendarUnitSecond:
614 switch (biggerUnit) {
615 case kCFCalendarUnitMinute:
616 case kCFCalendarUnitHour:
617 case kCFCalendarUnitDay:
618 case kCFCalendarUnitWeekday:
619 case kCFCalendarUnitWeek:
620 case kCFCalendarUnitMonth:
621 case kCFCalendarUnitYear:
622 case kCFCalendarUnitEra:
623 // goto calculate;
624 range.location = 0;
625 range.length = 60;
626 break;
627 }
628 break;
629 case kCFCalendarUnitMinute:
630 switch (biggerUnit) {
631 case kCFCalendarUnitHour:
632 case kCFCalendarUnitDay:
633 case kCFCalendarUnitWeekday:
634 case kCFCalendarUnitWeek:
635 case kCFCalendarUnitMonth:
636 case kCFCalendarUnitYear:
637 case kCFCalendarUnitEra:
638 // goto calculate;
639 range.location = 0;
640 range.length = 60;
641 break;
642 }
643 break;
644 case kCFCalendarUnitHour:
645 switch (biggerUnit) {
646 case kCFCalendarUnitDay:
647 case kCFCalendarUnitWeekday:
648 case kCFCalendarUnitWeek:
649 case kCFCalendarUnitMonth:
650 case kCFCalendarUnitYear:
651 case kCFCalendarUnitEra:
652 // goto calculate;
653 range.location = 0;
654 range.length = 24;
655 break;
656 }
657 break;
658 case kCFCalendarUnitDay:
659 switch (biggerUnit) {
660 case kCFCalendarUnitWeek:
661 case kCFCalendarUnitMonth:
662 case kCFCalendarUnitYear:
663 case kCFCalendarUnitEra:
664 goto calculate;
665 break;
666 }
667 break;
668 case kCFCalendarUnitWeekday:
669 switch (biggerUnit) {
670 case kCFCalendarUnitWeek:
671 case kCFCalendarUnitMonth:
672 case kCFCalendarUnitYear:
673 case kCFCalendarUnitEra:
674 goto calculate;
675 break;
676 }
677 break;
678 case kCFCalendarUnitWeekdayOrdinal:
679 switch (biggerUnit) {
680 case kCFCalendarUnitMonth:
681 case kCFCalendarUnitYear:
682 case kCFCalendarUnitEra:
683 goto calculate;
684 break;
685 }
686 break;
687 case kCFCalendarUnitWeek:
688 switch (biggerUnit) {
689 case kCFCalendarUnitMonth:
690 case kCFCalendarUnitYear:
691 case kCFCalendarUnitEra:
692 goto calculate;
693 break;
694 }
695 break;
696 case kCFCalendarUnitMonth:
697 switch (biggerUnit) {
698 case kCFCalendarUnitYear:
699 case kCFCalendarUnitEra:
700 goto calculate;
701 break;
702 }
703 break;
704 case kCFCalendarUnitYear:
705 switch (biggerUnit) {
706 case kCFCalendarUnitEra:
707 goto calculate;
708 break;
709 }
710 break;
711 case kCFCalendarUnitEra:
712 break;
713 }
714 }
715 return range;
716
717 calculate:;
718 ucal_clear(calendar->_cal);
719 UCalendarDateFields smallField = __CFCalendarGetICUFieldCode(smallerUnit);
720 UCalendarDateFields bigField = __CFCalendarGetICUFieldCode(biggerUnit);
721 UCalendarDateFields yearField = __CFCalendarGetICUFieldCode(kCFCalendarUnitYear);
722 UCalendarDateFields fieldToAdd = smallField;
723 if (kCFCalendarUnitWeekday == smallerUnit) {
724 fieldToAdd = __CFCalendarGetICUFieldCode(kCFCalendarUnitDay);
725 }
726 int32_t dow = -1;
727 if (kCFCalendarUnitWeekdayOrdinal == smallerUnit) {
728 UErrorCode status = U_ZERO_ERROR;
729 UDate udate = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
730 ucal_setMillis(calendar->_cal, udate, &status);
731 dow = ucal_get(calendar->_cal, UCAL_DAY_OF_WEEK, &status);
732 fieldToAdd = __CFCalendarGetICUFieldCode(kCFCalendarUnitWeek);
733 }
734 // Set calendar to first instant of big unit
735 __CFCalendarSetToFirstInstant(calendar, biggerUnit, at);
736 if (kCFCalendarUnitWeekdayOrdinal == smallerUnit) {
737 UErrorCode status = U_ZERO_ERROR;
738 // roll day forward to first 'dow'
739 while (ucal_get(calendar->_cal, (kCFCalendarUnitMonth == biggerUnit) ? UCAL_WEEK_OF_MONTH : UCAL_WEEK_OF_YEAR, &status) != 1) {
740 ucal_add(calendar->_cal, UCAL_DAY_OF_MONTH, 1, &status);
741 }
742 while (ucal_get(calendar->_cal, UCAL_DAY_OF_WEEK, &status) != dow) {
743 ucal_add(calendar->_cal, UCAL_DAY_OF_MONTH, 1, &status);
744 }
745 }
746 int32_t minSmallValue = INT32_MAX;
747 int32_t maxSmallValue = INT32_MIN;
748 UErrorCode status = U_ZERO_ERROR;
749 int32_t bigValue = ucal_get(calendar->_cal, bigField, &status);
750 for (;;) {
751 int32_t smallValue = ucal_get(calendar->_cal, smallField, &status);
752 if (smallValue < minSmallValue) minSmallValue = smallValue;
753 if (smallValue > maxSmallValue) maxSmallValue = smallValue;
754 ucal_add(calendar->_cal, fieldToAdd, 1, &status);
755 if (bigValue != ucal_get(calendar->_cal, bigField, &status)) break;
756 if (biggerUnit == kCFCalendarUnitEra && ucal_get(calendar->_cal, yearField, &status) > 10000) break;
757 // we assume an answer for 10000 years can be extrapolated to 100000 years, to save time
758 }
759 status = U_ZERO_ERROR;
760 range.location = minSmallValue;
761 if (smallerUnit == kCFCalendarUnitMonth) range.location = 1;
762 range.length = maxSmallValue - minSmallValue + 1;
763 if (biggerUnit == kCFCalendarUnitEra && ucal_get(calendar->_cal, yearField, &status) > 10000) range.length = 100000;
764
765 return range;
766 }
767
768 CFRange CFCalendarGetRangeOfUnit(CFCalendarRef calendar, CFCalendarUnit smallerUnit, CFCalendarUnit biggerUnit, CFAbsoluteTime at) {
769 return __CFCalendarGetRangeOfUnit2(calendar, smallerUnit, biggerUnit, at);
770 }
771
772 CFIndex CFCalendarGetOrdinalityOfUnit(CFCalendarRef calendar, CFCalendarUnit smallerUnit, CFCalendarUnit biggerUnit, CFAbsoluteTime at) {
773 CFIndex result = kCFNotFound;
774 if (!__validUnits(smallerUnit, biggerUnit)) return result;
775 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFIndex, calendar, _ordinalityOfUnit:smallerUnit inUnit:biggerUnit forAT:at);
776 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
777 if (!calendar->_cal) __CFCalendarSetupCal(calendar);
778 if (calendar->_cal) {
779 UErrorCode status = U_ZERO_ERROR;
780 ucal_clear(calendar->_cal);
781 if (kCFCalendarUnitWeek == smallerUnit && kCFCalendarUnitYear == biggerUnit) {
782 UDate udate = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
783 ucal_setMillis(calendar->_cal, udate, &status);
784 int32_t val = ucal_get(calendar->_cal, UCAL_WEEK_OF_YEAR, &status);
785 return val;
786 } else if (kCFCalendarUnitWeek == smallerUnit && kCFCalendarUnitMonth == biggerUnit) {
787 UDate udate = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
788 ucal_setMillis(calendar->_cal, udate, &status);
789 int32_t val = ucal_get(calendar->_cal, UCAL_WEEK_OF_MONTH, &status);
790 return val;
791 }
792 UCalendarDateFields smallField = __CFCalendarGetICUFieldCode(smallerUnit);
793 // Set calendar to first instant of big unit
794 __CFCalendarSetToFirstInstant(calendar, biggerUnit, at);
795 UDate curr = ucal_getMillis(calendar->_cal, &status);
796 UDate goal = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
797 result = 1;
798 const int multiple_table[] = {0, 0, 16, 19, 24, 26, 24, 28, 14, 14, 14};
799 int multiple = (1 << multiple_table[flsl(smallerUnit) - 1]);
800 Boolean divide = false, alwaysDivide = false;
801 while (curr < goal) {
802 ucal_add(calendar->_cal, smallField, multiple, &status);
803 UDate newcurr = ucal_getMillis(calendar->_cal, &status);
804 if (curr < newcurr && newcurr <= goal) {
805 result += multiple;
806 curr = newcurr;
807 } else {
808 // Either newcurr is going backwards, or not making
809 // progress, or has overshot the goal; reset date
810 // and try smaller multiples.
811 ucal_setMillis(calendar->_cal, curr, &status);
812 divide = true;
813 // once we start overshooting the goal, the add at
814 // smaller multiples will succeed at most once for
815 // each multiple, so we reduce it every time through
816 // the loop.
817 if (goal < newcurr) alwaysDivide = true;
818 }
819 if (divide) {
820 multiple = multiple / 2;
821 if (0 == multiple) break;
822 divide = alwaysDivide;
823 }
824 }
825 }
826 return result;
827 }
828
829 Boolean _CFCalendarComposeAbsoluteTimeV(CFCalendarRef calendar, /* out */ CFAbsoluteTime *atp, const char *componentDesc, int *vector, int count) {
830 if (!calendar->_cal) __CFCalendarSetupCal(calendar);
831 if (calendar->_cal) {
832 UErrorCode status = U_ZERO_ERROR;
833 ucal_clear(calendar->_cal);
834 ucal_set(calendar->_cal, UCAL_YEAR, 1);
835 ucal_set(calendar->_cal, UCAL_MONTH, 0);
836 ucal_set(calendar->_cal, UCAL_DAY_OF_MONTH, 1);
837 ucal_set(calendar->_cal, UCAL_HOUR_OF_DAY, 0);
838 ucal_set(calendar->_cal, UCAL_MINUTE, 0);
839 ucal_set(calendar->_cal, UCAL_SECOND, 0);
840 const char *desc = componentDesc;
841 Boolean doWOY = false;
842 char ch = *desc;
843 while (ch) {
844 UCalendarDateFields field = __CFCalendarGetICUFieldCodeFromChar(ch);
845 if (UCAL_WEEK_OF_YEAR == field) {
846 doWOY = true;
847 }
848 desc++;
849 ch = *desc;
850 }
851 desc = componentDesc;
852 ch = *desc;
853 while (ch) {
854 UCalendarDateFields field = __CFCalendarGetICUFieldCodeFromChar(ch);
855 int value = *vector;
856 if (UCAL_YEAR == field && doWOY) field = UCAL_YEAR_WOY;
857 if (UCAL_MONTH == field) value--;
858 ucal_set(calendar->_cal, field, value);
859 vector++;
860 desc++;
861 ch = *desc;
862 }
863 UDate udate = ucal_getMillis(calendar->_cal, &status);
864 CFAbsoluteTime at = (udate / 1000.0) - kCFAbsoluteTimeIntervalSince1970;
865 if (atp) *atp = at;
866 return U_SUCCESS(status) ? true : false;
867 }
868 return false;
869 }
870
871 Boolean _CFCalendarDecomposeAbsoluteTimeV(CFCalendarRef calendar, CFAbsoluteTime at, const char *componentDesc, int **vector, int count) {
872 if (!calendar->_cal) __CFCalendarSetupCal(calendar);
873 if (calendar->_cal) {
874 UErrorCode status = U_ZERO_ERROR;
875 ucal_clear(calendar->_cal);
876 UDate udate = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
877 ucal_setMillis(calendar->_cal, udate, &status);
878 char ch = *componentDesc;
879 while (ch) {
880 UCalendarDateFields field = __CFCalendarGetICUFieldCodeFromChar(ch);
881 int value = ucal_get(calendar->_cal, field, &status);
882 if (UCAL_MONTH == field) value++;
883 *(*vector) = value;
884 vector++;
885 componentDesc++;
886 ch = *componentDesc;
887 }
888 return U_SUCCESS(status) ? true : false;
889 }
890 return false;
891 }
892
893 Boolean _CFCalendarAddComponentsV(CFCalendarRef calendar, /* inout */ CFAbsoluteTime *atp, CFOptionFlags options, const char *componentDesc, int *vector, int count) {
894 if (!calendar->_cal) __CFCalendarSetupCal(calendar);
895 if (calendar->_cal) {
896 UErrorCode status = U_ZERO_ERROR;
897 ucal_clear(calendar->_cal);
898 UDate udate = floor((*atp + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
899 ucal_setMillis(calendar->_cal, udate, &status);
900 char ch = *componentDesc;
901 while (ch) {
902 UCalendarDateFields field = __CFCalendarGetICUFieldCodeFromChar(ch);
903 int amount = *vector;
904 if (options & kCFCalendarComponentsWrap) {
905 ucal_roll(calendar->_cal, field, amount, &status);
906 } else {
907 ucal_add(calendar->_cal, field, amount, &status);
908 }
909 vector++;
910 componentDesc++;
911 ch = *componentDesc;
912 }
913 udate = ucal_getMillis(calendar->_cal, &status);
914 *atp = (udate / 1000.0) - kCFAbsoluteTimeIntervalSince1970;
915 return U_SUCCESS(status) ? true : false;
916 }
917 return false;
918 }
919
920 Boolean _CFCalendarGetComponentDifferenceV(CFCalendarRef calendar, CFAbsoluteTime startingAT, CFAbsoluteTime resultAT, CFOptionFlags options, const char *componentDesc, int **vector, int count) {
921 if (!calendar->_cal) __CFCalendarSetupCal(calendar);
922 if (calendar->_cal) {
923 UErrorCode status = U_ZERO_ERROR;
924 ucal_clear(calendar->_cal);
925 UDate curr = floor((startingAT + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
926 UDate goal = floor((resultAT + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
927 ucal_setMillis(calendar->_cal, curr, &status);
928 int direction = (startingAT <= resultAT) ? 1 : -1;
929 char ch = *componentDesc;
930 while (ch) {
931 UCalendarDateFields field = __CFCalendarGetICUFieldCodeFromChar(ch);
932 const int multiple_table[] = {0, 0, 16, 19, 24, 26, 24, 28, 14, 14, 14};
933 int multiple = direction * (1 << multiple_table[flsl(__CFCalendarGetCalendarUnitFromChar(ch)) - 1]);
934 Boolean divide = false, alwaysDivide = false;
935 int result = 0;
936 while ((direction > 0 && curr < goal) || (direction < 0 && goal < curr)) {
937 ucal_add(calendar->_cal, field, multiple, &status);
938 UDate newcurr = ucal_getMillis(calendar->_cal, &status);
939 if ((direction > 0 && curr < newcurr && newcurr <= goal) || (direction < 0 && newcurr < curr && goal <= newcurr)) {
940 result += multiple;
941 curr = newcurr;
942 } else {
943 // Either newcurr is going backwards, or not making
944 // progress, or has overshot the goal; reset date
945 // and try smaller multiples.
946 ucal_setMillis(calendar->_cal, curr, &status);
947 divide = true;
948 // once we start overshooting the goal, the add at
949 // smaller multiples will succeed at most once for
950 // each multiple, so we reduce it every time through
951 // the loop.
952 if ((direction > 0 && goal < newcurr) || (direction < 0 && newcurr < goal)) alwaysDivide = true;
953 }
954 if (divide) {
955 multiple = multiple / 2;
956 if (0 == multiple) break;
957 divide = alwaysDivide;
958 }
959 }
960 *(*vector) = result;
961 vector++;
962 componentDesc++;
963 ch = *componentDesc;
964 }
965 return U_SUCCESS(status) ? true : false;
966 }
967 return false;
968 }
969
970 Boolean CFCalendarComposeAbsoluteTime(CFCalendarRef calendar, /* out */ CFAbsoluteTime *atp, const char *componentDesc, ...) {
971 va_list args;
972 va_start(args, componentDesc);
973 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), Boolean, calendar, _composeAbsoluteTime:atp :componentDesc :args);
974 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
975 int idx, cnt = strlen((char *)componentDesc);
976 STACK_BUFFER_DECL(int, vector, cnt);
977 for (idx = 0; idx < cnt; idx++) {
978 int arg = va_arg(args, int);
979 vector[idx] = arg;
980 }
981 va_end(args);
982 return _CFCalendarComposeAbsoluteTimeV(calendar, atp, componentDesc, vector, cnt);
983 }
984
985 Boolean CFCalendarDecomposeAbsoluteTime(CFCalendarRef calendar, CFAbsoluteTime at, const char *componentDesc, ...) {
986 va_list args;
987 va_start(args, componentDesc);
988 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), Boolean, calendar, _decomposeAbsoluteTime:at :componentDesc :args);
989 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
990 int idx, cnt = strlen((char *)componentDesc);
991 STACK_BUFFER_DECL(int *, vector, cnt);
992 for (idx = 0; idx < cnt; idx++) {
993 int *arg = va_arg(args, int *);
994 vector[idx] = arg;
995 }
996 va_end(args);
997 return _CFCalendarDecomposeAbsoluteTimeV(calendar, at, componentDesc, vector, cnt);
998 }
999
1000 Boolean CFCalendarAddComponents(CFCalendarRef calendar, /* inout */ CFAbsoluteTime *atp, CFOptionFlags options, const char *componentDesc, ...) {
1001 va_list args;
1002 va_start(args, componentDesc);
1003 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), Boolean, calendar, _addComponents:atp :options :componentDesc :args);
1004 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
1005 int idx, cnt = strlen((char *)componentDesc);
1006 STACK_BUFFER_DECL(int, vector, cnt);
1007 for (idx = 0; idx < cnt; idx++) {
1008 int arg = va_arg(args, int);
1009 vector[idx] = arg;
1010 }
1011 va_end(args);
1012 return _CFCalendarAddComponentsV(calendar, atp, options, componentDesc, vector, cnt);
1013 }
1014
1015 Boolean CFCalendarGetComponentDifference(CFCalendarRef calendar, CFAbsoluteTime startingAT, CFAbsoluteTime resultAT, CFOptionFlags options, const char *componentDesc, ...) {
1016 va_list args;
1017 va_start(args, componentDesc);
1018 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), Boolean, calendar, _diffComponents:startingAT :resultAT :options :componentDesc :args);
1019 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
1020 int idx, cnt = strlen((char *)componentDesc);
1021 STACK_BUFFER_DECL(int *, vector, cnt);
1022 for (idx = 0; idx < cnt; idx++) {
1023 int *arg = va_arg(args, int *);
1024 vector[idx] = arg;
1025 }
1026 va_end(args);
1027 Boolean ret = _CFCalendarGetComponentDifferenceV(calendar, startingAT, resultAT, options, componentDesc, vector, cnt);
1028 return ret;
1029 }
1030
1031 Boolean CFCalendarGetTimeRangeOfUnit(CFCalendarRef calendar, CFCalendarUnit unit, CFAbsoluteTime at, CFAbsoluteTime *startp, CFTimeInterval *tip) {
1032 CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), Boolean, calendar, _rangeOfUnit:unit startTime:startp interval:tip forAT:at);
1033 __CFGenericValidateType(calendar, CFCalendarGetTypeID());
1034 if (kCFCalendarUnitWeekdayOrdinal == unit) return false;
1035 if (kCFCalendarUnitWeekday == unit) unit = kCFCalendarUnitDay;
1036 if (!calendar->_cal) __CFCalendarSetupCal(calendar);
1037 if (calendar->_cal) {
1038 ucal_clear(calendar->_cal);
1039 __CFCalendarSetToFirstInstant(calendar, unit, at);
1040 UErrorCode status = U_ZERO_ERROR;
1041 UDate start = ucal_getMillis(calendar->_cal, &status);
1042 UCalendarDateFields field = __CFCalendarGetICUFieldCode(unit);
1043 ucal_add(calendar->_cal, field, 1, &status);
1044 UDate end = ucal_getMillis(calendar->_cal, &status);
1045 if (end == start && kCFCalendarUnitEra == unit) {
1046 // ICU refuses to do the addition, probably because we are
1047 // at the limit of UCAL_ERA. Use alternate strategy.
1048 CFIndex limit = ucal_getLimit(calendar->_cal, UCAL_YEAR, UCAL_MAXIMUM, &status);
1049 if (100000 < limit) limit = 100000;
1050 ucal_add(calendar->_cal, UCAL_YEAR, limit, &status);
1051 end = ucal_getMillis(calendar->_cal, &status);
1052 }
1053 if (U_SUCCESS(status)) {
1054 if (startp) *startp = (double)start / 1000.0 - kCFAbsoluteTimeIntervalSince1970;
1055 if (tip) *tip = (double)(end - start) / 1000.0;
1056 return true;
1057 }
1058 }
1059
1060 return false;
1061 }
1062
1063 #undef BUFFER_SIZE
1064