1/*
2 * Copyright (c) 2014 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-2013, 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
38struct __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
47static 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
53static CFHashCode __CFCalendarHash(CFTypeRef cf) {
54    CFCalendarRef calendar = (CFCalendarRef)cf;
55    return CFHash(calendar->_identifier);
56}
57
58static 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
63static 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
72static CFTypeID __kCFCalendarTypeID = _kCFRuntimeNotATypeID;
73
74static 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
86CF_PRIVATE void __CFCalendarInitialize(void) {
87    __kCFCalendarTypeID = _CFRuntimeRegisterClass(&__CFCalendarClass);
88}
89
90CFTypeID CFCalendarGetTypeID(void) {
91    if (_kCFRuntimeNotATypeID == __kCFCalendarTypeID) __CFCalendarInitialize();
92    return __kCFCalendarTypeID;
93}
94
95CF_PRIVATE 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
127static void __CFCalendarSetupCal(CFCalendarRef calendar) {
128    calendar->_cal = __CFCalendarCreateUCalendar(calendar->_identifier, calendar->_localeID, calendar->_tz);
129}
130
131static void __CFCalendarZapCal(CFCalendarRef calendar) {
132    ucal_close(calendar->_cal);
133    calendar->_cal = NULL;
134}
135
136CFCalendarRef 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
148CFCalendarRef 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
178CFStringRef CFCalendarGetIdentifier(CFCalendarRef calendar) {
179    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFStringRef, calendar, calendarIdentifier);
180    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
181    return calendar->_identifier;
182}
183
184CFLocaleRef CFCalendarCopyLocale(CFCalendarRef calendar) {
185    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFLocaleRef, calendar, _copyLocale);
186    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
187    return (CFLocaleRef)CFLocaleCreate(kCFAllocatorSystemDefault, calendar->_localeID);
188}
189
190void 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
203CFTimeZoneRef CFCalendarCopyTimeZone(CFCalendarRef calendar) {
204    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFTimeZoneRef, calendar_copyTimeZone);
205    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
206    return (CFTimeZoneRef)CFRetain(calendar->_tz);
207}
208
209void 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
220CFIndex 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
230void 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
239CFIndex 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
246void 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
253CFDateRef 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
266void 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
290static 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
309static 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
332static 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
350CFRange 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
367CFRange 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
384static 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
455static 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
505static 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
605static CFRange __CFCalendarGetRangeOfUnit2(CFCalendarRef calendar, CFCalendarUnit smallerUnit, CFCalendarUnit biggerUnit, CFAbsoluteTime at) __attribute__((noinline));
606static 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
768CFRange CFCalendarGetRangeOfUnit(CFCalendarRef calendar, CFCalendarUnit smallerUnit, CFCalendarUnit biggerUnit, CFAbsoluteTime at) {
769	return __CFCalendarGetRangeOfUnit2(calendar, smallerUnit, biggerUnit, at);
770}
771
772CFIndex 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
829Boolean _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
871Boolean _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
893Boolean _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
920Boolean _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
970Boolean 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
985Boolean 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
1000Boolean 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
1015Boolean 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
1031Boolean 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
1065