Is there an easy way to create ordinals in C#?
Asked Answered
W

23

232

Is there an easy way in C# to create Ordinals for a number? For example:

  • 1 returns 1st
  • 2 returns 2nd
  • 3 returns 3rd
  • ...etc

Can this be done through String.Format() or are there any functions available to do this?

Whorton answered 21/8, 2008 at 14:55 Comment(0)
B
353

This page gives you a complete listing of all custom numerical formatting rules:

Custom numeric format strings

As you can see, there is nothing in there about ordinals, so it can't be done using String.Format. However its not really that hard to write a function to do it.

public static string AddOrdinal(int num)
{
    if( num <= 0 ) return num.ToString();

    switch(num % 100)
    {
        case 11:
        case 12:
        case 13:
            return num + "th";
    }
    
    switch(num % 10)
    {
        case 1:
            return num + "st";
        case 2:
            return num + "nd";
        case 3:
            return num + "rd";
        default:
            return num + "th";
    }
}

Update: Technically Ordinals don't exist for <= 0, so I've updated the code above. Also removed the redundant ToString() methods.

Also note, this is not internationalized. I've no idea what ordinals look like in other languages.

Bael answered 21/8, 2008 at 15:3 Comment(14)
Assert.AreEqual("0", AddOrdinal(0)); See wisegeek.com/what-is-an-ordinal-number.htmSchramke
Using an extention method (or whatever it's called -- see @Stu's answer) would work great here. @Si, Adding that condition would be very easy if it is required.Twedy
If I made it an extension method I would call it "ToOrdinalString".Bael
Forgot about '11th, 12th 13th'... should be an interview question. :-)Gayla
Nice answer. As a note, the .ToString() calls are redundant, removing these improves readability (slightly)Efficacious
Don't programmers typically refer to the 0th item?Marielamariele
Yeah, well programmers are weird ;)Bael
There are four redundant string concatenations. :)Ker
@IanWarburton what redundant concatenations? I was not attempting to create the smallest code, but readable code. I still don't see any redundant concatenations though.Bael
@Bael num + appears five times. Why not look up the text and then do a single concatenation.Ker
@IanWarburton There is no redundancy since only a single return statement will be hit. If you aren't happy with the answer, please supply your own, showing us the "proper" way to do this, and why it matters.Hydracid
Does anyone how to convert 1 to First , 2 to Second... and so on. The long version of the ordinals, not the short like 1st, 2nd, etc. ?Hic
Ian Warburton's Apr 6 '17 at 16:32 answer (with my edit suggestion) is the answer so far that's: a) most factored and b) not unnecessarily cryptic and c) not significantly inefficient.Sulfapyridine
Just for completeness sake I've added another answer based on pattern matching and making use of only one switch statement. Added also some benchmarks for reference https://mcmap.net/q/117320/-is-there-an-easy-way-to-create-ordinals-in-cSpade
L
78

Remember internationalisation!

The solutions here only work for English. Things get a lot more complex if you need to support other languages.

For example, in Spanish "1st" would be written as "1.o", "1.a", "1.os" or "1.as" depending on whether the thing you're counting is masculine, feminine or plural!

So if your software needs to support different languages, try to avoid ordinals.

Lyophilic answered 22/9, 2008 at 15:17 Comment(5)
@ Andomar: "The first 2 readers" => in Italian (and Spanish too, I suppose) "first" is plural here. So you have singular masculine, singulare feminine, plural masculine, plural feminine; maybe some language has also a neutral case (distinguing things from men/animals)Silsby
That said, you don't have to avoid ordinals: include them in localization, once you know all the case you could face, or (make your customer) accept some limitations.Silsby
This explains why the .NET team steered clear of adding it to the DateTime formattersPhospholipide
moment.js has an "ordinal" formatting function by locale so it seems doable, also wish they would have done it in .NET for DateTimeConrado
It would all be very simple if you all used the "." character for ordinals, like we do in German )))) 1. 2. 3. 4. 5. , etc. Though the algorithm would be that much more interesting if one would write out the number, and have to append inflection in 4 grammatical cases with 3 different articles, in addition to the singular and plural cases of the 12 different combinations. Come to think of it, don't the russians have 2 more, plus vocativ, and some nordic languages have 15, i think. I'd have loved to see that implementation in .NET.Gift
I
24

