Display the Age of a Resource

Displaying the age of a MODX Resources in various forms.

By Bob Ray  |  Updated: February 24, 2023  |  6 min read
Display the Age of a Resource

You might sometime have a need to display the age of a Resource. The old MODX Forums, for example, show how long ago a post was entered in years, months, weeks, days, hours, and seconds. The display shows the age in the largest and second-largest of these that contain a non-zero value. For example, you might see posts with messages like one of these:

1 year, 9 months ago
2 weeks, 3 days ago
5 minutes, 30 seconds ago

The new “MODX Community” Forum uses a similar scheme.

I didn’t have access to the Discuss code that does this (it’s not in the version of Discuss released at this writing), so I had to reverse engineer it. I didn’t look very hard for the code because I thought creating it would be fun.

I started from the excellent `time_duration` function created by Aidan Lister. I modified it to work as a Snippet in MODX, added some MODX-specific sanity checks and features, and modified it to (optionally) produce the two-value display you see in the Forums.

By default (i.e., with no properties in the tag) it creates the two-value string you see in the Forums based on the createdon field of the current Resource. Using the properties, you can choose to have it display the full value, omit certain of the terms, and/or display zero values. You can also control which Resources it reports on, which field it uses, and what it returns in the case of non-date fields, empty fields, or unpublished Resources (if using the publishedon field).

The Code

/* ResourceAge snippet */
function time_duration($seconds, $full = false, $use = null, $zeros = false) {
    /* Define time periods */
    $periods = array(
        'years'   => 31556926,
        'Months'  => 2629743,
        'weeks'   => 604800,
        'days'    => 86400,
        'hours'   => 3600,
        'minutes' => 60,
        'seconds' => 1
    );

    /* Break into periods */
    $seconds = (float) $seconds;
    $segments = array();
    $haveOne = false;
    foreach ($periods as $period => $value) {
        if ($use && strpos($use, $period[0]) === false) {
            continue;
        }

        /* round the current number down */
        $count = floor($seconds / $value);

        if ($count == 0 && !$zeros) {
            continue;
        }
        $segments[strtolower($period)] = $count;
        $seconds = $seconds % $value;
        if (!$full && $seconds)  {
            if ($haveOne) {
                break;
            } else {
                $haveOne = true;
            }

        }
    }

    /* Build the string */
    $string = array();
    foreach ($segments as $key => $value) {
        $segment_name = substr($key, 0, -1);
        $segment = $value . ' ' . $segment_name;
        if ($value != 1) {
            $segment .= 's';
        }
        $string[] = $segment;
    }

    return implode(', ', $string);
}
/** @var $scriptProperties array */
/* Save some typing */
$sp = $scriptProperties;

/* Get the property values sent in the tag */
$docId = $modx->getOption('docId', $sp, null);
$field = $modx->getOption('field', $sp, 'createdon');
$full = $modx->getOption('full', $sp, false);
$use = $modx->getOption('use', $sp, null);
$zeros = $modx->getOption('zeros', $sp, false);
$unpublishedMsg = $modx->getOption('unpublishedMsg', $sp, 'Not Published');
$errorMsg = $modx->getOption('errorMsg', $sp, 'Field is empty or not a date field');

/* Return a message if the field is publishedon and the
   doc is not published. */
if ($field == 'publishedon' && (!$doc->get('published'))) {
    return $unpublishedMsg;
}

/* Use the current resource if $docId is not set */
$doc = $docId? $modx->getObject('modResource', $docId) : $modx->resource;

/* get the timestamp of the selected field */
$co = strtotime($doc->get($field));

/* return an error message for empty or non-date fields */
if ($co === false) {
    return $errorMsg;
}
/* get the elapsed time in seconds */
$sec = time() - $co;
return time_duration($sec, $full, $use, $zeros);

The Snippet uses $modx->getOption() to retrieve the properties sent in the Snippet tag and provides a default value for each of them. In the next article, we’ll look at some of the finer points of using $modx->getOption(). For now, back using our Snippet.

Usage

In its simplest form, the Snippet returns the age as a two-value string based on the createdon field of the current Resource:

This page was created [[!ResourceAge]] ago.

This page was created 2 months and 2 days ago.

If you want the value of another Resource, you can send its ID in the &docId property.

If you want to use a field other than createdon (e.g., editedon or publishedon, send the name of the field in the &field property. Using the pub_date or unpub_date fields is discouraged because they are typically emptied when the Resources is published or unpublished. Be sure the field you use is a date field.

If you want the full display, use &full=1. That will give you a result like this:

This page was published 1 year, 7 months, 2 weeks, 1 day, 4 hours, 3 minutes, 20 seconds ago.

You can select the units used and ignored by sending the &use property. It defaults to yMwdhms (years, months, weeks, days, hours, minutes, seconds). You can leave out any of the letters to omit that unit. Note that M (uppercase) is for month and m (lowercase) is for minute.

By default, zero values are not displayed, but if you include &zeros=1, they’ll be included.

The &unpublishedMsg property sets the message returned when you’ve used publishedon for the field and the Resource is unpublished. The default is “Not Published”. If you don’t want a message returned, use &unpublishedMsg=``.

The &errorMsg property sets the message returned when the field is empty or not a date field. The default is “Field is empty or not a date field”. If you don’t want a message returned, use &errorMsg=``.

Use in Tpl Chunks

I have not tested this, but depending on the parsing order, the Snippet should work in Tpl Chunks used by getResources or another aggregator if you use this form:

[[ResourceAge? &docId=`[[+id]]` ]]

Notes

The values displayed are a little imprecise, since they don’t take account of specific month lengths or leap years (months are assumed to be about 30.44 days). Having the Snippet take into account the specific months involved in the interval and deal with leap years and leap seconds would lengthen the code and slow it down considerably. I didn’t consider it worth the effort, but feel free to add the appropriate modifications.

The second value is also rounded down, so a message posted 6 days, 23 hours, 59 minutes, and 59 seconds ago would be displayed as 6 days, 23 hours ago, even though it is one second shy of a week ago. You can always use the “full” value if you need more accuracy.

If I can find the time, I may release this Snippet as an Extra.

Addendum (Thanks Mark Hamstra)

My friend Mark Hamstra has reminded me that there is also an output filter ago that's very similar to the snippet in this article. I'm generally not a fan or output filters, but this one does a very good job.

My snippet has a few extra features such as allowing you to control the units used, producing an error message for invalid dates, and displaying a message for unpublished resources. On the other hand, the output filter does a better job of internationalizing the output.

Here's the form for the output modifier:

[[*publishedon:ago]]

Bob Ray is the author of the MODX: The Official Guide and dozens of MODX Extras including QuickEmail, NewsPublisher, SiteCheck, GoRevo, Personalize, EZfaq, MyComponent and many more. His website is Bob’s Guides. It not only includes a plethora of MODX tutorials but there are some really great bread recipes there, as well.