42

I want to convert nsdate in to relative format like "Today","Yesterday","a week ago","a month ago","a year ago","date as it is".

I have written following method for it.. but some how its just printing as it is date.. can you please tell me what should be the problem?

//Following is my function which converts the date into relative string

+(NSString *)getDateDiffrence:(NSDate *)strDate{
    NSDateFormatter *df = [[NSDateFormatter alloc] init];

    df.timeStyle = NSDateFormatterMediumStyle;
    df.dateStyle = NSDateFormatterShortStyle;
    df.doesRelativeDateFormatting = YES;
    NSLog(@"STRING DATEEE : %@ REAL DATE TODAY %@",[df stringFromDate:strDate],[NSDate date]);
      return [df stringFromDate:strDate];

}

I have date string with the following format "2013-10-29T09:38:00"

When I tried to give the NSDate object then its always return me null date.
so I tried to convert that date in to yyyy-MM-dd HH:mm:ssZZZZ then I pass this date to function then it's just printing whole date..

How to solve this problem?

//Following is the code I call the above function

NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss"];
NSDate *date = [formatter dateFromString:[threadDict objectForKey:@"lastMessageDate"]];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ssZZZZ"];

NSString *date1 = [formatter stringFromDate:date];
NSDate *date_d = [formatter dateFromString:date1];
NSString *resultstr=[UserManager getDateDiffrence:date];

self.dateLabel.text=resultstr;
ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
BhavikKama
  • 8,566
  • 12
  • 94
  • 164
  • Using NSDateComponents, you can achieve it **https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/DatesAndTimes/Articles/dtCalendricalCalculations.html** – Kumar KL Dec 10 '13 at 06:41
  • In those cases it is better to use NSDateComponentsFormatter – Ricardo Jan 11 '16 at 18:00

16 Answers16

57

For simplicity I'm assuming that the dates you are formatting are all in the past (no "tomorrow" or "next week"). It's not that it can't be done but it would be more cases to deal with and more strings to return.


You can use components:fromDate:toDate:options: with whatever combination of date components you are looking for to get the number of years, months, weeks, days, hours, etc. between two dates. By then going though them in order from most significant (e.g. year) to least significant (e.g. day), you can format a string based only on the most significant component.

For example: a date that is 1 week, 2 days and 7 hours ago would be formatted as "1 week".

If you want to create special strings for a special number of a unit, like "tomorrow" for "1 day ago" then you can check the value of that component after you have determined that it is the most significant component.

The code would look something like this:

- (NSString *)relativeDateStringForDate:(NSDate *)date
{
    NSCalendarUnit units = NSCalendarUnitDay | NSCalendarUnitWeekOfYear | 
                           NSCalendarUnitMonth | NSCalendarUnitYear;

    // if `date` is before "now" (i.e. in the past) then the components will be positive
    NSDateComponents *components = [[NSCalendar currentCalendar] components:units
                                                                   fromDate:date
                                                                     toDate:[NSDate date]
                                                                    options:0];

    if (components.year > 0) {
        return [NSString stringWithFormat:@"%ld years ago", (long)components.year];
    } else if (components.month > 0) {
        return [NSString stringWithFormat:@"%ld months ago", (long)components.month];
    } else if (components.weekOfYear > 0) {
        return [NSString stringWithFormat:@"%ld weeks ago", (long)components.weekOfYear];
    } else if (components.day > 0) {
        if (components.day > 1) {
            return [NSString stringWithFormat:@"%ld days ago", (long)components.day];
        } else {
            return @"Yesterday";
        }
    } else {
        return @"Today";
    }
}

If your dates could also be in the future then you can check the absolute value of the components in the same order and then check if it's positive or negative to return the appropriate strings. I'me only showing the year below:

if ( abs(components.year > 0) ) { 
    // year is most significant component
    if (components.year > 0) {
        // in the past
        return [NSString stringWithFormat:@"%ld years ago", (long)components.year];
    } else {
        // in the future
        return [NSString stringWithFormat:@"In %ld years", (long)components.year];
    }
} 
Thomas Denney
  • 1,578
  • 2
  • 15
  • 25