Simple, clean, quick

private static string GetOrdinalSuffix(int num)
{
    string number = num.ToString();
    if (number.EndsWith("11")) return "th";
    if (number.EndsWith("12")) return "th";
    if (number.EndsWith("13")) return "th";
    if (number.EndsWith("1")) return "st";
    if (number.EndsWith("2")) return "nd";
    if (number.EndsWith("3")) return "rd";
    return "th";
}

Or better yet, as an extension method

public static class IntegerExtensions
{
    public static string DisplayWithSuffix(this int num)
    {
        string number = num.ToString();
        if (number.EndsWith("11")) return number + "th";
        if (number.EndsWith("12")) return number + "th";
        if (number.EndsWith("13")) return number + "th";
        if (number.EndsWith("1")) return number + "st";
        if (number.EndsWith("2")) return number + "nd";
        if (number.EndsWith("3")) return number + "rd";
        return number + "th";
    }
}

Now you can just call

int a = 1;
a.DisplayWithSuffix(); 

or even as direct as

1.DisplayWithSuffix();
Intercalate answered 17/10, 2014 at 21:12 Comment(1)
Better put the result of num.ToString() into a variable avoiding a lot of method calls.Ivers
S
23

My version of Jesse's version of Stu's and samjudson's versions :)

Included unit test to show that the accepted answer is incorrect when number < 1

/// <summary>
/// Get the ordinal value of positive integers.
/// </summary>
/// <remarks>
/// Only works for english-based cultures.
/// Code from: https://mcmap.net/q/117320/-is-there-an-easy-way-to-create-ordinals-in-c/31066#31066
/// With help: http://www.wisegeek.com/what-is-an-ordinal-number.htm
/// </remarks>
/// <param name="number">The number.</param>
/// <returns>Ordinal value of positive integers, or <see cref="int.ToString"/> if less than 1.</returns>
public static string Ordinal(this int number)
{
    const string TH = "th";
    string s = number.ToString();

    // Negative and zero have no ordinal representation
    if (number < 1)
    {
        return s;
    }

    number %= 100;
    if ((number >= 11) && (number <= 13))
    {
        return s + TH;
    }

    switch (number % 10)
    {
        case 1: return s + "st";
        case 2: return s + "nd";
        case 3: return s + "rd";
        default: return s + TH;
    }
}

[Test]
public void Ordinal_ReturnsExpectedResults()
{
    Assert.AreEqual("-1", (1-2).Ordinal());
    Assert.AreEqual("0", 0.Ordinal());
    Assert.AreEqual("1st", 1.Ordinal());
    Assert.AreEqual("2nd", 2.Ordinal());
    Assert.AreEqual("3rd", 3.Ordinal());
    Assert.AreEqual("4th", 4.Ordinal());
    Assert.AreEqual("5th", 5.Ordinal());
    Assert.AreEqual("6th", 6.Ordinal());
    Assert.AreEqual("7th", 7.Ordinal());
    Assert.AreEqual("8th", 8.Ordinal());
    Assert.AreEqual("9th", 9.Ordinal());
    Assert.AreEqual("10th", 10.Ordinal());
    Assert.AreEqual("11th", 11.Ordinal());
    Assert.AreEqual("12th", 12.Ordinal());
    Assert.AreEqual("13th", 13.Ordinal());
    Assert.AreEqual("14th", 14.Ordinal());
    Assert.AreEqual("20th", 20.Ordinal());
    Assert.AreEqual("21st", 21.Ordinal());
    Assert.AreEqual("22nd", 22.Ordinal());
    Assert.AreEqual("23rd", 23.Ordinal());
    Assert.AreEqual("24th", 24.Ordinal());
    Assert.AreEqual("100th", 100.Ordinal());
    Assert.AreEqual("101st", 101.Ordinal());
    Assert.AreEqual("102nd", 102.Ordinal());
    Assert.AreEqual("103rd", 103.Ordinal());
    Assert.AreEqual("104th", 104.Ordinal());
    Assert.AreEqual("110th", 110.Ordinal());
    Assert.AreEqual("111th", 111.Ordinal());
    Assert.AreEqual("112th", 112.Ordinal());
    Assert.AreEqual("113th", 113.Ordinal());
    Assert.AreEqual("114th", 114.Ordinal());
    Assert.AreEqual("120th", 120.Ordinal());
    Assert.AreEqual("121st", 121.Ordinal());
    Assert.AreEqual("122nd", 122.Ordinal());
    Assert.AreEqual("123rd", 123.Ordinal());
    Assert.AreEqual("124th", 124.Ordinal());
}
Schramke answered 6/3, 2009 at 21:31 Comment(0)
C
15

