178

I want to display numbers as follows

  • 1 as 1st,
  • 2 as 2nd,
  • ...,
  • 150 as 150th.

How should I find the correct ordinal suffix (st, nd, rd or th) for each number in my code?

ArK
  • 20,698
  • 67
  • 109
  • 136
  • 2
    See my answer here. http://stackoverflow.com/questions/69262/is-there-an-easy-way-in-net-to-get-st-nd-rd-and-th-endings-for-numbers/69284#69284 That question was for .NET, but I answered with a PHP solution, so it should help you out. – nickf Jun 24 '10 at 12:51
  • The only way I can think of doing this is have a if statement going up for every number you could possible have, IE, if(1) then "st" elseif (2) then "nd" etc etc if (23000) then "nd". It's a problem if you have big numbers but you could write a program to write the code for you, it could loop all the numbers printing the ifs for you to copy + paste into your code. – Tom Gullen Jun 24 '10 at 12:52
  • 2
    @Tom, a lookup table might be better, just initialize it with the 23000 values and get the value at index n, where n is the number you want the ordinal. – John Boker Jun 24 '10 at 12:55
  • 2
    Col. Shrapnel. you may brilliant but not all. any way thanks for showing interest on my question – ArK Jun 24 '10 at 12:56
  • @John, very very clever idea, it would be very quick to access as well as each index represents the number you are looking up. – Tom Gullen Jun 24 '10 at 12:56
  • @Tom & @Gullen, lookup table is only better than doing calculation if the calculation is expensive, like to calculate logarithm or square root, but for this question, doing calculation is cheaper than initializing the lookup table. – Lukman Jun 24 '10 at 13:00
  • @Lukman maybe you didnt get the memo, these answers in the comments are for finding the worst possible way of a solution to the problem. – John Boker Jun 24 '10 at 13:03
  • Look at this post. The function there works https://kitson-consulting.co.uk/blog/php-code-add-st-nd-rd-or-th-number – Junior Nov 02 '21 at 18:07

17 Answers17

329

from wikipedia:

$ends = array('th','st','nd','rd','th','th','th','th','th','th');
if (($number %100) >= 11 && ($number%100) <= 13)
   $abbreviation = $number. 'th';
else
   $abbreviation = $number. $ends[$number % 10];

Where $number is the number you want to write. Works with any natural number.

As a function:

function ordinal($number) {
    $ends = array('th','st','nd','rd','th','th','th','th','th','th');
    if ((($number % 100) >= 11) && (($number%100) <= 13))
        return $number. 'th';
    else
        return $number. $ends[$number % 10];
}
//Example Usage
echo ordinal(100);
neshkeev
  • 6,280
  • 3
  • 26
  • 47
