NSDateFormatter setDateFormat 的序数月日后缀选项

Posted

技术标签:

【中文标题】NSDateFormatter setDateFormat 的序数月日后缀选项【英文标题】:Ordinal Month-day Suffix Option for NSDateFormatter setDateFormat 【发布时间】:2009-08-15 22:41:42 【问题描述】:

我使用 NSDateFormatter 的哪个 setDateFormat 选项来获取一个月-日的序数后缀?

例如下面的 sn-p 当前生成: 8 月 15 日星期六下午 3:11

我必须改变什么才能得到: 8 月 15 日星期六下午 3:11

NSDate *date = [NSDate date];
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
[dateFormatter setDateFormat:@"h:mm a EEEE MMMM d"];
NSString *dateString = [dateFormatter stringFromDate:date]; 
NSLog(@"%@", dateString);

php 中,我会在上述情况下使用它:<?php echo date('h:m A l F jS') ?>

是否有一个 NSDateFormatter 等效于 PHP 格式化字符串中的 S 选项?

【问题讨论】:

【参考方案1】:

这些答案都不像我使用的那样美观,所以我想我会分享:


斯威夫特 3:

func daySuffix(from date: Date) -> String 
    let calendar = Calendar.current
    let dayOfMonth = calendar.component(.day, from: date)
    switch dayOfMonth 
    case 1, 21, 31: return "st"
    case 2, 22: return "nd"
    case 3, 23: return "rd"
    default: return "th"
    


目标-C:

- (NSString *)daySuffixForDate:(NSDate *)date 
    NSCalendar *calendar = [NSCalendar currentCalendar];
    NSInteger dayOfMonth = [calendar component:NSCalendarUnitDay fromDate:date];
    switch (dayOfMonth) 
        case 1:
        case 21:
        case 31: return @"st";
        case 2:
        case 22: return @"nd";
        case 3:
        case 23: return @"rd";
        default: return @"th";
    


显然,这仅适用于英语。

【讨论】:

