john
john

Reputation: 527

Swift display time ago from Date (NSDate)

In a cell I want to display the time ago from the NSDate in the Parse server. Here is the code but its not working. Nothing is changing, and the data isn't being parsed.

    if let createdat = (object?["createdAt"] as? String){
            let pastDate = Date(timeIntervalSinceNow: TimeInterval(createdat)!)
            cell.TimeAgo.text = pastDate.timeAgoDisplay()
        }


       extension Date {
          func timeAgoDisplay() -> String {
        let secondsAgo = Int(Date().timeIntervalSince(self))

        let minute = 60
        let hour = 60 * minute
        let day = 24 * hour
        let week = 7 * day

        if secondsAgo < minute {
            return "\(secondsAgo) sec ago"
        } else if secondsAgo < hour {
            return "\(secondsAgo / minute) min ago"
        } else if secondsAgo < day {
            return "\(secondsAgo / hour) hrs ago"
        } else if secondsAgo < week {
            return "\(secondsAgo / day) days ago"
        }

        return "\(secondsAgo / week) weeks ago"
    }
}

Upvotes: 35

Views: 25852

Answers (10)

Fattie
Fattie

Reputation: 12647

Now trivial:

///This example rounds to "one unit", examples "27d" or "3m".
let _agoFormatter: DateComponentsFormatter = {
    let f = DateComponentsFormatter()
    f.unitsStyle = .abbreviated
    f.allowedUnits = [.day, .hour, .minute]
    f.maximumUnitCount = 1
    return f
}()

Simply play with the various values, eg maximumUnitCount to get what you want.

// let d ... the Date you are interested in

