66

I want to take an integer and get its ordinal, i.e.:

1 -> "First"
2 -> "Second"
3 -> "Third"
...
djangofan
  • 28,471
  • 61
  • 196
  • 289
lakemalcom
  • 1,016
  • 1
  • 8
  • 17

13 Answers13

162

If you're OK with 1st, 2nd, 3rd etc, here's some simple code that will correctly handle any integer:

public static String ordinal(int i) {
    String[] suffixes = new String[] { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" };
    switch (i % 100) {
    case 11:
    case 12:
    case 13:
        return i + "th";
    default:
        return i + suffixes[i % 10];

    }
}

Here's some tests for edge cases:

public static void main(String[] args) {
    int[] tests = {0, 1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 20, 21, 22, 23, 24, 100, 101, 102, 103, 104, 111, 112, 113, 114, 1000};
    for (int test : tests) {
        System.out.println(ordinal(test));
    }
}

Output:

0th
1st
2nd
3rd
4th
5th
10th
11th
12th
13th
14th
20th
21st
22nd
23rd
24th
100th
101st
102nd
103rd
104th
111th
112th
113th
114th
1000th
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • 2
    How comes this is the accepted answer? OP asked ordinal in words, and the answer is not doing that. Probably he turned down the requirement, but still are answers that do what he asked. – desgraci Aug 21 '18 at 19:22
  • 6
    @desgraci Not sure about being the accepted answer, but I imagine it's the most upvoted one because it's more generally useful to other people. – Thunderforge Sep 19 '18 at 17:56
  • In Java 17+ you can use switch expressions: `return switch (i % 100) { case 11, 12, 13 -> i + "th"; default -> i + suffixes[i % 10]; };` – ATutorMe Oct 24 '22 at 01:07
46

Using the excellent ICU4J (there's also an excellent C version) you can also do this and get the Ordinals as plain words;

RuleBasedNumberFormat nf = new RuleBasedNumberFormat(Locale.UK, RuleBasedNumberFormat.SPELLOUT);
for(int i = 0; i <= 30; i++)
{
    System.out.println(i + " -> "+nf.format(i, "%spellout-ordinal"));
}

for example produces

0 -> zeroth
1 -> first
2 -> second
3 -> third
4 -> fourth
5 -> fifth
6 -> sixth
7 -> seventh
8 -> eighth
9 -> ninth
10 -> tenth
11 -> eleventh
12 -> twelfth
13 -> thirteenth
14 -> fourteenth
15 -> fifteenth
16 -> sixteenth
17 -> seventeenth
18 -> eighteenth
19 -> nineteenth
20 -> twentieth
21 -> twenty-first
22 -> twenty-second
23 -> twenty-third
24 -> twenty-fourth
25 -> twenty-fifth
26 -> twenty-sixth
27 -> twenty-seventh
28 -> twenty-eighth
29 -> twenty-ninth
30 -> thirtieth
JFK
  • 1,527
  • 16
  • 21
  • 10
    I think this should have been chosen as the accepted answer as it directly answers the question. – TheGT Apr 13 '17 at 16:00
  • note that for some languages like italian %spellout-ordinal does not exist and must choose between %spellout-ordinal-masculine and %spellout-ordinal-feminine, may be some languages have more genres – Testo Testini Jan 20 '20 at 17:27
  • This is great, but... I'm not entirely convinced by how the library handles things from a locale point of view. The sample code in this answer says it uses a UK locale, but it gives (for example) `one hundred one thousand three hundred eighty-second` for the number `101,382`. I would expect it to be `one hundred and one thousand three hundred and eighty-second` (with those "and"s added) . That would (I think) be more natural for the UK. This feels more like a US locale way of writing out the words. – andrewJames Jun 26 '20 at 01:39
  • For `1st`, `2nd`, `3rd`, etc. use `RuleBasedNumberFormat.ORDINAL` and then `nf.format(x)`. – Raman Jul 22 '21 at 06:50
21

Another solution

public static String ordinal(int i) {
    int mod100 = i % 100;
    int mod10 = i % 10;
    if(mod10 == 1 && mod100 != 11) {
        return i + "st";
    } else if(mod10 == 2 && mod100 != 12) {
        return i + "nd";
    } else if(mod10 == 3 && mod100 != 13) {
        return i + "rd";
    } else {
        return i + "th";
    }
}

Pro: does not require an array to be initialized (less garbage)
Con: not a one-liner...

klamann
  • 1,697
  • 17
  • 28
19

I've figured out how to do this in Android in a pretty simple way. All you need to do is to add the dependency to your app's build.gradle file:

implementation "com.ibm.icu:icu4j:53.1"

Next, create this method:

Kotlin:

fun Number?.getOrdinal(): String? {
    if (this == null) {
        return null
    }

    val format = "{0,ordinal}"

    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        android.icu.text.MessageFormat.format(format, this)
    } else {
        com.ibm.icu.text.MessageFormat.format(format, this)
    }
}

Java:

public static String getNumberOrdinal(Number number) {
        if (number == null) {
            return null;
        }

        String format = "{0,ordinal}";

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            return android.icu.text.MessageFormat.format(format, number);
        } else {
            return com.ibm.icu.text.MessageFormat.format(format, number);
        }
    }