这是最好的答案! @cbh2000 您能否包括如何将其连接到日期字符串中。我不确定为什么 dateFormatter.dateFormat = "F (daySuffix(date)) MM yy" 不起作用,谢谢! @BenSullivan 如果你打算这样做,如果应该是"F '\(daySuffix(date))' MM yy"。注意刻度线,它告诉 NSDateFormatter “th”、“st”和“rd”不应被解释为特殊符号。 不需要fallthroughs,你可以把箱子放在一起:case 1, 21, 31: 这个答案根本不处理本地化。请改为使用序数格式化程序来格式化这样的数字!【参考方案2】:
NSDate *date = [NSDate date];
NSDateFormatter *prefixDateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[prefixDateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
[prefixDateFormatter setDateFormat:@"h:mm a EEEE MMMM d"];
NSString *prefixDateString = [prefixDateFormatter stringFromDate:date];
NSDateFormatter *monthDayFormatter = [[[NSDateFormatter alloc] init] autorelease];
[monthDayFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
[monthDayFormatter setDateFormat:@"d"];     
int date_day = [[monthDayFormatter stringFromDate:date] intValue];  
NSString *suffix_string = @"|st|nd|rd|th|th|th|th|th|th|th|th|th|th|th|th|th|th|th|th|th|st|nd|rd|th|th|th|th|th|th|th|st";
NSArray *suffixes = [suffix_string componentsSeparatedByString: @"|"];
NSString *suffix = [suffixes objectAtIndex:date_day];   
NSString *dateString = [prefixDateString stringByAppendingString:suffix];   
NSLog(@"%@", dateString);

【讨论】:

我可能对此做出的唯一改进是简单地创建数组,而不是从 componentsSeparatedByString 生成它。这可以通过数组文字语法 @[ @"st", @"nd", @"rd", ... ]; 轻松完成 你在代码中有一个小错误: NSString *suffix = [suffixes objectAtIndex:date_day];那应该改为 NSString *suffix = [suffixes objectAtIndex:date_day-1];因为我们总是从 0 开始计数 :) 。否则很好的解决方案! 这是一个有效的答案,因为它完全无视用户的语言环境? NSDateFormatter 根本不支持该功能。 我对这个答案投了反对票,因为为每一天创建一个字符串,拆分字符串,然后使用这一天作为索引是脆弱和混淆的。阅读:很容易出错,可能会崩溃,而且很难跟上。我确信马特现在是一个更好的程序员,但我不同意这里的 40 多个支持者。 ios 9 通过 NSNumberFormatter 支持此功能:***.com/a/31033712/276626【参考方案3】:

这很容易做到从 iOS9 开始

NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
formatter.numberStyle = NSNumberFormatterOrdinalStyle;
NSArray<NSNumber *> *numbers = @[@1, @2, @3, @4, @5];

for (NSNumber *number in numbers) 
    NSLog(@"%@", [formatter stringFromNumber:number]);

// "1st", "2nd", "3rd", "4th", "5th"

取自NSHipster

斯威夫特 2.2:

let numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = .OrdinalStyle
let numbers: [Int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for number in numbers 
    print(numberFormatter.stringFromNumber(number)!)

【讨论】:

下面的方法虽然有效,但看起来很老套。我想要这样的东西,但我如何让它与 NSDateFormatter 一起工作?我收到一个包含日期数字的字符串。 正如你所指出的,最低iOS版本是iOS9,所以要注意这一点 这很好,但我们仍然可以使用与 NSDateFormatter 配合使用的东西。【参考方案4】:

这是生成后缀的方法的另一种实现。它产生的后缀只在英语中有效,在其他语言中可能不正确:

- (NSString *)suffixForDayInDate:(NSDate *)date

    NSInteger day = [[[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] components:NSDayCalendarUnit fromDate:date] day];
    if (day >= 11 && day <= 13) 
        return @"th";
     else if (day % 10 == 1) 
        return @"st";
     else if (day % 10 == 2) 
        return @"nd";
     else if (day % 10 == 3) 
        return @"rd";
     else 
        return @"th";
    

【讨论】:

这失败了一天,值为 11 简单修复,只需先检查 day==11。【参考方案5】:

Mac OS 10.5 和 iPhone 上的日期格式化程序使用 TR35 作为其格式说明符标准。本规范不允许在任何日期使用这样的后缀;如果你想要一个,你必须自己生成它。

【讨论】:

【参考方案6】:

这已经在 Foundation 中实现了。

let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .ordinal
numberFormatter.locale = Locale.current

numberFormatter.string(for: 1) //Should produce 1st
numberFormatter.string(for: 2) //Should produce 2nd
numberFormatter.string(for: 3) //Should produce 3rd
numberFormatter.string(for: 4) //Should produce 4th

【讨论】:

【参考方案7】:

这将分两步进行格式化:首先,创建一个带有适当后缀的日期的子字符串,然后为其余部分创建一个格式字符串,插入已经格式化的日期。

func ordinalDate(date: Date) -> String 
    let ordinalFormatter = NumberFormatter()
    ordinalFormatter.numberStyle = .ordinal
    let day = Calendar.current.component(.day, from: date)
    let dayOrdinal = ordinalFormatter.string(from: NSNumber(value: day))!

    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "h:mm a EEEE MMMM '\(dayOrdinal)'"
    return dateFormatter.string(from: Date())

由于序数日是由NumberFormatter 构建的,它应该适用于所有语言,而不仅仅是英语。

您可以通过将dateFormat 的赋值替换为以下内容来获得为当前语言环境排序的格式字符串:

dateFormatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "h:mm a EEEE MMMM d", options: 0, locale: dateFormatter.locale)?.replacingOccurrences(of: "d", with: "'\(dayOrdinal)'")

请注意其他几个人的建议,即创建格式化程序很昂贵,因此您应该在经常调用的代码中缓存和重用它们。

【讨论】:

您确定自定义格式字符串总是被 DateFormatter 接受为有效的日期格式吗? 不确定您的意思,但“单引号”中的内容是格式化字符串的文字部分。 对,所以字符串最终会变成"h:mm a EEEE MMMM '1st'"。你怎么能确定日期格式化程序支持这个字符串,因为它不再使用正确的日期格式语法? 我的意思是,你怎么知道把它放在单引号中会使日期格式化程序忽略它? 因为这就是文档所说的:Date Formatting Guide。固定格式使用Unicode Technical Standard #35定义的语法。【参考方案8】:

Matt Andersen 的回答非常详尽,SDJMcHattie 也是如此。但是 NSDateFormatter 在 cpu 上非常重,如果你称之为 100 倍,你真的会看到影响,所以这里是从上面的答案中得出的组合解决方案。 (请注意以上仍然正确)

NSDateFormatter 创建起来非常昂贵。创建一次重复使用,但要注意:它不是线程安全的,所以每个线程一个。

假设 self.date = [NSDate date];

   - (NSString *)formattedDate

    static NSDateFormatter *_dateFormatter = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
        _dateFormatter = [[NSDateFormatter alloc] init];
        _dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
        _dateFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
    );

    _dateFormatter.dateFormat = [NSString stringWithFormat:@"h:mm a EEEE MMMM d'%@'", [self suffixForDayInDate:self.date]];
   NSString *date = [_dateFormatter stringFromDate:self.date];

    return date;