David Rönnqvist
  • 56,267
  • 18
  • 167
  • 205
  • 3
    You shouldn't use `%zd` for the various properties of `NSDateComponents` because the properties of `NSDateComponents` use `NSInteger`, not `size_t`. [The String Programming Guide](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/Strings/Articles/formatSpecifiers.html#//apple_ref/doc/uid/TP40004265-SW5) recommends using `%ld` and casting to `long`. On 64-bit platforms the cast will be a no-op. – dreamlax Dec 10 '13 at 13:14
  • Is there a way to get the exact day using `NSDateComponent`s? Like Tuesday, Sunday, etc. – n00bProgrammer Jan 08 '14 at 12:48
  • 1
    @n00bProgrammer You can get the `weekDay` component (e.g. 1-7) and use it as the index for the localized `weekdaySymbols` from a `NSDateFormatter` – David Rönnqvist Jan 08 '14 at 13:17
  • 7
    It's only good to for the time difference but today/yesterday doesn't work because everything is considered as today if it's within last 24 hours :/ – budiDino May 15 '14 at 05:44
  • @budidino do you have any solution to this problem? – Shamsiddin Saidov Mar 17 '20 at 12:02
21

Please note that as of iOS 13 there is now RelativeDateTimeFormatter which does it all most of it for you! WWDC 2019 video here.

let formatter = RelativeDateTimeFormatter()
let dateString = formatter.localizedString(for: aDate, relativeTo: now)

// en_US: "2 weeks ago"
// es_ES: "hace 2 semanas"
// zh_TW: "2 週前"

I've left my previous answer below for posterity. Cheers!

⚠️ You will want to read through the previous answer for some key tips to avoid certain bugs. Hint: use the end of the current day's date/time for the relative date when comparing dates that are not today!


Here's my answer (in Swift 3!) and why it's better.

Answer:

func datePhraseRelativeToToday(from date: Date) -> String {

    // Don't use the current date/time. Use the end of the current day 
    // (technically 0h00 the next day). Apple's calculation of 
    // doesRelativeDateFormatting niavely depends on this start date.
    guard let todayEnd = dateEndOfToday() else {
        return ""
    }

    let calendar = Calendar.autoupdatingCurrent

    let units = Set([Calendar.Component.year,
                 Calendar.Component.month,
                 Calendar.Component.weekOfMonth,
                 Calendar.Component.day])

    let difference = calendar.dateComponents(units, from: date, to: todayEnd)

    guard let year = difference.year,
        let month = difference.month,
        let week = difference.weekOfMonth,
        let day = difference.day else {
            return ""
    }

    let timeAgo = NSLocalizedString("%@ ago", comment: "x days ago")

    let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.locale = Locale.autoupdatingCurrent
        formatter.dateStyle = .medium
        formatter.doesRelativeDateFormatting = true
        return formatter
    }()

    if year > 0 {
        // sample output: "Jan 23, 2014"
        return dateFormatter.string(from: date)
    } else if month > 0 {
        let formatter = DateComponentsFormatter()
        formatter.unitsStyle = .brief // sample output: "1mth"
        formatter.allowedUnits = .month
        guard let timePhrase = formatter.string(from: difference) else {
            return ""
        }
        return String(format: timeAgo, timePhrase)
    } else if week > 0 {
        let formatter = DateComponentsFormatter()
        formatter.unitsStyle = .brief; // sample output: "2wks"
        formatter.allowedUnits = .weekOfMonth
        guard let timePhrase = formatter.string(from: difference) else {
            return ""
        }
        return String(format: timeAgo, timePhrase)
    } else if day > 1 {
            let formatter = DateComponentsFormatter()
            formatter.unitsStyle = .abbreviated; // sample output: "3d"
            formatter.allowedUnits = .day
            guard let timePhrase = formatter.string(from: difference) else {
                return ""
            }
            return String(format: timeAgo, timePhrase)
    } else {
        // sample output: "Yesterday" or "Today"
        return dateFormatter.string(from: date)
    }
}

func dateEndOfToday() -> Date? {
    let calendar = Calendar.autoupdatingCurrent
    let now = Date()
    let todayStart = calendar.startOfDay(for: now)
    var components = DateComponents()
    components.day = 1
    let todayEnd = calendar.date(byAdding: components, to: todayStart)
    return todayEnd
}

Remember to reuse your formatters to avoid any performance hit! Hint: extensions on DateFormatter and DateComponentsFormatter are good ideas.

