CFDateIntervalFormatter.c
1 /* CFDateIntervalFormatter.c 2 Copyright (c) 1998-2018, Apple Inc. and the Swift project authors 3 4 Portions Copyright (c) 2019, Apple Inc. and the Swift project authors 5 Licensed under Apache License v2.0 with Runtime Library Exception 6 See http://swift.org/LICENSE.txt for license information 7 See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 8 */ 9 10 #include <CoreFoundation/CFDateIntervalFormatter.h> 11 #include <CoreFoundation/CFRuntime.h> 12 #include "CFInternal.h" 13 #include "CFRuntime_Internal.h" 14 15 #include <CoreFoundation/CFCalendar.h> 16 #include <CoreFoundation/CFDate.h> 17 #include <CoreFoundation/CFDateFormatter.h> 18 #include <CoreFoundation/CFDateInterval.h> 19 #include <CoreFoundation/CFLocale.h> 20 #include <CoreFoundation/CFTimeZone.h> 21 22 #include <unicode/udateintervalformat.h> 23 24 #if TARGET_OS_WASI 25 #define LOCK() do {} while (0) 26 #define UNLOCK() do {} while (0) 27 #else 28 #include <dispatch/dispatch.h> 29 30 #define LOCK() do { dispatch_semaphore_wait(formatter->_lock, DISPATCH_TIME_FOREVER); } while(0) 31 #define UNLOCK() do { dispatch_semaphore_signal(formatter->_lock); } while(0) 32 #endif 33 34 CF_INLINE void __CFReleaseIfNotNull(CFTypeRef object) { 35 if (object) { 36 CFRelease(object); 37 } 38 } 39 40 CF_INLINE CFTypeRef __CFRetainIfNotNull(CFTypeRef object) { 41 if (object) { 42 CFRetain(object); 43 } 44 45 return object; 46 } 47 48 struct __CFDateIntervalFormatter { 49 CFRuntimeBase _base; 50 CFLocaleRef _locale; 51 CFCalendarRef _calendar; 52 CFTimeZoneRef _timeZone; 53 CFStringRef _dateTemplateFromStyles; 54 CFStringRef _dateTemplate; 55 void *_formatter; 56 CFDateIntervalFormatterStyle _dateStyle; 57 CFDateIntervalFormatterStyle _timeStyle; 58 _CFDateIntervalFormatterBoundaryStyle _boundaryStyle; 59 #if !TARGET_OS_WASI 60 dispatch_semaphore_t _lock; 61 #endif 62 bool _modified:1; 63 bool _useTemplate:1; 64 }; 65 66 static void __CFDateIntervalFormatterDeallocate(CFTypeRef object); 67 const CFRuntimeClass __CFDateIntervalFormatterClass = { 68 0, 69 "CFDateIntervalFormatter", 70 NULL, // init 71 NULL, // copy 72 &__CFDateIntervalFormatterDeallocate, // dealloc 73 NULL, // equals 74 NULL, // hash 75 NULL, // copyFormattingDescription 76 NULL, // copyDescription 77 }; 78 79 static CFStringRef createLocaleIDFromLocaleAndCalendar(CFLocaleRef locale, CFCalendarRef calendar) { 80 CFMutableDictionaryRef dict; 81 82 { 83 CFDictionaryRef immutableDict = CFLocaleCreateComponentsFromLocaleIdentifier(kCFAllocatorSystemDefault, CFLocaleGetIdentifier(locale)); 84 dict = CFDictionaryCreateMutableCopy(kCFAllocatorSystemDefault, 0, immutableDict); 85 CFRelease(immutableDict); 86 } 87 88 if (calendar) { 89 CFDictionarySetValue(dict, kCFLocaleCalendar, calendar); 90 } 91 CFStringRef localeID = CFLocaleCreateLocaleIdentifierFromComponents(kCFAllocatorSystemDefault, dict); 92 CFRelease(dict); 93 94 return localeID; 95 } 96 97 static void updateDateTemplate(CFDateIntervalFormatterRef dif, CFDateIntervalFormatterStyle dateStyle, CFDateIntervalFormatterStyle timeStyle); 98 static void updateDateTemplateFromCurrentSettings(CFDateIntervalFormatterRef dif) { 99 updateDateTemplate(dif, dif->_dateStyle, dif->_timeStyle); 100 } 101 102 static void updateDateTemplate(CFDateIntervalFormatterRef dif, CFDateIntervalFormatterStyle dateStyle, CFDateIntervalFormatterStyle timeStyle) { 103 CFDateFormatterRef formatter; 104 { 105 CFLocaleRef locale = dif->_locale ? CFRetain(dif->_locale) : CFLocaleCopyCurrent(); 106 CFCalendarRef unretainedCalendar = dif->_calendar ?: (CFCalendarRef)CFLocaleGetValue(locale, kCFLocaleCalendar); 107 formatter = CFDateFormatterCreate(kCFAllocatorSystemDefault, locale, (CFDateFormatterStyle)dateStyle, (CFDateFormatterStyle)timeStyle); 108 CFDateFormatterSetProperty(formatter, kCFDateFormatterCalendar, unretainedCalendar); 109 CFRelease(locale); 110 } 111 112 CFStringRef template = CFDateFormatterGetFormat(formatter); 113 __CFReleaseIfNotNull(dif->_dateTemplateFromStyles); 114 dif->_dateTemplateFromStyles = CFStringCreateCopy(kCFAllocatorSystemDefault, template); 115 CFRelease(formatter); 116 } 117 118 static void updateFormatter(CFDateIntervalFormatterRef dif) { 119 if (dif->_modified && dif->_formatter != NULL) { 120 udtitvfmt_close(dif->_formatter); 121 dif->_formatter = NULL; 122 dif->_modified = false; 123 } 124 125 if (dif->_formatter == NULL) { 126 CFLocaleRef locale = dif->_locale; 127 if (locale) { 128 CFRetain(locale); 129 } else { 130 locale = CFLocaleCopyCurrent(); 131 } 132 133 CFCalendarRef unretainedCalendar = dif->_calendar; 134 if (unretainedCalendar == NULL) { 135 unretainedCalendar = (CFCalendarRef)CFLocaleGetValue(locale, kCFDateFormatterCalendar); 136 } 137 138 CFStringRef localeID = createLocaleIDFromLocaleAndCalendar(locale, unretainedCalendar); 139 140 char localeBuffer[100] = {0}; 141 CFStringGetCString(localeID, localeBuffer, 100, kCFStringEncodingUTF8); 142 143 UniChar timeZoneID[100] = {0}; 144 CFTimeZoneRef timeZone = dif->_timeZone; 145 if (timeZone) { 146 CFRetain(timeZone); 147 } else { 148 timeZone = CFTimeZoneCopyDefault(); 149 } 150 CFStringRef unretainedTimeZoneName = CFTimeZoneGetName(timeZone); 151 CFStringGetCharacters(unretainedTimeZoneName, CFRangeMake(0, __CFMin(CFStringGetLength(unretainedTimeZoneName), 100)), timeZoneID); 152 153 CFStringRef unretainedTemplate = dif->_dateTemplateFromStyles; 154 if (dif->_useTemplate) { 155 unretainedTemplate = dif->_dateTemplate; 156 } 157 UniChar templateStr[100] = {0}; 158 CFStringGetCharacters(unretainedTemplate, CFRangeMake(0, __CFMin(CFStringGetLength(unretainedTemplate), 100)), templateStr); 159 160 UErrorCode status = U_ZERO_ERROR; 161 dif->_formatter = udtitvfmt_open(localeBuffer, templateStr, CFStringGetLength(unretainedTemplate), timeZoneID, CFStringGetLength(unretainedTimeZoneName), &status); 162 if (U_FAILURE(status)) { 163 CFLog(kCFLogLevelError, CFSTR("udtitvfmt_open failed! Tried to set template: %@ for locale %s and timezone %@ and got status code: %s"), unretainedTemplate, localeBuffer, unretainedTimeZoneName, u_errorName(status)); 164 } 165 if (!(dif->_formatter)) { 166 CFLog(kCFLogLevelError, CFSTR("udtitvfmt_open failed! Formatter is NULL! -- locale: %s, template: %@, timezone: %@, status: %s"), localeBuffer, unretainedTemplate, unretainedTimeZoneName, u_errorName(status)); 167 } 168 169 #if TARGET_OS_MAC 170 UDateIntervalFormatAttributeValue uDateIntervalMinimizationStyle = UDTITVFMT_MINIMIZE_NONE; 171 const _CFDateIntervalFormatterBoundaryStyle type = dif->_boundaryStyle; 172 switch (type) { 173 case kCFDateIntervalFormatterBoundaryStyleMinimizeAdjacentMonths: 174 uDateIntervalMinimizationStyle = UDTITVFMT_MINIMIZE_ADJACENT_MONTHS; 175 176 default: 177 break; 178 } 179 180 if (dif->_formatter) { 181 udtitvfmt_setAttribute(dif->_formatter, UDTITVFMT_MINIMIZE_TYPE, uDateIntervalMinimizationStyle, &status); 182 if (U_FAILURE(status)) { 183 CFLog(kCFLogLevelError, CFSTR("udtitvfmt_setAttribute failed! Tried to set minimize type: %d and got status code: %s"), (int)dif->_boundaryStyle, u_errorName(status)); 184 } 185 } 186 #endif 187 188 CFRelease(locale); 189 CFRelease(localeID); 190 CFRelease(timeZone); 191 } 192 } 193 194 CFDateIntervalFormatterRef CFDateIntervalFormatterCreate(CFAllocatorRef allocator, CFLocaleRef locale, CFDateIntervalFormatterStyle dateStyle, CFDateIntervalFormatterStyle timeStyle) { 195 struct __CFDateIntervalFormatter *memory; 196 uint32_t size = sizeof(struct __CFDateIntervalFormatter) - sizeof(CFRuntimeBase); 197 if (!allocator) { 198 allocator = __CFGetDefaultAllocator(); 199 } 200 __CFGenericValidateType(allocator, CFAllocatorGetTypeID()); 201 memory = (struct __CFDateIntervalFormatter *)_CFRuntimeCreateInstance(allocator, _kCFRuntimeIDCFDateIntervalFormatter, size, NULL); 202 if (!memory) { 203 return (CFDateIntervalFormatterRef _Nonnull)NULL; 204 } 205 206 switch (dateStyle) { 207 case kCFDateIntervalFormatterNoStyle: 208 case kCFDateIntervalFormatterShortStyle: 209 case kCFDateIntervalFormatterMediumStyle: 210 case kCFDateIntervalFormatterLongStyle: 211 case kCFDateIntervalFormatterFullStyle: break; 212 default: 213 CFAssert2(0, __kCFLogAssertion, "%s(): unknown date style %ld", __PRETTY_FUNCTION__, dateStyle); 214 memory->_dateStyle = kCFDateIntervalFormatterMediumStyle; 215 break; 216 } 217 switch (timeStyle) { 218 case kCFDateIntervalFormatterNoStyle: 219 case kCFDateIntervalFormatterShortStyle: 220 case kCFDateIntervalFormatterMediumStyle: 221 case kCFDateIntervalFormatterLongStyle: 222 case kCFDateIntervalFormatterFullStyle: break; 223 default: 224 CFAssert2(0, __kCFLogAssertion, "%s(): unknown time style %ld", __PRETTY_FUNCTION__, dateStyle); 225 memory->_timeStyle = kCFDateIntervalFormatterMediumStyle; 226 break; 227 } 228 229 memory->_dateStyle = dateStyle; 230 memory->_timeStyle = timeStyle; 231 232 memory->_locale = locale ? CFRetain(locale) : NULL; 233 234 memory->_calendar = NULL; 235 memory->_timeZone = NULL; 236 memory->_dateTemplateFromStyles = NULL; 237 memory->_dateTemplate = CFRetain(CFSTR("")); 238 memory->_formatter = NULL; 239 memory->_boundaryStyle = kCFDateIntervalFormatterBoundaryStyleDefault; 240 #if !TARGET_OS_WASI 241 memory->_lock = dispatch_semaphore_create(1); 242 #endif 243 memory->_modified = false; 244 memory->_useTemplate = false; 245 246 return (CFDateIntervalFormatterRef)memory; 247 } 248 249 void _CFDateIntervalFormatterInitializeFromCoderValues(CFDateIntervalFormatterRef formatter, 250 int64_t dateStyle, 251 int64_t timeStyle, 252 CFStringRef _Nullable dateTemplate, 253 CFStringRef _Nullable dateTemplateFromStyles, 254 Boolean modified, 255 Boolean useTemplate, 256 CFLocaleRef _Nullable locale, 257 CFCalendarRef _Nullable calendar, 258 CFTimeZoneRef _Nullable timeZone) { 259 LOCK(); 260 formatter->_dateStyle = dateStyle; 261 formatter->_timeStyle = timeStyle; 262 263 #define __CFSetObjectField(field, value) \ 264 { \ 265 __auto_type _value = value; \ 266 if (field != _value) { \ 267 __CFReleaseIfNotNull(field); \ 268 field = (__typeof(_value))__CFRetainIfNotNull(_value); \ 269 } \ 270 } 271 272 __CFSetObjectField(formatter->_dateTemplate, dateTemplate); 273 __CFSetObjectField(formatter->_dateTemplateFromStyles, dateTemplateFromStyles); 274 275 formatter->_modified = modified; 276 formatter->_useTemplate = useTemplate; 277 278 __CFSetObjectField(formatter->_locale, locale); 279 __CFSetObjectField(formatter->_calendar, calendar); 280 __CFSetObjectField(formatter->_timeZone, timeZone); 281 282 UNLOCK(); 283 } 284 285 void _CFDateIntervalFormatterCopyCoderValues(CFDateIntervalFormatterRef formatter, 286 int64_t *dateStyle, 287 int64_t *timeStyle, 288 CFStringRef _Nullable *dateTemplate, 289 CFStringRef _Nullable *dateTemplateFromStyles, 290 Boolean *modified, 291 Boolean *useTemplate, 292 CFLocaleRef _Nullable *locale, 293 CFCalendarRef _Nullable *calendar, 294 CFTimeZoneRef _Nullable *timeZone) { 295 LOCK(); 296 297 *dateStyle = formatter->_dateStyle; 298 *timeStyle = formatter->_timeStyle; 299 *dateTemplate = __CFRetainIfNotNull(formatter->_dateTemplate); 300 *dateTemplateFromStyles = __CFRetainIfNotNull(formatter->_dateTemplateFromStyles); 301 *modified = formatter->_modified; 302 *useTemplate = formatter->_useTemplate; 303 *locale = __CFRetainIfNotNull(formatter->_locale); 304 *calendar = (CFCalendarRef)__CFRetainIfNotNull(formatter->_calendar); 305 *timeZone = __CFRetainIfNotNull(formatter->_timeZone); 306 307 UNLOCK(); 308 } 309 310 CFDateIntervalFormatterRef CFDateIntervalFormatterCreateCopy(CFAllocatorRef _Nullable allocator, CFDateIntervalFormatterRef formatter) { 311 LOCK(); 312 CFDateIntervalFormatterRef newFormatter = CFDateIntervalFormatterCreate(allocator, formatter->_locale, formatter->_dateStyle, formatter->_timeStyle); 313 314 if (formatter->_calendar) { 315 newFormatter->_calendar = _CFCalendarCreateCopy(allocator, formatter->_calendar); 316 } 317 if (formatter->_timeZone) { 318 newFormatter->_timeZone = CFRetain(formatter->_timeZone); 319 } 320 if (formatter->_dateTemplateFromStyles) { 321 newFormatter->_dateTemplateFromStyles = CFStringCreateCopy(allocator, formatter->_dateTemplateFromStyles); 322 } 323 if (formatter->_dateTemplate) { 324 newFormatter->_dateTemplate = CFStringCreateCopy(allocator, formatter->_dateTemplate); 325 } 326 327 newFormatter->_dateStyle = formatter->_dateStyle; 328 newFormatter->_timeStyle = formatter->_timeStyle; 329 newFormatter->_modified = formatter->_modified; 330 newFormatter->_useTemplate = formatter->_useTemplate; 331 UNLOCK(); 332 333 return newFormatter; 334 } 335 336 static void __CFDateIntervalFormatterDeallocate(CFTypeRef object) { 337 CFDateIntervalFormatterRef formatter = (CFDateIntervalFormatterRef)object; 338 339 __CFReleaseIfNotNull(formatter->_locale); 340 __CFReleaseIfNotNull(formatter->_calendar); 341 __CFReleaseIfNotNull(formatter->_timeZone); 342 __CFReleaseIfNotNull(formatter->_dateTemplateFromStyles); 343 __CFReleaseIfNotNull(formatter->_dateTemplate); 344 345 if (formatter->_formatter) { 346 udtitvfmt_close(formatter->_formatter); 347 } 348 349 #if !TARGET_OS_WASI 350 dispatch_release(formatter->_lock); 351 #endif 352 } 353 354 CFLocaleRef CFDateIntervalFormatterCopyLocale(CFDateIntervalFormatterRef formatter) { 355 LOCK(); 356 CFLocaleRef locale = formatter->_locale; 357 if (locale) { 358 CFRetain(locale); 359 } 360 UNLOCK(); 361 362 if (!locale) { 363 locale = CFLocaleCopyCurrent(); 364 } 365 return locale; 366 } 367 368 void CFDateIntervalFormatterSetLocale(CFDateIntervalFormatterRef formatter, CFLocaleRef locale) { 369 LOCK(); 370 if (locale != formatter->_locale) { 371 __CFReleaseIfNotNull(formatter->_locale); 372 formatter->_locale = locale ? CFLocaleCreateCopy(kCFAllocatorSystemDefault, locale) : NULL; 373 formatter->_modified = true; 374 updateDateTemplateFromCurrentSettings(formatter); 375 } 376 UNLOCK(); 377 } 378 379 CFCalendarRef CFDateIntervalFormatterCopyCalendar(CFDateIntervalFormatterRef formatter) { 380 LOCK(); 381 CFCalendarRef calendar = (CFCalendarRef)__CFRetainIfNotNull(formatter->_calendar); 382 if (!calendar) { 383 if (formatter->_locale) { 384 calendar = (CFCalendarRef)CFLocaleGetValue(formatter->_locale, kCFLocaleCalendar); 385 CFRetain(calendar); 386 } else { 387 calendar = CFCalendarCopyCurrent(); 388 } 389 } 390 UNLOCK(); 391 return calendar; 392 } 393 394 void CFDateIntervalFormatterSetCalendar(CFDateIntervalFormatterRef formatter, CFCalendarRef calendar) { 395 LOCK(); 396 if (calendar != formatter->_calendar) { 397 __CFReleaseIfNotNull(formatter->_calendar); 398 formatter->_calendar = calendar ? _CFCalendarCreateCopy(kCFAllocatorSystemDefault, calendar) : NULL; 399 formatter->_modified = true; 400 updateDateTemplateFromCurrentSettings(formatter); 401 } 402 UNLOCK(); 403 } 404 405 CFTimeZoneRef CFDateIntervalFormatterCopyTimeZone(CFDateIntervalFormatterRef formatter) { 406 LOCK(); 407 CFTimeZoneRef timeZone = formatter->_timeZone; 408 if (timeZone) { 409 CFRetain(timeZone); 410 } 411 UNLOCK(); 412 413 if (!timeZone) { 414 timeZone = CFTimeZoneCopyDefault(); 415 } 416 return timeZone; 417 } 418 419 void CFDateIntervalFormatterSetTimeZone(CFDateIntervalFormatterRef formatter, CFTimeZoneRef timeZone) { 420 LOCK(); 421 if (timeZone != formatter->_timeZone) { 422 __CFReleaseIfNotNull(formatter->_timeZone); 423 formatter->_timeZone = timeZone ? CFRetain(timeZone) : NULL; 424 formatter->_modified = true; 425 updateDateTemplateFromCurrentSettings(formatter); 426 } 427 UNLOCK(); 428 } 429 430 CFStringRef CFDateIntervalFormatterCopyDateTemplate(CFDateIntervalFormatterRef formatter) { 431 LOCK(); 432 CFStringRef dateTemplate = formatter->_dateTemplate; 433 if (dateTemplate) { 434 CFRetain(dateTemplate); 435 } else { 436 dateTemplate = formatter->_dateTemplateFromStyles; 437 if (dateTemplate) { 438 CFRetain(dateTemplate); 439 } 440 } 441 UNLOCK(); 442 443 return dateTemplate; 444 } 445 446 void CFDateIntervalFormatterSetDateTemplate(CFDateIntervalFormatterRef formatter, CFStringRef dateTemplate) { 447 if (!dateTemplate) { 448 dateTemplate = CFSTR(""); 449 } 450 451 LOCK(); 452 if (!CFEqual(dateTemplate, formatter->_dateTemplate)) { 453 __CFReleaseIfNotNull(formatter->_dateTemplate); 454 formatter->_dateTemplate = CFStringCreateCopy(kCFAllocatorSystemDefault, dateTemplate); 455 formatter->_modified = true; 456 formatter->_useTemplate = true; 457 } 458 UNLOCK(); 459 } 460 461 CFDateIntervalFormatterStyle CFDateIntervalFormatterGetDateStyle(CFDateIntervalFormatterRef formatter) { 462 LOCK(); 463 CFDateIntervalFormatterStyle result = formatter->_dateStyle; 464 UNLOCK(); 465 return result; 466 } 467 468 void CFDateIntervalFormatterSetDateStyle(CFDateIntervalFormatterRef formatter, CFDateIntervalFormatterStyle dateStyle) { 469 LOCK(); 470 formatter->_dateStyle = dateStyle; 471 formatter->_modified = true; 472 formatter->_useTemplate = false; 473 updateDateTemplateFromCurrentSettings(formatter); 474 UNLOCK(); 475 } 476 477 CFDateIntervalFormatterStyle CFDateIntervalFormatterGetTimeStyle(CFDateIntervalFormatterRef formatter) { 478 LOCK(); 479 CFDateIntervalFormatterStyle result = formatter->_timeStyle; 480 UNLOCK(); 481 return result; 482 } 483 484 void CFDateIntervalFormatterSetTimeStyle(CFDateIntervalFormatterRef formatter, CFDateIntervalFormatterStyle timeStyle) { 485 LOCK(); 486 formatter->_timeStyle = timeStyle; 487 formatter->_modified = true; 488 formatter->_useTemplate = false; 489 updateDateTemplateFromCurrentSettings(formatter); 490 UNLOCK(); 491 } 492 493 _CFDateIntervalFormatterBoundaryStyle _CFDateIntervalFormatterGetBoundaryStyle(CFDateIntervalFormatterRef formatter) { 494 LOCK(); 495 _CFDateIntervalFormatterBoundaryStyle result = formatter->_boundaryStyle; 496 UNLOCK(); 497 return result; 498 } 499 500 void _CFDateIntervalFormatterSetBoundaryStyle(CFDateIntervalFormatterRef formatter, _CFDateIntervalFormatterBoundaryStyle boundaryStyle) { 501 LOCK(); 502 formatter->_boundaryStyle = boundaryStyle; 503 formatter->_modified = true; 504 UNLOCK(); 505 } 506 507 CFStringRef CFDateIntervalFormatterCreateStringFromDateToDate(CFDateIntervalFormatterRef formatter, CFDateRef fromDate, CFDateRef toDate) { 508 LOCK(); 509 510 CFStringRef resultStr = CFSTR(""); 511 updateFormatter(formatter); 512 513 if (formatter->_formatter) { 514 UDate fromUDate = (CFDateGetAbsoluteTime(fromDate) + kCFAbsoluteTimeIntervalSince1970) * 1000.0; 515 UDate toUDate = (CFDateGetAbsoluteTime(toDate) + kCFAbsoluteTimeIntervalSince1970) * 1000.0; 516 517 #define BUFFER_LENGTH 1000 518 519 UChar result[BUFFER_LENGTH]; 520 memset(result, '\0', BUFFER_LENGTH * sizeof(UChar)); 521 UErrorCode status = U_ZERO_ERROR; 522 int32_t len = udtitvfmt_format(formatter->_formatter, fromUDate, toUDate, result, BUFFER_LENGTH, 0, &status); 523 524 if (len > BUFFER_LENGTH) { 525 UChar *resultp = (UChar *)calloc(len, sizeof(UChar)); 526 status = U_ZERO_ERROR; 527 len = udtitvfmt_format(formatter->_formatter, fromUDate, toUDate, resultp, len, 0, &status); 528 resultStr = CFStringCreateWithCharacters(kCFAllocatorSystemDefault, resultp, len); 529 free(resultp); 530 } else { 531 resultStr = CFStringCreateWithCharacters(kCFAllocatorSystemDefault, result, len); 532 } 533 } else { 534 resultStr = CFSTR(""); 535 } 536 UNLOCK(); 537 538 return resultStr; 539 } 540 541 CFStringRef CFDateIntervalFormatterCreateStringFromDateInterval(CFDateIntervalFormatterRef formatter, CFDateIntervalRef interval) { 542 CFDateRef start = CFDateIntervalCopyStartDate(interval); 543 CFDateRef end = CFDateIntervalCopyEndDate(interval); 544 CFStringRef result = CFDateIntervalFormatterCreateStringFromDateToDate(formatter, start, end); 545 CFRelease(start); 546 CFRelease(end); 547 return result; 548 }