print( _agoFormatter.string(from: d, to: Date.now )

That's it.

Upvotes: 0

sbiljeek
sbiljeek

Reputation: 67

How to display time ago from Date

You can use timeIntervalSince to get the difference between two dates in seconds. Then compare secondsAgo to seconds, minutes, hours, days, weeks, months and years to determine the correct String suffix.

You can use this function to return formatted time ago by simply passing it a date and preference for long (3 days ago) or short (3d ago).

See my full guide here: How to display time ago from Date

Full code:

func getTimeSince(date: Date, isShort: Bool = false) -> String {
    var formattedString = String()
    let now = Date()
    let secondsAgo = Int(now.timeIntervalSince(date))
    
    let twoSeconds = 2
    let minute = 60
    let twoMinutes = minute * 2
    let hour = 60 * minute
    let twoHours = hour * 2
    let day = 24 * hour
    let twoDays = day * 2
    let week = 7 * day
    let twoWeeks = week * 2
    let month = 4 * week
    let twoMonths = month * 2
    let year = 12 * month
    let twoYears = year * 2
    
    let secondString = isShort ? "s ago" : " second ago"
    let secondsString = isShort ? "s ago" : " seconds ago"
    let minuteString = isShort ? "m ago" : " minute ago"
    let minutesString = isShort ? "m ago" : " minutes ago"
    let hourString = isShort ? "h ago" : " hour ago"
    let hoursString = isShort ? "h ago" : " hours ago"
    let dayString = isShort ? "d ago" : " day ago"
    let daysString = isShort ? "d ago" : " days ago"
    let weekString = isShort ? "w ago" : " week ago"
    let weeksString = isShort ? "w ago" : " weeks ago"
    let monthString = isShort ? "mo ago" : " month ago"
    let monthsString = isShort ? "mo ago" : " months ago"
    let yearString = isShort ? "y ago" : " year ago"
    let yearsString = isShort ? "y ago" : " years ago"
    
    if secondsAgo < twoSeconds {
        formattedString = "\(secondsAgo)\(secondString)"
    } else if secondsAgo < minute {
        formattedString = "\(secondsAgo)\(secondsString)"
    } else if secondsAgo < twoMinutes {
        formattedString = "\(secondsAgo / minute)\(minuteString)"
    } else if secondsAgo < hour {
        formattedString = "\(secondsAgo / minute)\(minutesString)"
    } else if secondsAgo < twoHours {
        formattedString = "\(secondsAgo / hour)\(hourString)"
    } else if secondsAgo < day {
        formattedString = "\(secondsAgo / hour)\(hoursString)"
    } else if secondsAgo < twoDays {
        formattedString = "\(secondsAgo / day)\(dayString)"
    } else if secondsAgo < week {
        formattedString = "\(secondsAgo / day)\(daysString)"
    } else if secondsAgo < twoWeeks {
        formattedString = "\(secondsAgo / week)\(weekString)"
    } else if secondsAgo < month {
        formattedString = "\(secondsAgo / week)\(weeksString)"
    } else if secondsAgo < twoMonths {
        formattedString = "\(secondsAgo / month)\(monthString)"
    } else if secondsAgo < year {
        formattedString = "\(secondsAgo / month)\(monthsString)"
    } else if secondsAgo < twoYears {
        formattedString = "\(secondsAgo / year)\(yearString)"
    } else {
        formattedString = "\(secondsAgo / year)\(yearsString)"
    }
    return formattedString
}

Usage:

let formattedTimeAgo = self.getTimeSince(date: date, isShort: false)

Upvotes: -1

Julien Kode
Julien Kode

Reputation: 5479

If you just want a Time Ago extension for Date go to the bottom of the answer 😊

I'll show you an example just to get seconds ago and after I'll show your extension updated.

Note: you can use directly the date from Pase if you want:

if let pastDate = (object?["createdAt"] as? Date) {
    cell.TimeAgo.text = pastDate.timeAgoDisplay()
}

Since Swift 5.1

Example how to display seconds ago with Swift 5.1:

Since iOS13 Apple introduce a new class RelativeDateTimeFormatter

extension Date {
    func timeAgoDisplay() -> String {
        let formatter = RelativeDateTimeFormatter()
        formatter.unitsStyle = .full
        return formatter.localizedString(for: self, relativeTo: Date())
    }
}

This class will allow you to get a time ago string based on your language. It automatically select the right unit of time based on your interval, here is an example:

|--------------------------|------------------|
| Time interval in seconds |      Display     |
|--------------------------|------------------|
|             -6           |   6 seconds ago  |
|            -60           |   1 minute ago   |
|           -600           |  10 minutes ago  |
|          -6000           |    1 hour ago    |
|         -60000           |   16 hours ago   |
|--------------------------|------------------|

You'll notice that it handle automatically plurals for you.

Swift 3 or Swift 4

Example how to get seconds ago with Swift 3 or Swift 4:

First: To get the number of seconds ago we need to check if we have one minutes or less, to get the current Date minus one minute you can write that:

let minuteAgo = calendar.date(byAdding: .minute, value: -1, to: Date())!

Second: Now compare the 2 dates! (In the case of your extension we replace yourDate by self) and get the difference between this 2 dates.

if (minuteAgo < yourDate) {
    let diff = Calendar.current.dateComponents([.second], from: yourDate, to: Date()).second ?? 0
    print("\(diff) sec ago")
}

That's all, now you can print the time ago !

So your extension is like this: (This is a simple extension to get the time ago)

extension Date {
    func timeAgoDisplay() -> String {
 
        let calendar = Calendar.current
        let minuteAgo = calendar.date(byAdding: .minute, value: -1, to: Date())!
        let hourAgo = calendar.date(byAdding: .hour, value: -1, to: Date())!
        let dayAgo = calendar.date(byAdding: .day, value: -1, to: Date())!
        let weekAgo = calendar.date(byAdding: .day, value: -7, to: Date())!

        if minuteAgo < self {
            let diff = Calendar.current.dateComponents([.second], from: self, to: Date()).second ?? 0
            return "\(diff) sec ago"
        } else if hourAgo < self {
            let diff = Calendar.current.dateComponents([.minute], from: self, to: Date()).minute ?? 0
            return "\(diff) min ago"
        } else if dayAgo < self {
            let diff = Calendar.current.dateComponents([.hour], from: self, to: Date()).hour ?? 0
            return "\(diff) hrs ago"
        } else if weekAgo < self {
            let diff = Calendar.current.dateComponents([.day], from: self, to: Date()).day ?? 0
            return "\(diff) days ago"
        }
        let diff = Calendar.current.dateComponents([.weekOfYear], from: self, to: Date()).weekOfYear ?? 0
        return "\(diff) weeks ago"
    }
}

To use it, this is very straightforward:

var now = Date()
now.timeAgoDisplay()

Upvotes: 106

V&#237;t Zadina
V&#237;t Zadina

Reputation: 562

If you have only timestamp from 1970. You can use this function to return time ago.

private func timestampToStringAgo(timestamp: Int64) -> String{
        let actualTime = Int64(Date().timeIntervalSince1970*1000)
        var lastSeenTime = actualTime - timestamp
        lastSeenTime /= 1000 //seconds
        var lastTimeString = ""
        if lastSeenTime < 60 {
            if lastSeenTime == 1 {
                lastTimeString = String(lastSeenTime) + " second ago"
            } else {
                lastTimeString = String(lastSeenTime) + " seconds ago"
            }
        } else {
            lastSeenTime /= 60
            if lastSeenTime < 60 {
                if lastSeenTime == 1 {
                    lastTimeString =  String(lastSeenTime) + " minute ago"
                } else {
                    lastTimeString =  String(lastSeenTime) + " minutes ago"
                }
                
            } else {
                lastSeenTime /= 60
                if lastSeenTime < 24 {
                    if lastSeenTime == 1 {
                        lastTimeString = String(lastSeenTime) + " hour ago"
                    } else {
                        lastTimeString = String(lastSeenTime) + " hours ago"
                    }
                } else {
                    lastSeenTime /= 24
                    if lastSeenTime == 1 {
                        lastTimeString = String(lastSeenTime) + " day ago"
                    } else {
                        lastTimeString = String(lastSeenTime) + " days ago"
                    }
                }
            }
        }
        return lastTimeString
    }

Upvotes: 0

hbk
hbk

Reputation: 11184

It's a bit tricky)

Basically u need to

  • check interval
  • select interested components
  • check if quotient required and if it's applicable for the current language

iOS provide few ways to do this

  1. DateComponentsFormatter

    +: no work with localization and other string-related stuff

    -: not very flexible

    let formatter = DateComponentsFormatter()
    formatter.allowedUnits = [.hour, .minute, .second]
    formatter.unitsStyle = .brief
    formatter.zeroFormattingBehavior = .dropAll
    
    let result = formatter.string(from: Date().advanced(by: -300), to: Date())
    result
    

Result:

"5min"

  1. RelativeDateTimeFormatter

    +: no work with localization and other string-related stuff

    -: iOS13+

    let formatter = RelativeDateTimeFormatter()
    formatter.unitsStyle = .full
    return formatter.localizedString(for: self, relativeTo: Date())
    
  2. Custom way

    +: flexible

    -: code everything

Sample:

import Foundation

public extension Date {

  struct DetailedDateSuffix {

    let year: String
    let month: String
    let week: String
    let day: String
    let hour: String
    let min: String
    let second: String

    let quotient: String
    let suffix: String

    public init(year: String,
                week: String,
                month: String,
                day: String,
                hour: String,
                min: String,
                second: String,
                quotient: String,
                suffix: String = String.empty) {
      self.year = year
      self.month = month
      self.week = week
      self.day = day
      self.hour = hour
      self.min = min
      self.second = second
      self.quotient = quotient
      self.suffix = suffix
    }
  }

  func toDetailedReadableFormat(_ suffix: DetailedDateSuffix) -> String {

    if #available(iOS 13.0, *) {
      let formatter = RelativeDateTimeFormatter()
      formatter.unitsStyle = .full
      return formatter.localizedString(for: self, relativeTo: Date())
    } else {
      let calendar = Calendar.current
      let ageComponents = calendar.dateComponents(
        [
          .year,
          .month,
          .weekOfYear,
          .day,
          .hour,
          .minute,
          .second
        ],
        from: self,
        to: Date())

      var description: String = String.empty

      if let years = ageComponents.year,
        let months = ageComponents.month,
        let weeks = ageComponents.weekOfYear,
        let days = ageComponents.day,
        let hours = ageComponents.hour,
        let min = ageComponents.minute,
        let sec = ageComponents.second {

        var requireQuotient = false

        if years > 0 {
          description = "\(years)" + suffix.year
          requireQuotient = years == 1
        } else if months > 0 {
          description = "\(months)" + suffix.month
          requireQuotient = months == 1
        } else if weeks > 0 {
          description = "\(weeks)" + suffix.week
          requireQuotient = weeks == 1
        } else if days > 0 {
          description = "\(days)" + suffix.day
          requireQuotient = days == 1
        } else if hours > 0 {
          description = "\(hours)" + suffix.hour
          requireQuotient = hours == 1
        } else if min > 0 {
          description = "\(min)" + suffix.min
          requireQuotient = min == 1
        } else if sec > 0 {
          description = "\(sec)" + suffix.second
          requireQuotient = sec == 1
        }

        description = requireQuotient ? "\(description)\(suffix.quotient)" : description
        description = "\(description)\(suffix.suffix)"
      }

      return description
    }
  }
}

