So my intention is to put out dates that would look like the following:
Today, August 28
Tomorrow, August 29
Friday, August 30
...etc
The issue
I use this approach in Swift 3:
struct SharedFormatters {
private static let dateWithRelativeFormatting: DateFormatter = {
let df = DateFormatter()
df.dateStyle = .medium
df.doesRelativeDateFormatting = true
return df
}()
private static let dateWithoutRelativeFormatting: DateFormatter = {
let df = DateFormatter()
df.dateStyle = .medium
df.doesRelativeDateFormatting = false
return df
}()
private static let longDateWithoutYear: DateFormatter = {
let df = DateFormatter()
df.dateFormat = "MMMM d"
df.doesRelativeDateFormatting = false
return df
}()
static func string(from date: Date) -> String {
let val = dateWithRelativeFormatting.string(from: date)
let val2 = dateWithoutRelativeFormatting.string(from: date)
return val == val2 ? longDateWithoutYear.string(from: date) : val
}
}
Based on the documentation about NSDateFormatter I think that the only solution for your issue is to present two date strings for the days you would like to be relatively shown. You should keep in mind that in different languages there are differences in the relative dates. From the documentation:
If a date formatter uses relative date formatting, where possible it replaces the date component of its output with a phrase—such as “today” or “tomorrow”—that indicates a relative date. The available phrases depend on the locale for the date formatter; whereas, for dates in the future, English may only allow “tomorrow,” French may allow “the day after the day after tomorrow".
I believe that if you define that you're going to show relatively for example yesterday, today and tomorrow you can use 2 NSDateFormatters. With the first one you will show the relative value and with the second you will display the actual date. For the non-relative dates you will display only the non-relative value.
It seems there is no way to accomplish this kind of date formatting without going into a custom implementation. So, the short answer to this question is 'No'.
However, the problem still exists, so below is my own solution to the problem.
Make a subclass of NSDateFormatter and implement custom init and override stringFromDate:
method.
- (instancetype)initWithDateFormat:(NSString *)dateFormat
{
self = [super init];
if (self) {
NSLocale *locale = [NSLocale currentLocale];
self.locale = locale;
self.timeStyle = NSDateFormatterNoStyle;
self.dateStyle = NSDateFormatterShortStyle;
self.doesRelativeDateFormatting = YES;
self.dateFormat = dateFormat;
}
return self;
}
- (NSString *)stringFromDate:(NSDate *)date
{
NSString *dateFormat = self.dateFormat;
self.dateFormat = nil;
BOOL didRelativeDateFormatting = self.doesRelativeDateFormatting;
self.doesRelativeDateFormatting = YES;
NSString *result = [super stringFromDate:date];
if ([result rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].location != NSNotFound) {
self.dateFormat = dateFormat;
self.doesRelativeDateFormatting = NO;
result = [super stringFromDate:date];
}
self.dateFormat = dateFormat;
self.doesRelativeDateFormatting = didRelativeDateFormatting;
return result;
}
Since doesRelativeDateFormatting
and dateFormat
are mutually exclusive we try to use relative formatting first. Given that we set self.dateStyle = NSDateFormatterShortStyle
in the init method, we know the date will contain decimal numbers unless it is replaced with words meaning 'today', 'tomorrow' and anything that may appear in other languages. If we did not spot any numbers we accept this result.
If there are digital numbers in the string we assume the relative formatting did not happen, so we apply our own date format.