Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
"Time ago" function for Swift (based on MatthewYork's DateTools for Objective-C)
func timeAgoSinceDate(date:NSDate, numericDates:Bool) -> String {
let calendar = NSCalendar.currentCalendar()
let unitFlags = NSCalendarUnit.CalendarUnitMinute | NSCalendarUnit.CalendarUnitHour | NSCalendarUnit.CalendarUnitDay | NSCalendarUnit.CalendarUnitWeekOfYear | NSCalendarUnit.CalendarUnitMonth | NSCalendarUnit.CalendarUnitYear | NSCalendarUnit.CalendarUnitSecond
let now = NSDate()
let earliest = now.earlierDate(date)
let latest = (earliest == now) ? date : now
let components:NSDateComponents = calendar.components(unitFlags, fromDate: earliest, toDate: latest, options: nil)
if (components.year >= 2) {
return "\(components.year) years ago"
} else if (components.year >= 1){
if (numericDates){
return "1 year ago"
} else {
return "Last year"
}
} else if (components.month >= 2) {
return "\(components.month) months ago"
} else if (components.month >= 1){
if (numericDates){
return "1 month ago"
} else {
return "Last month"
}
} else if (components.weekOfYear >= 2) {
return "\(components.weekOfYear) weeks ago"
} else if (components.weekOfYear >= 1){
if (numericDates){
return "1 week ago"
} else {
return "Last week"
}
} else if (components.day >= 2) {
return "\(components.day) days ago"
} else if (components.day >= 1){
if (numericDates){
return "1 day ago"
} else {
return "Yesterday"
}
} else if (components.hour >= 2) {
return "\(components.hour) hours ago"
} else if (components.hour >= 1){
if (numericDates){
return "1 hour ago"
} else {
return "An hour ago"
}
} else if (components.minute >= 2) {
return "\(components.minute) minutes ago"
} else if (components.minute >= 1){
if (numericDates){
return "1 minute ago"
} else {
return "A minute ago"
}
} else if (components.second >= 3) {
return "\(components.second) seconds ago"
} else {
return "Just now"
}
}
@germanger

This comment has been minimized.

Copy link

commented May 19, 2015

cool, do you have a gist that considers future dates too? "In a day", "In 1 day", "In 2 weeks"

@Farhadsaadatpei

This comment has been minimized.

Copy link

commented Jul 13, 2015

I get different results in return, can you tell me what causes this?
Example:
Return 9223372036854775807 months ago

@jacks205

This comment has been minimized.

Copy link

commented Sep 29, 2015

@arnaudlamy

This comment has been minimized.

Copy link

commented Jun 13, 2016

@sebastienboulogne

This comment has been minimized.

Copy link

commented Sep 16, 2016

Hi, do you think we could have a version for Swift 3 ? Thx

@msamoylov

This comment has been minimized.

Copy link

commented Sep 19, 2016

@tneginareb

This comment has been minimized.

Copy link

commented Oct 9, 2016

Swift 3

 func timeAgoSinceDate(date:NSDate, numericDates:Bool) -> String {
        let calendar = NSCalendar.current
        let unitFlags: Set<Calendar.Component> = [.minute, .hour, .day, .weekOfYear, .month, .year, .second]
        let now = NSDate()
        let earliest = now.earlierDate(date as Date)
        let latest = (earliest == now as Date) ? date : now
        let components = calendar.dateComponents(unitFlags, from: earliest as Date,  to: latest as Date)

        if (components.year! >= 2) {
            return "\(components.year!) years ago"
        } else if (components.year! >= 1){
            if (numericDates){
                return "1 year ago"
            } else {
                return "Last year"
            }
        } else if (components.month! >= 2) {
            return "\(components.month!) months ago"
        } else if (components.month! >= 1){
            if (numericDates){
                return "1 month ago"
            } else {
                return "Last month"
            }
        } else if (components.weekOfYear! >= 2) {
            return "\(components.weekOfYear!) weeks ago"
        } else if (components.weekOfYear! >= 1){
            if (numericDates){
                return "1 week ago"
            } else {
                return "Last week"
            }
        } else if (components.day! >= 2) {
            return "\(components.day!) days ago"
        } else if (components.day! >= 1){
            if (numericDates){
                return "1 day ago"
            } else {
                return "Yesterday"
            }
        } else if (components.hour! >= 2) {
            return "\(components.hour!) hours ago"
        } else if (components.hour! >= 1){
            if (numericDates){
                return "1 hour ago"
            } else {
                return "An hour ago"
            }
        } else if (components.minute! >= 2) {
            return "\(components.minute!) minutes ago"
        } else if (components.minute! >= 1){
            if (numericDates){
                return "1 minute ago"
            } else {
                return "A minute ago"
            }
        } else if (components.second! >= 3) {
            return "\(components.second!) seconds ago"
        } else {
            return "Just now"
        }

    }

@cshireman

This comment has been minimized.

Copy link

commented Oct 11, 2016

Awesome! You just saved me an hour.

@ReeceKenney

This comment has been minimized.

Copy link

commented Oct 23, 2016

Perfect thanks a bunch!

@masterravi

This comment has been minimized.

Copy link

commented Dec 19, 2016

Great,
Thanks guys for the efforts .Thats why I love gist will surely start contributing as much as possible soon .

@balitax

This comment has been minimized.

Copy link

commented Dec 30, 2016

Very Nice!
Thanks Man, awesome code.

@sp71

This comment has been minimized.

Copy link

commented Jan 11, 2017

Sweet!

@toddkramer

This comment has been minimized.

Copy link

commented Jan 17, 2017

I made a Swift 3 framework for this. Has support for CocoaPods, Carthage, SPM, all platforms (iOS, macOS, tvOS, watchOS), and 42 localizations. Check it out and let me know what you think! https://github.com/toddkramer/LocalizedTimeAgo

@nikolay-dementiev

This comment has been minimized.

Copy link

commented Jan 20, 2017

Thanks

@heinrisch

This comment has been minimized.

Copy link

commented Jan 31, 2017

Using Date instead of NSDate:

func timeAgoSinceDate(_ date:Date, numericDates:Bool = false) -> String {
        let calendar = NSCalendar.current
        let unitFlags: Set<Calendar.Component> = [.minute, .hour, .day, .weekOfYear, .month, .year, .second]
        let now = Date()
        let earliest = now < date ? now : date
        let latest = (earliest == now) ? date : now
        let components = calendar.dateComponents(unitFlags, from: earliest,  to: latest)
        
        if (components.year! >= 2) {
            return "\(components.year!) years ago"
        } else if (components.year! >= 1){
            if (numericDates){
                return "1 year ago"
            } else {
                return "Last year"
            }
        } else if (components.month! >= 2) {
            return "\(components.month!) months ago"
        } else if (components.month! >= 1){
            if (numericDates){
                return "1 month ago"
            } else {
                return "Last month"
            }
        } else if (components.weekOfYear! >= 2) {
            return "\(components.weekOfYear!) weeks ago"
        } else if (components.weekOfYear! >= 1){
            if (numericDates){
                return "1 week ago"
            } else {
                return "Last week"
            }
        } else if (components.day! >= 2) {
            return "\(components.day!) days ago"
        } else if (components.day! >= 1){
            if (numericDates){
                return "1 day ago"
            } else {
                return "Yesterday"
            }
        } else if (components.hour! >= 2) {
            return "\(components.hour!) hours ago"
        } else if (components.hour! >= 1){
            if (numericDates){
                return "1 hour ago"
            } else {
                return "An hour ago"
            }
        } else if (components.minute! >= 2) {
            return "\(components.minute!) minutes ago"
        } else if (components.minute! >= 1){
            if (numericDates){
                return "1 minute ago"
            } else {
                return "A minute ago"
            }
        } else if (components.second! >= 3) {
            return "\(components.second!) seconds ago"
        } else {
            return "Just now"
        }
        
    }
@begimai

This comment has been minimized.

Copy link

commented Feb 16, 2017

thank you, guys!

@MHX792

This comment has been minimized.

Copy link

commented Feb 17, 2017

@heinrisch let calendar = NSCalendar.current should be changed to let calendar = Calendar.current otherwise the month, year, week components were 0 for me.

@jinthagerman

This comment has been minimized.

Copy link

commented Feb 26, 2017

I rewrote this to be more swift-y because I needed future dates as well. Naming could be better I guess

struct DateComponentUnitFormatter {
    
    private struct DateComponentUnitFormat {
        let unit: Calendar.Component
        
        let singularUnit: String
        let pluralUnit: String
        
        let futureSingular: String
        let pastSingular: String
    }
    
    private let formats: [DateComponentUnitFormat] = [
        
        DateComponentUnitFormat(unit: .year,
                                singularUnit: "year",
                                pluralUnit: "years",
                                futureSingular: "Next year",
                                pastSingular: "Last year"),
        
        DateComponentUnitFormat(unit: .month,
                                singularUnit: "month",
                                pluralUnit: "months",
                                futureSingular: "Next month",
                                pastSingular: "Last month"),
        
        DateComponentUnitFormat(unit: .weekOfYear,
                                singularUnit: "week",
                                pluralUnit: "weeks",
                                futureSingular: "Next week",
                                pastSingular: "Last week"),
        
        DateComponentUnitFormat(unit: .day,
                                singularUnit: "day",
                                pluralUnit: "days",
                                futureSingular: "Tomorrow",
                                pastSingular: "Yesterday"),
        
        DateComponentUnitFormat(unit: .hour,
                                singularUnit: "hour",
                                pluralUnit: "hours",
                                futureSingular: "In an hour",
                                pastSingular: "An hour ago"),
        
        DateComponentUnitFormat(unit: .minute,
                                singularUnit: "minute",
                                pluralUnit: "minutes",
                                futureSingular: "In a minute",
                                pastSingular: "A minute ago"),
        
        DateComponentUnitFormat(unit: .second,
                                singularUnit: "second",
                                pluralUnit: "seconds",
                                futureSingular: "Just now",
                                pastSingular: "Just now"),
        
        ]
    
    func string(forDateComponents dateComponents: DateComponents, useNumericDates: Bool) -> String {
        for format in self.formats {
            let unitValue: Int
            
            switch format.unit {
            case .year:
                unitValue = dateComponents.year ?? 0
            case .month:
                unitValue = dateComponents.month ?? 0
            case .weekOfYear:
                unitValue = dateComponents.weekOfYear ?? 0
            case .day:
                unitValue = dateComponents.day ?? 0
            case .hour:
                unitValue = dateComponents.hour ?? 0
            case .minute:
                unitValue = dateComponents.minute ?? 0
            case .second:
                unitValue = dateComponents.second ?? 0
            default:
                assertionFailure("Date does not have requried components")
                return ""
            }
            
            switch unitValue {
            case 2 ..< Int.max:
                return "\(unitValue) \(format.pluralUnit) ago"
            case 1:
                return useNumericDates ? "\(unitValue) \(format.singularUnit) ago" : format.pastSingular
            case -1:
                return useNumericDates ? "In \(-unitValue) \(format.singularUnit)" : format.futureSingular
            case Int.min ..< -1:
                return "In \(-unitValue) \(format.pluralUnit)"
            default:
                break
            }
        }
        
        return "Just now"
    }
}

extension Date {
    
    func timeAgoSinceNow(useNumericDates: Bool = false) -> String {
        
        let calendar = Calendar.current
        let unitFlags: Set<Calendar.Component> = [.minute, .hour, .day, .weekOfYear, .month, .year, .second]
        let now = Date()
        let components = calendar.dateComponents(unitFlags, from: self, to: now)
        
        let formatter = DateComponentUnitFormatter()
        return formatter.string(forDateComponents: components, useNumericDates: useNumericDates)
    }
}

https://gist.github.com/jinthagerman/009c85b7bbd0a40dcbba747e89a501bf

@FeleciaGenet

This comment has been minimized.

Copy link

commented Sep 5, 2017

Golden. Thanks for the time saver.

If anyone found themselves here and needed usage:



if let timeAgo = timeAgoSinceDate(yourInfoAsDateFormat) {
                    
                   yourLabel.text = timeAgo
                    
                }
@oleander

This comment has been minimized.

Copy link

commented Nov 30, 2017

Here's a another version

import Foundation

extension Date {
  fileprivate struct Item {
    let multi: String
    let single: String
    let last: String
    let value: Int?
  }

  fileprivate var components: DateComponents {
    return Calendar.current.dateComponents(
      [.minute, .hour, .day, .weekOfYear, .month, .year, .second],
      from: Calendar.current.date(byAdding: .second, value: -1, to: Date())!,
      to: self
    )
  }

  fileprivate var items: [Item] {
    return [
      Item(multi: "years ago", single: "1 year ago", last: "Last year", value: components.year),
      Item(multi: "months ago", single: "1 month ago", last: "Last month", value: components.month),
      Item(multi: "weeks ago", single: "1 week ago", last: "Last week", value: components.weekday),
      Item(multi: "days ago", single: "1 day ago", last: "Last day", value: components.day),
      Item(multi: "minutes ago", single: "1 minute ago", last: "Last minute", value: components.minute),
      Item(multi: "seconds ago", single: "Just now", last: "Last second", value: components.second)
    ]
  }

  public func timeAgo(numericDates: Bool = false) -> String {
    for item in items {
      switch (item.value, numericDates) {
      case let (.some(step), _) where step == 0:
        continue
      case let (.some(step), true) where step == 1:
        return item.last
      case let (.some(step), false) where step == 1:
        return item.single
      case let (.some(step), _):
        return String(step) + " " + item.multi
      default:
        continue
      }
    }

    return "Just now"
  }
}

print(Calendar.current.date(byAdding: .day, value: 1, to: Date())!.timeAgo())
@dariukas

This comment has been minimized.

Copy link

commented Aug 6, 2018

A Swift 4 version without force unwrapping:

extension Date {
    func timeAgoSinceDate(numericDates:Bool) -> String {
        let calendar = Calendar.current
        let now = Date()
        let earliest = self < now ? self : now
        let latest =  self > now ? self : now
        
        let unitFlags: Set<Calendar.Component> = [.minute, .hour, .day, .weekOfMonth, .month, .year, .second]
        let components: DateComponents = calendar.dateComponents(unitFlags, from: earliest, to: latest)
        
        if let year = components.year {
            if (year >= 2) {
                return "\(year) years ago"
            } else if (year >= 1) {
                return stringToReturn(flag: numericDates, strings: ("1 year ago", "Last year"))
            }
        }
        
        if let month = components.month {
            if (month >= 2) {
                return "\(month) months ago"
            } else if (month >= 2) {
                return stringToReturn(flag: numericDates, strings: ("1 month ago", "Last month"))
            }
        }
        
        if let weekOfYear = components.weekOfYear {
            if (weekOfYear >= 2) {
                return "\(weekOfYear) months ago"
            } else if (weekOfYear >= 2) {
                return stringToReturn(flag: numericDates, strings: ("1 week ago", "Last week"))
            }
        }
        
        if let day = components.day {
            if (day >= 2) {
                return "\(day) days ago"
            } else if (day >= 2) {
                return stringToReturn(flag: numericDates, strings: ("1 day ago", "Yesterday"))
            }
        }
        
        if let hour = components.hour {
            if (hour >= 2) {
                return "\(hour) hours ago"
            } else if (hour >= 2) {
                return stringToReturn(flag: numericDates, strings: ("1 hour ago", "An hour ago"))
            }
        }
        
        if let minute = components.minute {
            if (minute >= 2) {
                return "\(minute) minutes ago"
            } else if (minute >= 2) {
                return stringToReturn(flag: numericDates, strings: ("1 minute ago", "A minute ago"))
            }
        }
        
        if let second = components.second {
            if (second >= 3) {
                return "\(second) seconds ago"
            }
        }
        
        return "Just now"
    }
    
    private func stringToReturn(flag:Bool, strings: (String, String)) -> String {
        if (flag){
            return strings.0
        } else {
            return strings.1
        }
    }
}
@oluckyman

This comment has been minimized.

Copy link

commented Aug 8, 2018

Sorry for interrupting the party, but seems like "time ago" feature is implemented in DateTools with locales and just works:
https://github.com/MatthewYork/DateTools#time-ago

let timeAgoDate = 2.days.earlier
print("Time Ago: ", timeAgoDate.timeAgoSinceNow)
print("Time Ago: ", timeAgoDate.shortTimeAgoSinceNow)

//Output:
//Time Ago: 2 days ago
//Time Ago: 2d
@dariukas

This comment has been minimized.

Copy link

commented Aug 16, 2018

The framework looks nice. However, sometimes only a feature is needed, not the whole framework = why to buy the whole cow if you need only milk.

@merlos

This comment has been minimized.

Copy link

commented Sep 23, 2018

This is my version. It is based on @dariukas, but fixes some bugs

import Foundation

extension Date {
    ///
    /// Provides a humanised date. For instance: 1 minute, 1 week ago, 3 months ago
    ///
    /// - Parameters:
    //      - numericDates: Set it to true to get "1 year ago", "1 month ago" or false if you prefer "Last year", "Last month"
    ///
    func timeAgo(numericDates:Bool) -> String {
        let calendar = Calendar.current
        let now = Date()
        let earliest = self < now ? self : now
        let latest =  self > now ? self : now
        
        let unitFlags: Set<Calendar.Component> = [.minute, .hour, .day, .weekOfMonth, .month, .year, .second]
        let components: DateComponents = calendar.dateComponents(unitFlags, from: earliest, to: latest)
        //print("")
        //print(components)
        if let year = components.year {
            if (year >= 2) {
                return "\(year) years ago"
            } else if (year >= 1) {
                return numericDates ?  "1 year ago" : "Last year"
            }
        }
        if let month = components.month {
            if (month >= 2) {
                return "\(month) months ago"
            } else if (month >= 1) {
                return numericDates ? "1 month ago" : "Last month"
            }
        }
        if let weekOfMonth = components.weekOfMonth {
            if (weekOfMonth >= 2) {
                return "\(weekOfMonth) weeks ago"
            } else if (weekOfMonth >= 1) {
                return numericDates ? "1 week ago" : "Last week"
            }
        }
        if let day = components.day {
            if (day >= 2) {
                return "\(day) days ago"
            } else if (day >= 1) {
                return numericDates ? "1 day ago" : "Yesterday"
            }
        }
        if let hour = components.hour {
            if (hour >= 2) {
                return "\(hour) hours ago"
            } else if (hour >= 1) {
                return numericDates ? "1 hour ago" : "An hour ago"
            }
        }
        if let minute = components.minute {
            if (minute >= 2) {
                return "\(minute) minutes ago"
            } else if (minute >= 1) {
                return numericDates ? "1 minute ago" : "A minute ago"
            }
        }
        if let second = components.second {
            if (second >= 3) {
                return "\(second) seconds ago"
            }
        }
        return "Just now"
    }
}
@dx777

This comment has been minimized.

Copy link

commented Sep 23, 2018

@merlos nice, thank you !

@demonukg

This comment has been minimized.

Copy link

commented Nov 14, 2018

@merlos awesome! thank you

@rafalwojcik

This comment has been minimized.

Copy link

commented Dec 4, 2018

My version in Swift 4.2:

import Foundation

extension Date {
    func timeAgo(numericDates: Bool) -> String {
        let calendar = Calendar.current
        let now = Date()
        let earliest = self < now ? self : now
        let latest =  self > now ? self : now

        let unitFlags: Set<Calendar.Component> = [.minute, .hour, .day, .weekOfMonth, .month, .year, .second]
        let components: DateComponents = calendar.dateComponents(unitFlags, from: earliest, to: latest)

        let year = components.year ?? 0
        let month = components.month ?? 0
        let weekOfMonth = components.weekOfMonth ?? 0
        let day = components.day ?? 0
        let hour = components.hour ?? 0
        let minute = components.minute ?? 0
        let second = components.second ?? 0

        switch (year, month, weekOfMonth, day, hour, minute, second) {
            case (let year, _, _, _, _, _, _) where year >= 2: return "\(year) years ago"
            case (let year, _, _, _, _, _, _) where year == 1 && numericDates: return "1 year ago"
            case (let year, _, _, _, _, _, _) where year == 1 && !numericDates: return "Last year"
            case (_, let month, _, _, _, _, _) where month >= 2: return "\(month) months ago"
            case (_, let month, _, _, _, _, _) where month == 1 && numericDates: return "1 month ago"
            case (_, let month, _, _, _, _, _) where month == 1 && !numericDates: return "Last month"
            case (_, _, let weekOfMonth, _, _, _, _) where weekOfMonth >= 2: return "\(weekOfMonth) weeks ago"
            case (_, _, let weekOfMonth, _, _, _, _) where weekOfMonth == 1 && numericDates: return "1 week ago"
            case (_, _, let weekOfMonth, _, _, _, _) where weekOfMonth == 1 && !numericDates: return "Last week"
            case (_, _, _, let day, _, _, _) where day >= 2: return "\(day) days ago"
            case (_, _, _, let day, _, _, _) where day == 1 && numericDates: return "1 day ago"
            case (_, _, _, let day, _, _, _) where day == 1 && !numericDates: return "Yesterday"
            case (_, _, _, _, let hour, _, _) where hour >= 2: return "\(hour) hours ago"
            case (_, _, _, _, let hour, _, _) where hour == 1 && numericDates: return "1 hour ago"
            case (_, _, _, _, let hour, _, _) where hour == 1 && !numericDates: return "An hour ago"
            case (_, _, _, _, _, let minute, _) where minute >= 2: return "\(minute) minutes ago"
            case (_, _, _, _, _, let minute, _) where minute == 1 && numericDates: return "1 minute ago"
            case (_, _, _, _, _, let minute, _) where minute == 1 && !numericDates: return "A minute ago"
            case (_, _, _, _, _, _, let second) where second >= 3: return "\(second) seconds ago"
            default: return "Just now"
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.