After seeing the following post
The deep dive instinct in me wanted to explore more about PHP date handling.
The thing that put me over the edge to start it is easter_date. Especially the workaround for the timezone.
The basics
I hope everyone has moved away from most of the date/time functions and uses the DateTime or even better the DateTimeImmutable classes.
$year = 2026;
$month = 4;
// old
$lastDayOfMonth = date('d', strtotime("last day of $year-$month"));
// new
$lastDayOfMonth = new DateTimeImmutable("last day of $year-$month")->format('d');
// alternative
$lastDayOfMonth = cal_days_in_month(CAL_GREGORIAN, $month, $year);
The alternative function comes from the calendar extension. Most of the times this extension is added to PHP. And it provides a few useful functions, like easter_day I mentioned before. But be aware that it could be missing.
To manipulate the date there are the add and sub methods.
$year = 2026;
$month = 4;
$firstDayOfMonth = new DateTimeImmutable("$year-$month-1");
$lastDayOfMonth = $firstDayOfMonth->add(DateInterval::createFromDateString("1 month - 1 day"));
The example is a long way to get the last day of the month. It does introduce the DateInterval class.
The DateInterval class is useful when periods come into play.
$year = 2026;
$month = 4;
$firstDate = new DateTimeImmutable("$year-$month-23")->add(new DateInterval('P1M'));
$secondDate = new DateTimeImmutable("$year-$month-14");
echo $firstDate->diff($secondDate)->format('%r%a') . ' days between both dates';
// -39 days between both dates
More advanced
The Period library that started my curiosity provides a nicer way of working with periods, but PHP also provides a DatePeriod class.
$year = 2026;
$month = 4;
// library
$period = Period::fromMonth($year, $month);
$period->startDate;
$period->endDate;
$period->dateInterval();
foreach ($period->dateRangeForward(new DateInterval('P1D')) as $datepoint) {
$datepoint->format('Y-m-d');
}
// PHP
$period = $period = new DatePeriod(new DateTimeImmutable("$year-$month-1"), new DateInterval('P1D'), new DateTimeImmutable("last day of $year-$month"));
$period->start;
$period->end;
$period->interval;
foreach($period as $datepoint) {
$datepoint->format('Y-m-d');
}
Where the Period library shines is by providing shortcuts and comparison functions.
But if you don't need all those functions a Dateperiod child class can make the code easier to work with.
class ComparableDatePeriod extends DatePeriod
{
public static function createMonth(int $year, int $month): static {
return new static(new DateTimeImmutable("$year-$month-1"), new DateInterval('P1D'), new DateTimeImmutable("last day of $year-$month"));
}
public static function withInterval(DateTimeImmutable $start, DateInterval $interval): static {
return new static($start, new DateInterval('P1D'), $start->add($interval));
}
public function containsDay(DateTimeImmutable $day) : bool {
foreach($this as $d) {
if($d == $day) {
return true;
}
}
return false;
}
public function containsPeriod(ComparableDatePeriod $period) {
$hasBeginAndEnd = 0;
foreach($this as $day) {
if($day == $period->start) {
$hasBeginAndEnd++;
}
if($day == $period->end) {
$hasBeginAndEnd++;
}
}
return $hasBeginAndEnd == 2;
}
}
$year = 2026;
$month = 4;
$period = ComparableDatePeriod::createMonth($year, $month);
$period->containsDay(new DateTimeImmutable("$year-5-1"));
$period->containsPeriod(ComparableDatePeriod::withInterval(new DateTimeImmutable("$year-$month-4"), new DateInterval('P1M')));
Conclusion
The relative formats in PHP are very powerful because they not only provide a more human way of setting the date, but it also comes with date calculations.
Next time people suggest the Carbon library as the default, think of what the application needs before adding it.
Top comments (0)