Swift DateIntervalFormatter: keep dates out of the string even if interval spans multiple days

Viewed 784

I am trying to format a time interval. I want a result that looks like this:

10:45 - 12:00 AM

I can get very close to this using DateInvervalFormatter:

let cal = Calendar.current
let formatter = DateIntervalFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .short
let start = Date()
let end = cal.date(byAdding: .hour, value: 1, to: start)
formatter.string(from: DateInterval(start: start, end: end!))

The above (in the en_US locale) will produce an output such as:

5:27 – 6:27 PM

Looks good right? However, this does not work if the two dates in the interval are on different days. For example:

let formatter = DateIntervalFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .short
let startComponents = DateComponents(year: 2020, month: 1, day: 1, hour: 23, minute: 45)
let start = cal.date(from: startComponents)
let end = cal.date(byAdding: .hour, value: 1, to: start!)
formatter.string(from: DateInterval(start: start!, end: end!))

Despite setting dateStyle to .none, the string produced in the above example (in the en_US locale) is:

1/1/2020, 11:45 PM – 1/2/2020, 12:45 AM

What I want is:

11:45 PM – 12:45 AM

How can I get this? I know I could use a DateFormatter to format each date (start and end) into just a time, and then append the two strings together with a hyphen (-) in the middle, but this is not necessarily localization-friendly.

1 Answers

What I ended up with is:

extension Date {
    
    func formatTimeInterval(
        to: Date,
        timeZone: TimeZone = .autoupdatingCurrent
    ) -> String {
        
        let formatter = DateIntervalFormatter()
        formatter.timeZone = timeZone
        formatter.timeStyle = .short
        formatter.dateStyle = .none
        
        // we need to manually strip any "date" metadata, because for some locales,
        // even if we set `formatter.dateStyle = .none`, if the dates are in two different days
        // we will still get the date information in the end result (e.g. for the US locale)
        
        let calendar = Calendar.current
        let fromComponents = calendar.dateComponents([.hour, .minute, .second], from: self)
        let toComponents = calendar.dateComponents([.hour, .minute, .second], from: to)
        let fromDate = calendar.date(from: fromComponents)!
        let toDate = calendar.date(from: toComponents)!
        return formatter.string(from: fromDate, to: toDate)
    }
}

However, if the interval dates are in different months (say 31st August - 1 September), it adds a date starting at year 1, something like

1/1/1, 11:45 PM – 1/2/1, 12:45 AM

No clean solution to this still...

Related