PHP code to add st, nd, rd or th to a number

This one's short and sweet: while working on a PHP application I needed to format some numbers as text so that, for example, '1' became '1st', '13' became '13th', '22' became '22nd' and so on. There isn't a built-in PHP function to do this (as far as I know) and none of the code snippets I came across online were very neat, so I ended up writing my own function. Since it's something that I imagine gets used a lot, I thought I'd put it up here. I am releasing it into the public domain so everyone is free to use the code however they wish, although some acknowledgement or feedback if it's been useful to you would be appreciated!


<?php
function ordinal($input_number)
{
  $number = (string) $input_number;
  $last_digit = substr($number, -1);
  $second_last_digit = substr($number, -2, 1);
  $suffix = 'th';
  if ($second_last_digit != '1')
  {
    switch ($last_digit)
    {
      case '1':
        $suffix = 'st';
        break;
      case '2':
        $suffix = 'nd';
        break;
      case '3':
        $suffix = 'rd';
        break;
      default:
        break;
    }
  }
  if ((string) $number === '1') $suffix = 'st';
  return $number.$suffix;
}
?>

Comments

Here's a similar snippet of code, this time in JavaScript, which will output the date that a page was last modified in human readable format. See https://secure.kitserve.org.uk/pete/ with JavaScript enabled for an example of it in use. Note that the suffix is only correctly generated for numbers less than 100, for example '111' would be turned into '111st'! This isn't a problem for dates, of course, but you'll want to bear it in mind if you're using the code for more general numbers. Generalising the code to fix this issue would be trivial, for example by adding something along the lines of

var num_remainder = day%100;

and changing the if and switch statements to operate on num_remainder instead of day.


// Written for http://www.kitserve.org.uk/ on February 18th, 2010 and released into the public domain.
// This script outputs the date that the current document was last modified in human readable format.
// You are welcome to use it yourself, links back to kitserve.org.uk would be appreciated!

// Initialise variables:
var fulldate = new Date(document.lastModified);
var day      = fulldate.getDate();
var month    = fulldate.getMonth();
var year     = fulldate.getFullYear();

// Set the names of the months in an array:
var monthname = new Array();
monthname[0]  = 'January';
monthname[1]  = 'February';
monthname[2]  = 'March';
monthname[3]  = 'April';
monthname[4]  = 'May';
monthname[5]  = 'June';
monthname[6]  = 'July';
monthname[7]  = 'August';
monthname[8]  = 'September';
monthname[9]  = 'October';
monthname[10] = 'November';
monthname[11] = 'December';

// Set a suffix for the day number:
var day_suffix = 'th';
if (day < 10 | day > 20)
{
  switch (day%10)
  {
    case 1:
      day_suffix = 'st';
      break;
    case 2:
      day_suffix = 'nd';
      break;
    case 3:
      day_suffix = 'rd';
      break;
    default:
      break;
  }
}

// Output the date:
document.write('This page was last updated on ' + monthname[month] + ' ' + day + day_suffix + ', ' + year + '.');

Alternatively, you can copy the code from https://secure.kitserve.org.uk/pete/scripts/lastupdate.js - please don't hotlink to it, if you want to use the code on one of your own sites copy the .js file across. As with the PHP example above, any comments are welcomed.

Nick Hatter - Tue, 27/04/2010 - 21:35

Permalink

Instead of breaking out on the switch-case, could you just return with the literal concatenated instead, as shown below:

The only problem I can see with this is that the return will then be cast as a string, not an integer - so if you want to increment the number down the line you'd need to pass the original value - we've now got two variables $original_num and $ordinal_num, or the need to always call ordinal() on-the-fly, which might get a bit confusing, especially if you're a retard like me :)

Also handling real number arguments is a bit of a problem, since ordinal(3.1412) -> '3.1412nd' , lol! Obviously you could grep the decimal point and either call die/exit or force an exception, but that's a bit extreme. A better solution overall would be to use a well-known tightly-typed language and extend the relevant Number class. But we all know Pete hates Java, hohoho.

Joe ;)

There's nothing wrong with Java, it's structurally a much better language than PHP, I don't like weakly-typed languages for exactly the sort of reasons you mentioned. Unfortunately, loads of web hosts have PHP enabled, whereas comparatively few are set up to handle Java servlets.

Besides that, Java is generally considered overkill for the sort of basic text handling we're discussing here. Of course, the flip side of this is that most people end up using PHP for quite complicated data handling websites as well and then get in a mess, whereas using Java would have protected them from their mistakes a lot more. Personally I prefer C++, but that's a rant for another day, and you'd have to be a bit mad to do your server-side web processing in C++ anyway!

I don't see the problem with $original_num for internal processing vs. $ordinal_num for output to the HTML page, as you ought to be sanitising background data before outputting it to the page anyway — if you don't, you're inviting XSS attacks, not to mention all kinds of annoying HTML glitches with data that contains quotes, apostrophes, ampersands, etc...

As for the problem with non-integer numbers, a quick and dirty way to deal with it would be a cast to int of the input variable — not ideal, but then this function shouldn't be called on non-int variables anyway! If you want to do it the proper way and protect users from themselves(!), you could add a conditional based on PHP's is_int() function. If it's false, just return the input variable unchanged...

.. forgot about is_int() .. the point you make about feature-creep is particularly pertinent in bioinformatics - e.g. the Perl package BioPerl is a great repository of algorithms that could run a lot quicker in pretty much any other language, bar BASIC!

Anonymous - Tue, 17/05/2011 - 14:53

Permalink

For values less than 10, this routine doesn't work, as the $second_last_digit will always be the same as $last_digit due to the way PHP works.

$second_last_digit = substr($number, -2, 1);

If the string in only one character, it will return that character, which then breaks your logic.

Anonymous - Fri, 31/08/2012 - 00:54

Permalink

After looking at different solutions, I thought it would be better to just use the functions built into PHP. Not sure if this is more efficient, but it's certainly less code and works every time.


<?php
function ordinal($int)
{
  $two = substr($int, -2);
  $day = $two < 32 ? $two : '2' . substr($two, -1);
  $suffix = date('S', strtotime('May ' . $day));
  return $int . $suffix;
}
?>

Add new comment

CAPTCHA