iosswiftdatedatetime

Determination of the time difference


I have an APiI in which there is JSON from which I receive date: String, I convert it to Date type, but I need to somehow define the time difference in the extension.

Should compare this date with the current one and calculate the number of hours/days between these dates? When there are less than 24 hours between the dates, show n hours ago, otherwise show amount of full days between the dates (n days ago).

With my extension for date:

extension Date {

    func toString(withFormat format: String = "MM.dd") -> String {

        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = format
        let str = dateFormatter.string(from: self)

        return str
    }
    
    func toStringCoin(withFormat format: String = "HH") -> String {

        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = format
        let str = dateFormatter.string(from: self)

        return str
    }
}

My extension for String:

   func toDate(withFormat format: String = "MM/dd/yyyy")-> Date?{

        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = format
        let date = dateFormatter.date(from: self)

        return date

    }
    
    func toDateCoin(withFormat format: String = "MM/dd/yyyy HH:mm")-> Date?{

        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = format
        let date = dateFormatter.date(from: self)
        return date
    }

Snippet of my code how I should get this difference

 Text("\(newsModel.date.convertCoin(time: newsModel.date))" + " " + "ago".localized)

Solution

  • Creating strings that describe the difference between the current date and some other date

    (e.g. "Three days ago".)

    A DateFormatter won't do what you want. Those generate strings that represent a date, not the difference between dates.


    Edit:

    As of iOS 13/Mac OS 10.15, there is a new formatter RelativeDateTimeFormatter that sounds like it would do what you want. However, it doesn't seem to offer as much control of the units it uses. If you always want hours and days, but never weeks, I'm not sure if RelativeDateTimeFormatter will give you that much control.


    Here is how you could create such a thing yourself:

    DateComponentsFormatter is pretty close. It will generate strings for time intervals, with good control of the units it uses. It would be pretty easy to build a single language date offset string using a DateComponentsFormatter (handling localization to other languages would greatly complicate it, but let's ignore that for now.)

    Such a function might look like this:

    func offsetFromNowString(for otherDate: Date) -> String {
        let formatter = DateComponentsFormatter()
        formatter.unitsStyle = .full // <- Change this line to .abbreviated for shorter units
    
        // Only include hours and days in the difference string
        formatter.allowedUnits = [.hour, .day] 
    
        // Switch this to true if you want it to say "about 3 days ago"
        formatter.includesApproximationPhrase = false
    
        // Use hours or days, not both. Change value to 2 if you want both
        formatter.maximumUnitCount = 1
     
        let offset = Date().distance(to: otherDate)
        let offsetSuffix = offset > 0 ? " from now" : " ago"
        return (formatter.string(from: fabs(offset)) ?? "") + offsetSuffix
    }
    

    And you could test it with code like this:

    let nowString = DateFormatter.localizedString(from: Date(), dateStyle: .medium, timeStyle: .short)
    
    print("Now is \(nowString)\n")
    for _ in 1...10 {
        // Create a random date ±10 days from now
        let random = Double.random(in: -86400*10...86400*10)
        let otherDate = Date(timeIntervalSinceNow: random)
        
        let otherDateString = DateFormatter.localizedString(from: otherDate, dateStyle: .medium, timeStyle: .short)
        
        print("\(otherDateString) is \(offsetFromNowString(for:otherDate))")
    }
    

    That generates output like

    Now is Feb 25, 2023 at 8:31 AM
    
    Feb 17, 2023 at 4:06 PM is 8 days ago
    Feb 21, 2023 at 1:24 AM is 4 days ago
    Feb 28, 2023 at 1:14 PM is 3 days from now
    Feb 18, 2023 at 2:04 PM is 7 days ago
    Feb 27, 2023 at 8:51 AM is 2 days from now
    Feb 25, 2023 at 9:53 AM is 1 hour from now
    Mar 2, 2023 at 1:39 PM is 5 days from now
    Mar 1, 2023 at 3:31 PM is 4 days from now
    Feb 23, 2023 at 11:25 AM is 2 days ago
    Feb 20, 2023 at 7:43 PM is 5 days ago
    

    If you change formatter.unitsStyle to .abbreviated, you get output like this:

    Now is Feb 25, 2023 at 9:40 AM
    
    Mar 3, 2023 at 4:24 AM is 6d from now
    Feb 28, 2023 at 4:46 PM is 3d from now
    Feb 21, 2023 at 7:01 PM is 4d ago
    Feb 25, 2023 at 6:39 AM is 3h ago
    Feb 22, 2023 at 3:10 AM is 3d ago
    Feb 26, 2023 at 2:30 PM is 1d from now
    Mar 2, 2023 at 9:20 AM is 5d from now
    Feb 23, 2023 at 9:10 AM is 2d ago
    Mar 2, 2023 at 9:53 PM is 6d from now
    Feb 17, 2023 at 2:05 AM is 8d ago
    

    (Note that formatters are somewhat expensive to create. If you are going to be generating a lot of "date offset strings" you'd want to refactor the code above to lazily create a DateComponentsFormatter and reuse it.)