What’s The Best Date Format?
When it comes to displaying dates and times, there’s no single format that works for every situation. With such wide international and regional variation in formatting, and so many different situations in which dates and times are used, every case is unique — from verbose and lyrical sentences like Monday the 21st of March at 12:18am (EST)
, to enigmatic and compact output like Today
.
But when it comes to working with dates programatically — storing, comparing and sorting with them — there’s really no need for such a wide variety of formats. Indeed, it makes much more sense to unify the use of programatic dates, and use the same format for everything.
So what’s the best format to use?
It would need to tick several boxes — think of these as ‘must-have’ features:
- it stores a complete date and time, providing all the raw information we need to create any other format
- we can use it as the input for creating and converting dates — eg. it must be compatible with the timestamp argument to JavaScript’s
new Date
constructor - it always use the same, fixed timezone, to provide a frame of reference for comparison
Over and above those core requirements, there’s a few ‘would-be-nice’ features:
- its format is natively sortable without additional conversion (ie. if used as a filename, it would create a chronological sort-order)
- it has no internal whitespace — to avoid parsing ambiguities such as excessive contiguous space, or tab/space difference
- it provides at least some level of human-readability, chiefly for programmers’ sake
- it should give us at least some ‘free’ information, ie. values such as the year or the hour being easily obtained by string parsing, without having to create and parse new date objects every time
What about timezones?
It’s not really necessary to store dates and times in different timezones — every given timestamp has a UTC/GMT equivalent. So the simplest thing to do is record all timestamps in UTC, converting them to local-time only when outputting to the client. This gives us a common frame of reference for programatically comparing dates, without losing any information we need for internationalization.
Since JavaScript evaluates on the client, it uses the client’s clock as a time reference. So any timestamp passed through the Date
constructor is automatically converted to the user’s local time.
My ultimate recommendation
In the past, until I actually sat down and though this out seriously, I generally used the RFC 2822 date format (eg. "Mon, 21 Mar 2011 00:18:56 +0000"
) — mostly because it’s so easy to generate in PHP (defined by the formatting character "r"
), and it ticks all the must-have boxes. But it’s not natively sortable, and not the choice I ultimately arrived at:
My ultimate recommendation is ISO 8601 (2004) in UTC.
There are several variations of this format, and the one I’ve specifically chosen uses the date-time delimiter "T"
and the timezone designator "Z"
(which designates UTC); other variations use a space as the delimiter, or designate the timezone as "+00:00"
, but I don’t recommend either of those — the space makes it incompatible with new Date
in Firefox, and the longer more complex timezone designator is simply unnecessary.
So the timestamps we’re talking about look like this: "2011-03-21T00:18:56Z"
Although this format is not as easy to generate as others (see How to use the ISO format below), it does tick all the other boxes, both the must-haves and would-be-nices. And it also provides a number of additional benefits — bonus features, you might say, which solidify its status as the ideal choice:
- it’s always a constant length
- it’s the format used natively by JSON (eg. in JavaScript, if you pass a
Date
object throughJSON.stringify
, it’s converted to this format) - it’s the format and profile recommended by the W3C
- it’s compatible with the
DATESTAMP
column-type in SQL, albeit as ‘relaxed’ syntax
An issue I’ve discovered with PHP and MySQL, is that when a timestamp in this format stored in a DATESTAMP
column, its "T"
delimiter is replaced with a space, and its "Z"
token is removed, converting it to the strict DATESTAMP
format. But this is not what we want, and not acceptable.
All I do to fix this is re-convert the output, but I’d love to hear of a more permanent solution — a MySQL config option or something?
How to use the ISO format
PHP doesn’t have a pre-defined constant or token for creating this precise format, although the formatting-token "c"
creates one very similar (same standard, different variant). We could then convert the differences with string-replacement; but I reckon the simplest thing to do is just compile it manually, from individual date and time components, using the gmdate function to get UTC:
$timestamp = gmdate('Y-m-dTH:i:sZ');
Once we have our timestamp, we can use it on the client as the timestamp argument to new Date
. For example, we could first output the raw timestamp to static HTML:
<dl class="listing">
<dt>Date created:</dt>
<dd id="listing-timestamp"><?php echo gmdate('Y-m-dTH:i:sZ'); ?></dd>
</dl>
And then use JavaScript to convert it to a re-formatted locale-specific date and time, eg:
var dd = document.getElementById("listing-timestamp");
dd.innerHTML = new Date(dd.innerHTML).toLocaleString();
Of course the mechanics of passing timestamps around between the client and server will vary tremendously with the application. A PHP script may output the timestamp directly to server-generated JavaScript; it might be the response of an Ajax request, or it might be written to static HTML, like the example above. However it’s done, the most common use-case is likely to be server-generated timestamps which output to the client for conversion, and the processes I’ve described so far will do that. But what of other cases?
You might be working entirely on the server, and only outputting fully-formatted, user-ready dates. PHP provides numerous ways of internally converting one date to another, but the one I find most straightforward is to pass the timestamp through strtotime (to convert it to a numeric value), and then use that as the second argument to date:
$rfcdate = date('r', strtotime($timestamp));
Conversely, you might be generating and converting entirely on the client, and in that case you’ll need a way of generating the timestamp in JavaScript. The following function will do the job:
function getTimestamp()
{
var dateobj = new Date();
dateobj.setTime(dateobj.getTime() + (dateobj.getTimezoneOffset() * 60000));
var datetime = {
date : [
dateobj.getFullYear(),
dateobj.getMonth() + 1,
dateobj.getDate()
],
time : [
dateobj.getHours(),
dateobj.getMinutes(),
dateobj.getSeconds()
]
};
for(var key in datetime)
{
if(!datetime.hasOwnProperty(key)) { continue; }
for(var i in datetime[key])
{
if(!datetime[key].hasOwnProperty(i)) { continue; }
var n = datetime[key][i];
datetime[key][i] = (n < 10 ? '0' : '') + n;
}
}
return datetime.date.join('-') + 'T'
+ datetime.time.join(':') + 'Z';
}
The timestamp it produces can then be passed to new Date
, and processed in locale-time from there:
var timestamp = getTimestamp();
...
var rfcdate = new Date(timestamp).toString();