You'll have to roll your own. From the top of my head:

public static string Ordinal(this int number)
{
  var work = number.ToString();
  if ((number % 100) == 11 || (number % 100) == 12 || (number % 100) == 13)
    return work + "th";
  switch (number % 10)
  {
    case 1: work += "st"; break;
    case 2: work += "nd"; break;
    case 3: work += "rd"; break;
    default: work += "th"; break;
  }
  return work;
}

You can then do

Console.WriteLine(432.Ordinal());

Edited for 11/12/13 exceptions. I DID say from the top of my head :-)

Edited for 1011 -- others have fixed this already, just want to make sure others don't grab this incorrect version.

Counterman answered 21/8, 2008 at 14:59 Comment(0)
T
15

I rather liked elements from both Stu's and samjudson's solutions and worked them together into what I think is a usable combo:

public static string Ordinal(this int number)
{
    var s = number.ToString();

    number %= 100;
    return s + (number is >= 11 and <= 13
        ? "th"
        : (number % 10) switch
        {
            1 => "st",
            2 => "nd",
            3 => "rd",
            _ => "th"
        });
Theroid answered 27/8, 2008 at 19:56 Comment(6)
what's the rationale behind using a constant for "th"?Asteroid
because it's used twice in the code. Just utilizing the age-old wisdom that you shouldn't repeat yourself :) In this case, the .NET runtime should only create one copy of the string while with two "th"s in the code, there'd be two strings created and referenced in memory.Theroid
and also, if the value of TH ever changes, you'll be set.Behindhand
@Jesse - You get my +1, but I don't believe .NET handles strings this way, see yoda.arachsys.com/csharp/strings.html#interning, my reading of that is each reference to the "th" literal would reference the same bit of memory. But I agree about DRY :)Schramke
@Si - Rereading my last response, I'm reversing what I said and agree with your assessment. .NET is pretty darn smart when it comes to string handling and you have to go out of your way to make it work badly.Theroid
Removing duplication like this just hinders readability I think, hence the confusion "Why the TH?". I don't think DRY should be interpreted as 'remove all duplication whatever the cost'.Umber
K
9

While I haven't benchmarked this yet, you should be able to get better performance by avoiding all the conditional case statements.

This is java, but a port to C# is trivial:

public class NumberUtil {
  final static String[] ORDINAL_SUFFIXES = {
    "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"
  };

  public static String ordinalSuffix(int value) {
    int n = Math.abs(value);
    int lastTwoDigits = n % 100;
    int lastDigit = n % 10;
    int index = (lastTwoDigits >= 11 && lastTwoDigits <= 13) ? 0 : lastDigit;
    return ORDINAL_SUFFIXES[index];
  }

  public static String toOrdinal(int n) {
    return new StringBuffer().append(n).append(ordinalSuffix(n)).toString();
  }
}

