开发者

NSNumberFormatter and 'th' 'st' 'nd' 'rd' (ordinal) number endings

开发者 https://www.devze.com 2023-01-08 17:04 出处:网络
Is there a way to use NSNumberFormatter to get the \'th\' \'st\' \'nd\' \'rd\' number endings? EDIT: Looks like it does not exist. Here\'s what I\'m using.

Is there a way to use NSNumberFormatter to get the 'th' 'st' 'nd' 'rd' number endings?

EDIT:

Looks like it does not exist. Here's what I'm using.

+(NSString*)ordinalNumberFormat:(NSInteger)num{
    NSString *ending;

    int ones = num % 10;
    int tens = floor(num / 10);
    tens = tens % 10;
    if(tens == 1){
        ending = @"th";
    }else {
        switch (ones) {
            case 1:
                ending = @"st";
       开发者_StackOverflow         break;
            case 2:
                ending = @"nd";
                break;
            case 3:
                ending = @"rd";
                break;
            default:
                ending = @"th";
                break;
        }
    }
    return [NSString stringWithFormat:@"%d%@", num, ending];
}

Adapted from nickf's answer here Is there an easy way in .NET to get "st", "nd", "rd" and "th" endings for numbers?


The correct way to do this from iOS 9 onwards, is:

NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
numberFormatter.numberStyle = NSNumberFormatterOrdinalStyle;

NSLog(@"%@", [numberFormatter stringFromNumber:@(1)]); // 1st
NSLog(@"%@", [numberFormatter stringFromNumber:@(2)]); // 2nd
NSLog(@"%@", [numberFormatter stringFromNumber:@(3)]); // 3rd, etc.

Alternatively:

NSLog(@"%@", [NSString localizedStringFromNumber:@(1)
                                     numberStyle:NSNumberFormatterOrdinalStyle]); // 1st


As of iOS 9

Swift 4

private var ordinalFormatter: NumberFormatter = {
    let formatter = NumberFormatter()
    formatter.numberStyle = .ordinal
    return formatter
}()

extension Int {
    var ordinal: String? {
        return ordinalFormatter.string(from: NSNumber(value: self))
    }
}

It's probably best to have the formatter outside the extension...


This does the trick in one method (for English). Thanks nickf https://stackoverflow.com/a/69284/1208690 for original code in PHP, I just adapted it to objective C:-

-(NSString *) addSuffixToNumber:(int) number
{
    NSString *suffix;
    int ones = number % 10;
    int tens = (number/10) % 10;

    if (tens ==1) {
        suffix = @"th";
    } else if (ones ==1){
        suffix = @"st";
    } else if (ones ==2){
        suffix = @"nd";
    } else if (ones ==3){
        suffix = @"rd";
    } else {
        suffix = @"th";
    }

    NSString * completeAsString = [NSString stringWithFormat:@"%d%@", number, suffix];
    return completeAsString;
}


Other Swift solutions do not produce correct result and contain mistakes. I have translated CmKndy solution to Swift

extension Int {

    var ordinal: String {
        var suffix: String
        let ones: Int = self % 10
        let tens: Int = (self/10) % 10
        if tens == 1 {
            suffix = "th"
        } else if ones == 1 {
            suffix = "st"
        } else if ones == 2 {
            suffix = "nd"
        } else if ones == 3 {
            suffix = "rd"
        } else {
            suffix = "th"
        }
        return "\(self)\(suffix)"
    }

}

test result: 0th 1st 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd


Since the question asked for a number formatter, here's a rough one I made.

//
//  OrdinalNumberFormatter.h
//

#import <Foundation/Foundation.h>


@interface OrdinalNumberFormatter : NSNumberFormatter {

}

@end

and the implementation:

//
//  OrdinalNumberFormatter.m
//

#import "OrdinalNumberFormatter.h"


@implementation OrdinalNumberFormatter

- (BOOL)getObjectValue:(id *)anObject forString:(NSString *)string errorDescription:(NSString **)error {
    NSInteger integerNumber;
    NSScanner *scanner;
    BOOL isSuccessful = NO;
    NSCharacterSet *letters = [NSCharacterSet letterCharacterSet];

    scanner = [NSScanner scannerWithString:string];
    [scanner setCaseSensitive:NO];
    [scanner setCharactersToBeSkipped:letters];

    if ([scanner scanInteger:&integerNumber]){
        isSuccessful = YES;
        if (anObject) {
            *anObject = [NSNumber numberWithInteger:integerNumber];
        }
    } else {
        if (error) {
            *error = [NSString stringWithFormat:@"Unable to create number from %@", string];
        }
    }

    return isSuccessful;
}

- (NSString *)stringForObjectValue:(id)anObject {
    if (![anObject isKindOfClass:[NSNumber class]]) {
        return nil;
    }

    NSString *strRep = [anObject stringValue];
    NSString *lastDigit = [strRep substringFromIndex:([strRep length]-1)];

    NSString *ordinal;


    if ([strRep isEqualToString:@"11"] || [strRep isEqualToString:@"12"] || [strRep isEqualToString:@"13"]) {
        ordinal = @"th";
    } else if ([lastDigit isEqualToString:@"1"]) {
        ordinal = @"st";
    } else if ([lastDigit isEqualToString:@"2"]) {
        ordinal = @"nd";
    } else if ([lastDigit isEqualToString:@"3"]) {
        ordinal = @"rd";
    } else {
        ordinal = @"th";
    }

    return [NSString stringWithFormat:@"%@%@", strRep, ordinal];
}