Then, you can simply use it like this:

Kotlin:

val ordinal = 2.getOrdinal()

Java:

String ordinal = getNumberOrdinal(2)

How it works

Starting from Android N (API 24) Android uses icu.text instead of regular java.text (more info here), which already contains internationalized implementation for ordinal numbers. So the solution is obviously simple - to add the icu4j library to the project and use it on versions below the Nougat

Alexander Krol
  • 1,418
  • 15
  • 15
  • 3
    Important: this does not work with newer versions of ICU4J (63.1 at least); it will throw on API level <24 because java.util.Locale.Category is unsupported. – soren121 Jan 08 '19 at 22:35
  • 3
    Too much apk size added for such simple feature, +9mb. – oxied Mar 04 '20 at 05:06
  • In other words: android.icu.text.MessageFormat.format("{0,ordinal}", number). Note that using java.text.MessageFormat.format instead will raise an exception. +1 for internationalization, this should be the accepted answer! – Pat Lee Oct 09 '20 at 15:36
6

In 1 line:

public static String ordinal(int i) {
    return i % 100 == 11 || i % 100 == 12 || i % 100 == 13 ? i + "th" : i + new String[]{"th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"}[i % 10];
}
nvrandow
  • 722
  • 10
  • 13
  • 1
    Boy will that give the garbage collector a fun time XD – Ky - Jan 23 '17 at 21:12
  • @Supuhstar, the array is easily proven to be unable to leave the scope of that method, so compiler and runtime can do pretty heavy optimizations on this. Even with my most pessimistic prediction, this method won't produce any garbage after a very short while. – M. Prokhorov Dec 18 '17 at 12:20
  • @M.Prokhorov I'd rather a solution not rely on JVM implementation details that might change between versions and environments, but instead on strictly-defined language behavior that doesn't – Ky - Dec 18 '17 at 12:35
  • @Supuhstar, any talk about garbage in Java **is** relying on JVM implementation details. Java only defines that objects which go out of scope get garbage collected. It does not define any notion of garbage objects which impact app performance, and it does't talk about avoiding creating objects because of this. – M. Prokhorov Dec 18 '17 at 14:21
  • @M.Prokhorov right, which is why I think it's more reliably performant to make your array a static variable, guaranteed to be in-scope with any JVM throughout the app's lifecycle, thus guaranteed to never be garage-collected – Ky - Dec 18 '17 at 15:10
3

Bohemians answer is very good but I recommend improving the error handling. With the original version of ordinal if you supply a negative integer an ArrayIndexOutOfBoundsException will be thrown. I think my version below is clearer. I hope the junit is also useful so it is not necessary to visually check the output.

public class FormattingUtils {

    /**
     * Return the ordinal of a cardinal number (positive integer) (as per common usage rather than set theory).
     * {@link http://stackoverflow.com/questions/6810336/is-there-a-library-or-utility-in-java-to-convert-an-integer-to-its-ordinal}
     * 
     * @param i
     * @return
     * @throws {@code IllegalArgumentException}
     */
    public static String ordinal(int i) {
        if (i < 0) {
            throw new IllegalArgumentException("Only +ve integers (cardinals) have an ordinal but " + i + " was supplied");
        }

        String[] sufixes = new String[] { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" };
        switch (i % 100) {
        case 11:
        case 12:
        case 13:
            return i + "th";
        default:
            return i + sufixes[i % 10];
        }
    }
}


import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;

public class WhenWeCallFormattingUtils_Ordinal {

    @Test
    public void theEdgeCasesAreCovered() {
        int[] edgeCases = { 0, 1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 20, 21, 22, 23, 24, 100, 101, 102, 103, 104, 111, 112,
                113, 114 };
        String[] expectedResults = { "0th", "1st", "2nd", "3rd", "4th", "5th", "10th", "11th", "12th", "13th", "14th",
                "20th", "21st", "22nd", "23rd", "24th", "100th", "101st", "102nd", "103rd", "104th", "111th", "112th",
                "113th", "114th" };

        for (int i = 0; i < edgeCases.length; i++) {
            assertThat(FormattingUtils.ordinal(edgeCases[i])).isEqualTo(expectedResults[i]);
        }
    }