Note, the reduction of conditionals and the use of the array lookup should speed up performance if generating a lot of ordinals in a tight loop. However, I also concede that this isn't as readable as the case statement solution.

Kriemhild answered 21/9, 2008 at 17:52 Comment(2)
Sorry that I benchmarked this in C#, your version is not faster than si618's solution.Bearnard
check this answer https://mcmap.net/q/117320/-is-there-an-easy-way-to-create-ordinals-in-c for some benchmarksSpade
B
3

Similar to Ryan's solution, but even more basic, I just use a plain array and use the day to look up the correct ordinal:

private string[] ordinals = new 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" };
DateTime D = DateTime.Now;
String date = "Today's day is: "+ D.Day.ToString() + ordinals[D.Day];

I have not had the need, but I would assume you could use a multidimensional array if you wanted to have multiple language support.

From what I can remember from my Uni days, this method requires minimal effort from the server.

Bracy answered 2/5, 2013 at 13:48 Comment(0)
Q
3
private static string GetOrd(int num) => $"{num}{(!(Range(11, 3).Any(n => n == num % 100) ^ Range(1, 3).All(n => n != num % 10)) ? new[] { "ˢᵗ", "ⁿᵈ", "ʳᵈ" }[num % 10 - 1] : "ᵗʰ")}";

If anyone is looking for one-liner.

Quincentenary answered 27/4, 2016 at 0:29 Comment(1)
kudos for the unicode superscript { "ˢᵗ", "ⁿᵈ", "ʳᵈ", "ᵗʰ" }Latoya
W
3

The accepted answer with switch-expressions and pattern-matching from c# 8 and 9.

No unneeded string conversion or allocations.

string.Concat(number, number < 0 ? "" : (number % 100) switch 
{   
    11 or 12 or 13 => "th", 
    int n => (n % 10) switch 
    { 
        1 => "st", 
        2 => "nd", 
        3 => "rd", 
        _ => "th", 
    }
})

Or as unfriendly one-liner:

$"{number}{(number < 0 ? "" : (number % 100) switch { 11 or 12 or 13 => "th", int n => (n % 10) switch { 1 => "st", 2 => "nd", 3 => "rd", _ => "th" }})}"
Warr answered 28/6, 2022 at 20:16 Comment(0)
P
2

I use this extension class:

public static class Int32Extensions
{
    public static string ToOrdinal(this int i)
    {
        return (i + "th")
            .Replace("1th", "1st")
            .Replace("2th", "2nd")
            .Replace("3th", "3rd");
    }
}
Participate answered 23/9, 2016 at 16:15 Comment(2)
11st, 12nd, 13rdFogged
You forgot to consider 11, 12, 13Hearse
K
2

Requested "less redundancy" version of samjudson's answer...

public static string AddOrdinal(int number)
{
    if (number <= 0) return number.ToString();

    string GetIndicator(int num)
    {
        switch (num % 100)
        {
            case 11:
            case 12:
            case 13:
                return "th";
        }

        switch (num % 10)
        {
            case 1:
                return "st";
            case 2:
                return "nd";
            case 3:
                return "rd";
            default:
                return "th";
        }
    }

    return number + GetIndicator(number);
}
Ker answered 6/4, 2017 at 16:32 Comment(1)
I would Expose "GetIndicator" as a public static and rename it to a more mnemonic name (i.e. "OrdinalSuffix"). The Caller might want the number part in different formats (i.e. with commas).Sulfapyridine
J
2

The Humanizer nuget package will provide helper methods for you. Disclaimer, I am a contributor to this project.

Ordinalize turns a number into an ordinal string used to denote the position in an ordered sequence such as 1st, 2nd, 3rd, 4th:

1.Ordinalize() => "1st"
5.Ordinalize() => "5th"

You can also call Ordinalize on a numeric string and achieve the same result: "21".Ordinalize() => "21st"

Ordinalize also supports grammatical gender for both forms. You can pass an argument to Ordinalize to specify which gender the number should be outputted in. The possible values are GrammaticalGender.Masculine, GrammaticalGender.Feminine and GrammaticalGender.Neuter:

