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
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;
}
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.
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".
It appears that TTTTimeIntervalFormatter in FormatterKit can also help with generating relative time interval strings.
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];
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With