Ordinal Month-day Suffix Option for NSDateFormatter setDateFormat

IphoneCocoaNsdateNsdateformatter

Iphone Problem Overview


What setDateFormat option for NSDateFormatter do I use to get a month-day's ordinal suffix?

e.g. the snippet below currently produces:
3:11 PM Saturday August 15

What must I change to get:
3:11 PM Saturday August 15th

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);

In PHP, I'd use this for the case above:
<?php echo date('h:m A l F jS') ?>

Is there an NSDateFormatter equivalent to the S option in the PHP formatting string?

Iphone Solutions


Solution 1 - Iphone

None of these answers were as aesthetically pleasing as what I'm using, so I thought I would share:


Swift 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"
    }
}

Objective-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";
    }
}

Obviously, this only works for English.

Solution 2 - Iphone

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);

Solution 3 - Iphone

This is easily done as of 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"

Taken from NSHipster

Swift 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)!)
}

Solution 4 - Iphone

Here's another implementation for a method to generate the suffix. The suffixes it produces are only valid in English and may not be correct in other languages:

- (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";
    }
}

Solution 5 - Iphone

Date formatters on Mac OS 10.5 and the iPhone use TR35 as their format specifier standard. This spec doesn't allow for such a suffix on any date; if you want one, you'll have to generate it yourself.

Solution 6 - Iphone

This is already implemented in the 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

Solution 7 - Iphone

This will do the formatting in two steps: first, create a sub-string that is the day with an appropriate suffix, then create a format string for the remaining parts, plugging in the already-formatted day.

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())
}

Since the ordinal day is built by NumberFormatter, it should work in all languages, not just English.

You could get a format string ordered for the current locale by replacing the assignment to dateFormat with this:

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

Note the advice from several others that creating formatters is expensive, so you should cache and reuse them in code that is called frequently.

Solution 8 - Iphone

Matt Andersen's answer is quite elaborate, and so is SDJMcHattie. But NSDateFormatter is quite heavy on the cpu and if you call this 100x you really see the impact, so here is a combined solution derived from the answers above. (Please note that the above are still correct)

NSDateFormatter is crazily expensive to create. Create once and reuse, but beware: it's not thread safe, so one per thread.

Assuming 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's code, this is more convenient than using an array */

- (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";
    }
}

Output: 3:11 PM Saturday August 15th

Solution 9 - Iphone

None of the answers uses the ordinal number style already present in Number Formatter in swift.

    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)
    }

Solution 10 - Iphone

This will give string in format "10:10 PM Saturday, 2nd August"

   -(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;
    }

Solution 11 - Iphone

Or if you want the suffix for any number:

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"
            }
        }
    }
}

The thinking being that there are 5 possibilities for positive numbers. It's first place digit is 1 being "st". It's second place digit is 2 being "2nd". It's third place digit is 3 being "rd". Any other case is "th", or if it's second place digit is 1, then the above rules do not apply and it is "th".

Modulo 100 gives us the digit's last two numbers, so we can check for 11 to 13. Modulo 10 gives us the digit's last number, so we can check for 1, 2, 3 if not caught by the first condition.

Try that extension in playgrounds:

let a = -1 

a.suffix() // "st"

let b = 1112 

b.suffix() // "th"

let c = 32 

c.suffix() // "nd"

Would love to see if there is an even shorter way to write this using binary operations and/or an array!

Solution 12 - Iphone

   - (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);

}

Solution 13 - Iphone

I added these two methods to NSDate with a category 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;
}

You could make them more efficient by combining them and indexing the one's place digit of the day within your format string as the switch point. I opted to separate the functionality so the ordinal suffixes can be called separately for different date formats.

Solution 14 - Iphone

- (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;
}

Solution 15 - Iphone

I just used some of the answers on here to solve this problem myself, but I think my solution is just a little bit different from some of the solutions here.

I like using NumberFormatter to properly handle ordinal formatting (with localization), and DateFormatter for the rest, but I don't like that some of these solutions require re-building the date formatter per-use (which is expensive).

Here's what I'm using, which should give decent localization by way way of leaning on Apple's APIs, and shouldn't be too heavy on processing because of the static creation of the formatters (requires 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)
}

Solution 16 - Iphone

Swift 5 - Number Formatter + Date Formatter

You can use the ordinal number style already present in NumberFormatter in swift.

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: This way you will avoid the force unwrap, you can use a custom Calendar and, if needed, you can define date Format: String outside the function (just pass it through the method declaration).

Solution 17 - Iphone

The NSDateFormatter documentation says that all the format options it supports are listed in TR35.

Why do you want this? If you're making something for a machine to parse, you should use ISO 8601 format, or RFC 2822 format if you have to. Neither one of those requires or allows an ordinal suffix.

If you're showing dates to the user, you should use one of the formats from the user's locale settings.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMatt AndersenView Question on Stackoverflow
Solution 1 - Iphonecbh2000View Answer on Stackoverflow
Solution 2 - IphoneMatt AndersenView Answer on Stackoverflow
Solution 3 - IphoneGerardView Answer on Stackoverflow
Solution 4 - IphoneSDJMcHattieView Answer on Stackoverflow
Solution 5 - IphoneTimView Answer on Stackoverflow
Solution 6 - IphoneogantopkayaView Answer on Stackoverflow
Solution 7 - IphoneSteve MadsenView Answer on Stackoverflow
Solution 8 - IphoneEdwinView Answer on Stackoverflow
Solution 9 - IphoneDhiraj DasView Answer on Stackoverflow
Solution 10 - IphoneYogesh LolusareView Answer on Stackoverflow
Solution 11 - IphoneDave ThomasView Answer on Stackoverflow
Solution 12 - IphoneParesh HirparaView Answer on Stackoverflow
Solution 13 - IphoneLyticView Answer on Stackoverflow
Solution 14 - IphoneProsenjit GoswamiView Answer on Stackoverflow
Solution 15 - IphonetbogosiaView Answer on Stackoverflow
Solution 16 - IphoneEnriqueView Answer on Stackoverflow
Solution 17 - IphonePeter HoseyView Answer on Stackoverflow