73

I am wondering if there is a method or format string I'm missing in .NET to convert the following:

   1 to 1st
   2 to 2nd
   3 to 3rd
   4 to 4th
  11 to 11th
 101 to 101st
 111 to 111th

This link has a bad example of the basic principle involved in writing your own function, but I am more curious if there is an inbuilt capacity I'm missing.

Solution

Scott Hanselman's answer is the accepted one because it answers the question directly.

For a solution however, see this great answer.

Community
  • 1
  • 1
Matt Mitchell
  • 40,943
  • 35
  • 118
  • 185
  • 4
    They're called ordinal numbers (1st, 2nd, etc.) as opposed to cardinal numbers (1,2,3, etc.), FYI. – pc1oad1etter Sep 16 '08 at 04:15
  • 1
    This was answered quite elegantly here: http://stackoverflow.com/questions/20156/ordinals-in-c# – Portman Sep 17 '08 at 14:32
  • Too bad this question seems to already have an answer, but I would suggest try using Humanizer library which you can install through nugget: https://github.com/Humanizr/Humanizer#ordinalize 1.Ordinalize() => "1st" 5.Ordinalize() => "5th" – BAKARI SHEGHEMBE Sep 11 '22 at 19:48

11 Answers11

87

It's a function which is a lot simpler than you think. Though there might be a .NET function already in existence for this, the following function (written in PHP) does the job. It shouldn't be too hard to port it over.

function ordinal($num) {
    $ones = $num % 10;
    $tens = floor($num / 10) % 10;
    if ($tens == 1) {
        $suff = "th";
    } else {
        switch ($ones) {
            case 1 : $suff = "st"; break;
            case 2 : $suff = "nd"; break;
            case 3 : $suff = "rd"; break;
            default : $suff = "th";
        }
    }
    return $num . $suff;
}
nickf
  • 537,072
  • 198
  • 649
  • 721
  • 4
    What about localization? – macbirdie Sep 16 '08 at 21:26
  • 2
    Localization will mean that you have to create separate functions for each language. In german, you could just append "ter", but "1ter" "2ter" "3ter" looks really bad even though it's grammatically correct. In french, it's a bit better, but there is no universal way for every language. – Michael Stum Sep 17 '08 at 09:14
  • @Michael Stum: I'm not too familiar with all the international ordinal formats but would a string.Format(resString, number) suffice? Or do some languages not combine numbers with ordinal (pre/suff)ixes? – Matt Mitchell Jun 21 '10 at 05:15
  • 3
    @MichaelStum: Actually in german you could NOT just add "ter". Consider "Heute ist der 1te Januar" (today is 1st of January). Or "Klicken Sie den 5ten Button" (click the 5th button). Just to name two of dozens of cases. You have to consider the proper Flexion (engl. inflection) for every single use. – Regexident Jan 26 '12 at 13:04
  • 1
    Combining a number and such a suffix is unusual in German. You either write it as "1." or "erster"/"erste". The latter is generally used is texts and rarely needs to be generated automatically. – CodesInChaos Jun 06 '14 at 10:03
  • Won't this fail for numbers between 10 and 20? – dannio Aug 17 '15 at 07:56
  • 1
    @dannio Nope, there's a condition for `$tens` – Rufus L Nov 13 '19 at 21:23
83

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();
Shahzad Qureshi
  • 1,776
  • 14
  • 15
60

No, there is no inbuilt capability in the .NET Base Class Library.

Scott Hanselman
  • 17,712
  • 6
  • 74
  • 89
55

@nickf: Here is the PHP function in C#:

public static string Ordinal(int number)
{
    string suffix = String.Empty;

    int ones = number % 10;
    int tens = (int)Math.Floor(number / 10M) % 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";
                break;
        }
    }
    return String.Format("{0}{1}", number, suffix);
}
Scott Dorman
  • 42,236
  • 12
  • 79
  • 110
  • Ha thanks, just about to post the code I wrote out. Yours beats mine anyway with the String.Format bit I think. – Matt Mitchell Sep 16 '08 at 04:21
  • 1) Why the conversion to decimal? A simple `(number / 10) % 10` does the trick. 2) Why do you initialize `suffix` to a value that will never be used? – CodesInChaos Jun 06 '14 at 10:05
  • @CodesInChaos: Without the conversion to decimal, you get a compiler error: `The call is ambiguous between the following methods or properties: 'System.Math.Floor(decimal)' and 'System.Math.Floor(double)'`. Initializing `suffix` to `String.Empty` is mostly habit but also helps to avoid accidental `Use of unassigned local variable 'suffix'` errors. – Scott Dorman Jun 06 '14 at 12:58
  • 1
    @ScottDorman 1) Only if you leave in the call to `Floor` which is nonsensical on integers. Integer division simply truncates towards zero, no need for casting to decimal or using `Floor`. `(number / 10) % 10` is simpler and works. 2) Those errors occur if you overlooked a code path. A compiler error tells you to fix that mistake instead of silently returning a useless value. – CodesInChaos Jun 06 '14 at 13:05
