views:

7702

answers:

5

I've got a timestamp as a string like:

Thu, 21 May 09 19:10:09 -0700

and I'd like to convert it to a relative time stamp like '20 minutes ago' or '3 days ago'.

What's the best way to do this using Objective-C for the iPhone?

A: 

Use the NSDate class:

timeIntervalSinceDate

returns the interval in seconds.

Quick exercise to implement this in objective-c:

  1. Get time "now" NSDate
  2. Get the NSDate you wish to compare with
  3. Get the interval in seconds using timeIntervalSinceDate

Then implement this pseudo code:

if (x < 60) // x seconds ago

else if( x/60 < 60) // floor(x/60) minutes ago

else if (x/(60*60) < 24) // floor(x/(60*60) hours ago

else if (x/(24*60*60) < 7) // floor(x(24*60*60) days ago

and so on...

then you need to decide whether a month is 30,31 or 28 days. Keep it simple - pick 30.

There might be a better way, but its 2am and this is the first thing that came to mind...

Nael El Shawwa
+10  A: 

Here are methods from Cocoa to help you to get relevant info (not sure if they are all available in coca-touch).

    NSDate * today = [NSDate date];
    NSLog(@"today: %@", today);

    NSString * str = @"Thu, 21 May 09 19:10:09 -0700";
    NSDate * past = [NSDate dateWithNaturalLanguageString:str
                            locale:[[NSUserDefaults 
                            standardUserDefaults] dictionaryRepresentation]];

    NSLog(@"str: %@", str);
    NSLog(@"past: %@", past);

    NSCalendar *gregorian = [[NSCalendar alloc]
                             initWithCalendarIdentifier:NSGregorianCalendar];
    unsigned int unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | 
                             NSDayCalendarUnit | 
                             NSHourCalendarUnit | NSMinuteCalendarUnit | 
                             NSSecondCalendarUnit;
    NSDateComponents *components = [gregorian components:unitFlags
                                                fromDate:past
                                                  toDate:today
                                                 options:0];

    NSLog(@"months: %d", [components month]);
    NSLog(@"days: %d", [components day]);
    NSLog(@"hours: %d", [components hour]);
    NSLog(@"seconds: %d", [components second]);

The NSDateComponents object seems to hold the difference in relevant units (as specified). If you specify all units you can then use this method:

void dump(NSDateComponents * t)
{
    if ([t year]) NSLog(@"%d years ago", [t year]);
    else if ([t month]) NSLog(@"%d months ago", [t month]);
    else if ([t day]) NSLog(@"%d days ago", [t day]);
    else if ([t minute]) NSLog(@"%d minutes ago", [t minute]);
    else if ([t second]) NSLog(@"%d seconds ago", [t second]);
}

If you want to calculate yourself you can have a look at:

NSDate timeIntervalSinceDate

And then use seconds in the algorithm.

Disclaimer: If this interface is getting deprecated (I haven't checked), Apple's preferred way of doing this via NSDateFormatters, as suggested in comments below, looks pretty neat as well - I'll keep my answer for historical reasons, it may still be useful for some to look at the logic used.

stefanB
ahh very nifty solution, thank you!
Nael El Shawwa
Note: the old NSCalendarDate/NSDateComponents way is being deprecated on Mac OS X. Apple seems to recommend using NSDateFormatters exclusively now. They make it quite easy to juggle with any components. Also see Gilean's answer.
andreb
A: 

Not sure why this isnt in cocoa-touch, i nice standard way of doing this would be great.

Set up some types to keep the data in, it will make it easier if you ever ned to localise it a bit more. (obviously expand if you need more time periods)

typedef struct DayHours {
    int Days;
    double Hours;
} DayHours;


+ (DayHours) getHourBasedTimeInterval:(double) hourBased withHoursPerDay:(double) hpd
{
    int NumberOfDays = (int)(fabs(hourBased) / hpd);
    float hoursegment = fabs(hourBased) - (NumberOfDays * hpd);
    DayHours dh;
    dh.Days = NumberOfDays;
    dh.Hours = hoursegment;
    return dh;
}

NOTE: I"m using an hour based calculation , as that is what my data is in. NSTimeInterval is second based. I also had to convert between the two.

Bluephlame
+10  A: 
-(NSString *)dateDiff:(NSString *)origDate {
    NSDateFormatter *df = [[NSDateFormatter alloc] init];
    [df setFormatterBehavior:NSDateFormatterBehavior10_4];
    [df setDateFormat:@"EEE, dd MMM yy HH:mm:ss VVVV"];
    NSDate *convertedDate = [df dateFromString:origDate];
    [df release];
    NSDate *todayDate = [NSDate date];
    double ti = [convertedDate timeIntervalSinceDate:todayDate];
    ti = ti * -1;
    if(ti < 1) {
     return @"never";
    } else  if (ti < 60) {
     return @"less than a minute ago";
    } else if (ti < 3600) {
     int diff = round(ti / 60);
     return [NSString stringWithFormat:@"%d minutes ago", diff];
    } else if (ti < 86400) {
     int diff = round(ti / 60 / 60);
     return[NSString stringWithFormat:@"%d hours ago", diff];
    } else if (ti < 2629743) {
     int diff = round(ti / 60 / 60 / 24);
     return[NSString stringWithFormat:@"%d days ago", diff];
    } else {
     return @"never";
    } 
}
Gilean
It can not handle internationalization. Wish Apple add relative time format to NSDateFormatter. Android has it.
an0
to add internationalization, just wrap the strings with NSLocalizedString() and you're good to go for most cases.
Carl Coryell-Martin
+4  A: 

I can't edit yet, but I took Gilean's code and made a couple of tweaks and made it a category of NSDateFormatter.

It accepts a format string so it will work w/ arbitrary strings and I added if clauses to have singular events be grammatically correct.

Cheers,

Carl C-M

@interface NSDateFormatter (Extras)
+ (NSString *)dateDifferenceStringFromString:(NSString *)dateString
                                  withFormat:(NSString *)dateFormat;

@end

@implementation NSDateFormatter (Extras)

+ (NSString *)dateDifferenceStringFromString:(NSString *)dateString
                                  withFormat:(NSString *)dateFormat
{
  NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
  [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
  [dateFormatter setDateFormat:dateFormat];
  NSDate *date = [dateFormatter dateFromString:dateString];
  [dateFormatter release];
  NSDate *now = [NSDate date];
  double time = [date timeIntervalSinceDate:now];
  time *= -1;
  if(time < 1) {
    return dateString;
  } else if (time < 60) {
    return @"less than a minute ago";
  } else if (time < 3600) {
    int diff = round(time / 60);
    if (diff == 1) 
      return [NSString stringWithFormat:@"1 minute ago", diff];
    return [NSString stringWithFormat:@"%d minutes ago", diff];
  } else if (time < 86400) {
    int diff = round(time / 60 / 60);
    if (diff == 1)
      return [NSString stringWithFormat:@"1 hour ago", diff];
    return [NSString stringWithFormat:@"%d hours ago", diff];
  } else if (time < 604800) {
    int diff = round(time / 60 / 60 / 24);
    if (diff == 1) 
      return [NSString stringWithFormat:@"yesterday", diff];
    if (diff == 7) 
      return [NSString stringWithFormat:@"last week", diff];
    return[NSString stringWithFormat:@"%d days ago", diff];
  } else {
    int diff = round(time / 60 / 60 / 24 / 7);
    if (diff == 1)
      return [NSString stringWithFormat:@"last week", diff];
    return [NSString stringWithFormat:@"%d weeks ago", diff];
  }   
}

@end
Carl Coryell-Martin