Question

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?

Was it helpful?

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

OTHER TIPS

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.

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

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

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.

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

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

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.

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

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!

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

}

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

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.

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

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top