// for Brazilian Portuguese locale
1.Ordinalize(GrammaticalGender.Masculine) => "1º"
1.Ordinalize(GrammaticalGender.Feminine) => "1ª"
1.Ordinalize(GrammaticalGender.Neuter) => "1º"
"2".Ordinalize(GrammaticalGender.Masculine) => "2º"
"2".Ordinalize(GrammaticalGender.Feminine) => "2ª"
"2".Ordinalize(GrammaticalGender.Neuter) => "2º"

Obviously this only applies to some cultures. For others passing gender in or not passing at all doesn't make any difference in the result.

In addition, Ordinalize supports variations some cultures apply depending on the position of the ordinalized number in a sentence. Use the argument wordForm to get one result or another. Possible values are WordForm.Abbreviation and WordForm.Normal. You can combine wordForm argument with gender but passing this argument in when it is not applicable will not make any difference in the result.

// Spanish locale
1.Ordinalize(WordForm.Abbreviation) => "1.er" // As in "Vivo en el 1.er piso"
1.Ordinalize(WordForm.Normal) => "1.º" // As in "He llegado el 1º"
"3".Ordinalize(GrammaticalGender.Feminine, WordForm.Abbreviation) => "3.ª"
"3".Ordinalize(GrammaticalGender.Feminine, WordForm.Normal) => "3.ª"
"3".Ordinalize(GrammaticalGender.Masculine, WordForm.Abbreviation) => "3.er"
"3".Ordinalize(GrammaticalGender.Masculine, WordForm.Normal) => "3.º"

If you want to go deeper, check those test cases: OrdinalizeTests.cs

Joni answered 27/4, 2022 at 7:47 Comment(0)
T
1
public static string OrdinalSuffix(int ordinal)
{
    //Because negatives won't work with modular division as expected:
    var abs = Math.Abs(ordinal); 

    var lastdigit = abs % 10; 

    return 
        //Catch 60% of cases (to infinity) in the first conditional:
        lastdigit > 3 || lastdigit == 0 || (abs % 100) - lastdigit == 10 ? "th" 
            : lastdigit == 1 ? "st" 
            : lastdigit == 2 ? "nd" 
            : "rd";
}
Therapy answered 16/12, 2013 at 12:2 Comment(0)
S
1

EDIT: As YM_Industries points out in the comment, samjudson's answer DOES work for numbers over 1000, nickf's comment seems to have gone, and I can't remember what the problem I saw was. Left this answer here for the comparison timings.

An awful lot of these don't work for numbers > 999, as nickf pointed out in a comment (EDIT: now missing).

Here is a version based off a modified version of samjudson's accepted answer that does.

public static String GetOrdinal(int i)
{
    String res = "";

    if (i > 0)
    {
        int j = (i - ((i / 100) * 100));

        if ((j == 11) || (j == 12) || (j == 13))
            res = "th";
        else
        {
            int k = i % 10;

            if (k == 1)
                res = "st";
            else if (k == 2)
                res = "nd";
            else if (k == 3)
                res = "rd";
            else
                res = "th";
        }
    }

    return i.ToString() + res;
}

Also Shahzad Qureshi's answer using string manipulation works fine, however it does have a performance penalty. For generating a lot of these, a LINQPad example program makes the string version 6-7 times slower than this integer one (although you'd have to be generating a lot to notice).

LINQPad example:

void Main()
{
    "Examples:".Dump();

    foreach(int i in new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 113, 122, 201, 202, 211, 212, 2013, 1000003, 10000013 })
        Stuff.GetOrdinal(i).Dump();

    String s;

    System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();

    for(int iter = 0; iter < 100000; iter++)
        foreach(int i in new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 113, 122, 201, 202, 211, 212, 2013, 1000003, 1000013 })
            s = Stuff.GetOrdinal(i);

    "Integer manipulation".Dump();
    sw.Elapsed.Dump();

    sw.Restart();

    for(int iter = 0; iter < 100000; iter++)
        foreach(int i in new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 113, 122, 201, 202, 211, 212, 2013, 1000003, 1000013 })
            s = (i.ToString() + Stuff.GetOrdinalSuffix(i));

    "String manipulation".Dump();
    sw.Elapsed.Dump();
}

