Search code examples
iosswiftnsdatenscalendar

Swift - check if a timestamp is yesterday, today, tomorrow, or X days ago


I'm trying to work out how to decide if a given timestamp occurs today, or +1 / -1 days. Essentially, I'd like to do something like this (Pseudocode)

IF days_from_today(timestamp) == -1 RETURN 'Yesterday'
ELSE IF days_from_today(timestamp) == 0 RETURN 'Today'
ELSE IF days_from_today(timestamp) == 1 RETURN 'Tomorrow'
ELSE IF days_from_today(timestamp) < 1 RETURN days_from_today(timestamp) + ' days ago'
ELSE RETURN 'In ' + days_from_today(timestamp) + ' ago'

Crucially though, it needs to be in Swift and I'm struggling with the NSDate / NSCalendar objects. I started with working out the time difference like this:

let calendar = NSCalendar.currentCalendar()
let date = NSDate(timeIntervalSince1970: Double(timestamp))
let timeDifference = calendar.components([.Second,.Minute,.Day,.Hour],
    fromDate: date, toDate: NSDate(), options: NSCalendarOptions())

However comparing in this way isn't easy, because the .Day is different depending on the time of day and the timestamp. In PHP I'd just use mktime to create a new date, based on the start of the day (i.e. mktime(0,0,0)), but I'm not sure of the easiest way to do that in Swift.

Does anybody have a good idea on how to approach this? Perhaps an extension to NSDate or something similar would be best?


Solution

  • Calendar has methods for all three cases

    func isDateInYesterday(_ date: Date) -> Bool
    func isDateInToday(_ date: Date) -> Bool
    func isDateInTomorrow(_ date: Date) -> Bool
    

    To calculate the days earlier than yesterday use

    func dateComponents(_ components: Set<Calendar.Component>, 
                          from start: Date, 
                              to end: Date) -> DateComponents
    

    pass [.day] to components and get the day property from the result.


    This is a function which considers also is in for earlier and later dates by stripping the time part (Swift 3+).

    func dayDifference(from interval : TimeInterval) -> String
    {
        let calendar = Calendar.current
        let date = Date(timeIntervalSince1970: interval)
        if calendar.isDateInYesterday(date) { return "Yesterday" }
        else if calendar.isDateInToday(date) { return "Today" }
        else if calendar.isDateInTomorrow(date) { return "Tomorrow" }
        else {
            let startOfNow = calendar.startOfDay(for: Date())
            let startOfTimeStamp = calendar.startOfDay(for: date)
            let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp)
            let day = components.day!
            if day < 1 { return "\(-day) days ago" }
            else { return "In \(day) days" }
        }
    }
    

    Alternatively you could use DateFormatter for Yesterday, Today and Tomorrow to get localized strings for free

    func dayDifference(from interval : TimeInterval) -> String
    {
        let calendar = Calendar.current
        let date = Date(timeIntervalSince1970: interval)
        let startOfNow = calendar.startOfDay(for: Date())
        let startOfTimeStamp = calendar.startOfDay(for: date)
        let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp)
        let day = components.day!
        if abs(day) < 2 {
            let formatter = DateFormatter()
            formatter.dateStyle = .short
            formatter.timeStyle = .none
            formatter.doesRelativeDateFormatting = true
            return formatter.string(from: date)
        } else if day > 1 {
            return "In \(day) days"
        } else {
            return "\(-day) days ago"
        }
    }
    

    Update:

    In macOS 10.15 / iOS 13 RelativeDateTimeFormatter was introduced to return (localized) strings relative to a specific date.