Every project reaches the point where it has to print "posted 3 days ago", work out whether a subscription has run out, or turn 2026-09-07 into something a human wants to read. All of that is one function and one class: date() for formatting, and DateTime for anything involving arithmetic. Learn where the line between them sits and PHP date handling stops being fiddly.
The catch is that a date is never just a number. It carries a timezone, and PHP will happily pick one for you if you do not โ usually the wrong one. That is the single biggest source of "the timestamp is off by five hours" bugs, so it is worth getting straight before anything else.
date() and the format characters
The date() function takes a format string and returns a formatted string. Every letter in that string means something; anything that is not a recognised letter is printed as-is. The second argument is a Unix timestamp, and if you leave it out you get the current time from time().
<?php
$ts = mktime(14, 30, 0, 9, 7, 2026); // 7 Sep 2026, 14:30:00
echo date('Y-m-d', $ts) . "\n"; // 2026-09-07
echo date('d/m/Y', $ts) . "\n"; // 07/09/2026
echo date('H:i:s', $ts) . "\n"; // 14:30:00
echo date('l, j F Y', $ts) . "\n"; // Monday, 7 September 2026
echo date('D M j, g:i a', $ts) . "\n"; // Mon Sep 7, 2:30 pm
Output:
The handful worth memorising: Y is the four-digit year and y the two-digit one, m is the month with a leading zero and n without, d and j are the same pair for the day, H is the 24-hour clock and g the 12-hour, i is minutes and s seconds. Note i, not m โ using m for minutes prints the month, and it is the most common PHP date typo there is.
mktime() builds a timestamp from parts, in the order hour, minute, second, month, day, year. That argument order surprises people every time; it is not day-month-year.
strtotime() reads dates written by humans
Going the other way, strtotime() turns a string into a timestamp, and it understands a startling range of English. It also understands relative phrases, which is where it earns its keep.
<?php
$base = strtotime('2026-09-07 14:30:00');
echo date('Y-m-d H:i', $base) . "\n";
echo date('Y-m-d', strtotime('+1 week', $base)) . "\n";
echo date('Y-m-d', strtotime('first day of next month', $base)) . "\n";
echo date('Y-m-d', strtotime('next friday', $base)) . "\n";
var_dump(strtotime('not a date')); // false
Output:
When it cannot parse the string it returns false, not 0 and not null. Since false is falsy and so is the timestamp 0, check with === false rather than a plain if. Passing an unchecked false into date() silently gives you 1 January 1970.
DateTime, for when you need to do arithmetic
Formatting is where date() stops. Once you need to add a month, subtract a period, or compare two moments, use the DateTime class. Its format() method takes exactly the same format characters, so nothing you learned above is wasted.
<?php
$start = new DateTime('2026-09-07');
$end = new DateTime('2026-12-25');
echo $start->format('l, j F Y') . "\n";
$later = (clone $start)->modify('+45 days');
echo $later->format('Y-m-d') . "\n";
// DateTime objects compare directly
var_dump($start < $end);
Output:
Two things to notice. modify() changes the object in place and returns it, so clone first if you still need the original โ forgetting that is a quiet bug where a variable you thought was fixed drifts. And the comparison operators work directly on DateTime objects, which is the whole reason this class is worth reaching for.
diff() answers "how long between these two"
DateTime::diff() returns a DateInterval, an object whose properties hold the gap broken into years, months and days. The one you usually want is days, which is the total, as opposed to d, which is the day part left over after the months.
<?php
$start = new DateTime('2026-09-07');
$end = new DateTime('2026-12-25');
$gap = $start->diff($end);
echo $gap->days . " days in total\n";
echo $gap->m . " months and " . $gap->d . " days\n";
echo $gap->format('%m month(s), %d day(s)') . "\n";
Output:
That distinction between days and d is the reason "posted 3 days ago" widgets sometimes say "posted 3 days ago" for a post from last year. days is the number you want for an elapsed count; y, m and d are for reading a gap out loud.
Timezones, and why PHP date output moves
Every one of the functions above resolves against a default timezone. If date.timezone is not set in php.ini, PHP falls back to UTC โ so a server in Karachi prints times five hours behind what everyone in the office expects, and nobody notices until a deadline lands on the wrong day.
<?php
$ts = mktime(14, 30, 0, 9, 7, 2026);
date_default_timezone_set('UTC');
echo 'UTC: ' . date('Y-m-d H:i', $ts) . "\n";
date_default_timezone_set('Asia/Karachi');
echo 'Karachi: ' . date('Y-m-d H:i', $ts) . "\n";
echo 'Current default: ' . date_default_timezone_get() . "\n";
Output:
Look closely and that output proves the point twice over. The first example on this page built the same timestamp with mktime(14, 30, ...) and printed 14:30; here the identical call prints 12:30 in UTC. Nothing changed but the timezone in force when date() ran. mktime() interprets the parts you give it in whatever timezone is current, so the number you type in is not the number that comes out unless you pin both ends.
Set it once, near the top of the file your app always loads, and never think about it again. The safest habit for anything stored is to keep the database in UTC and convert only when displaying, using DateTimeZone with a DateTime object.
Three mistakes with PHP date handling
Leaving the timezone to chance. The same code prints different times on your XAMPP and on the live server, because their php.ini files disagree. Call date_default_timezone_set() explicitly rather than trusting the default, and you have removed a whole category of bug that only appears after deployment.
Comparing dates as strings. '2026-09-07' < '2026-12-25' happens to work because ISO format sorts alphabetically, so people conclude string comparison is fine. Change the format and it stops working: '25/12/2026' < '07/09/2027' returns false, even though December 2026 really does come before September 2027. PHP never sees a date at all โ it compares '2' against '0', decides the first string is larger, and stops there. Compare DateTime objects, which compare as moments.
Assuming strtotime() reads your format. It resolves ambiguity by punctuation, not by where you live: slashes mean American month/day/year, so 03/04/2026 is 4 March. Dashes and dots mean day-month-year, so 03-04-2026 is 3 April. Same digits, two different days. For any format you control, use DateTime::createFromFormat() and state the format instead of hoping.
Common questions about PHP date functions
How do I get the current date in PHP?
date('Y-m-d') with no second argument gives today, and date('Y-m-d H:i:s') gives the date and time. For an object you can do arithmetic on, new DateTime() with no argument is now.
What is the difference between date() and DateTime?
date() formats a timestamp into a string and stops there. DateTime is an object that can be modified, compared and subtracted from another. Use date() for display, DateTime the moment any calculation is involved.
How do I calculate someone's age in PHP?
Make two DateTime objects and read the year part of the difference: (new DateTime('1998-04-12'))->diff(new DateTime())->y. It handles leap years and the "birthday has not happened yet this year" case for you, which hand-rolled arithmetic on years does not.
Why does my PHP date show the wrong time?
Almost always the timezone. Print date_default_timezone_get() to see what PHP thinks it is; if it says UTC and you expected local time, that is your answer. The second suspect is a false from strtotime() being passed into date(), which renders as 1 January 1970.
How do I convert a string to a date in PHP?
strtotime() for anything conversational, and DateTime::createFromFormat('d/m/Y', $input) when you know the exact layout. The second is strict, so it tells you when the input is malformed instead of guessing.
What is next
Formatting a date is really string work with extra rules, and the same is true of most output a PHP page produces. PHP strings covers quotes and interpolation, the functions worth knowing, and how to format text so it lines up the way you meant.
Loading comments...