    @Test(expected = IllegalArgumentException.class)
    public void supplyingANegativeNumberCausesAnIllegalArgumentException() {
        FormattingUtils.ordinal(-1);
    }

}
Michael Shaw
  • 143
  • 1
  • 6
1
private static String getOrdinalIndicator(int number) {
        int mod = number;
        if (number > 13) {
            mod = number % 10;
        }
        switch (mod) {
        case 1:
            return "st";
        case 2:
            return "nd";
        case 3:
            return "rd";
        default:
            return "th";
        }
    }
0

In Scala for a change,

List(1, 2, 3, 4, 5, 10, 11, 12, 13, 14 , 19, 20, 23, 33, 100, 113, 123, 101, 1001, 1011, 1013, 10011) map {
    case a if (a % 10) == 1 && (a % 100) != 11 => a + "-st"
    case b if (b % 10) == 2 && (b % 100) != 12 => b + "-nd"
    case c if (c % 10) == 3 && (c % 100) != 13 => c + "-rd"
    case e                                     => e + "-th"
  }  foreach println
Abel Terefe
  • 1,440
  • 20
  • 17
0

I got a long, complicated one but easy to understand the concept

private static void convertMe() {

    Scanner in = new Scanner(System.in);
    try {
        System.out.println("input a number to convert: ");
        int n = in.nextInt();

        String s = String.valueOf(n);
        //System.out.println(s);

        int len = s.length() - 1;
        if (len == 0){
            char lastChar = s.charAt(len);
            if (lastChar == '1'){
                System.out.println(s + "st");
            } else if (lastChar == '2') {
                System.out.println(s + "nd");
            } else if (lastChar == '3') {
                System.out.println(s + "rd");
            } else {
                System.out.println(s + "th");
            }
        } else if (len > 0){
            char lastChar = s.charAt(len);
            char preLastChar = s.charAt(len - 1);
            if (lastChar == '1' && preLastChar != '1'){ //not ...11
                System.out.println(s + "st");
            } else if (lastChar == '2' && preLastChar != '1'){ //not ...12
                System.out.println(s + "nd");
            } else if (lastChar == '3' && preLastChar != '1'){ //not ...13
                System.out.println(s + "rd");
            } else {
                System.out.println(s + "th");
            }
        }


    } catch(InputMismatchException exception){
        System.out.println("invalid input");
    }


}
Tuan
  • 51
  • 5
0
static String getOrdinal(int input) {
    if(input<=0) {
        throw new IllegalArgumentException("Number must be > 0");
    }
    int lastDigit = input % 10;
    int lastTwoDigit = input % 100;
    if(lastTwoDigit >= 10 && lastTwoDigit <= 20) {
        return input+"th";
    }
    switch (lastDigit) {
    case 1:
        return input+"st";
    case 2:
        return input+"nd";
    case 3:
        return input+"rd";

    default:
        return input+"th";
    }
}
Saheb
  • 51
  • 6
0

Just incase if somone looking for a Kotlin extension version :


fun Int.toEnOrdinal(): String {
    val suffixes = arrayListOf("th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th")
    return when (this % 100) {
        11, 12, 13 -> "{$this}th"
        else -> "$this" + suffixes[this % 10]
    }
}

then all you need to do is to call this function on any number like

5.toUKOrdinal() --> 5th

21.toUKOrdinal() --> 21st

etc

bastami82
  • 5,955
  • 7
  • 33
  • 44
-1

Best and Simple way, Here we go:

import java.util.*;
public class Numbers 
{
    public final static String print(int num)
    {
        num = num%10;
        String str = "";
        switch(num)
        {
        case 1:     
            str = "st";
            break;
        case 2:     
            str = "nd";
            break;
        case 3:     
            str = "rd";
            break;
        default: 
            str = "th";             
        }
        return str;
    }

    public static void main(String[] args) 
    {
        Scanner sc = new Scanner(System.in);
        System.out.print("Enter a number: ");
        int number = sc.nextInt();
        System.out.print(number + print(number));
    }
}
Siddhesh
  • 1
  • 5
-2
public static String getOrdinalFor(int value) {
         int tenRemainder = value % 10;
         switch (tenRemainder) {
          case 1:
           return value+"st";
          case 2:
           return value+"nd";
          case 3:
           return value+"rd";
          default:
           return value+"th";
         }
        }
Tell Me How
  • 672
  • 12
  • 16
  • 1
    I don't see this handling numbers 11, 12 and 13? Use and if to check for them eg - ```if (number >= 11 && number <= 13) { return number + "th"; }``` – Enoobong Mar 08 '17 at 19:11