As a person who used to write code that had to run in 64K of memory, the way MODX date strings are sometimes handled has always disturbed me. I'm calling them date strings, but they are more correctly called date/time strings. I'm too lazy to type that over and over, so I'll just be using the word 'date' to describe them.
MODX Date Fields
MODX stores dates like the Resource
editedon fields as Unix timestamps. That means they're just a big integer that contains the number of seconds since January 1st, 1970. This is great for doing date comparisons, computing the time interval between two dates, or seeing how long it's been between a date and right now, but it's not so great when you want to show a date to a site visitor.
To solve that problem, MODX automatically converts the timestamps to a human-readable date when you put a tag like this on a page:
This conversion is done at a very low level by xPDO. In fact, when you get the date in code with something like this:
$d = $resource->get('createdon');
What you get back looks something like this:
Somewhere in MODX I think there's a place to set the format of that string, but a) I can never remember where, and b) I want different formats on different pages.
Setting the Date Format with Output Modifiers
PHP has a number of date-handling functions, but most of the time, you need either
strftime() (usually both).
These two functions are complimentary. The strtotime() function converts a date like the one shown above to a Unix timestamp. It has evolved over the years to the point where it can turn almost any conceivable human-readable date into the correct timestamp. It will handle strings as varied as the following:
- Tuesday, June 24 2008
- June 1st 2013:22:34
- 2012-01-03 7pm
- 01-03-13 6 a.m.
In fact, it's difficult to come up with a date and time it can't handle.
The strftime() function does the opposite. It converts a Unix timestamp to a human-readable date. Its first argument specifies the format you want and it's flexible enough to put the date in any reasonable form (with one exception: it won't do things like 1st, fourth, or 2nd). The
strftime() function also respects the current
locale setting, which can be set with the
locale MODX System Setting, or by a Context Setting or User Setting with the same name. You can also set the locale in code with PHP's
setLocale() method. That means, for example, that the day and month names will be translated into the appropriate language.
It's easy to remember which function is which because
strtotime() does exactly what its name implies, it converts a string to a timestamp. You can remember that
strftime() converts a timestamp to a human-readable string because it has an
f in the middle of it, which tells us it formats a timestamp.
Back to Our Problem
To help us format the date string they way we want, MODX provides two output modifiers, named
date. The first one one simply calls the
strtotime() PHP function and returns the output. The second one (
date) calls PHP's
strftime() function. Because the tag is already converted to a human-readable string before the first output modifier gets it, we need to convert it back to a Unix timestamp with the
strtotime modifier, then format that timestamp with the
[[*createdon:strtotime:date=`%A %B %d, %Y`]]
That will display something like this:
Monday January 10, 2011
Notice that any spaces or punctuation in the format string (e.g., commas, colons, or dashes) will be taken literally and output at their locations in the displayed string
Explaining all the possible tokens in the format string used with the
date modifier is beyond the scope of this article, but they are very well documented here.
Another option if you're working in code is the PHP date() function, though I seldom use it because it won't respect the
locale setting. Note that if you switch back and forth between
date(), the format strings are not the same.
So What's My Problem
I mentioned above that this process disturbs me. It's because not just one, but two completely unnecessary conversions are being performed in this process. The date is already a timestamp in the MODX database. It's being converted to a human-readable string by xPDO, then back into the exact timestamp it was originally by the
strtotime output modifier. Since parsing a format string is involved in both conversions, they take time. It's a trivial amount of time, but because both conversions are technically unnecessary and because site visitors are waiting for the page to load, the process offends my sensibilities.
Take the example of a blog where the age of each post in months, days, and years is displayed. The calculation can't be cached, because it changes constantly. If a page displays 10 posts and the conversion is done with output modifiers, every page load will be delayed by 20 unnecessary conversions.
Because xPDO does the first conversion from timestamp to human-readable date on every date you get, this can't really be solved with output modifiers. Even a custom modifier is going to get passed the human-readable date. It can be solved in code, though.
There may be a better way to get a raw date field in code (PDO would certainly do it), but this works and will cut the processing time considerably. The MODX Resource fields are currently contained in the
_fields member variable of the Resource object. So the raw timestamp for the
createdon field is available as
$resource->_fields['createdon']. That means that we can get the timestamp for any MODX Resource date field with a fairly simple snippet:
[[!DateFormat? &field=`createdon` &formatString=`%A %B %d, %Y'`]]
<?php /* DateFormat snippet */ $field = $scriptProperties['field']; $format = $scriptProperties['formatString']; $timestamp = $modx->resource->_fields[$field]; return strftime($format, $timestamp);
This only solves the problem for the date fields of the current Resource, but with a little modification, the method could be used any time you have a Resource object in code and want to display one of its date fields. If you want to display a date field from another resource, you could pass the Resource's ID in a
&docId property in the tag, get the Resource object with
$modx->getObject('modResource', $scriptProperties['docId']), and get the timestamp from its
_fields member. You could also pass the
alias, but that would be a little slower.
A Word of Warning
_fields member of the Resource object is meant to be used internally. It's not part of the official MODX database interface. That means it could stop working in a future version of MODX. I don't think that's likely any time soon, but if it were to happen, your date displays might be incorrect or blank. They could also throw a PHP error. You'd have to rewrite the snippet to correct the problem.