@end

Instantiate this as an Interface Builder object and attach the Text Field's formatter outlet to it. For finer control (such as setting maximum and minimum values, you should create an instance of the formatter, set the properties as you wish and attach it to text field using it's setFormatter: method.

You can download the class from GitHub (including an example project)


-- Swift 4/5 --

     let num = 1
     let formatter = NumberFormatter()
     formatter.numberStyle = .ordinal
     let day = formatter.string(from: NSNumber(value: num))
     
     print(day!)
     result - 1st


It's quite simple in English. Here's a swift extension:

extension Int {
    var ordinal: String {
        get {
            var suffix = "th"
            switch self % 10 {
                case 1:
                    suffix = "st"
                case 2:
                    suffix = "nd"
                case 3:
                    suffix = "rd"
                default: ()
            }
            if 10 < (self % 100) && (self % 100) < 20 {
                suffix = "th"
            }
            return String(self) + suffix
        }
    }
}

Then call something like:

    cell.label_position.text = (path.row + 1).ordinal


Just adding another implementation as a class method. I didn't see this question posted until after I implemented this from an example in php.

+ (NSString *)buildRankString:(NSNumber *)rank
{
    NSString *suffix = nil;
    int rankInt = [rank intValue];
    int ones = rankInt % 10;
    int tens = floor(rankInt / 10);
    tens = tens % 10;
    if (tens == 1) {
        suffix = @"th";
    } else {
        switch (ones) {
            case 1 : suffix = @"st"; break;
            case 2 : suffix = @"nd"; break;
            case 3 : suffix = @"rd"; break;
            default : suffix = @"th";
        }
    }
    NSString *rankString = [NSString stringWithFormat:@"%@%@", rank, suffix];
    return rankString;
}


Here's a compact Swift extension suitable for all integer types:

extension IntegerType {
    func ordinalString() -> String {
        switch self % 10 {
        case 1...3 where 11...13 ~= self % 100: return "\(self)" + "th"
        case 1:    return "\(self)" + "st"
        case 2:    return "\(self)" + "nd"
        case 3:    return "\(self)" + "rd"
        default:   return "\(self)" + "th"
        }
    }
}

Example usage:

let numbers = (0...30).map { $0.ordinalString() }
print(numbers.joinWithSeparator(", "))

Output:

0th, 1st, 2nd, 3rd, 4th, 5th, 6th, 7th, 8th, 9th, 10th, 11th, 12th, 13th, 14th, 15th, 16th, 17th, 18th, 19th, 20th, 21st, 22nd, 23rd, 24th, 25th, 26th, 27th, 28th, 29th, 30th


There is a simple solution for this

Swift

let formatter = NumberFormatter()
formatter.numberStyle = .ordinal
let first = formatter.string(from: 1) // 1st
let second = formatter.string(from: 2) // 2nd

Obj-c

NSNumberFormatter *numberFormatter = [NSNumberFormatter new];
numberFormatter.numberStyle = NSNumberFormatterOrdinalStyle;
NSString* first = [numberFormatter stringFromNumber:@(1)]; // 1st
NSString* second = [numberFormatter stringFromNumber:@(2)]; // 2nd

Referance: hackingwithswift.com


I'm not aware of this capability. However, it's possible to do this yourself. In English, the ordinal (th, st, nd, rd, etc) has a really simple pattern:

If the number ends with: => Use:

  • 0 => th
  • 1 => st
  • 2 => nd
  • 3 => rd
  • 4 => th
  • 5 => th
  • 6 => th
  • 7 => th
  • 8 => th
  • 9 => th
  • 11 => th
  • 12 => th
  • 13 => th

This will not spell out the word for you, but it will allow you to do something like: "42nd", "1,340,697th", etc.

This gets more complicated if you need it localized.


A clean Swift version (for English only):

func ordinal(number: Int) -> String {
    if (11...13).contains(number % 100) {
        return "\(number)th"
    }
    switch number % 10 {
        case 1: return "\(number)st"
        case 2: return "\(number)nd"
        case 3: return "\(number)rd"
        default: return "\(number)th"
    }
}

Can be done as an extension for Int:

extension Int {

    func ordinal() -> String {
        return "\(self)\(ordinalSuffix())"
    }

    func ordinalSuffix() -> String {
        if (11...13).contains(self % 100) {
            return "th"
        }
        switch self % 10 {
            case 1: return "st"
            case 2: return "nd"
            case 3: return "rd"
            default: return "th"
        }
    }

}


The following example demonstrates how to handle any number. It's in c# however it can easily converted to any language.

http://www.bytechaser.com/en/functions/b6yhfyxh78/convert-number-to-ordinal-like-1st-2nd-in-c-sharp.aspx