14

This has already been covered but I'm unsure how to link to it. Here is the code snippit:

    public static string Ordinal(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 + "st";
            case 2: return number + "nd";
            case 3: return number + "rd";
            default: return number + "th";
        }
    }

FYI: This is as an extension method. If your .NET version is less than 3.5 just remove the this keyword

[EDIT]: Thanks for pointing that it was incorrect, that's what you get for copy / pasting code :)

mezoid
  • 28,090
  • 37
  • 107
  • 148
mjallday
  • 9,796
  • 9
  • 51
  • 71
9

Here's a Microsoft SQL Server Function version:

CREATE FUNCTION [Internal].[GetNumberAsOrdinalString]
(
    @num int
)
RETURNS nvarchar(max)
AS
BEGIN

    DECLARE @Suffix nvarchar(2);
    DECLARE @Ones int;  
    DECLARE @Tens int;

    SET @Ones = @num % 10;
    SET @Tens = FLOOR(@num / 10) % 10;

    IF @Tens = 1
    BEGIN
        SET @Suffix = 'th';
    END
    ELSE
    BEGIN

    SET @Suffix = 
        CASE @Ones
            WHEN 1 THEN 'st'
            WHEN 2 THEN 'nd'
            WHEN 3 THEN 'rd'
            ELSE 'th'
        END
    END

    RETURN CONVERT(nvarchar(max), @num) + @Suffix;
END
nickf
  • 537,072
  • 198
  • 649
  • 721
redcalx
  • 8,177
  • 4
  • 56
  • 105
  • I just wrote that function almost verbatim! Differences: master db, cast instead of convert, and I use slightly different indenting. Great minds, I guess... – John Gietzen May 08 '09 at 20:21
  • +1 - Just had the need for a SQL version - saved me writing one – HeavenCore Apr 26 '12 at 14:15
  • Superb, but only if we are fetching from SQL. But in this case I am formatting a .net DateTime variable. But this function will be immensely useful. – santubangalore Jun 27 '14 at 04:45
2

I know this isn't an answer to the OP's question, but because I found it useful to lift the SQL Server function from this thread, here is a Delphi (Pascal) equivalent:

function OrdinalNumberSuffix(const ANumber: integer): string;
begin
  Result := IntToStr(ANumber);
  if(((Abs(ANumber) div 10) mod 10) = 1) then // Tens = 1
    Result := Result + 'th'
  else
    case(Abs(ANumber) mod 10) of
      1: Result := Result + 'st';
      2: Result := Result + 'nd';
      3: Result := Result + 'rd';
      else
        Result := Result + 'th';
    end;
end;

Does ..., -1st, 0th make sense?

avenmore
  • 2,809
  • 3
  • 33
  • 34
1

Another flavor:

/// <summary>
/// Extension methods for numbers
/// </summary>
public static class NumericExtensions
{
    /// <summary>
    /// Adds the ordinal indicator to an integer
    /// </summary>
    /// <param name="number">The number</param>
    /// <returns>The formatted number</returns>
    public static string ToOrdinalString(this int number)
    {
        // Numbers in the teens always end with "th"

        if((number % 100 > 10 && number % 100 < 20))
            return number + "th";
        else
        {
            // Check remainder

            switch(number % 10)
            {
                case 1:
                    return number + "st";

                case 2:
                    return number + "nd";

                case 3:
                    return number + "rd";

                default:
                    return number + "th";
            }
        }
    }
}
Frank Hoffman
  • 887
  • 1
  • 11
  • 16
  • Actually a good answer. But it is generic, i.e not specific to dates of a month. I meant only dates. So above 100 may be not applicable. – santubangalore Jun 27 '14 at 04:44
0
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";
}
Faust
  • 15,130
  • 9
  • 54
  • 111
-3
else if (choice=='q')
{
    qtr++;

    switch (qtr)
    {
        case(2): strcpy(qtrs,"nd");break;
        case(3):
        {
           strcpy(qtrs,"rd");
           cout<<"End of First Half!!!";
           cout<<" hteam "<<"["<<hteam<<"] "<<hs;
           cout<<" vteam "<<" ["<<vteam;
           cout<<"] ";
           cout<<vs;dwn=1;yd=10;

           if (beginp=='H') team='V';
           else             team='H';
           break;
       }
       case(4): strcpy(qtrs,"th");break;
CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
-6

I think the ordinal suffix is hard to get... you basically have to write a function that uses a switch to test the numbers and add the suffix.

There's no reason for a language to provide this internally, especially when it's locale specific.

You can do a bit better than that link when it comes to the amount of code to write, but you have to code a function for this...

Hugh Buchanan
  • 2,011
  • 3
  • 14
  • 9