Date.ComponentsFormatStyle
This is part of the FormatStyle Deep Dive series
This formatter gives you a localized string representation of the how much time has passed between the two dates. You can specify the units to display.
Note: This style cannot output
AttributedString
values by appending.attributed
.
Download the Xcode Playground with all examples
let testRange = Date(timeIntervalSince1970: 0)..<Date(timeIntervalSinceReferenceDate: 0)
testRange.formatted(.components(style: .abbreviated, fields: [.day])) // "11,323 days"
testRange.formatted(.components(style: .narrow, fields: [.day])) // "11,323days"
testRange.formatted(.components(style: .wide, fields: [.day])) // "11,323 days"
testRange.formatted(.components(style: .spellOut, fields: [.day])) // "eleven thousand three hundred twenty-three days"
testRange.formatted(.components(style: .condensedAbbreviated, fields: [.day])) // "11,323d"
You have a few options available to you for the fields:
parameter:
- day
- hour
- minute
- month
- second
- week
- year
You should include all of the Field
types that you would like to possibly display. I say “possibly” here because there’s no guarantee that the system will choose to show every field you specify. The documentation is non-existant online, but the header file says the following:
/// - fields: The fields to be included in the output string. Chosen automatically based on the interval being formatted if unspecified. Fields with 0 value are dropped.
Which explains the following:
testRange.formatted(.components(style: .condensedAbbreviated, fields: [.day, .month, .year, .hour, .second, .week])) // "31y"
Since the difference between the days are exactly 31 years, we only see that unit displayed.
let appleReferenceDay = Date(timeIntervalSinceReferenceDate: 0)
let twosday = Calendar(identifier: .gregorian).date(from: twosdayDateComponents)!
let secondRange = appleReferenceDay..<twosday
// "21 yrs, 1 mth, 3 wks, 9 hr, 1,342 sec"
secondRange.formatted(.components(style: .abbreviated, fields: [.day, .month, .year, .hour, .second, .week]))
// "21yrs 1mth 3wks 9hr 1,342sec"
secondRange.formatted(.components(style: .narrow, fields: [.day, .month, .year, .hour, .second, .week]))
// "21 years, 1 month, 3 weeks, 9 hours, 1,342 seconds"
secondRange.formatted(.components(style: .wide, fields: [.day, .month, .year, .hour, .second, .week]))
// "twenty-one years, one month, three weeks, nine hours, one thousand three hundred forty-two seconds"
secondRange.formatted(.components(style: .spellOut, fields: [.day, .month, .year, .hour, .second, .week]))
// "21y 1mo 3w 9h 1,342s"
secondRange.formatted(.components(style: .condensedAbbreviated, fields: [.day, .month, .year, .hour, .second, .week]))
Customizing the locale is as easy as adding the .locale()
method at the end of the .components
call:
let franceLocale = Locale(identifier: "fr_FR")
// "vingt-et-un ans, un mois, trois semaines, neuf heures et mille trois cent quarante-deux secondes"
secondRange.formatted(.components(style: .spellOut, fields: [.day, .month, .year, .hour, .second, .week]).locale(franceLocale))
Further Customization
You can initialize and store an instance of the style for even further customization:
let componentsFormat = Date.ComponentsFormatStyle(
style: .wide,
locale: Locale(identifier: "fr_FR"),
calendar: Calendar(identifier: .gregorian),
fields: [
.day,
.month,
.year,
.hour,
.second,
.week,
]
)
componentsFormat.format(secondRange) // "21 ans, 1 mois, 3 semaines, 9 heures et 1 342 secondes"
secondRange.formatted(componentsFormat) // "21 ans, 1 mois, 3 semaines, 9 heures et 1 342 secondes"
And finally, combine it with a custom format style, and you get:
struct InFrench: FormatStyle {
typealias FormatInput = Range<Date>
typealias FormatOutput = String
static let componentsFormat = Date.ComponentsFormatStyle(
style: .wide,
locale: Locale(identifier: "fr_FR"),
calendar: Calendar(identifier: .gregorian),
fields: [
.day,
.month,
.year,
.hour,
.second,
.week,
]
)
func format(_ value: Range<Date>) -> String {
InFrench.componentsFormat.format(value)
}
}
extension FormatStyle where Self == InFrench {
static var inFrench: InFrench { .init() }
}
secondRange.formatted(.inFrench) // "21 ans, 1 mois, 3 semaines, 9 heures et 1 342 secondes"
Download the Xcode Playground with all examples