Sam's infrequently-updated cabinet of curiosities
Tuesday, 15 February 2005

Two Digit Year

One of the main reasons I started using WordPress was to have pretty URLs. It helps with permanence, because I don't find them annoying and get urges to shift to a new system; it helps the user, because hackable URLs are a form of navigation; and oh! it's so much more elegant!

Pretty WordPress URLs typically look something like this:

http://example.com/2005/02/14/example-post/

Which is all fine and dandy, but it's not for me. Posts are so infrequent that I don't need daily archives, and my aesthetic sensibilities demand that the year string be two digits only.

http://example.com/05/02/example-post

Doesn't it make you swoon with delight?

A four digit year is more usable, of course, both because it's more easily recognizable for what it is and because it'll still be problem-free in a hundred years or so, or a thousand. Nevertheless, I'm willing to take on a little work in my extreme old age in the name of beauty.

The problem is that WordPress doesn't let me do it -- %year% in the path template is a four digit string, and that's that. No choice. It's easy enough to hack it in quite elegantly, but this method works as a plugin.

Oh What a Hack

We want to fool WordPress into thinking it got a four-digit year as an input when, in fact, it didn't.

My .htaccess file has a rule that looks something like this:

RewriteRule ^days/([0-9]{4})/... /index.php?year=$1&mo...

(The real thing is absurdly long, so I've cut most of it.)

The rule matches any requests that start with "days/", followed by four numbers, and redirects them to the index.php file. The $1 is everything wrapped in parentheses; that is, it's the string of four numbers matched by [0-9]{4}. You try to visit days/2004/ and the server redirects you to index.php?year=2004.

Faking our four digit year is a simple matter of adjusting the rule to match "days/" followed by two digits, then automagically adding the 20 to the redirect.

RewriteRule ^days/([0-9]{2})/... /index.php?year=20$1&mo...

With older versions this meant manual editing of the .htaccess file, but bleeding-edge builds of WordPress 1.5 (the final of which was released earlier today) make it much simpler.

We can take advantage of the glorious rewrite_rules_array filter hook, and put something like this in a plugin:

function twodigityear_rules($rules) {

    $newrules = array();
    while(list($match, $redirect) = each($rules)) {
        $match = str_replace('{4}', '{2}', $match);
        $redirect = str_replace('year=$1', 'year=20$1', $redirect);
        $newrules[$match] = $redirect;
    }

return $newrules;
}
add_filter('rewrite_rules_array', 'twodigityear_rules', 5);

The next time WordPress generates its rewrite rules, they'll be created my way, baby. Back in the dark ages before 1.5, that was the easy bit; the hard and oh-so-annoying bit was modifying every single core WordPress function which output a URL containing the date. (In most cases, it just meant changing date('Y' to date('y', but it still made upgrades a chore.) Once again, version 1.5 makes it brilliantly easy:

function twodigityear_filter($url) {
    $url = preg_replace("#/[0-9]{2}([0-9]{2})/#", "/$1/", $url);
    return $url;
}
add_filter('post_link', 'twodigityear_filter');
add_filter('year_link', 'twodigityear_filter');
add_filter('month_link', 'twodigityear_filter');
add_filter('day_link', 'twodigityear_filter');

Voilà!

Improvements

Sure that works, but not with entries before 2000. And if you already use four-digit years in your permalinks, it'll break all incoming links. A better solution is to add a new filter, letting us manipulate the query it after the server has redirected the request but before WordPress has finished processing it.

If the request uses one of those outmoded four-digit years, the visitor will be given a 301 redirect to the short version. If the two-digit year string starts with a "9", it'll have "19" prepended; if it starts with anything else, "20". "99" becomes "1999" but "00" becomes "2000".

function twodigityear_query($query) {
    if (preg_match("#year=([0-9]{2,4})#", $query, $match)) {
        $year = $match[1];

        if (strlen($year) == 4) {
            $request = $_SERVER['REQUEST_URI'];
            $request = str_replace("/$year/", '/'.substr($year, 2, 2).'/', $request);
            header("HTTP/1.1 301 Moved Permanently");
            header("Location: $request");
            return $query;
        }
        if (substr($year, 0, 1) == 9) {
            $newyear = "19".$year;
        } else {
            $newyear = "20".$year;
        }
        $query = str_replace("year=$year", "year=$newyear", $query);
    }
    return $query;
}
add_filter('query_string', 'twodigityear_query');

Done and done. This method is more flexible, but for a new blog, without the possibility of orphaned links or pre-2000 entries, the first method is fine. It's more hackish, but arguably better, since it requires a miniscule amount less processing time -- every little bit counts.

The second method also requires a few trivial changes to the earlier functions, as can be seen in the final plugin. Download it here.