Why it's better:

  • Utilizes DateFormatter's "Yesterday" and "Today". This is already translated by Apple, which saves you work!
  • Uses DateComponentsFormatter's already translated "1 week" string. (Again less work for you, courtesy of Apple.) All you have to do is translate the "%@ ago" string.
  • The other answers incorrectly calculate the time when the day switches from "today" to "yesterday" to etc. Fixed constants are a big NO-NO because reasons. Also, the other answers use the current date/time when they should use the end of the current day's date/time.
  • Uses autoupdatingCurrent for Calendar & Locale which ensures your app is immediately up to date with the user's calendar and language preferences in Settings.app

This answer was inspired by DateTools on GitHub.

ChrisJF
  • 6,822
  • 4
  • 36
  • 41
  • Is there a way to do backwards formatting, i.e. get actual date from string Today, Tomorrow? – grep Jan 25 '22 at 21:09
10

Swift update, thanks to objective-c answer of David Rönnqvist, it will work for the past dates.

func relativeDateStringForDate(date : NSDate) -> NSString {

        let todayDate = NSDate()
        let units: NSCalendarUnit = [.Hour, .Day, .Month, .Year, .WeekOfYear]
        let components = NSCalendar.currentCalendar().components(units, fromDate: date , toDate: todayDate, options: NSCalendarOptions.MatchFirst )

        let year =  components.year
        let month = components.month
        let day = components.day
        let hour = components.hour
        let weeks = components.weekOfYear
        // if `date` is before "now" (i.e. in the past) then the components will be positive

        if components.year > 0 {
            return NSString.init(format: "%d years ago", year);
        } else if components.month > 0 {
            return NSString.init(format: "%d months ago", month);
        } else if components.weekOfYear > 0 {
            return NSString.init(format: "%d weeks ago", weeks);
        } else if (components.day > 0) {
            if components.day > 1 {
                return NSString.init(format: "%d days ago", day);
            } else {
                return "Yesterday";
            }
        } else {
            return NSString.init(format: "%d hours ago", hour);
        }
    }
Saurabh Yadav
  • 957
  • 10
  • 20
9

FOR: SWIFT 3

Here's a Swift 3 version, for past dates, that handles all units and singular or plural in the returned String.

Example Use:

let oneWeekAgo = Calendar.current.date(byAdding: .weekOfYear, value: -1, to: Date())!

print(relativePast(for: oneWeekAgo)) // output: "1 week ago"

I based it on a riff off of Saurabh Yadav's. Thanks.

func relativePast(for date : Date) -> String {

    let units = Set<Calendar.Component>([.year, .month, .day, .hour, .minute, .second, .weekOfYear])
    let components = Calendar.current.dateComponents(units, from: date, to: Date())

    if components.year! > 0 {
        return "\(components.year!) " + (components.year! > 1 ? "years ago" : "year ago")

    } else if components.month! > 0 {
        return "\(components.month!) " + (components.month! > 1 ? "months ago" : "month ago")

    } else if components.weekOfYear! > 0 {
        return "\(components.weekOfYear!) " + (components.weekOfYear! > 1 ? "weeks ago" : "week ago")

    } else if (components.day! > 0) {
        return (components.day! > 1 ? "\(components.day!) days ago" : "Yesterday")

    } else if components.hour! > 0 {
        return "\(components.hour!) " + (components.hour! > 1 ? "hours ago" : "hour ago")

    } else if components.minute! > 0 {
        return "\(components.minute!) " + (components.minute! > 1 ? "minutes ago" : "minute ago")

    } else {
        return "\(components.second!) " + (components.second! > 1 ? "seconds ago" : "second ago")
    }
}
ziligy
  • 1,447
  • 16
  • 10
7

To avoid the 24-hour problem mentioned by Budidino to David's answer, I altered it to like this below -