public class Stuff
{
        // Use integer manipulation
        public static String GetOrdinal(int i)
        {
                String res = "";

                if (i > 0)
                {
                        int j = (i - ((i / 100) * 100));

                        if ((j == 11) || (j == 12) || (j == 13))
                                res = "th";
                        else
                        {
                                int k = i % 10;

                                if (k == 1)
                                        res = "st";
                                else if (k == 2)
                                        res = "nd";
                                else if (k == 3)
                                        res = "rd";
                                else
                                        res = "th";
                        }
                }

                return i.ToString() + res;
        }

        // Use string manipulation
        public static string GetOrdinalSuffix(int num)
        {
                if (num.ToString().EndsWith("11")) return "th";
                if (num.ToString().EndsWith("12")) return "th";
                if (num.ToString().EndsWith("13")) return "th";
                if (num.ToString().EndsWith("1")) return "st";
                if (num.ToString().EndsWith("2")) return "nd";
                if (num.ToString().EndsWith("3")) return "rd";
                return "th";
        }
}
Sherrell answered 16/12, 2014 at 16:59 Comment(3)
I can't find @nickf's comment, what's wrong with samjudson's answer? It seems to me like it handles numbers above 1000 just fine while being a lot more readable than yours.Oscitancy
It's a fair comment, I just ran a test set and I can't find any problems. There don't seem to have been any edits to Sam's answer either so I can only imagine I was going mad. I've edited my answer to reflect that.Sherrell
Haha, we all have moments like that don't we? We look back at old code and go "why the hell did I write this?"Oscitancy
U
1

Based off the other answers:

public static string Ordinal(int n)
{   
    int     r = n % 100,     m = n % 10;

    return (r<4 || r>20) && (m>0 && m<4) ? n+"  stndrd".Substring(m*2,2) : n+"th";                                              
}
Unionism answered 16/5, 2017 at 19:36 Comment(1)
1ST PLACE: Most Unnecessarily Cryptic Answer. "Unnecessarily": Code size / performance benefits not worth readability costs. "Cryptic": Significant translation needed to map to "Layperson" Requirements.Sulfapyridine
S
1

While there are plenty of good answers in here, I guess there is room for another one, this time based on pattern matching, if not for anything else, then at least for debatable readability

public static string Ordinals1(this int number)
{
    switch (number)
    {
        case int p when p % 100 == 11:
        case int q when q % 100 == 12:
        case int r when r % 100 == 13:
            return $"{number}th";
        case int p when p % 10 == 1:
            return $"{number}st";
        case int p when p % 10 == 2:
            return $"{number}nd";
        case int p when p % 10 == 3:
            return $"{number}rd";
        default:
            return $"{number}th";
    }
}

and what makes this solution special? nothing but the fact that I'm adding some performance considerations for various other solutions

frankly I doubt performance really matters for this particular scenario (who really needs the ordinals of millions of numbers) but at least it surfaces some comparisons to be taken into account...

1 million items for reference (your millage may vary based on machine specs of course)

with pattern matching and divisions (this answer)

~622 ms

with pattern matching and strings (this answer)

~1967 ms

with two switches and divisions (accepted answer)

~637 ms

with one switch and divisions (another answer)

~725 ms

