PHP Date and Time Functions: The Working Reference (2026)

PHP has more date and time handling code than almost any language — partly because it’s been around since 1995, partly because it shipped multiple competing APIs at different times. The result: well-meaning developers reach for date(), strtotime(), and mktime() because they’re documented everywhere, then hit subtle bugs that DateTimeImmutable would have prevented entirely.

This is the working reference for PHP date and time functions in 2026. The modern API patterns to use, the legacy ones to avoid in new code, the timezone handling that actually works, and the specific pitfalls that account for 80% of date-related production bugs. Built from years of refactoring legacy PHP and writing new PHP that doesn’t break at midnight on October 31st.

Modern vs legacy PHP date API

Use caseModern (use this)Legacy (avoid in new code)
Current timestamp(new DateTimeImmutable())->getTimestamp()time()
Format date$dt->format(‘Y-m-d H:i:s’)date(‘Y-m-d H:i:s’, $ts)
Parse date stringDateTimeImmutable::createFromFormat()strtotime()
Add/subtract time$dt->modify(‘+1 day’) or ->add(new DateInterval(…))strtotime(‘+1 day’, $ts)
Date arithmetic$dt2->diff($dt1)$ts2 – $ts1
Timezone conversion$dt->setTimezone(new DateTimeZone(‘UTC’))date_default_timezone_set() globally

The pattern: prefer DateTimeImmutable everywhere. It’s immutable (no spooky action at a distance from method calls), timezone-aware by default, and the parsing methods fail loudly instead of silently. Legacy functions still work; just don’t reach for them in new code.

Why DateTimeImmutable beats DateTime

PHP’s DateTime class is mutable. Its methods modify the object in place rather than returning a new one. This is a footgun:

// DateTime — mutable. Bug-prone.
$start = new DateTime('2026-01-01');
$end = $start;          // both reference the same object
$end->modify('+30 days');
echo $start->format('Y-m-d'); // 2026-01-31 — surprise!

// DateTimeImmutable — safe.
$start = new DateTimeImmutable('2026-01-01');
$end = $start->modify('+30 days'); // returns new object
echo $start->format('Y-m-d'); // 2026-01-01 — correct

Use DateTimeImmutable for any new code. Convert legacy DateTime usage as you touch it. The bugs DateTime causes are rare per line of code but catastrophic when they hit (financial calculations, scheduling, log timestamps).

Timezone handling that actually works

The single most common PHP date bug pattern: server timezone differs from user timezone. The cure is consistency:

  • Store everything in UTC. Database columns, log timestamps, API payloads. UTC removes the “what timezone did the server have at the time?” question forever.
  • Convert to user timezone only at display time. The user’s preferred timezone is stored on their profile. Render-time conversion is the only place timezone-specific logic should live.
  • Always specify timezone explicitly. new DateTimeImmutable(‘now’, new DateTimeZone(‘UTC’)) is safer than new DateTimeImmutable(‘now’) even if your server is configured to UTC. Explicit beats implicit.
  • Avoid date_default_timezone_set() in libraries. It’s global state that affects every other piece of code in the request. Pass timezones explicitly through your code instead.
  • Use DateTimeImmutable::setTimezone() for conversions. Returns a new DateTimeImmutable with the same instant in a different timezone. The display value changes; the underlying instant doesn’t.

Parsing user-supplied date strings safely

strtotime() is one of PHP’s most-used and most-dangerous functions. It accepts dozens of input formats, silently returns false on parse failure, and interprets ambiguous dates inconsistently across server locales.

// Wrong — silent failure
$ts = strtotime($userInput);
if ($ts === false) { /* handle */ }
// strtotime returns false for unparseable input. Easy to forget to check.

// Right — explicit format, fails loudly
try {
    $dt = DateTimeImmutable::createFromFormat('Y-m-d', $userInput);
    if ($dt === false) {
        $errors = DateTimeImmutable::getLastErrors();
        throw new InvalidArgumentException(json_encode($errors));
    }
} catch (Exception $e) { /* handle */ }

The DateTimeImmutable::createFromFormat() pattern enforces a known format and surfaces parse errors immediately. Use it for any date string coming from user input, third-party APIs, or files where you control the expected format.