- (NSString *)relativeDateStringForDate:(NSDate *)date
{

NSCalendarUnit units = NSDayCalendarUnit | NSWeekOfYearCalendarUnit |
NSMonthCalendarUnit | NSYearCalendarUnit ;
NSCalendar *cal = [NSCalendar currentCalendar];
NSDateComponents *components1 = [cal components:(NSCalendarUnitEra|NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay) fromDate:[NSDate date]];
NSDate *today = [cal dateFromComponents:components1];

components1 = [cal components:(NSCalendarUnitEra|NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay) fromDate:date];
NSDate *thatdate = [cal dateFromComponents:components1];

// if `date` is before "now" (i.e. in the past) then the components will be positive
NSDateComponents *components = [[NSCalendar currentCalendar] components:units
                                                               fromDate:thatdate
                                                                 toDate:today
                                                                options:0];

if (components.year > 0) {
    return [NSString stringWithFormat:@"%ld years ago", (long)components.year];
} else if (components.month > 0) {
    return [NSString stringWithFormat:@"%ld months ago", (long)components.month];
} else if (components.weekOfYear > 0) {
    return [NSString stringWithFormat:@"%ld weeks ago", (long)components.weekOfYear];
} else if (components.day > 0) {
    if (components.day > 1) {
        return [NSString stringWithFormat:@"%ld days ago", (long)components.day];
    } else {
        return @"Yesterday";
    }
} else {
    return @"Today";
}
}

Basically, it creates 2 new dates without time pieces included.Then the comparison is done for "days" difference.

user3034970
  • 171
  • 1
  • 8
4

check NSDate-TimeAgo, it also supports multiple languages.

tassar
  • 578
  • 3
  • 9
  • 1
    NSDate-TimeAgo has been superseded by [DateTools](https://github.com/MatthewYork/DateTools). Just use `[date timeAgoSinceNow]` or `[date timeAgoSinceDate:otherDate]`. – user2067021 Jun 02 '16 at 05:55
3

You will need to work out this logic yourself. You will need to determine the number of days in between those two dates.

Here is a relatively naive approach:

+ (NSString *) dateDifference:(NSDate *)date
{
    const NSTimeInterval secondsPerDay = 60 * 60 * 24;
    NSTimeInterval diff = [date timeIntervalSinceNow] * -1.0;

    // if the difference is negative, then the given date/time is in the future
    // (because we multiplied by -1.0 to make it easier to follow later)
    if (diff < 0)
        return @"In the future";

    diff /= secondsPerDay; // get the number of days

    // if the difference is less than 1, the date occurred today, etc.
    if (diff < 1)
        return @"Today";
    else if (diff < 2)
        return @"Yesterday";
    else if (diff < 8)
        return @"Last week";
    else
        return [date description]; // use a date formatter if necessary
}

It is naive for a number of reasons:

  1. It doesn't take into account leap days
  2. It assumes there are 86400 seconds in a day (there is such a thing as leap seconds!)

However, this should at least help you head in the right direction. Also, avoid using get in method names. Using get in a method name typically indicates that the caller must provide their own output buffer. Consider NSArray's method, getItems:range:, and NSString's method, getCharacters:range:.

dreamlax
  • 93,976
  • 29
  • 161
  • 209
  • To help get started, here's some code for fist and late day of week: http://stackoverflow.com/questions/1106943/nscalendar-first-day-of-week/17259765#17259765 – Jasper Blues Dec 10 '13 at 06:47
  • -1 for relying on a day being 24 hours. +1 for at least pointing it out. – David Rönnqvist Dec 10 '13 at 06:50
  • @AnoopVaidya I started from the top and down voted when I saw `const NSTimeInterval secondsPerDay = 60 * 60 * 24` but when I got to "It is naive for a number of reasons: [...]" I reverted it :) – David Rönnqvist Dec 10 '13 at 06:56
  • This is absolutely awful. I'm typing this at 8:50 am. Your code would say that yesterday 8:51 am was "today". Anyone following your advice will take time to unlearn it and do things properly. – gnasher729 Dec 11 '14 at 08:52
3
NSString* AgoStringFromTime(NSDate* dateTime)
{
    NSDictionary *timeScale = @{@"sec"  :@1,
                                @"min"  :@60,
                                @"hr"   :@3600,
                                @"day"  :@86400,
                                @"week" :@605800,
                                @"month":@2629743,
                                @"year" :@31556926};
    NSString *scale;
    int timeAgo = 0-(int)[dateTime timeIntervalSinceNow];
    if (timeAgo < 60) {
        scale = @"sec";
    } else if (timeAgo < 3600) {
        scale = @"min";
    } else if (timeAgo < 86400) {
        scale = @"hr";
    } else if (timeAgo < 605800) {
        scale = @"day";
    } else if (timeAgo < 2629743) {
        scale = @"week";
    } else if (timeAgo < 31556926) {
        scale = @"month";
    } else {
        scale = @"year";
    }

    timeAgo = timeAgo/[[timeScale objectForKey:scale] integerValue];
    NSString *s = @"";
    if (timeAgo > 1) {
        s = @"s";
    }

    return [NSString stringWithFormat:@"%d %@%@", timeAgo, scale, s];
}
Chigs79
  • 162
  • 4