Iacopo
  • 4,224
  • 1
  • 23
  • 25
  • Although a bit difficult to understand at first, I do now think it best represents how the ordinal suffix system works for English. – erisco Jun 24 '10 at 14:44
  • 7
    I like your solution. Additionally if you prefer not to generate **0th** modify the last line to be `$abbreviation = ($number)? $number. $ends[$number % 10] : $number;` – Gavin Jackson Feb 26 '14 at 10:02
  • 1
    @GavinJackson your addition to this excellent solution really helped me out (+1). Could you please explain to me what is going on in the calculation? I want to understand. Cheers! EDIT: Found an answer: [conditional operator](http://stackoverflow.com/questions/1080247/what-is-the-php-operator-called-and-what-does-it-do) – Andrew Fox Nov 11 '14 at 07:37
  • Sorry, had to break the 111 votes to 112 :D Made it a function in Delphi along with a demo app: http://pastebin.com/wvmz1CHY – Jerry Dodge Feb 05 '15 at 03:58
  • one liner version of your function for those inclined: `return $n . ($n %100 > 3 && $n %100 < 21 ? 'th' : array('th','st','nd','rd','th','th','th','th','th','th')[$n %10]) ;` – 99 Problems - Syntax ain't one Sep 05 '16 at 12:04
  • If you don't want the one-liner as suggested, you could do a return inside the if statement before declaring the `$ends` array. You can also drop the `else` if you do that. – rybo111 Feb 25 '18 at 00:45
  • 1
    @HafezDivandari - are you sure? Just tested with 7.1.19 and seems to work fine – Iacopo Dec 05 '18 at 09:35
  • @lacopo You're right, I was testing with http://phpfiddle.org and it returned a buggy output, sorry. – Hafez Divandari Dec 07 '18 at 16:49
184

PHP has built-in functionality for this. It even handles internationalization!

$locale = 'en_US';
$nf = new NumberFormatter($locale, NumberFormatter::ORDINAL);
echo $nf->format($number);

Note that this functionality is only available in PHP 5.3.0 and later.

Community
  • 1
  • 1
Jeremy Kauffman
  • 10,293
  • 5
  • 42
  • 52
27

This can be accomplished in a single line by leveraging similar functionality in PHP's built-in date/time functions. I humbly submit:

Solution:

function ordinalSuffix( $n )
{
  return date('S',mktime(1,1,1,1,( (($n>=10)+($n>=20)+($n==0))*10 + $n%10) ));
}

Detailed Explanation:

The built-in date() function has suffix logic for handling nth-day-of-the-month calculations. The suffix is returned when S is given in the format string:

date( 'S' , ? );

Since date() requires a timestamp (for ? above), we'll pass our integer $n as the day parameter to mktime() and use dummy values of 1 for the hour, minute, second, and month:

date( 'S' , mktime( 1 , 1 , 1 , 1 , $n ) );

This actually fails gracefully on values out of range for a day of the month (i.e. $n > 31) but we can add some simple inline logic to cap $n at 29:

date( 'S', mktime( 1, 1, 1, 1, ( (($n>=10)+($n>=20))*10 + $n%10) ));

The only positive value(May 2017) this fails on is $n == 0, but that's easily fixed by adding 10 in that special case:

date( 'S', mktime( 1, 1, 1, 1, ( (($n>=10)+($n>=20)+($n==0))*10 + $n%10) ));

Update, May 2017

As observed by @donatJ, the above fails above 100 (e.g. "111st"), since the >=20 checks are always returning true. To reset these every century, we add a filter to the comparison:

date( 'S', mktime( 1, 1, 1, 1, ( (($n>=10)+($n%100>=20)+($n==0))*10 + $n%10) ));

Just wrap it in a function for convenience and off you go!

Andrew Kozak
  • 1,631
  • 2
  • 22
  • 35
17

Here is a one-liner:

$a = <yournumber>;
echo $a.substr(date('jS', mktime(0,0,0,1,($a%10==0?9:($a%100>20?$a%10:$a%100)),2000)),-2);

Probably the shortest solution. Can of course be wrapped by a function:

function ordinal($a) {
  // return English ordinal number
  return $a.substr(date('jS', mktime(0,0,0,1,($a%10==0?9:($a%100>20?$a%10:$a%100)),2000)),-2);
}

Regards, Paul

EDIT1: Correction of code for 11 through 13.

EDIT2: Correction of code for 111, 211, ...

EDIT3: Now it works correctly also for multiples of 10.

Paul
  • 8,974
  • 3
  • 28
  • 48
  • I like this approach, but alas, it doesn't work :-( 30th comes out as 30st. 40th comes out as 40st. etc. – Flukey Jun 24 '10 at 13:59
  • 1
    Yeah, sorry. When I read the question I thought, hey this should be possible by a single line of code. And I just typed it away. As you see from my edits, I'm improving. After the third edit now I think it's quite done. At least all numbers from 1 to 150 print out nicely on my screen. – Paul Jun 24 '10 at 14:02
  • Looks good up to 500! (haven't tested it further than that). Good work! :-) – Flukey Jun 24 '10 at 14:04
13

from http://www.phpro.org/examples/Ordinal-Suffix.html

<?php

/**
 *
 * @return number with ordinal suffix
 *
 * @param int $number
 *
 * @param int $ss Turn super script on/off
 *
 * @return string
 *
 */
function ordinalSuffix($number, $ss=0)
{

    /*** check for 11, 12, 13 ***/
    if ($number % 100 > 10 && $number %100 < 14)
    {
        $os = 'th';
    }
    /*** check if number is zero ***/
    elseif($number == 0)
    {
        $os = '';
    }
    else
    {
        /*** get the last digit ***/
        $last = substr($number, -1, 1);

        switch($last)
        {
            case "1":
            $os = 'st';
            break;

            case "2":
            $os = 'nd';
            break;

            case "3":
            $os = 'rd';
            break;

            default:
            $os = 'th';
        }
    }

    /*** add super script ***/
    $os = $ss==0 ? $os : '<sup>'.$os.'</sup>';

    /*** return ***/
    return $number.$os;
}
?> 
John Boker
  • 82,559
  • 17
  • 97
  • 130
12

Simple and Easy Answer will be:

$Day = 3; 
echo date("S", mktime(0, 0, 0, 0, $Day, 0));

//OUTPUT - rd
WhiteHorse
  • 718
  • 2
  • 8
  • 15
  • 1
    Doesnt say anything about dates in the question tho. I upvoted anyway - it's a quick and simple solution when you know the number range is going to be <32 – cronoklee Feb 03 '16 at 12:27
  • 1
    OP wants this to work at least up to 150. Yours starts breaking at 32st since 0/32/0000 converts to 12/32/-0001, which rolls over to 1/1/0000, meaning the day 32 turns into the day 1 and gets an "st." As @cronoklee said, it works for numbers 31 and under, but that's not what the OP was asking for. – Stevish Feb 23 '22 at 21:11
10

I wrote this for PHP4. It's been working ok & it's pretty economical.

function getOrdinalSuffix($number) {
    $number = abs($number) % 100;
    $lastChar = substr($number, -1, 1);
    switch ($lastChar) {
        case '1' : return ($number == '11') ? 'th' : 'st';
        case '2' : return ($number == '12') ? 'th' : 'nd';
        case '3' : return ($number == '13') ? 'th' : 'rd'; 
    }
    return 'th';  
}
iletras
  • 248
  • 2
  • 6
  • Vishal Kumar, could you expand/expound/explain that a bit more? Tks! – iletras Apr 04 '17 at 02:36
  • 2
    I know this is late, but to answer @iletras, This function starts by getting the mod 100 of the number (divide by 100, return the remainder, which effectively removes everything from the hundreds place up), and puts that in the `$number` variable. Then it grabs the last digit and puts that in `$lastChar`. Since most cases will need a 'th' at the end, he only focuses on the special cases: numbers ending in 1, 2 or 3. In each case, this function looks for the exception (11, 12 and 13) to return 'th' in those cases, or 'st', 'nd' or 'rd' for all the others. I personally like this one the best. – Stevish Feb 23 '22 at 21:21
4

you just need to apply given function.

function addOrdinalNumberSuffix($num) {
  if (!in_array(($num % 100),array(11,12,13))){
    switch ($num % 10) {
      // Handle 1st, 2nd, 3rd
      case 1:  return $num.'st';
      case 2:  return $num.'nd';
      case 3:  return $num.'rd';
    }
  }
  return $num.'th';
}
Chintan Thummar
  • 1,462
  • 19
  • 32
3

Generically, you can use that and call echo get_placing_string(100);

<?php
function get_placing_string($placing){
    $i=intval($placing%10);
    $place=substr($placing,-2); //For 11,12,13 places

    if($i==1 && $place!='11'){
        return $placing.'st';
    }
    else if($i==2 && $place!='12'){
        return $placing.'nd';
    }

    else if($i==3 && $place!='13'){
        return $placing.'rd';
    }
    return $placing.'th';
}
?>
Uzair Bin Nisar
  • 675
  • 5
  • 7
2

I made a function that does not rely on the PHP's date(); function as it's not necessary, but also made it as compact and as short as I think is currently possible.

The code: (121 bytes total)

function ordinal($i) { // PHP 5.2 and later
  return($i.(($j=abs($i)%100)>10&&$j<14?'th':(($j%=10)>0&&$j<4?['st', 'nd', 'rd'][$j-1]:'th')));
}

More compact code below.

It works as follows:

printf("The %s hour.\n",    ordinal(0));   // The 0th hour.
printf("The %s ossicle.\n", ordinal(1));   // The 1st ossicle.
printf("The %s cat.\n",     ordinal(12));  // The 12th cat.
printf("The %s item.\n",    ordinal(-23)); // The -23rd item.

Stuff to know about this function:

  • It deals with negative integers the same as positive integers and keeps the sign.
  • It returns 11th, 12th, 13th, 811th, 812th, 813th, etc. for the -teen numbers as expected.
  • It does not check decimals, but will leave them in place (use floor($i), round($i), or ceil($i) at the beginning of the final return statement).
  • You could also add format_number($i) at the beginning of the final return statement to get a comma-separated integer (if you're displaying thousands, millions, etc.).
  • You could just remove the $i from the beginning of the return statement if you only want to return the ordinal suffix without what you input.

This function works commencing PHP 5.2 released November 2006 purely because of the short array syntax. If you have a version before this, then please upgrade because you're nearly a decade out of date! Failing that, just replace the in-line ['st', 'nd', 'rd'] with a temporary variable containing array('st', 'nd', 'rd');.

The same function (without returning the input), but an exploded view of my short function for better understanding:

function ordinal($i) {
  $j = abs($i); // make negatives into positives
  $j = $j%100; // modulo 100; deal only with ones and tens; 0 through 99

  if($j>10 && $j<14) // if $j is over 10, but below 14 (so we deal with 11 to 13)
    return('th'); // always return 'th' for 11th, 13th, 62912th, etc.

  $j = $j%10; // modulo 10; deal only with ones; 0 through 9

  if($j==1) // 1st, 21st, 31st, 971st
    return('st');

  if($j==2) // 2nd, 22nd, 32nd, 582nd
    return('nd'); // 

  if($j==3) // 3rd, 23rd, 33rd, 253rd
    return('rd');

  return('th'); // everything else will suffixed with 'th' including 0th
}

Code Update:

Here's a modified version that is 14 whole bytes shorter (107 bytes total):

function ordinal($i) {
  return $i.(($j=abs($i)%100)>10&&$j<14?'th':@['th','st','nd','rd'][$j%10]?:'th');
}

Or for as short as possible being 25 bytes shorter (96 bytes total):

function o($i){return $i.(($j=abs($i)%100)>10&&$j<14?'th':@['th','st','nd','rd'][$j%10]?:'th');}

With this last function, simply call o(121); and it'll do exactly the same as the other functions I listed.

Code Update #2:

Ben and I worked together and cut it down by 38 bytes (83 bytes total):

function o($i){return$i.@(($j=abs($i)%100)>10&&$j<14?th:[th,st,nd,rd][$j%10]?:th);}

We don't think it can possibly get any shorter than this! Willing to be proven wrong, however. :)

Hope you all enjoy.

Community
  • 1
  • 1
nxasdf
  • 1,088
  • 1
  • 11
  • 11
2

An even shorter version for dates in the month (up to 31) instead of using mktime() and not requiring pecl intl:

function ordinal($n) {
    return (new DateTime('Jan '.$n))->format('jS');
}

or procedurally:

echo date_format(date_create('Jan '.$n), 'jS');

This works of course because the default month I picked (January) has 31 days.

Interestingly enough if you try it with February (or another month without 31 days), it restarts before the end:

...clip...
31st
1st
2nd
3rd

so you could count up to this month's days with the date specifier t in your loop: number of days in the month.

Dan Dart
  • 342
  • 3
  • 13
1
function ordinal($number){

    $last=substr($number,-1);
    if( $last>3 || $last==0 || ( $number >= 11 && $number <= 19 ) ){
      $ext='th';
    }else if( $last==3 ){
      $ext='rd';
    }else if( $last==2 ){
      $ext='nd';
    }else{
      $ext='st';
    }
    return $number.$ext;
  }
xyz
  • 405
  • 1
  • 6
  • 19
1

Here is the correct solution

$numberFormatter = new NumberFormatter('en_US', NumberFormatter::ORDINAL);

echo $numberFormatter->format(11);
MUHINDO
  • 788
  • 6
  • 10
0

Found an answer in PHP.net

<?php
function ordinal($num)
{
    // Special case "teenth"
    if ( ($num / 10) % 10 != 1 )
    {
        // Handle 1st, 2nd, 3rd
        switch( $num % 10 )
        {
            case 1: return $num . 'st';
            case 2: return $num . 'nd';
            case 3: return $num . 'rd';  
        }
    }
    // Everything else is "nth"
    return $num . 'th';
}
?>
mysticmo
  • 47
  • 3
0

Here's another very short version using the date functions. It works for any number (not constrained by days of the month) and takes into account that *11th *12th *13th does not follow the *1st *2nd *3rd format.

function getOrdinal($n)
{
    return $n . date_format(date_create('Jan ' . ($n % 100 < 20 ? $n % 20 : $n % 10)), 'S');
}
0

I realise this is an ancient post, however it's probably worth adding that as of PHP 8, the match control structure allows a more concise:

$ordinal = match(in_array(abs($position)%100, [11, 12, 13]) ? 0 : abs($position)%10) { 1 => 'st', 2 => 'nd', 3 => 'rd', default => 'th' };

If you need to append that to the position to get a place:

$place = $position.match(in_array(abs($position)%100, [11, 12, 13]) ? 0 : abs($position)%10) { 1 => 'st', 2 => 'nd', 3 => 'rd', default => 'th' };

You can obviously simplify that out if you know your position will always be positive or less than 11 or whatever.

Jon Marnock
  • 3,155
  • 1
  • 20
  • 15
-1

I fond this small snippet

<?php

  function addOrdinalNumberSuffix($num) {
    if (!in_array(($num % 100),array(11,12,13))){
      switch ($num % 10) {
        // Handle 1st, 2nd, 3rd
        case 1:  return $num.'st';
        case 2:  return $num.'nd';
        case 3:  return $num.'rd';
      }
    }
    return $num.'th';
  }

?>

HERE

niksmac
  • 2,667
  • 3
  • 34
  • 50
  • 1
    I am not quite sure what this answer offers in addition to ChintanThummar's answer. Well, it hints that ChintanThummar made a copyright violation, unless he wrote the code at your source... – Andreas Rejbrand Feb 04 '15 at 21:11