Joe's
Digital Garden

Date-Time Handling

Some thoughts regarding how to handle date-times in large multi-user PHP applications.

Dates are not integers or strings

Dates are not integers, even though unix timestamps makes them seem as such. Dates are not strings either, even though they may be stored in the database as such. Dates are complex domain objects and should be handled in the model layer and formatted to a string when printing to the view. The classes of the DateTimeInterface are the best solution for handling this.

As such a date or date time passed in a request should be converted into an object and passed into the model. The model should return objects as dates to the controller and into the view -- not the string representation stored in the database.

Date times at rest should be stored in the ISO 8601 format.

Time stamps are used to store a discrete point in time. They are not dates or date times and should not be used in this function. Use them to calculate the passage of time between two points in which we are not interested in the date in which those points occurred, e.g. calculating the run-time of a script, or in a stopwatch application. In these cases we aren't concerned with the time in which the points occurred, we are interested in the elapsed number of seconds between the two points.

Define Date Format Constants

Early in the development cycle, define a set of constants to cover your common date formats:

namespace Utils\Date;

const 
    DATE_FORMAT_ISO = 'Y-m-d'
    DATE_FORMAT_US = 'm/d/Y'
    DATE_FORMAT_UK = 'd/m/Y
;

Always use your format constants, introducing an abritrary format is the same as using "magic" numbers throughout your code:

use Utils\Date;

echo date(Date::DATE_FORMAT_ISO); // YES!
echo date('Y-m-d'); // NO!

$DateTime = new \DateTimeImmutable();
echo $DateTime->format(Date::DATE_FORMAT_US); // YES!
echo $DateTime->format('Y-m-d'); // NO!

On Timezones

The following behaviors should be true:

  1. Server timezone should be UTC, but if it can't then all servers should at least be on the same time zone.
  2. Store all dates and date-times in the server time zone. Conduct all manipulations or date-math in the model in the server time zone.
  3. Convert the date or date-time from the server time zone to user's time zone in the controller or view layers; convert an incoming date or date-time from the user's time zone to the server time zone before passing into the model.

Consolidate View-Layer Date Manipulation

Early in development produce a method for collecting a time zone and preferred date format from the user. Introduce a utility function for views and controllers to quickly convert from a user's time zone and format:

namespace Utils\Date;

use \DateTimeInterface;
use \DateTimeImmutable;
use \DateTimezone;

const ERROR_INVALID_DATE_STRING = "Date provided is an invalid format";

function displayDate(DateTimeInterface $DateTime, array $session)
{
    return $DateTime
        ->setTimezone($session['user']['timezone'])
        ->format($session['user']['dateFormat']);
}

function displayDateTime(DateTimeInterface $DateTime, array $session)
{
    return $DateTime
        ->setTimezone($session['user']['timezone'])
        ->format($Session['user']['dateTimeFormat']);
}

function normalizeDateTime(string $dateTime, array $session)
{
    $DateTime = DateTimeImmutable::createFromFormat(
        $session['user']['dateTimeFormat'],
        new DateTimezone($session['user']['timezone']
    );

    if (!$DateTime) {
        $DateTime = DateTimeImmutable::createFromFormat(
            $session['user']['dateFormat'],
            new DateTimezone($session['user']['timezone']
        );
    }

    if (!$DateTime) {
        throw new \RuntimeException(ERROR_INVALID_DATE_STRING);
    }

    return $DateTime->setTimezone(default_timezone_get());
}

In a view, outgoing to the user we would then see usage of these functions to quickly convert from the server time to the user's time:

<?php

use Utils\Date;

?>

<h1>Today's Date Is <?= Date::displayDate($Today, $_SESSION) ?></h1>

Linked References

  • php-best-practices
  • php-view

    The View object is created by the Controller and then fed the various Entities returned from the [[Application Service]] layer. This ensures that all properties required by the View are either set or set to some known default. It also allows us to perform any last minute formatting of the data such as [[datetime handling]], normalizing number or currency representations (trailing zeros, comma vs periods, currency signs), etc. We should avoid any kind of business logic in the View model. Simple formatting is one thing, but no where should we declare new symbols nor modify existing symbols except changing their expression -- e.g. converting $20.00 to $20,00 does not change the value, but the expression 20.00 + 20.00 does.