13

Does anyone know of a library or something that will convert an NSDate into strings like the examples below?

1 hour ago
yesterday
last Thursday
2 days ago
last month
6 months ago
last year
keegan3d
  • 10,357
  • 9
  • 53
  • 77

5 Answers5

17

NSDateFormatter will do a great deal of what is mentioned above by using setDoesRelativeDateFormatting: on an instance. From there, you can control the formatting using the date style and the time style to fine tune it to your needs.

If that doesn't get you what you need then check out SORelativeDateTransformer, it is an NSValueTransformer subclass that attempts to provide the same behavior you see with relative dates on SO.

Finally, if you want to roll your own or do more research here is the (likely) original question on the topic... yes it is question 11.

Community
  • 1
  • 1
David Schaefgen
  • 800
  • 7
  • 9
  • 1
    Funny, I've *never* seen that method on `NSDateFormatter` before, and I swear I've looked for it... – Dave DeLong Feb 08 '11 at 21:02
  • Awesome, very thorough answer! SORelativeDateTransformer is exactly what I'm looking for and knowing about NSDateFormatter is useful too. – keegan3d Feb 09 '11 at 02:18
  • 4
    Worth noting that `NSDateFormatter` only has a handful of these relative strings. It's pretty much just "Tomorrow", "Today", and "Yesterday". – jscs Oct 12 '11 at 02:24
16

Date math sucks. Check out Dave Delong's Chronology library (https://github.com/davedelong/Chronology), or, for iOS 13+, (NS)RelativeDateTimeFormatter

I'm leaving the below for posterity, but it is decidedly the Wrong Thing To Do. My updated answer is above.

Here's an NSDate category method I threw together that uses the Stack Overflow relativity calculation linked by David as well as other tips cobbled from other similar SO questions:

- (NSString *)relativeDateString
{
    const int SECOND = 1;
    const int MINUTE = 60 * SECOND;
    const int HOUR = 60 * MINUTE;
    const int DAY = 24 * HOUR;
    const int MONTH = 30 * DAY;

    NSDate *now = [NSDate date];
    NSTimeInterval delta = [self timeIntervalSinceDate:now] * -1.0;

    NSCalendar *calendar = [NSCalendar currentCalendar];
    NSUInteger units = (NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit);
    NSDateComponents *components = [calendar components:units fromDate:self toDate:now options:0];

    NSString *relativeString;

    if (delta < 0) {
        relativeString = @"!n the future!";

    } else if (delta < 1 * MINUTE) {
        relativeString = (components.second == 1) ? @"One second ago" : [NSString stringWithFormat:@"%d seconds ago",components.second];

    } else if (delta < 2 * MINUTE) {
        relativeString =  @"a minute ago";

    } else if (delta < 45 * MINUTE) {
        relativeString = [NSString stringWithFormat:@"%d minutes ago",components.minute];

    } else if (delta < 90 * MINUTE) {
        relativeString = @"an hour ago";

    } else if (delta < 24 * HOUR) {
        relativeString = [NSString stringWithFormat:@"%d hours ago",components.hour];

    } else if (delta < 48 * HOUR) {
        relativeString = @"yesterday";

    } else if (delta < 30 * DAY) {
        relativeString = [NSString stringWithFormat:@"%d days ago",components.day];

    } else if (delta < 12 * MONTH) {
        relativeString = (components.month <= 1) ? @"one month ago" : [NSString stringWithFormat:@"%d months ago",components.month];

    } else {
        relativeString = (components.year <= 1) ? @"one year ago" : [NSString stringWithFormat:@"%d years ago",components.year];

    }

    return relativeString;
}
MikeyWard
  • 1,123
  • 9
  • 19
5

DO NOT use your own custom string translation. Writing your own translation that does something like "3 seconds ago" not only has the problem of needing to be updated constantly, but it also does not localize into other languages.

Instead, use Apple's built in NSDateFormatter:

NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.doesRelativeDateFormatting = YES;
formatter.locale = [NSLocale currentLocale];
formatter.dateStyle = NSDateFormatterShortStyle;
formatter.timeStyle = NSDateFormatterShortStyle;
NSString *timeString = [formatter stringFromDate:self.date];

This will output something like "Today, 11:10 PM", or "7/17/14, 5:44 AM".

Jeffrey Sun
  • 7,789
  • 1
  • 24
  • 17
3

It appears that TTTTimeIntervalFormatter in FormatterKit can also help with generating relative time interval strings.

Isaac
  • 10,668
  • 5
  • 59
  • 68
1

DateTools is an awesome comprehensive framework for doing all sorts of date related functions. https://github.com/MatthewYork/DateTools

As an NSDate category, it's easier to use than SORelativeDateTransformer.

However, there's an issue with getting accurate day values. (E.g. If today is Monday morning, Saturday evening will be reported as "Yesterday"; should be "Two days ago")

This should work better (using DateTools):

- (NSString *)relativeDateStringForDate:(NSDate *)d
{
    if ([d daysAgo] < 1) {
        //Return as is
        return [d timeAgoSinceNow];
    }
    else { //More than 24 hours ago. The default implementation will underreport (round down) day durations.
           //We should modify the reference date we're using to midnight on the current day.

        NSDate *date = [NSDate date];
        date = [date dateByAddingDays:1];
        NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
        NSUInteger preservedComponents = (NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit);
        NSDate *todayAtMidnight = [calendar dateFromComponents:[calendar components:preservedComponents fromDate:date]];

        return [d timeAgoSinceDate:todayAtMidnight];

    }
}
retcon
  • 43
  • 4