Ordinal Month-day Suffix Option for NSDateFormatter setDateFormat
IphoneCocoaNsdateNsdateformatterIphone 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.