Ordinal Month-day Suffix Option for NSDateFormatter setDateFormat

Viewed 27555

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?

17 Answers

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

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

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

Related