NSDateFormatter has a lovely support for producing relative dates such as "Today", "Tomorrow", "Yesterday" when these are supported by the current language. A great advantage is that all of these are localised for you already – you don't need to translate the strings.
You can turn on this functionality with:
[dateFormatter setDoesRelativeDateFormatting: YES];
The down side is that this only seems to work for an instance that is using one of the predefined formats, such as:
[dateFormatter setDateStyle: NSDateFormatterShortStyle];
If you set up the date formatter to use a custom format, like this:
[dateFormatter setDateStyle: @"EEEE"];
Then when you call:
[dateFormatter stringFromDate: date];
… you will just get back an empty string.
I'd like to be able to get relative strings when possible, and use my own custom format when it is not.
I set up a category on NSDateFormatter to provide a workaround for this. An explanation follows the code.
In NSDateFormatter+RelativeDateFormat.h:
@interface NSDateFormatter (RelativeDateFormat)
-(NSString*) relativeStringFromDateIfPossible:(NSDate *)date;
@end
In NSDateFormatter+RelativeDateFormat.m:
@implementation NSDateFormatter (RelativeDateFormat)
-(NSString*) relativeStringFromDateIfPossible:(NSDate *)date
{
static NSDateFormatter *relativeFormatter;
static NSDateFormatter *absoluteFormatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
const NSDateFormatterStyle arbitraryStyle = NSDateFormatterShortStyle;
relativeFormatter = [[NSDateFormatter alloc] init];
[relativeFormatter setDateStyle: arbitraryStyle];
[relativeFormatter setTimeStyle: NSDateFormatterNoStyle];
[relativeFormatter setDoesRelativeDateFormatting: YES];
absoluteFormatter = [[NSDateFormatter alloc] init];
[absoluteFormatter setDateStyle: arbitraryStyle];
[absoluteFormatter setTimeStyle: NSDateFormatterNoStyle];
[absoluteFormatter setDoesRelativeDateFormatting: NO];
});
NSLocale *const locale = [self locale];
if([relativeFormatter locale] != locale)
{
[relativeFormatter setLocale: locale];
[absoluteFormatter setLocale: locale];
}
NSCalendar *const calendar = [self calendar];
if([relativeFormatter calendar] != calendar)
{
[relativeFormatter setCalendar: calendar];
[absoluteFormatter setCalendar: calendar];
}
NSString *const maybeRelativeDateString = [relativeFormatter stringFromDate: date];
const BOOL isRelativeDateString = ![maybeRelativeDateString isEqualToString: [absoluteFormatter stringFromDate: date]];
if(isRelativeDateString)
{
return maybeRelativeDateString;
}
else
{
return [self stringFromDate: date];
}
}
@end
It maintains two date formatters using an (arbitrary) standard format. They format identically except that one will provide relative date strings and the other will not.
By formatting a given date with both formatters, it is possible to see if the relative date formatter is giving a special relative date. There is a special relative date when the two formatters give different results.
You can find out more about dispatch_once
here.
The implementation does not handle time components that you may have placed in to your format string. When a relative date string is available, your format sting is ignored and you get the relative date string.