2

Here is code I created for my use:

+ (NSString*) getTimestampForDate:(NSDate*)date {

    NSDate* sourceDate = date;

    // Timezone Offset compensation (optional, if your target users are limited to a single time zone.)

    NSTimeZone* sourceTimeZone = [NSTimeZone timeZoneWithName:@"America/New_York"];
    NSTimeZone* destinationTimeZone = [NSTimeZone systemTimeZone];

    NSInteger sourceGMTOffset = [sourceTimeZone secondsFromGMTForDate:sourceDate];
    NSInteger destinationGMTOffset = [destinationTimeZone secondsFromGMTForDate:sourceDate];

    NSTimeInterval interval = destinationGMTOffset - sourceGMTOffset;

    NSDate* destinationDate = [[NSDate alloc] initWithTimeInterval:interval sinceDate:sourceDate];

    // Timestamp calculation (based on compensation)

    NSCalendar* currentCalendar = [NSCalendar currentCalendar];
    NSCalendarUnit unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit |  NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit;

    NSDateComponents *differenceComponents = [currentCalendar components:unitFlags fromDate:destinationDate toDate:[NSDate date] options:0];//Use `date` instead of `destinationDate` if you are not using Timezone offset correction

    NSInteger yearDifference = [differenceComponents year];
    NSInteger monthDifference = [differenceComponents month];
    NSInteger dayDifference = [differenceComponents day];
    NSInteger hourDifference = [differenceComponents hour];
    NSInteger minuteDifference = [differenceComponents minute];

    NSString* timestamp;

    if (yearDifference == 0
        && monthDifference == 0
        && dayDifference == 0
        && hourDifference == 0
        && minuteDifference <= 2) {

        //"Just Now"

        timestamp = @"Just Now";

    } else if (yearDifference == 0
               && monthDifference == 0
               && dayDifference == 0
               && hourDifference == 0
               && minuteDifference < 60) {

        //"13 minutes ago"

        timestamp = [NSString stringWithFormat:@"%ld minutes ago", (long)minuteDifference];

    } else if (yearDifference == 0
               && monthDifference == 0
               && dayDifference == 0
               && hourDifference == 1) {

        //"1 hour ago" EXACT

        timestamp = @"1 hour ago";

    } else if (yearDifference == 0
               && monthDifference == 0
               && dayDifference == 0
               && hourDifference < 24) {

        timestamp = [NSString stringWithFormat:@"%ld hours ago", (long)hourDifference];

    } else {

        NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
        [formatter setLocale:[NSLocale currentLocale]];

        NSString* strDate, *strDate2 = @"";

        if (yearDifference == 0
            && monthDifference == 0
            && dayDifference == 1) {

            //"Yesterday at 10:23 AM", "Yesterday at 5:08 PM"

            [formatter setDateFormat:@"hh:mm a"];
            strDate = [formatter stringFromDate:date];

            timestamp = [NSString stringWithFormat:@"Yesterday at %@", strDate];

        } else if (yearDifference == 0
                   && monthDifference == 0
                   && dayDifference < 7) {

            //"Tuesday at 7:13 PM"

            [formatter setDateFormat:@"EEEE"];
            strDate = [formatter stringFromDate:date];
            [formatter setDateFormat:@"hh:mm a"];
            strDate2 = [formatter stringFromDate:date];

            timestamp = [NSString stringWithFormat:@"%@ at %@", strDate, strDate2];

        } else if (yearDifference == 0) {

            //"July 4 at 7:36 AM"

            [formatter setDateFormat:@"MMMM d"];
            strDate = [formatter stringFromDate:date];
            [formatter setDateFormat:@"hh:mm a"];
            strDate2 = [formatter stringFromDate:date];

            timestamp = [NSString stringWithFormat:@"%@ at %@", strDate, strDate2];

        } else {

            //"March 24 2010 at 4:50 AM"

            [formatter setDateFormat:@"d MMMM yyyy"];
            strDate = [formatter stringFromDate:date];
            [formatter setDateFormat:@"hh:mm a"];
            strDate2 = [formatter stringFromDate:date];

            timestamp = [NSString stringWithFormat:@"%@ at %@", strDate, strDate2];
        }
    }

    return timestamp;
}
n00bProgrammer
  • 4,261
  • 3
  • 32
  • 60
  • 1
    If it is 9am current time, your code will label all the time from 9am yesterday through 9am now, as being "Today". 'yesterday' is considered anytime after 9 hours ago. In other words, you need to compare the date to the last midnight not the current time. – mahboudz Oct 02 '14 at 20:41
  • You are right. I had realised this, and used `NSDateComponents`. I used the `components: fromDate: toDate: options:` from `NSCalendar` for accurate time differences. If you need code for this, I'd be happy to provide it. – n00bProgrammer Oct 04 '14 at 05:54
  • @n00bProgrammer I also have the same issue. Can you share the code snippet. – iosCurator Dec 11 '14 at 06:23
