Date ordinals: An ugly solution to an ugly problem

A friend bumped into what appears to be a very irritating problem yesterday with the PHP date_format() function, which is used by format_date() to show date and time strings on Drupal. This function uses the "S" format character, which returns the english ordinal number suffix for the current day of the month. E.g: "st" on the first day, "nd" on the second day, and so on. (And the date() function does too, coincidentally)

The problem is that when you're working in a non-english locale, the ordinal suffixes returned remain the english ones. Oops.

I thought that this should not be a very hard problem to fix, so I dug through the PHP code a little and found that the "S" format character is hard-coded to use output from the internal english_suffix() function. As that function name implies, there is no provision for using non-english ordinal number suffixes in PHP. Ugh!

Undeterred I decided that this was fixable via an LD_PRELOAD hack, but since the english_suffix() function is not compiled into a library, but into the PHP core, that appears to not actually work. (I may be wrong, but my C-fu is pretty weak and I am happy to be corrected about this - povided you also supply working code ;-)

Some more googling turned up the rename_function() function that's built right into PHP itself. It requires the APD PECL module, which is less than ideal but a not insurmountable problem. However, the APD module won't compile for PHP 5.3 and so I kept on googling.

The next possible solution was runkit_function_rename() which is provided via the Runkit PECL library. Now Runkit also didn't compile, but I found that someone had done a bit of work to port it forward and put the  result at https://github.com/zenovich/runkit and that module compiles fine :-)

The result is a little library file that provides date_format() and date() functions that takes an optional third parameter containing a valid locale string. If this parameter is empty, the current system locale will be used.

The replacement functions use "magic name" callbacks to translate a number into an ordinal suffix for a given language, so you can easily add your own. The snippet of code below will let you have a play after you build, enable and configure the Runkit PECL extension from github:

<?php
  require_once('date_patch.inc');

  setlocale(LC_TIME, 'fi_FI.UTF-8');

  print date('jS F');
  print date('jS F', NULL, 'fr_FR.UTF-8');

  $date_time = date_create('@' . time());
  print date_format($date_time, 'jS F');
  print date_format($date_time, 'jS F', 'fi_FI.UTF-8');
?>

Success of sorts! Yay! (But hnnngg!)

AttachmentSize
date_patch.inc_.txt5.1 KB

Comments

The usual answer is to use strftime() (which uses the libc strftime() and is hence locale-aware) rather than date() or date_format() if you need localised formatting. I would caution quite strongly against runkit — it's definitely not intended to be used anywhere near a production server.

Oh yes, this was more of a "is this problem fixable at all and how" rather than a "you should roll this out on production servers immediately" type post.

I generally use strftime() in my own code, but that doesn't have a formatter for an ordinal suffix, so it doesn't resolve the original problem if the user wanted to use the ordinal (as opposed to not having any, which also means they're not in english on a non-english site ;-)

However, Drupals API uses date_format() only and so all code that uses Drupals API is currently setup to use the single character date() style formatters and not the %n strftime() ones. Simply prefixing them all with a percent sign on't work, as they don't match character for character... that would be un-fun to fix (though maybe it's an idea to have a go at this for Drupal 8).