| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See http://swift.org/LICENSE.txt for license information |
| // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| |
| import CoreFoundation |
| |
| #if _runtime(_ObjC) |
| internal let kCFDateIntervalFormatterNoStyle = CFDateIntervalFormatterStyle.noStyle |
| internal let kCFDateIntervalFormatterShortStyle = CFDateIntervalFormatterStyle.shortStyle |
| internal let kCFDateIntervalFormatterMediumStyle = CFDateIntervalFormatterStyle.mediumStyle |
| internal let kCFDateIntervalFormatterLongStyle = CFDateIntervalFormatterStyle.longStyle |
| internal let kCFDateIntervalFormatterFullStyle = CFDateIntervalFormatterStyle.fullStyle |
| |
| internal let kCFDateIntervalFormatterBoundaryStyleDefault = _CFDateIntervalFormatterBoundaryStyle.cfDateIntervalFormatterBoundaryStyleDefault |
| #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) |
| internal let kCFDateIntervalFormatterBoundaryStyleMinimizeAdjacentMonths = _CFDateIntervalFormatterBoundaryStyle.cfDateIntervalFormatterBoundaryStyleMinimizeAdjacentMonths |
| #endif |
| #endif |
| |
| extension DateIntervalFormatter { |
| // Keep these in sync with CFDateIntervalFormatterStyle. |
| public enum Style: UInt { |
| case none = 0 |
| case short = 1 |
| case medium = 2 |
| case long = 3 |
| case full = 4 |
| } |
| } |
| |
| internal extension DateIntervalFormatter.Style { |
| init(_ cfStyle: CFDateIntervalFormatterStyle) { |
| switch cfStyle { |
| case kCFDateIntervalFormatterNoStyle: self = .none |
| case kCFDateIntervalFormatterShortStyle: self = .short |
| case kCFDateIntervalFormatterMediumStyle: self = .medium |
| case kCFDateIntervalFormatterLongStyle: self = .long |
| case kCFDateIntervalFormatterFullStyle: self = .full |
| default: fatalError() |
| } |
| } |
| } |
| |
| internal extension CFDateIntervalFormatterStyle { |
| init(_ style: DateIntervalFormatter.Style) { |
| switch style { |
| case .none: self = kCFDateIntervalFormatterNoStyle |
| case .short: self = kCFDateIntervalFormatterShortStyle |
| case .medium: self = kCFDateIntervalFormatterMediumStyle |
| case .long: self = kCFDateIntervalFormatterLongStyle |
| case .full: self = kCFDateIntervalFormatterFullStyle |
| } |
| } |
| } |
| |
| internal extension DateIntervalFormatter.BoundaryStyle { |
| init(_ cfStyle: _CFDateIntervalFormatterBoundaryStyle) { |
| switch cfStyle { |
| case kCFDateIntervalFormatterBoundaryStyleDefault: self = .default |
| #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) |
| case kCFDateIntervalFormatterBoundaryStyleMinimizeAdjacentMonths: self = .minimizeAdjacentMonths |
| #endif |
| default: fatalError() |
| } |
| } |
| } |
| |
| internal extension _CFDateIntervalFormatterBoundaryStyle { |
| init(_ style: DateIntervalFormatter.BoundaryStyle) { |
| switch style { |
| case .default: self = kCFDateIntervalFormatterBoundaryStyleDefault |
| #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) |
| case .minimizeAdjacentMonths: self = kCFDateIntervalFormatterBoundaryStyleMinimizeAdjacentMonths |
| #endif |
| } |
| } |
| } |
| |
| // DateIntervalFormatter is used to format the range between two NSDates in a locale-sensitive way. |
| // DateIntervalFormatter returns nil and NO for all methods in Formatter. |
| |
| open class DateIntervalFormatter: Formatter { |
| let core: CFDateIntervalFormatter |
| |
| public override init() { |
| core = CFDateIntervalFormatterCreate(nil, nil, kCFDateIntervalFormatterShortStyle, kCFDateIntervalFormatterShortStyle) |
| super.init() |
| } |
| |
| private init(cfFormatter: CFDateIntervalFormatter) { |
| self.core = cfFormatter |
| super.init() |
| } |
| |
| public required init?(coder: NSCoder) { |
| guard coder.allowsKeyedCoding else { fatalError("Requires a keyed coding-capable archiver.") } |
| |
| func cfObject<T: NSObject & _CFBridgeable>(of aClass: T.Type, from coder: NSCoder, forKey key: String) -> T.CFType? { |
| if coder.containsValue(forKey: key) { |
| let object = coder.decodeObject(forKey: key) as? T |
| return object?._cfObject |
| } else { |
| return nil |
| } |
| } |
| |
| let core = CFDateIntervalFormatterCreate(nil, nil, kCFDateIntervalFormatterMediumStyle, kCFDateIntervalFormatterMediumStyle) |
| _CFDateIntervalFormatterInitializeFromCoderValues(core, |
| coder.decodeInt64(forKey: "NS.dateStyle"), |
| coder.decodeInt64(forKey: "NS.timeStyle"), |
| cfObject(of: NSString.self, from: coder, forKey: "NS.dateTemplate"), |
| cfObject(of: NSString.self, from: coder, forKey: "NS.dateTemplateFromStyle"), |
| coder.decodeBool(forKey: "NS.modified"), |
| coder.decodeBool(forKey: "NS.useTemplate"), |
| cfObject(of: NSLocale.self, from: coder, forKey: "NS.locale"), |
| cfObject(of: NSCalendar.self, from: coder, forKey: "NS.calendar"), |
| cfObject(of: NSTimeZone.self, from: coder, forKey: "NS.timeZone")) |
| self.core = core |
| |
| super.init(coder: coder) |
| } |
| |
| open override func encode(with aCoder: NSCoder) { |
| guard aCoder.allowsKeyedCoding else { fatalError("Requires a keyed coding-capable archiver.") } |
| super.encode(with: aCoder) |
| |
| var dateStyle: Int64 = 0 |
| var timeStyle: Int64 = 0 |
| var dateTemplate: Unmanaged<CFString>? |
| var dateTemplateFromStyles: Unmanaged<CFString>? |
| var modified: Bool = false |
| var useTemplate: Bool = false |
| var locale: Unmanaged<CFLocale>? |
| var calendar: Unmanaged<CFCalendar>? |
| var timeZone: Unmanaged<CFTimeZone>? |
| |
| _CFDateIntervalFormatterCopyCoderValues(core, |
| &dateStyle, |
| &timeStyle, |
| &dateTemplate, |
| &dateTemplateFromStyles, |
| &modified, |
| &useTemplate, |
| &locale, |
| &calendar, |
| &timeZone); |
| |
| aCoder.encode(dateStyle, forKey: "NS.dateStyle") |
| aCoder.encode(timeStyle, forKey: "NS.timeStyle") |
| |
| let dateTemplateNS = dateTemplate?.takeRetainedValue()._nsObject |
| aCoder.encode(dateTemplateNS, forKey: "NS.dateTemplate") |
| |
| let dateTemplateFromStylesNS = dateTemplateFromStyles?.takeRetainedValue()._nsObject |
| aCoder.encode(dateTemplateFromStylesNS, forKey: "NS.dateTemplateFromStyles") |
| |
| aCoder.encode(modified, forKey: "NS.modified"); |
| aCoder.encode(useTemplate, forKey: "NS.useTemplate") |
| |
| let localeNS = locale?.takeRetainedValue()._nsObject |
| aCoder.encode(localeNS, forKey: "NS.locale") |
| |
| let calendarNS = calendar?.takeRetainedValue()._nsObject |
| aCoder.encode(calendarNS, forKey: "NS.calendar") |
| |
| let timeZoneNS = timeZone?.takeRetainedValue()._nsObject |
| aCoder.encode(timeZoneNS, forKey: "NS.timeZone") |
| } |
| |
| /*@NSCopying*/ open var locale: Locale! { |
| get { return CFDateIntervalFormatterCopyLocale(core)._swiftObject } |
| set { CFDateIntervalFormatterSetLocale(core, newValue?._cfObject) } |
| } |
| |
| /*@NSCopying*/ open var calendar: Calendar! { |
| get { return CFDateIntervalFormatterCopyCalendar(core)._swiftObject } |
| set { CFDateIntervalFormatterSetCalendar(core, newValue?._cfObject) } |
| } |
| |
| /*@NSCopying*/ open var timeZone: TimeZone! { |
| get { return CFDateIntervalFormatterCopyTimeZone(core)._swiftObject } |
| set { CFDateIntervalFormatterSetTimeZone(core, newValue?._cfObject) } |
| } |
| |
| open var dateTemplate: String! { |
| get { return CFDateIntervalFormatterCopyDateTemplate(core)._swiftObject } |
| set { CFDateIntervalFormatterSetDateTemplate(core, newValue?._cfObject) } |
| } |
| |
| open var dateStyle: Style { |
| get { return Style(CFDateIntervalFormatterGetDateStyle(core)) } |
| set { CFDateIntervalFormatterSetDateStyle(core, CFDateIntervalFormatterStyle(newValue)) } |
| } |
| open var timeStyle: Style { |
| get { return Style(CFDateIntervalFormatterGetTimeStyle(core)) } |
| set { CFDateIntervalFormatterSetTimeStyle(core, CFDateIntervalFormatterStyle(newValue)) } |
| } |
| |
| internal enum BoundaryStyle: UInt { |
| case `default` = 0 |
| #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) |
| case minimizeAdjacentMonths = 1 |
| #endif |
| } |
| |
| internal var boundaryStyle: BoundaryStyle { |
| get { return BoundaryStyle(_CFDateIntervalFormatterGetBoundaryStyle(core)) } |
| set { _CFDateIntervalFormatterSetBoundaryStyle(core, _CFDateIntervalFormatterBoundaryStyle(newValue) )} |
| } |
| |
| /* |
| If the range smaller than the resolution specified by the dateTemplate, a single date format will be produced. If the range is larger than the format specified by the dateTemplate, a locale-specific fallback will be used to format the items missing from the pattern. |
| |
| For example, if the range is 2010-03-04 07:56 - 2010-03-04 19:56 (12 hours) |
| - The pattern jm will produce |
| for en_US, "7:56 AM - 7:56 PM" |
| for en_GB, "7:56 - 19:56" |
| - The pattern MMMd will produce |
| for en_US, "Mar 4" |
| for en_GB, "4 Mar" |
| If the range is 2010-03-04 07:56 - 2010-03-08 16:11 (4 days, 8 hours, 15 minutes) |
| - The pattern jm will produce |
| for en_US, "3/4/2010 7:56 AM - 3/8/2010 4:11 PM" |
| for en_GB, "4/3/2010 7:56 - 8/3/2010 16:11" |
| - The pattern MMMd will produce |
| for en_US, "Mar 4-8" |
| for en_GB, "4-8 Mar" |
| */ |
| open func string(from fromDate: Date, to toDate: Date) -> String { |
| return CFDateIntervalFormatterCreateStringFromDateToDate(core, fromDate._cfObject, toDate._cfObject)._swiftObject |
| } |
| |
| open func string(from dateInterval: DateInterval) -> String? { |
| let result = CFDateIntervalFormatterCreateStringFromDateToDate(core, dateInterval.start._cfObject, dateInterval.end._cfObject)._swiftObject |
| return result.isEmpty ? nil : result |
| } |
| |
| open override func string(for obj: Any) -> String? { |
| guard let interval = obj as? DateInterval else { |
| return nil |
| } |
| |
| return string(from: interval) |
| } |
| |
| open override func editingString(for obj: Any) -> String? { |
| return nil |
| } |
| |
| open override func copy(with zone: NSZone? = nil) -> Any { |
| return DateIntervalFormatter(cfFormatter: CFDateIntervalFormatterCreateCopy(nil, core)) |
| } |
| } |