/* SDJMcHattie 的代码,这比使用数组更方便 */

- (NSString *)suffixForDayInDate:(NSDate *)date
    NSInteger day = [[[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] components:NSDayCalendarUnit fromDate:date] day];
    if (day >= 11 && day <= 13) 
        return @"th";
     else if (day % 10 == 1) 
        return @"st";
     else if (day % 10 == 2) 
        return @"nd";
     else if (day % 10 == 3) 
        return @"rd";
     else 
        return @"th";
    

输出:8 月 15 日星期六下午 3:11

【讨论】:

【参考方案9】:

这将给出格式为“8 月 2 日星期六晚上 10:10”的字符串

   -(NSString*) getTimeInString:(NSDate*)date
    
        NSString* string=@"";
        NSDateComponents *components = [[NSCalendar currentCalendar] components: NSCalendarUnitDay fromDate:date];

        if(components.day == 1 || components.day == 21 || components.day == 31)
             string = @"st";
        else if (components.day == 2 || components.day == 22)
            string = @"nd";
        else if (components.day == 3 || components.day == 23)
             string = @"rd";
        else
             string = @"th";
        

        NSDateFormatter *prefixDateFormatter = [[NSDateFormatter alloc] init];    [prefixDateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
        [prefixDateFormatter setDateFormat:[NSString stringWithFormat:@"h:mm a EEEE, d'%@' MMMM",string]];

        NSString *dateString = [prefixDateFormatter stringFromDate:date];

        return dateString;
    

【讨论】:

【参考方案10】:

所有答案都没有使用 swift 中 Number Formatter 中已经存在的序数样式。

    var dateString: String 
       let calendar = Calendar.current
       let dateComponents = calendar.component(.day, from: date)
       let numberFormatter = NumberFormatter()
       numberFormatter.numberStyle = .ordinal
       let day = numberFormatter.string(from: dateComponents as NSNumber)
       let dateFormatter = DateFormatter()
       dateFormatter.dateFormat = "MMM"
       return day! + dateFormatter.string(from: date)
    

【讨论】:

【参考方案11】:

或者如果你想要任何数字的后缀:

extension Int 

    public func suffix() -> String 
        let absSelf = abs(self)

        switch (absSelf % 100) 

        case 11...13:
            return "th"
        default:
            switch (absSelf % 10) 
            case 1:
                return "st"
            case 2:
                return "nd"
            case 3:
                return "rd"
            default:
                return "th"
            
        
    

认为正数有 5 种可能性。它的第一位数字是 1 是“st”。它的第二位数字是 2 是“2nd”。它的第三位数字是 3 是“rd”。任何其他情况都是“th”,或者如果它的第二位数字是1,则上述规则不适用,它是“th”。

模 100 为我们提供了数字的最后两个数字,因此我们可以检查 11 到 13。模 10 为我们提供数字的最后一个数字,因此如果第一个条件没有捕捉到,我们可以检查 1、2、3。

在游乐场尝试该扩展:

let a = -1 

a.suffix() // "st"

let b = 1112 

b.suffix() // "th"

let c = 32 

c.suffix() // "nd"

很想看看是否有更短的方法来使用二进制操作和/或数组来编写它!

【讨论】:

【参考方案12】:
   - (void)viewDidLoad
 
  NSDate *date = [NSDate date];
        NSDateFormatter *prefixDateFormatter = [[[NSDateFormatter alloc] init] autorelease];
        [prefixDateFormatter setDateFormat:@"yyy-dd-MM"];
        date = [prefixDateFormatter dateFromString:@"2014-6-03"]; //enter yourdate
        [prefixDateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
        [prefixDateFormatter setDateFormat:@"EEEE MMMM d"];

        NSString *prefixDateString = [prefixDateFormatter stringFromDate:date];

        NSDateFormatter *monthDayFormatter = [[[NSDateFormatter alloc] init] autorelease];
        [monthDayFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];

        [monthDayFormatter setDateFormat:@"d"];
        int date_day = [[monthDayFormatter stringFromDate:date] intValue];
        NSString *suffix_string = @"|st|nd|rd|th|th|th|th|th|th|th|th|th|th|th|th|th|th|th|th|th|st|nd|rd|th|th|th|th|th|th|th|st";
        NSArray *suffixes = [suffix_string componentsSeparatedByString: @"|"];
        NSString *suffix = [suffixes objectAtIndex:date_day];
        NSString *dateString = [prefixDateString stringByAppendingString:suffix];
        NSLog(@"%@", dateString);


【讨论】:

【参考方案13】:

我将这两种方法添加到 NSDate 中,类别为 NSDate+Additions。

\- (NSString *)monthDayYear 


    NSDateFormatter * dateFormatter = NSDateFormatter.new;
    [dateFormatter setDateFormat:@"MMMM d*, YYYY"];
    NSString *dateString = [dateFormatter stringFromDate:self];

    return [dateString stringByReplacingOccurrencesOfString:@"*" withString:[self ordinalSuffixForDay]];


\- (NSString *)ordinalSuffixForDay 

NSDateFormatter * dateFormatter = NSDateFormatter.new;
[dateFormatter setDateFormat:@"d"];
NSString *dateString = [dateFormatter stringFromDate:self];
NSString *suffix = @"th";

if ([dateString length] == 2 && [dateString characterAtIndex:0] == '1') 
    return suffix;


switch ([dateString characterAtIndex:[dateString length]-1]) 
    case '1':
        suffix = @"st";
        break;
    case '2':
        suffix = @"nd";
        break;
    case '3':
        suffix = @"rd";
        break;


return suffix;

您可以通过组合它们并将格式字符串中当天的一个位数字作为切换点来索引它们,从而提高它们的效率。我选择将功能分开,以便可以针对不同的日期格式分别调用序数后缀。

【讨论】:

【参考方案14】:
- (NSString *)dayWithSuffixForDate:(NSDate *)date 

    NSInteger day = [[[NSCalendar currentCalendar] components:NSDayCalendarUnit fromDate:date] day];

    NSString *dayOfMonthWithSuffix, *suffix  = nil ;

    if(day>0 && day <=31)
    

        switch (day)
        
            case 1:
            case 21:
            case 31: suffix =  @"st";
                break;
            case 2:
            case 22: suffix = @"nd";
                break;
            case 3:
            case 23: suffix = @"rd";
                break;
            default: suffix = @"th";
                break;
        


            dayOfMonthWithSuffix = [NSString stringWithFormat:@"%ld%@", (long)day , suffix];
    


    return dayOfMonthWithSuffix;

【讨论】:

【参考方案15】:

我只是使用了这里的一些答案来自己解决这个问题,但我认为我的解决方案与这里的一些解决方案有点不同。

我喜欢使用 NumberFormatter 来正确处理序数格式(带有本地化),并使用 DateFormatter 来处理其余部分,但我不喜欢其中一些解决方案需要在每次使用时重新构建日期格式化程序(这很昂贵)。

这就是我正在使用的,它应该通过依赖 Apple 的 API 来提供不错的本地化,并且由于格式化程序的静态创建(需要 iOS 9.0+)而不应该在处理上过于繁重:

// ...in a class that needs ordinal date formatting

static let stringFormatDateFormatter: DateFormatter = 
    let formatter = DateFormatter()
    // A date format, replacing `d` with `'%@'` string format placeholder
    formatter.dateFormat = "MMM '%@', YYYY" 
    return formatter
()

static let ordinalFormatter: NumberFormatter = 
    let formatter = NumberFormatter()
    formatter.numberStyle = .ordinal
    return formatter
()

func ordinalDateString(from date: Date) -> String 

    // Pull the day from the date
    let day = NSCalendar.current.component(.day, from: date)

    // Create the ordinal, fall back to non-ordinal number due to optionality
    let dayOrdinal = Self.ordinalFormatter.string(for: day) ?? "\(day)"

    // Create the formatter with placeholder for day (e.g. "Jan %@, 2011")
    let dateStringFormat = Self.stringFormatDateFormatter.string(from: date)

    // Inject ordinal ("Jan 10th, 2011")
    return String(format: dateStringFormat, dayOrdinal)

【讨论】:

【参考方案16】:

Swift 5 - 数字格式化程序 + 日期格式化程序

您可以在 swift 中使用 NumberFormatter 中已经存在的序数样式。

func formatted(date: Date, in calendar: Calendar = Calendar.current) -> String 
    let numberFormatter = NumberFormatter()
    numberFormatter.numberStyle = .ordinal
    let day = calendar.component(.day, from: date)

    let dateFormat: String
    if let dayOrdinal = numberFormatter.string(from: NSNumber(integerLiteral: day)) 
        dateFormat = "E, '\(dayOrdinal)' MMM yyyy 'at' h:mma"
     else 
        dateFormat = "E, d MMM yyyy 'at' h:mma"
    

    let formatter = DateFormatter()
    formatter.dateFormat = dateFormat
    formatter.amSymbol = "am"
    formatter.pmSymbol = "pm"

    return formatter.string(from: date)

Obs:这样可以避免强制展开,可以使用自定义Calendar,如果需要,可以在函数外部定义date Format: String(只需通过方法声明传递)。

【讨论】:

【参考方案17】:

NSDateFormatter 文档说它支持的所有格式选项都是listed in TR35。

你为什么要这个?如果您正在为机器解析某些东西,您应该使用ISO 8601 format,如果必须的话,您应该使用RFC 2822 format。其中任何一个都不需要或允许使用序数后缀。

如果您向用户显示日期,您应该使用用户区域设置中的一种格式。

【讨论】:

显然原因是他想格式化用户将要阅读的内容。事实上,在我看来,您似乎很少有一台机器解析从另一个机器发送的日期......您似乎会选择自 1970 年 1 月 1 日以来将日期传递到毫秒。

以上是关于NSDateFormatter setDateFormat 的序数月日后缀选项的主要内容,如果未能解决你的问题,请参考以下文章

带有 NSDateFormatter 的 RestKit

共享 NSDateFormatter - 最佳实践?

NSDateFormatter 没有意义

NSDateFormatter 没有给出预期的结果

NSDateFormatter 给出不同的输出/错误(GMT?)时间

NSDateFormatter常见的使用方式