2

This is just a copy of a previous answer but it returns Just now if it is less than five seconds.

func relativePast(for date : Date) -> String {

    let units = Set<Calendar.Component>([.year, .month, .day, .hour, .minute, .second, .weekOfYear])
    let components = Calendar.current.dateComponents(units, from: date, to: Date())

    if components.year! > 0 {
        return "\(components.year!) " + (components.year! > 1 ? "years ago" : "year ago")

    } else if components.month! > 0 {
        return "\(components.month!) " + (components.month! > 1 ? "months ago" : "month ago")

    } else if components.weekOfYear! > 0 {
        return "\(components.weekOfYear!) " + (components.weekOfYear! > 1 ? "weeks ago" : "week ago")

    } else if (components.day! > 0) {
        return (components.day! > 1 ? "\(components.day!) days ago" : "Yesterday")

    } else if components.hour! > 0 {
        return "\(components.hour!) " + (components.hour! > 1 ? "hours ago" : "hour ago")

    } else if components.minute! > 0 {
        return "\(components.minute!) " + (components.minute! > 1 ? "minutes ago" : "minute ago")

    } else {
        return "\(components.second!) " + (components.second! > 5 ? "seconds ago" : "Just Now".replacingOccurrences(of: "0", with: "")
    }
}
Jesse Onolemen
  • 1,277
  • 1
  • 15
  • 32
1

The problem with doesRelativeDateFormatting is that it's pretty much limited to Yesterday, Today, Tomorrow. If you're looking for something more thorough, then take a look at the answers here.

Community
  • 1
  • 1
neilco
  • 7,964
  • 2
  • 36
  • 41
1

Complate Code If Futures Dates

NSCalendarUnit units = NSCalendarUnitDay | NSCalendarUnitWeekOfYear | 
                           NSCalendarUnitMonth | NSCalendarUnitYear;


    NSDateComponents *components = [[NSCalendar currentCalendar] components:units fromDate:date toDate:[NSDate date] options:0];

    if (components.year < 0) {
            return [NSString stringWithFormat:@"%ld years from now", labs((long)components.year)];
        } else if (components.month < 0) {
            return [NSString stringWithFormat:@"%ld months from now", labs((long)components.month)];
        } else if (components.weekOfYear < 0) {
            return [NSString stringWithFormat:@"%ld weeks from now", labs((long)components.weekOfYear)];
        } else if (components.day < 0) {
            if (components.day < 1) {
                return [NSString stringWithFormat:@"%ld days from now", labs((long)components.day)];
            } else {
                return @"Tomorrow";
            }
        }
        else if (components.year > 0) {
            return [NSString stringWithFormat:@"%ld years ago", (long)components.year];
        } else if (components.month > 0) {
            return [NSString stringWithFormat:@"%ld months ago", (long)components.month];
        } else if (components.weekOfYear > 0) {
            return [NSString stringWithFormat:@"%ld weeks ago", (long)components.weekOfYear];
        } else if (components.day > 0) {
            if (components.day > 1) {
                return [NSString stringWithFormat:@"%ld days ago", (long)components.day];
            } else {
                return @"Yesterday";
            }
        } else {
            return @"Today";
        }
  • You have a little mistake on the future dates days from now, it should be less than -1 e.g: if (components.day < -1) { return [NSString stringWithFormat:@"%ld days from now", labs((long)components.day)]; } – garanda Mar 22 '16 at 21:51
1

I have attached demo here please find on this link. TimestampAgo-Demo

Thanks to n00bprogrammer

Edit :- I made changes in Sourcetimezone with [NSTimeZone systemTimeZone] because due to static time zone , issue occure in GMT or UTC format. (second goes in minus) and change deprecated Methods.

Community
  • 1
  • 1
Badal Shah
  • 7,541
  • 2
  • 30
  • 65
1

Here is my solution in Swift 2 that avoid 24-hour problem by comparing two dates with a zero time.

extension NSDate {

private func dateWithZeroTime(date: NSDate) -> NSDate? {
    let calendar = NSCalendar.currentCalendar()
    let units: NSCalendarUnit = [.Day, .WeekOfYear, .Month, .Year]
    let components = calendar.components(units, fromDate: date)
    return calendar.dateFromComponents(components)
}

private func thisDay() -> NSDate? {
    return self.dateWithZeroTime(self)
}

private func today() -> NSDate? {
    return self.dateWithZeroTime(NSDate())
}

var relativeFormat: String? {
    let today = self.today()
    let thisDay = self.thisDay()
    
    let formatter = NSDateFormatter()
    formatter.dateStyle = NSDateFormatterStyle.LongStyle
    let dateString = formatter.stringFromDate(self)
    
    if nil != thisDay && nil != today {
        let units: NSCalendarUnit = [.Day, .WeekOfYear, .Month, .Year]
        let components = NSCalendar.currentCalendar().components(units, fromDate: thisDay!, toDate: today!, options: [])
        
        if (components.year > 0) {
            return components.year == 1 ? "A year ago, \(dateString)" : "\(components.year) years ago, \(dateString)"
        } else if (components.month > 0) {
            return components.month == 1 ? "A month ago, \(dateString)" : "\(components.month) months ago, \(dateString)"
        } else if (components.weekOfYear > 0) {
            return components.weekOfYear == 1 ? "A week ago, \(dateString)" : "\(components.weekOfYear) weeks ago, \(dateString)"
        } else if (components.day > 0) {
            return components.day == 1 ? "Yesterday, \(dateString)" : "\(self.dayOfTheWeek()), \(dateString)"
        } else {
            return "Today"
        }
    }
    
    return nil
}

func dayOfTheWeek() -> String {
    let weekdays = [
        "Sunday",
        "Monday",
        "Tuesday",
        "Wednesday",
        "Thursday",
        "Friday",
        "Saturday"
    ]
    
    let calendar: NSCalendar = NSCalendar.currentCalendar()
    let components: NSDateComponents = calendar.components(.Weekday, fromDate: self)
    return weekdays[components.weekday - 1]
}
}

The Swift 5 solution:

public extension Date {

  private func dateWithZeroTime(_ date: Date) -> Date? {
    let calendar = Calendar.current
    let units: Set<Calendar.Component> = Set( [.day, .weekOfYear, .month, .year])
    let components = calendar.dateComponents(units, from: date)
    return calendar.date(from: components)
  }

  private func thisDay() -> Date? {
    return self.dateWithZeroTime(self)
  }

  private func today() -> Date? {
    return self.dateWithZeroTime(Date())
  }

  var relativeFormat: String? {
    let formatter = DateFormatter()
    formatter.dateStyle = DateFormatter.Style.long
    let dateString = formatter.string(from: self)

    if let thisDay = self.thisDay(),
       let today = self.today() {
      let units: Set<Calendar.Component> = Set([.day, .weekOfYear, .month, .year])
      let components = Calendar.current.dateComponents(units, from: thisDay, to: today)
  
      if let year = components.year,
         year > 0 {
        return year == 1 ? "A year ago, \(dateString)" : "\(year) years ago, \(dateString)"
      } else if let month = components.month,
                month > 0 {
        return month == 1 ? "A month ago, \(dateString)" : "\(month) months ago, \(dateString)"
      } else if let weekOfYear = components.weekOfYear,
                weekOfYear > 0 {
        return weekOfYear == 1 ? "A week ago, \(dateString)" : "\(weekOfYear) weeks ago, \(dateString)"
      } else if let day = components.day,
                day > 0 {
        return day == 1 ? "Yesterday, \(dateString)" : dayOfWeekWithDateString(dateString)
      } else {
        return "Today"
      }
    }

    return nil
  }

  func dayOfTheWeek() -> String? {
    let weekdays = [
      "Sunday",
      "Monday",
      "Tuesday",
      "Wednesday",
      "Thursday",
      "Friday",
      "Saturday"
    ]

    let calendar = Calendar.current
    let components: DateComponents = calendar.dateComponents(Set([.weekday]), from: self)

    guard let weekday = components.weekday else { return nil }

    return weekdays[weekday - 1]
  }

  func dayOfWeekWithDateString(_ dateString: String) -> String {
    if let dayOfWeek = dayOfTheWeek() {
      return "\(dayOfWeek), \(dateString)"
    } else {
      return dateString
    }
  }
}
Dmytro Skorokhod
  • 424
  • 8
  • 17
0

To format the given "sourceDate" as "5:56 pm" for today, "yesterday" for any time yesterday, "January 16" for any day in the same year and "January 16, 2014". I am posting my own method.

sourceDate = //some date that you need to take into consideration


 NSDateComponents *components = [[NSCalendar currentCalendar] components:NSCalendarUnitMinute | NSCalendarUnitHour | NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:[NSDate date]];
    NSDateComponents *sourceDateComponents = [[NSCalendar currentCalendar] components:NSCalendarUnitMinute | NSCalendarUnitHour | NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate: sourceDate];

    NSString* timestamp;

    NSDateFormatter *formatSourceDate   =   [NSDateFormatter new];
    [formatSourceDate setAMSymbol:@"AM"];
    [formatSourceDate setPMSymbol:@"PM"];

    //same day - time in h:mm am/pm
    if (components.day == sourceDateComponents.day) {
        NSLogInfo(@"time");
        [formatSourceDate setDateFormat:@"h:mm a"];
        timestamp = [NSString stringWithFormat:@"%@",[formatSourceDate stringFromDate:date]];
        return timestamp;
    }
    else if (components.day - sourceDateComponents.day == 1) {
        //yesterday
        timestamp = NSLocalizedString(@"Yesterday", nil);
        return timestamp;
    }
    if (components.year == sourceDateComponents.year) {
        //september 29, 5:56 pm
        [formatSourceDate setDateFormat:@"MMMM d"];
        timestamp = [NSString stringWithFormat:@"%@",[formatSourceDate stringFromDate:date]];
        return timestamp;
    }
    [formatSourceDate setDateFormat:@"MMMM d year"];
    timestamp = [NSString stringWithFormat:@"%@",[formatSourceDate stringFromDate:date]];
    return timestamp;

    NSLogInfo(@"Timestamp : %@",timestamp);
ChrisOSX
  • 724
  • 2
  • 11
  • 28
iosCurator
  • 4,356
  • 2
  • 21
  • 25
0
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full

let now = NSDate()


let dateMakerFormatter = DateFormatter()

dateMakerFormatter.dateFormat = "yyyy/MM/dd HH:mm:ss z"
let dateString = "2017-03-13 10:38:54 +0000"
let stPatricksDay = dateMakerFormatter.date(from: dateString)!


let calendar = NSCalendar.current



let components = calendar.dateComponents([.hour, .minute,.weekOfMonth,.day,.year,.month,.second], from: stPatricksDay, to: now as Date)



if components.year! > 0 {
    formatter.allowedUnits = .year
} else if components.month! > 0 {
    formatter.allowedUnits = .month
} else if components.weekOfMonth! > 0 {
    formatter.allowedUnits = .weekOfMonth
} else if components.day! > 0 {
    formatter.allowedUnits = .day
} else if components.hour! > 0 {
    formatter.allowedUnits = .hour
} else if components.minute! > 0 {
    formatter.allowedUnits = .minute
} else {
    formatter.allowedUnits = .second
}

let formatString = NSLocalizedString("%@ ago", comment: "Used to say how much time has passed. e.g. '2 hours ago'")

 let timeString = formatter.string(from: components)

String(format: formatString, timeString!)
garg
  • 2,651
  • 1
  • 24
  • 21