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?
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?
Use the NSDate class:
timeIntervalSinceDate
returns the interval in seconds.
Quick exercise to implement this in objective-c:
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...
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.
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.
-(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";
}
}
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