Working with time

Working with time
Photo by Jazmin Quaynor / Unsplash

When working with time in general, there are a few concepts that can be used and might make sense. There are "dates", "time of day" and "moments in time". Unfortunately PHP only contains one construct (DateTime) to handle "moments in time" but nether "dates" nor "time of day".

But where does this distinction matter?

One example would be handling at what time of day a certain action is allowed or not. You might store the time allowedFrom and allowedTo to your configuration and need to validate those against the time someone wants to book. Time properties might even be stored as Time in a supporting database like Postgres. But as soon as it's pulled into PHP it will be a DateTime again. So to be able to compare the moments in time, you need to update the day of the moments in time as they otherwise are prefilled with the current day.

// Preparation
$allowedFromOnBookingDate = $configuration->allowedFrom->setDate(
    (int) $command->bookingStartingAt->format('Y'),
    (int) $command->bookingStartingAt->format('n'),
    (int) $command->bookingStartingAt->format('j'),
)
$allowedToOnBookingDate = $configuration->allowedTo->setDate(
    (int) $command->bookingStartingAt->format('Y'),
    (int) $command->bookingStartingAt->format('n'),
    (int) $command->bookingStartingAt->format('j'),
)

// Validation
if ($command->bookingStartingAt < $allowedFromOnBookingDate
    || $command->bookingStartingAt > $allowedToOnBookingDate
) {
    throw new BookingIsOutsideOfAllowedTimeFrame();
}

Forgetting it, will break the validation for bookings that are not for the current date.

A better option

Fortunatelly there is a library called aeon-php that wraps DateTime and makes a distinction between those concepts. This way you can compare "time of day" of different dates. Which transforms the example into the following:

// Validation
if ($command->bookingStartingAt->time()->isBefore($configuration->allowedFrom)
    || $command->bookingStartingAt->time()->isAfter($configuration->allowedTo)
) {
    throw new BookingIsOutsideOfAllowedTimeFrame();
}

The allowedFrom and allowedTo properties are objects of the class Time. The $command->bookingStartingAt is a DateTime of the aeon-php library and provides the getter time() that then can be used for validation.

When to use it

The best practice is to save everything as DateTime and not just Date. This is useful in and of itself just because it makes sense to work in UTC and convert to the user timezone to prevent issues with switches from summer time to winter time. But there are exceptions. And one of them is when you're not working with a moment in time but time of a day like in the example.

aeon-php seems to be the biggest library in the PHP ecosystem that makes a clean distinction between the concepts and offers a object oriented separation. Additionally it offers extensions for Twig, doctrine types for storage and much more. I can really recommend having a look into it when you have a little bit more advanced requirements when handling dates (and times).