Working with time
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).