Display numbers with ordinal suffix in PHP
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!
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.
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);
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.