This will convert date to string and also add ordinal in the date. You can modify the date formatte by changing NSDateFormatter object

-(NSString*) getOrdinalDateString:(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 *dateFormatte = [[NSDateFormatter alloc] init];
    [dateFormatte setFormatterBehavior:NSDateFormatterBehavior10_4];
    [dateFormatte setDateFormat:[NSString stringWithFormat:@"d'%@' MMMM yyyy",string]];

    NSString *dateString = [dateFormatte stringFromDate:date];
    return dateString;
}


Here's a Swift solution that cycles through the user's preferred languages until it finds one with known rules (which are pretty easy to add) for ordinal numbers:

    extension Int {
        var localizedOrdinal: String {

            func ordinalSuffix(int: Int) -> String {
                for language in NSLocale.preferredLanguages() as [String] {

                switch language {
                    case let l where l.hasPrefix("it"):
                        return "°"
                    case let l where l.hasPrefix("en"):
                        switch int {
                        case let x where x != 11 && x % 10 == 1:
                            return "st"
                        case let x where x != 12 && x % 10 == 2:
                            return "nd"
                        case let x where x != 13 && x % 10 == 3:
                            return "rd"
                        default:
                            return "th"
                        }
                    default:
                        break
                    }
                }

                return ""
            }

            return "\(self)" + ordinalSuffix(self)
        }
    }


Many of the solutions here don't handle higher numbers like 112. Here is a simple way to do it.

for(int i=0;i<1000;i++){
    int n = i;
    NSString* ordinal = @"th";
    if(n%10==1 && n%100!=11) ordinal = @"st";
    if(n%10==2 && n%100!=12) ordinal = @"nd";
    if(n%10==3 && n%100!=13) ordinal = @"rd";
    NSLog(@"You are the %d%@",i,ordinal);
}


Here's a short Int extension for the English language that also accounts for and displays negative integers correctly:

extension Int {
    func ordinal() -> String {
        let suffix: String!
        // treat negative numbers as positive for suffix
        let number = (self < 0 ? self * -1 : self)

        switch number % 10 {
        case 0:
            suffix = self != 0 ? "th" : ""
        case 1:
            suffix = "st"
        case 2:
            suffix = "nd"
        case 3:
            suffix = "rd"
        default:
            suffix = "th"
        }

        return String(self) + suffix
    }
}


- (NSString *) formatOrdinalNumber:(NSInteger )number{
    NSString *result = nil;
    //0 remains just 0
    if (number == 0) {
        result = @"0";
    }

    //test for number between 3 and 21 as they follow a
    //slightly different rule and all end with th
    else if (number > 3 && number < 21)
    {
        result = [NSString stringWithFormat:@"%ld th",(long)number];
    }
    else {
        //return the last digit of the number e.g. 102 is 2
        NSInteger lastdigit = number % 10;
        switch (lastdigit)
        {
            case 1: result = [NSString stringWithFormat:@"%ld st",(long)number]; break;
            case 2: result = [NSString stringWithFormat:@"%ld nd",(long)number]; break;
            case 3: result = [NSString stringWithFormat:@"%ld rd",(long)number]; break;
            default: result = [NSString stringWithFormat:@"%ld th",(long)number];
        }
    }
    return result;
}


You can try this, Its well simplified.

function numberToOrdinal(n) {

  if (n==0) {
    return n;
   }
   var j = n % 10,
       k = n % 100;


   if (j == 1 && k != 11) {
       return n + "st";
   }
   if (j == 2 && k != 12) {
       return n + "nd";
   }
   if (j == 3 && k != 13) {
       return n + "rd";
   }
   return n + "th";
}


public extension Int {
 var ordinalValue: String? {
    let formatter = NumberFormatter()
    formatter.numberStyle = .ordinal
    formatter.locale = Locale(identifier: "en_US")
    return formatter.string(from: NSNumber(value: self))
 }
}

Don't forget to add the Locale, if it is not added, it won't work. Usage:

let number = 2
let ordinalNumber = number.ordinalValue
print(ordinalNumber) //It will print 2nd


This was my brute force implementation to taking a NSString* representation of the date and returning the ordinal value. I feel it's much easier to read.

NSDictionary *ordinalDates = @{
    @"1": @"1st",
    @"2": @"2nd",
    @"3": @"3rd",
    @"4": @"4th",
    @"5": @"5th",
    @"6": @"6th",
    @"7": @"7th",
    @"8": @"8th",
    @"9": @"9th",
    @"10": @"10th",
    @"11": @"11th",
    @"12": @"12th",
    @"13": @"13th",
    @"14": @"14th",
    @"15": @"15th",
    @"16": @"16th",
    @"17": @"17th",
    @"18": @"18th",
    @"19": @"19th",
    @"20": @"20th",
    @"21": @"21st",
    @"22": @"22nd",
    @"23": @"23rd",
    @"24": @"24th",
    @"25": @"25th",
    @"26": @"26th",
    @"27": @"27th",
    @"28": @"28th",
    @"29": @"29th",
    @"30": @"30th",
    @"31": @"31st" };
0

精彩评论

暂无评论...
验证码 换一张
取 消