Upvotes: 5

nitin.agam
nitin.agam

Reputation: 2142

Here is the solution in Swift 5:

extension Date {

func timeAgo() -> String {

    let secondsAgo = Int(Date().timeIntervalSince(self))

    let minute = 60
    let hour = 60 * minute
    let day = 24 * hour
    let week = 7 * day
    let month = 4 * week

    let quotient: Int
    let unit: String
    if secondsAgo < minute {
        quotient = secondsAgo
        unit = "second"
    } else if secondsAgo < hour {
        quotient = secondsAgo / minute
        unit = "min"
    } else if secondsAgo < day {
        quotient = secondsAgo / hour
        unit = "hour"
    } else if secondsAgo < week {
        quotient = secondsAgo / day
        unit = "day"
    } else if secondsAgo < month {
        quotient = secondsAgo / week
        unit = "week"
    } else {
        quotient = secondsAgo / month
        unit = "month"
    }
    return "\(quotient) \(unit)\(quotient == 1 ? "" : "s") ago"
}
}

Upvotes: 7

Mari&#225;n ÄŒern&#253;
Mari&#225;n ÄŒern&#253;

Reputation: 15748

Swift 5.1 (iOS 13)

Since iOS 13 you can use Apple's RelativeDateFormatter. The advantage is the resulting string is localized.

