NSDateFormatter relative date formatting with custom format

元气小坏坏 提交于 2019-12-04 02:22:29
Lubakis

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.

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
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!