void Main()
{
    var timer = new Stopwatch();
    var numbers = Enumerable.Range(1, 1000000).ToList();

    // 1
    timer.Reset();
    timer.Start();
    var results1 = numbers.Select(p => p.Ordinals1()).ToList();
    timer.Stop();
    timer.Elapsed.TotalMilliseconds.Dump("with pattern matching and divisions");

    // 2
    timer.Reset();
    timer.Start();
    var results2 = numbers.Select(p => p.Ordinals2()).ToList();
    timer.Stop();
    timer.Elapsed.TotalMilliseconds.Dump("with pattern matching and strings");

    // 3
    timer.Reset();
    timer.Start();
    var results3 = numbers.Select(p => p.Ordinals3()).ToList();
    timer.Stop();
    timer.Elapsed.TotalMilliseconds.Dump("with two switches and divisons");
    
    // 4
    timer.Reset();
    timer.Start();
    var results4 = numbers.Select(p => p.Ordinals4()).ToList();
    timer.Stop();
    timer.Elapsed.TotalMilliseconds.Dump("with one switche and divisons");
}

public static class Extensions
{
    public static string Ordinals1(this int number)
    {
        switch (number)
        {
            case int p when p % 100 == 11:
            case int q when q % 100 == 12:
            case int r when r % 100 == 13:
                return $"{number}th";
            case int p when p % 10 == 1:
                return $"{number}st";
            case int p when p % 10 == 2:
                return $"{number}nd";
            case int p when p % 10 == 3:
                return $"{number}rd";
            default:
                return $"{number}th";
        }
    }

    public static string Ordinals2(this int number)
    {
        var text = number.ToString();
        switch (text)
        {
            case string p when p.EndsWith("11"):
                return $"{number}th";
            case string p when p.EndsWith("12"):
                return $"{number}th";
            case string p when p.EndsWith("13"):
                return $"{number}th";
            case string p when p.EndsWith("1"):
                return $"{number}st";
            case string p when p.EndsWith("2"):
                return $"{number}nd";
            case string p when p.EndsWith("3"):
                return $"{number}rd";
            default:
                return $"{number}th";
        }
    }

    public static string Ordinals3(this int number)
    {
        switch (number % 100)
        {
            case 11:
            case 12:
            case 13:
                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";
        }
    }

    public static string Ordinals4(this int number)
    {
        var ones = number % 10;
        var tens = Math.Floor(number / 10f) % 10;
        if (tens == 1)
        {
            return $"{number}th";
        }

        switch (ones)
        {
            case 1:
                return $"{number}th";
            case 2:
                return $"{number}nd";
            case 3:
                return $"{number}rd";
            default:
                return $"{number}th";
        }
    }
}

Spade answered 14/10, 2019 at 14:3 Comment(0)
S
0

FWIW, for MS-SQL, this expression will do the job. Keep the first WHEN (WHEN num % 100 IN (11, 12, 13) THEN 'th') as the first one in the list, as this relies upon being tried before the others.

CASE
  WHEN num % 100 IN (11, 12, 13) THEN 'th' -- must be tried first
  WHEN num % 10 = 1 THEN 'st'
  WHEN num % 10 = 2 THEN 'nd'
  WHEN num % 10 = 3 THEN 'rd'
  ELSE 'th'
END AS Ordinal

For Excel :

=MID("thstndrdth",MIN(9,2*RIGHT(A1)*(MOD(A1-11,100)>2)+1),2)

The expression (MOD(A1-11,100)>2) is TRUE (1) for all numbers except any ending in 11,12,13 (FALSE = 0). So 2 * RIGHT(A1) * (MOD(A1-11,100)>2) +1) ends up as 1 for 11/12/13, otherwise :
1 evaluates to 3
2 to 5,
3 to 7
others : 9
- and the required 2 characters are selected from "thstndrdth" starting from that position.

If you really want to convert that fairly directly to SQL, this worked for me for a handful of test values :

DECLARE @n as int
SET @n=13
SELECT SubString(  'thstndrdth'
                 , (SELECT MIN(value) FROM
                     (SELECT 9 as value UNION
                      SELECT 1+ (2* (ABS(@n) % 10)  *  CASE WHEN ((ABS(@n)+89) % 100)>2 THEN 1 ELSE 0 END)
                     ) AS Mins
                   )
                 , 2
                )