let date = Date().addingTimeInterval(-15000)

let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .full
let string = formatter.localizedString(for: date, relativeTo: Date())

print(string) // 4 hours ago

See for example this blog post.

Upvotes: 26

Madavaram Ramesh
Madavaram Ramesh

Reputation: 47

Finally got a simple solution for me in swift4.2

    let start = //Enter Start Date here....
    let end = Date()
    let formatter = DateComponentsFormatter()
    formatter.maximumUnitCount = 2
    formatter.unitsStyle = .full
    formatter.allowedUnits = [.year, .month, .day]
    let timeDifference = form.string(from: start, to: end)
    print(timeDifference)
    if timeDifference == "0 days"{
          print("Today")
    }
    else if  timeDifference == "1 days"{
           print("\(timeDifference!)day ago")
    }
    else{
           print("\(timeDifference!)days ago")
    }

Upvotes: 1

James Rochabrun
James Rochabrun

Reputation: 4377

    let now = Date()
let pastDate = Date(timeIntervalSinceNow: -60 * 60 * 24)

enum DisplayTime {
    case short
    case long

    var seconds: String {
        switch self {
        case .short: return "s"
        case .long: return "seconds"
        }
    }

    var minutes: String {
        switch self {
        case .short: return "m"
        case .long: return "minutes"
        }
    }

    var hours: String {
        switch self {
        case .short: return "h"
        case .long: return "hours"
        }
    }

    var days: String {
        switch self {
        case .short: return "d"
        case .long: return "days"
        }
    }

    var weeks: String {
        switch self {
        case .short: return "w"
        case .long: return "weeks"
        }
    }
}

extension Date {

    func timeAgoDisplay(_ display: DisplayTime) -> String {

        let secondsAgo = Int(Date().timeIntervalSince(self))

        let minute = 60
        let hour = 60 * minute
        let day = 24 * hour
        let week = 7 * day

        switch secondsAgo {
        case let seconds where seconds < minute : return "\(secondsAgo) \(display.seconds) ago"
        case let seconds where seconds < hour: return "\(secondsAgo / minute) \(display.minutes) ago"
        case let seconds where seconds < day: return "\(secondsAgo / hour) \(display.hours) ago"
        case let seconds where seconds < week: return "\(secondsAgo / day) \(display.days) ago"
        default: "\(secondsAgo / week) \(display.weeks) ago"
        }
        return "\(secondsAgo / week) \(display.weeks) ago"
    }
}

pastDate.timeAgoDisplay(.short)

Upvotes: 1

Sandeep Kalia
Sandeep Kalia

Reputation: 47

Pass time as String in format e.g. 2019-02-25 10:20:21 Pass dateformat in Variable dateFormat

let dateFormat = "yyyy-MM-dd HH:mm:ss"

    func timeInterval(timeAgo:String) -> String
        {
            let df = DateFormatter()

            df.dateFormat = dateFormat
            let dateWithTime = df.date(from: timeAgo)

            let interval = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: dateWithTime!, to: Date())

            if let year = interval.year, year > 0 {
                return year == 1 ? "\(year)" + " " + "year ago" : "\(year)" + " " + "years ago"
            } else if let month = interval.month, month > 0 {
                return month == 1 ? "\(month)" + " " + "month ago" : "\(month)" + " " + "months ago"
            } else if let day = interval.day, day > 0 {
                return day == 1 ? "\(day)" + " " + "day ago" : "\(day)" + " " + "days ago"
            }else if let hour = interval.hour, hour > 0 {
                return hour == 1 ? "\(hour)" + " " + "hour ago" : "\(hour)" + " " + "hours ago"
            }else if let minute = interval.minute, minute > 0 {
                return minute == 1 ? "\(minute)" + " " + "minute ago" : "\(minute)" + " " + "minutes ago"
            }else if let second = interval.second, second > 0 {
                return second == 1 ? "\(second)" + " " + "second ago" : "\(second)" + " " + "seconds ago"
            } else {
                return "a moment ago"

            }
        }

Upvotes: 1

Related Questions