Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Display numbers with ordinal suffix in PHP

Tags:

php

numbers

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);

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.


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!


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.