Date arithmetic without footguns

Treating dates as Unix timestamps for arithmetic works until daylight saving time transitions, leap seconds, or month boundaries. The DateTimeImmutable approach handles all three correctly:

// Add 30 days
$future = $dt->add(new DateInterval('P30D'));

// Subtract 6 months
$past = $dt->sub(new DateInterval('P6M'));

// Modify with relative format
$nextMonday = $dt->modify('next Monday');

// Difference between two dates
$interval = $dt2->diff($dt1);
echo $interval->days; // total days
echo $interval->y, ' years'; // years component
echo $interval->invert; // 1 if dt2 < dt1, 0 otherwise

The diff() method returns a DateInterval object with separate year/month/day/hour/minute/second components — useful for displaying “2 hours ago” or “3 days, 5 hours” without manual unit conversion.

The 5 most common PHP date bugs (and how to prevent each)

  • 1. Server timezone change breaks everything. A timezone-config update on the server changes how legacy code interprets stored timestamps. Prevention: always store UTC and convert at display time.
  • 2. strtotime returns false silently. An unexpected input format silently produces a Unix epoch (1970) instead of throwing. Prevention: use DateTimeImmutable::createFromFormat() with explicit format.
  • 3. DST transitions add or subtract an hour silently. Adding “1 day” can land at the same wall-clock time but a different UTC instant if a DST transition happens in between. Prevention: do arithmetic in UTC, convert to local for display only.
  • 4. February 29 + 1 year = March 1. Most date-add libraries normalize “Feb 29 + 1 year” to “Feb 28 of next non-leap year” or “Mar 1”. Prevention: be explicit about your intended behavior in code or tests.
  • 5. Comparing dates as strings. “2026-01-15” > “2026-01-09” works lexically because ISO format is sortable. “01/15/2026” vs “01/09/2026” doesn’t. Prevention: always compare DateTime objects directly, never strings.

Frequent recipes

// Start of today (UTC)
$startOfDay = (new DateTimeImmutable('today', new DateTimeZone('UTC')));

// First day of next month
$nextMonth = (new DateTimeImmutable('first day of next month'));

// Last Friday
$lastFriday = (new DateTimeImmutable('last Friday'));

// Convert UTC timestamp to user timezone for display
$utc = new DateTimeImmutable('@' . $unixTimestamp);
$userLocal = $utc->setTimezone(new DateTimeZone('Asia/Kolkata'));
echo $userLocal->format('Y-m-d H:i T');

// Iterate days between two dates
$period = new DatePeriod($start, new DateInterval('P1D'), $end);
foreach ($period as $day) {
    echo $day->format('Y-m-d') . PHP_EOL;
}

For broader PHP and WordPress development context, see my LINQ to SQL DataContext guide and ROWNUM simulation in MySQL.

Frequently asked questions

What’s the best PHP date function to use in 2026?

DateTimeImmutable for any new code. Avoid date() and strtotime() outside of trivial scripts — they have timezone footguns and silent failures. DateTimeImmutable plus DateTimeZone gives you immutability, predictable timezone behavior, and better testability.

How do I get the current timestamp in PHP?

time() returns Unix seconds. microtime(true) returns float seconds with microsecond precision. (new DateTimeImmutable())->getTimestamp() is the OO equivalent. All three respect server timezone, not user timezone — convert explicitly.

How do I convert a string to a date in PHP?

DateTimeImmutable::createFromFormat(‘Y-m-d H:i:s’, $str) for known formats. DateTimeImmutable::__construct() accepts most ISO 8601 strings. Avoid strtotime() — it silently returns false on parse failure and accepts ambiguous formats.

How do I handle timezones correctly in PHP?

Store everything in UTC (database, logs, APIs). Convert to user timezone only at display time. Use DateTimeImmutable plus DateTimeZone explicitly — never rely on date_default_timezone_set() for per-user logic.

Why does my PHP date show the wrong year?

Almost always either (1) server timezone differs from your assumption, (2) you’re using a relative format strtotime() interpreted differently than you expected, or (3) you’re outputting a UTC timestamp without converting. Always log the timezone alongside the timestamp during debugging.

Leave a Comment