3

In answering this question on how to create a "xxx <largest_time_units> ago" string based on comparing two dates, I wrote out a complete solution to the problem.

It involves using a DateComponentsFormatter with maximumUnitCount set to 1 and unitsStyle set to .full. Here is the code to create the DateComponentsFormatter:

 var timeFormatter:DateComponentsFormatter = {
    let temp = DateComponentsFormatter()
    temp.allowedUnits = [.year, .month, .weekOfMonth, .day, .hour, .minute, .second]
    temp.maximumUnitCount = 1
    temp.unitsStyle = .full
    return temp
}()

Then I wrote a function that uses the DateComponentsFormatter to output "xxx units ago" strings:

//Use our DateComponentsFormatter to generate a string showing "n <units> ago" where units is the largest units of the difference
//Between the now date and the specified date in te past
func timefromDateToNow(_ pastDate: Date) -> String {
    if let output = timeFormatter.string(from: pastDate, to: now) {
        return output + " ago"
    } else {
        return "error"
    }
}

I calculate dates in the past with code like this:

let value = Double.random(in: min ... max)
let past = now.addingTimeInterval(value)

Strangely, for certain values of value, I'm getting the string "0 months ago". The value I was able to capture was -408754.0, which is about 4 days, 17 hours.

Why would calling timeFormatter.string(from: Date(timeIntervalSinceNow: -408754.0), to: Date()) return a string of 0 months? It should display a result of "4 days"!

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • Without even having read this post I can tell you that `DateComponentsFormatter` has produced inconsistent, odd, and sometimes buggy (i.e. returning multiple components when limiting to 1) results for me for years. After reading this post, I've experienced this exact behavior as well. – trndjc Dec 05 '20 at 18:21
  • @bsod have you filed bug reports for any such behavior? – Duncan C Dec 05 '20 at 19:23
  • 1
    No I haven’t unfortunately, I’m a bad developer – trndjc Dec 05 '20 at 20:20
  • @DuncanC now you understand why I said that DCF is buggy. – Leo Dabus Dec 05 '20 at 20:29
  • An easy fix would be to use the time interval between the two dates `timeFormatter.string(from: Date().timeIntervalSince(pastDate))` – Leo Dabus Dec 06 '20 at 00:02
  • Darn, you've found the wormhole! (By the way, I got 5 days, not 4...) – matt Dec 06 '20 at 00:04
  • Cool. I didn't know about that. Thanks! – Duncan C Dec 06 '20 at 00:19
  • It looks like RelativeDateTimeFormatter wouldn't work for the use-case in the question I linked. The asker only wanted the largest units, and RelativeDateTimeFormatter doesn't appear to have the ability to select the units. – Duncan C Dec 06 '20 at 00:24
  • Have any of you submitted a bug using the new Feedback Assistant App? I just tried it and I get an error trying to log in. – Duncan C Dec 06 '20 at 00:37
  • @DuncanC I managed to fix this "0 months" issue by removing `weekOfMonth` from the `allowedUnits` array! – F. Lambert Dec 28 '20 at 16:09

1 Answers1

1

If you want a reference to how far long ago, consider RelativeDateTimeFormatter, e.g.

let formatter = RelativeDateTimeFormatter()
formatter.dateTimeStyle = .named
formatter.unitsStyle = .spellOut
formatter.formattingContext = .beginningOfSentence

let date = Date()

let longTimeAgo = date.addingTimeInterval(-10_000_000)
print(formatter.localizedString(for: longTimeAgo, relativeTo: date)) // Three months ago

let veryLongTimeAgo = date.addingTimeInterval(-100_000_000)
print(formatter.localizedString(for: veryLongTimeAgo, relativeTo: date)) // Three years ago
Rob
  • 415,655
  • 72
  • 787
  • 1,044