Strohbehn answered 19/9, 2014 at 13:46 Comment(1)
Yes, but the question specifically said C#. If you want to, you could post this question as a SQL question then post this answer as an answer. If we allowed answers for other languages popular questions would very quickly get swamped by answers for numerous popular languages, making it hard to find a useful answer in the requested language.Anhedral
L
0

This is the implementation in dart and can be modified according to the language.

String getOrdinalSuffix(int num){
    if (num.toString().endsWith("11")) return "th";
    if (num.toString().endsWith("12")) return "th";
    if (num.toString().endsWith("13")) return "th";
    if (num.toString().endsWith("1")) return "st";
    if (num.toString().endsWith("2")) return "nd";
    if (num.toString().endsWith("3")) return "rd";
    return "th";
}
Lowspirited answered 4/6, 2019 at 7:45 Comment(2)
Yes, but the question specifically said C#. If you want to, you could post this question as a Dart question then post this answer as an answer. If we allowed answers for other languages popular questions would very quickly get swamped by answers for numerous popular languages, making it hard to find a useful answer in the requested language.Anhedral
Porting this dart code to C# is super easy. I agree with what you're saying thoughLowspirited
B
0

Another one-liner, but without comparisons by only indexing the regex result into an array.

public static string GetOrdinalSuffix(int input)
{
    return new []{"th", "st", "nd", "rd"}[Convert.ToInt32("0" + Regex.Match(input.ToString(), "(?<!1)[1-3]$").Value)];
}

The PowerShell version can be shortened further:

function ord($num) { return ('th','st','nd','rd')[[int]($num -match '(?<!1)[1-3]$') * $matches[0]] }
Bullfinch answered 30/10, 2019 at 19:56 Comment(0)
G
0

Another 1 liner.

public static string Ordinal(this int n)
{    
 return n + (new [] {"st","nd","rd" }.ElementAtOrDefault((((n + 90) % 100 - 10) % 10 - 1)) ?? "th");
}
Grass answered 6/5, 2020 at 20:35 Comment(0)
S
-2

Here is the DateTime Extension class. Copy, Paste & Enjoy

public static class DateTimeExtensions
{
    public static string ToStringWithOrdinal(this DateTime d)
    {
        var result = "";
        bool bReturn = false;            
        
        switch (d.Day % 100)
        {
            case 11:
            case 12:
            case 13:
                result = d.ToString("dd'th' MMMM yyyy");
                bReturn = true;
                break;
        }

        if (!bReturn)
        {
            switch (d.Day % 10)
            {
                case 1:
                    result = d.ToString("dd'st' MMMM yyyy");
                    break;
                case 2:
                    result = d.ToString("dd'nd' MMMM yyyy");
                    break;
                case 3:
                    result = d.ToString("dd'rd' MMMM yyyy");
                    break;
                default:
                    result = d.ToString("dd'th' MMMM yyyy");
                    break;
            }

        }

        if (result.StartsWith("0")) result = result.Substring(1);
        return result;
    }
}

Result :

9th October 2014

Shively answered 9/10, 2014 at 15:16 Comment(1)
You're duplicating: a) the Date Format String (X5) and b) the entire rest of the Method (when the likely Use Case arises (if it hasn't already) that an ordinal suffix is needed for non-Day of Month purposes or even a Day of Month with a different Date Format String). Use the "OrdinalSuffix" Method I suggested be exposed from Ian Warburton's Apr 6 '17 at 16:32 answer (#20656).Sulfapyridine
D
-4

Another alternative that I used based on all the other suggestions, but requires no special casing:

public static string DateSuffix(int day)
{
    if (day == 11 | day == 12 | day == 13) return "th";
    Math.DivRem(day, 10, out day);
    switch (day)
    {
        case 1:
            return "st";
        case 2:
            return "nd";
        case 3:
            return "rd";
        default:
            return "th";
    }
}
Doyon answered 23/2, 2014 at 12:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.