DateComponentsFormatter returns wrong number of unit count

2.6k Views Asked by At

I've faced issue, when DateComponentsFormatter returns unexpected number of units. Does anyone faced same issue?

import Foundation

let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full;
formatter.maximumUnitCount = 1;

let date = Date(timeIntervalSinceNow: -14.7 * 24 * 60 * 60)
let dateString = formatter.string(from: date, to: Date()) // 2 weeks 1 day

I expect to receive "2 weeks", but have "2 weeks 1 day".

5

There are 5 best solutions below

2
Apurv On

You are passing -14.7 which is rounded of as -15. So you are getting 2 weeks 1 day. So round the number properly to get expected results.

5
Mknsri On

I solved the issue by checking for the comma separator and using it to substring the DateFormatters output, PlayGround example in Swift 3

// Test date
var df = DateFormatter()
df.dateFormat = "dd.MM.yyyy HH:mm:ss"
df.timeZone = TimeZone(secondsFromGMT: 0)
let fromDate = df.date(from: "01.01.2000 00:00:00")
var timeDifference = Date().timeIntervalSince(fromDate!)

// Setup formatter
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.includesApproximationPhrase = false
formatter.zeroFormattingBehavior = .dropAll
formatter.maximumUnitCount = 1
formatter.allowsFractionalUnits = false

// Use the configured formatter to generate the string.
var outputString = formatter.string(from: timeDifference) ?? ""

// Remove 2nd unit if exists
let commaIndex = outputString.characters.index(of: ",") ?? outputString.endIndex
outputString = outputString.substring(to: commaIndex)

// Result
print(outputString)
0
abagmut On

I would propose little bit more reliable solution (while still not sure it works for all cases)

let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.maximumUnitCount = 1

let date = Date(timeIntervalSinceNow: -14.7 * 24 * 60 * 60)
let dateString = formatter.string(from: date, to: Date()) ?? ""
if let nsRange = try? NSRegularExpression(pattern: "\\A\\d* \\w*").rangeOfFirstMatch(in: dateString, options: [], range: NSRange(location: 0, length: dateString.count)), let range = Range(nsRange, in: dateString) {
    let fixedString = String(dateString[range])
}
0
daddycool On

For those still looking for an answer, this is what I found out:

Currently, there is only 1 relatively pretty way to solve this issue and it's by using pod https://github.com/MatthewYork/DateTools.

At least until https://openradar.appspot.com/26354907 is resolved by Apple.

0
Milan Stevanovic On

After fiddling around with this problem for some time, I devised a simple workaround that might (or might not) work for some of you having this problem. What this method does is that it adds the amount of time described in the mentioned radar bug report that causes the bug to appear:

/// Workaround for a known bug in `DateComponentsFormatter` that causes `maximumUnitCount` variable value to be ignored sometimes https://openradar.appspot.com/26354907
///
/// - Parameters:
///   - fromDate: First (earlier) date.
///   - toDate: Second (later) date.
///   - dateFormatter: Instance of `DateComponentsFormatter` with properly set `maximumUnitCount` property.
/// - Returns: Corrected timestamp with only one unit. Eg. `1d 1h` will be corrected to `1d`.
private static func correctedTimestamp(from fromDate: Date, to toDate: Date, dateFormatter: DateComponentsFormatter) -> String? {
    guard
        let days = Calendar.current.dateComponents([.day], from: fromDate, to: toDate).day,
        let hours = Calendar.current.dateComponents([.hour], from: fromDate, to: toDate).hour,
        let minutes = Calendar.current.dateComponents([.minute], from: fromDate, to: toDate).minute,
        days > 0,
        days * 24 - hours == 0,
        days * 24 * 60 - minutes < 0,
        let timestamp = dateFormatter.string(from: fromDate, to: toDate.addingTimeInterval(Double(days * 24 * 60 - minutes) * 60))
    else { return nil }
    return timestamp
}