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