Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Relative string from NSDate

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
like image 525
keegan3d Avatar asked Feb 08 '11 19:02

keegan3d


5 Answers

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;
}
like image 28
MikeyWard Avatar answered Nov 05 '22 10:11

MikeyWard


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.

like image 139
David Schaefgen Avatar answered Nov 05 '22 11:11

David Schaefgen


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".

like image 5
Jeffrey Sun Avatar answered Nov 05 '22 10:11

Jeffrey Sun


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

like image 3
Isaac Avatar answered Nov 05 '22 12:11

Isaac


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];

    }
}
like image 1
retcon Avatar answered Nov 05 '22 10:11

retcon