12

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".

Timur Bernikovich
  • 5,660
  • 4
  • 45
  • 58

5 Answers5

2

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)
Mknsri
  • 87
  • 9
  • I propose to check number of "," occurrences depending on `maximumUnitCount` to make it more general. At the same time, is it known that all languages use comma to separate units? – Timur Bernikovich Feb 16 '17 at 13:10
  • The [OpenRadar post](https://openradar.appspot.com/26354907) only specifies that a `maximumUnitCount = 1` only exhibits the bug. I checked the [documentation](https://developer.apple.com/reference/foundation/datecomponentsformatter/) and didn't find any information on the separators. The fallback just uses the whole string unchanged so I think this is an acceptable fallback. – Mknsri Feb 16 '17 at 14:50
  • OK, I will upvote your answer because I do almost the same. – Timur Bernikovich Feb 16 '17 at 14:57
  • @TimurBernikowich it doesn't work for all languages e.g for Dutch I got it in this format: "1 week en 1 dag" – abagmut Dec 13 '17 at 08:47
  • @abagmut I propose to ignore unit count where it's impossible to remove second part or use TTT version.( – Timur Bernikovich Dec 13 '17 at 09:07
1

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.

Apurv
  • 17,116
  • 8
  • 51
  • 67
  • 5
    I've set `maximumUnitCount` - formatter should not return more than one unit. – Timur Bernikovich Dec 16 '16 at 14:27
  • 2
    Strange. As soon as u make it less than -14.5, it shows one unit only. If you make -15 onwards then also it shows 1 unit only. Somewhere between -14.5 to -14.99, it fails. Same is observed when start of any new week. I mean -21, -28 and so.. – Apurv Dec 16 '16 at 14:33
0

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])
}
abagmut
  • 911
  • 1
  • 10
  • 22
0

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.

daddycool
  • 113
  • 2
  • 6
0

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
}