Senior 4 min · March 06, 2026

PHP Timezone Handling — Double-Booked Slots from Blind Spot

London and Paris 10:00 AM slots merged — a timezone blind spot.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • PHP stores every moment as a Unix timestamp (seconds since 1970-01-01 UTC)
  • date() converts timestamps to human-readable strings using format codes
  • strtotime() parses English date strings like '+30 days' or 'next Monday'
  • DateTime class handles timezone-aware arithmetic and comparison
  • Performance: raw timestamp addition (int) is ~10x faster than DateTime modify()
  • Production pitfall: forgetting to set timezone causes incorrect times that silently propagate
Plain-English First

Think of your computer as a worker who's been counting seconds non-stop since January 1st, 1970 — that running total is called a 'Unix timestamp'. PHP lets you take that raw number and dress it up any way you want: '25 December 2024', '10:30 AM', 'Tuesday' — whatever your app needs. It's like converting a GPS coordinate into a human-readable street address. The coordinate is always there; you just choose how to display it.

Every meaningful app on the internet deals with time. Blog posts need publish dates. E-commerce orders need timestamps. Event platforms count down to zero. Booking systems block unavailable slots. If your PHP app can't handle dates correctly, you're building on sand — and your users will feel it the moment something shows '1970-01-01' where their birthday should be.

PHP's date and time tools solve a very specific problem: computers store time as a single big integer (seconds since 1970), but humans need to read '3rd April 2025 at 2:45 PM'. PHP bridges that gap. It also handles the genuinely tricky stuff — like the fact that noon in London and noon in New York happen at completely different moments — through its timezone system.

By the end of this article you'll know how to display formatted dates, do date arithmetic (add 7 days to an order date, for example), convert between timezones, and use the modern DateTime class that professionals use in production. You'll also know the two classic mistakes that trip up almost every beginner — and exactly how to avoid them.

The Unix Timestamp — PHP's Secret Clock

Before you can format a date, you need to understand what PHP is actually storing under the hood. PHP represents every moment in time as a single integer: the number of seconds that have passed since midnight on January 1st, 1970, UTC. This starting point is called the Unix Epoch, and it's the same standard used by Linux, Python, JavaScript, and virtually every programming language on Earth.

Why 1970? That's when Unix was being developed and the designers needed a fixed starting point. It's arbitrary, but it's universal — which matters when servers in different countries need to agree on when something happened.

The function time() gives you the current Unix timestamp right now. It's just a number — something like 1712000000. On its own that's useless to a human. But it's incredibly useful to a computer because you can do maths on it: add 86400 (the number of seconds in a day) and you get tomorrow. Subtract 604800 and you get a week ago. That simplicity is the whole point.

UnixTimestamp.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

// time() returns the current Unix timestamp — an integer counting
// seconds elapsed since 00:00:00 UTC on 1 January 1970.
$currentTimestamp = time();
echo "Current Unix timestamp: " . $currentTimestamp . "\n";

// Because it's just a number, maths works perfectly on it.
$secondsInOneDay = 86400; // 60 seconds × 60 minutes × 24 hours

$tomorrowTimestamp  = $currentTimestamp + $secondsInOneDay;
$yesterdayTimestamp = $currentTimestamp - $secondsInOneDay;

echo "Tomorrow's timestamp:   " . $tomorrowTimestamp . "\n";
echo "Yesterday's timestamp:  " . $yesterdayTimestamp . "\n";

// The difference between two timestamps gives you elapsed seconds.
$orderPlacedAt  = 1711900000; // a past order's timestamp
$orderShippedAt = 1711986400; // when it was shipped

$secondsBetween = $orderShippedAt - $orderPlacedAt;
$hoursBetween   = $secondsBetween / 3600; // 3600 seconds in an hour

echo "Hours between order and shipment: " . $hoursBetween . "\n";
Output
Current Unix timestamp: 1712045823
Tomorrow's timestamp: 1712132223
Yesterday's timestamp: 1711959423
Hours between order and shipment: 24
Why This Matters:
Storing dates in your database as Unix timestamps (integers) makes sorting, comparing and doing date arithmetic trivially fast. No string parsing needed — just number comparison. Many production systems store created_at as a plain integer for exactly this reason.
Production Insight
Timestamp arithmetic with integers is simple and fast, but watch out for leap seconds. They happen rarely but can mess up precise interval calculations.
PHP's time() doesn't account for leap seconds, so your timestamp may be off by 1 sec during a leap second event. For most apps that's fine, but if you're dealing with financial transactions or scientific measurements, use NTP-synchronized servers and consider the minor inaccuracy.
Rule: Use integers for ordering and simple math; use DateTime for anything involving human dates.
Key Takeaway
PHP time is an integer since 1970.
Math on integers is the fastest way to compare and calculate offsets.
But for month/year arithmetic, use DateTime — integer seconds doesn't know about months or DST.

Formatting Dates the Way Humans Actually Read Them — date()

The date() function is PHP's translator between that raw Unix integer and a readable string. It takes two arguments: a format string that describes what you want the output to look like, and optionally a timestamp to format (if you omit it, it uses time() — right now).

The format string is a sequence of special single-character codes. Each letter means something specific: Y means the four-digit year, m means the two-digit month, d means the two-digit day. You put them together with whatever separators you like — dashes, slashes, spaces, words — and PHP fills in the real values.

The trick is knowing which letters do what. PHP has dozens of format characters. You don't need to memorise all of them — you just need the common ones and you can always look the rest up. The key insight is that uppercase and lowercase matter: Y gives you 2025 but y gives you just 25. D gives you Mon but l (lowercase L) gives you Monday. Small typos in the format string cause frustrating bugs, so slow down when you write it.

DateFormatting.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php

// date(format, timestamp) — timestamp is optional, defaults to now.
// Let's build up from simple to complex.

$rightNow = time(); // grab current timestamp once, use it everywhere

// --- Basic date components ---
$fourDigitYear   = date('Y', $rightNow); // e.g. 2025
$twoDigitYear    = date('y', $rightNow); // e.g. 25
$monthNumber     = date('m', $rightNow); // e.g. 04  (zero-padded)
$monthName       = date('F', $rightNow); // e.g. April
$shortMonthName  = date('M', $rightNow); // e.g. Apr
$dayOfMonth      = date('d', $rightNow); // e.g. 02  (zero-padded)
$dayOfWeekFull   = date('l', $rightNow); // e.g. Wednesday  (lowercase L)
$dayOfWeekShort  = date('D', $rightNow); // e.g. Wed

// --- Time components ---
$hours24         = date('H', $rightNow); // 00–23
$hours12         = date('h', $rightNow); // 01–12
$minutes         = date('i', $rightNow); // 00–59
$seconds         = date('s', $rightNow); // 00–59
$amOrPm          = date('A', $rightNow); // AM or PM

// --- Useful combinations ---
// ISO 8601 format — common in APIs and databases
$isoFormat = date('Y-m-d', $rightNow);
echo "ISO date:          " . $isoFormat . "\n";

// Human-friendly format
$friendlyFormat = date('l, F jS Y', $rightNow);
// 'j' = day without leading zero, 'S' = ordinal suffix (st, nd, rd, th)
echo "Friendly date:     " . $friendlyFormat . "\n";

// Time in 12-hour format
$timeFormat = date('h:i A', $rightNow);
echo "Current time:      " . $timeFormat . "\n";

// Full datetime — perfect for logging
$fullDatetime = date('Y-m-d H:i:s', $rightNow);
echo "Full datetime:     " . $fullDatetime . "\n";

// Formatting a specific past timestamp (a product launch date)
$productLaunchTimestamp = mktime(9, 0, 0, 3, 15, 2024); // 9:00 AM, March 15 2024
$launchDisplay = date('D, d M Y \a\t H:i', $productLaunchTimestamp);
// Backslashes escape literal letters so PHP doesn't treat them as format codes
echo "Product launch:    " . $launchDisplay . "\n";
Output
ISO date: 2025-04-02
Friendly date: Wednesday, April 2nd 2025
Current time: 10:45 AM
Full datetime: 2025-04-02 10:45:33
Product launch: Fri, 15 Mar 2024 at 09:00
Watch Out: Literal Letters in Format Strings
If you try to write date('at H:i'), PHP sees 'a' as the am/pm code and 't' as days-in-month. To include real letters in a format string, escape them with a backslash: date('\a\t H:i'). It looks odd but it works — this trips up almost every beginner the first time.
Production Insight
date() is great for quick output, but it's not locale-aware. If your app supports multiple languages, use IntlDateFormatter for translated month/day names.
In production, always store dates in ISO 8601 format (Y-m-d) in databases. Human-friendly formats like 'Monday, April 2nd' are for display only and should be formatted client-side or with translation functions.
Rule: Keep date storage and formatting separate — store timestamps or ISO strings, format at the view layer.
Key Takeaway
date() translates numbers into strings.
Format characters are case-sensitive — Y vs y, D vs l.
Escape literal letters with backslash: \a\t.
Use IntlDateFormatter for multilingual production apps.

Building Custom Timestamps with mktime() and strtotime()

So far we've formatted the current time. But what about formatting a specific date — like a user's birthday, an event two weeks from now, or an order placed last Tuesday? You need a way to create a timestamp from known values.

PHP gives you two tools for this. mktime() is the precise, programmatic approach: you hand it exact hour, minute, second, month, day and year values and it returns the corresponding Unix timestamp. It's ideal when you already have the date broken into parts — from a form submission or a database row, for example.

strtotime() is the English-language wizard. You pass it a human-readable string like 'next Monday', 'last Friday', '+2 weeks', '15 March 2024', or 'tomorrow' and it figures out the timestamp. It's remarkably clever and it makes relative date calculations readable. Behind the scenes both functions return the same thing — a Unix timestamp — so you can pass their output straight into date().

CreateTimestamps.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?php

// ── mktime(hour, minute, second, month, day, year) ──────────────────
// Build a timestamp for a specific moment you know in full.

$userBirthdayTimestamp = mktime(
    0,    // hour   (midnight)
    0,    // minute
    0,    // second
    8,    // month  (August)
    22,   // day
    1995  // year
);

echo "Birthday: " . date('F jS, Y', $userBirthdayTimestamp) . "\n";

// Calculate how many years ago that was
$yearsAgo = date('Y') - date('Y', $userBirthdayTimestamp);
echo "That was " . $yearsAgo . " years ago.\n\n";

// ── strtotime() — plain English date parsing ─────────────────────────
// Converts a natural-language date string into a Unix timestamp.

$nextMondayTimestamp    = strtotime('next Monday');
$twoWeeksFromNow        = strtotime('+2 weeks');
$startOfLastMonth       = strtotime('first day of last month');
$specificDateTimestamp  = strtotime('25 December 2025 08:00:00');

echo "Next Monday:           " . date('D, d M Y', $nextMondayTimestamp) . "\n";
echo "Two weeks from now:    " . date('D, d M Y', $twoWeeksFromNow) . "\n";
echo "First of last month:   " . date('D, d M Y', $startOfLastMonth) . "\n";
echo "Christmas 2025 8am:    " . date('D, d M Y H:i', $specificDateTimestamp) . "\n\n";

// ── Real-world example: subscription expiry ──────────────────────────
// A user signs up today, gets a 30-day free trial.

$signUpTimestamp    = time();
$trialEndTimestamp  = strtotime('+30 days', $signUpTimestamp);
// strtotime() accepts an optional base timestamp as a second argument.

$signUpDate  = date('Y-m-d', $signUpTimestamp);
$trialEndDate = date('Y-m-d', $trialEndTimestamp);

echo "Account created:  " . $signUpDate . "\n";
echo "Trial expires:    " . $trialEndDate . "\n";

// Check if the trial is still active
if (time() < $trialEndTimestamp) {
    echo "Status: Trial is ACTIVE.\n";
} else {
    echo "Status: Trial has EXPIRED.\n";
}
Output
Birthday: August 22nd, 1995
That was 30 years ago.
Next Monday: Mon, 07 Apr 2025
Two weeks from now: Wed, 16 Apr 2025
First of last month: Sat, 01 Mar 2025
Christmas 2025 8am: Thu, 25 Dec 2025 08:00
Account created: 2025-04-02
Trial expires: 2025-05-02
Status: Trial is ACTIVE.
Pro Tip: Always Pass a Base Timestamp to strtotime()
When calculating relative dates ('+30 days', '+1 year'), always pass your reference timestamp as the second argument: strtotime('+30 days', $signUpTimestamp). If you omit it, PHP uses time() — which is fine in simple scripts but causes subtle bugs in tests or when processing past dates from a database.
Production Insight
strtotime() is powerful but unpredictable with ambiguous strings. '02/03/25' could be February 3 or March 2 depending on locale (PHP uses m/d/y by default). Always validate parsed timestamps with strict input formats.
In production, never rely on user-supplied date strings for strtotime() without sanitization. Use a form with separate day/month/year fields or a datetime picker that sends an ISO format.
Rule: strtotime() for internal relative dates only; for user input, use DateTime::createFromFormat() with an explicit format.
Key Takeaway
mktime() for exact known values.
strtotime() for English-like relative dates.
Always pass a base timestamp to strtotime() when calculating from a known point.
Validate strtotime() return — false means parse failure and leads to the 1970 bug.

Timezones and the Modern DateTime Class — The Professional Way

Here's where most tutorials let beginners down: they teach date() and time() and call it done. But timezone handling is where real apps live or die. A booking made at '9:00 AM' by a user in Tokyo means something completely different to a server in New York.

PHP's date_default_timezone_set() function sets a global timezone for your script — always call it at the top of your file, or better yet, set it in php.ini with date.timezone = 'Europe/London'. If you don't, PHP will warn you and fall back to UTC, which causes incorrect local times.

For anything beyond simple formatting, use the DateTime class. It's object-oriented, it handles timezones explicitly, and it lets you do date arithmetic cleanly with DateInterval. Think of DateTime as a smart calendar object you can ask questions: 'what's the date 45 days from now?', 'how many days between these two dates?'. It's cleaner, safer, and it's what you'll see in every professional PHP codebase.

DateTimeClass.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?php

// ── Step 1: Always declare your timezone ─────────────────────────────
// Do this at the top of every PHP file that works with dates.
date_default_timezone_set('America/New_York');

// ── Step 2: Create DateTime objects ──────────────────────────────────

// Current moment in the server's default timezone
$now = new DateTime();
echo "Current time (NY):     " . $now->format('Y-m-d H:i:s T') . "\n";

// A specific past date
$eventDate = new DateTime('2025-07-04 18:00:00');
echo "Event date:            " . $eventDate->format('l, F jS Y \a\t g:i A') . "\n";

// DateTime in a DIFFERENT timezone using DateTimeZone
$tokyoTimezone = new DateTimeZone('Asia/Tokyo');
$tokyoNow      = new DateTime('now', $tokyoTimezone);
echo "Same moment in Tokyo:  " . $tokyoNow->format('Y-m-d H:i:s T') . "\n\n";

// ── Step 3: Date arithmetic with DateInterval ─────────────────────────
// DateInterval uses the ISO 8601 duration format:
// P = Period, Y = years, M = months, D = days, T = time separator,
// H = hours, M = minutes, S = seconds.

$invoiceDueDate = new DateTime('2025-04-02'); // today
$invoiceDueDate->modify('+30 days');          // add 30 days in place
echo "Invoice due:           " . $invoiceDueDate->format('Y-m-d') . "\n";

// Or use add() with a DateInterval object for more control
$subscriptionStart = new DateTime('2025-01-15');
$oneYear           = new DateInterval('P1Y');  // Period of 1 Year
$subscriptionEnd   = clone $subscriptionStart; // clone so we don't mutate the original
$subscriptionEnd->add($oneYear);

echo "Subscription start:    " . $subscriptionStart->format('Y-m-d') . "\n";
echo "Subscription end:      " . $subscriptionEnd->format('Y-m-d') . "\n\n";

// ── Step 4: Difference between two dates ─────────────────────────────
$projectStart    = new DateTime('2025-01-01');
$projectDeadline = new DateTime('2025-04-02');

// diff() returns a DateInterval showing the gap between two DateTimes
$durationInterval = $projectStart->diff($projectDeadline);

echo "Project duration: ";
echo $durationInterval->m . " months and " . $durationInterval->d . " days\n";
echo "Total days:       " . $durationInterval->days . " days\n";

// ── Step 5: Comparing two DateTime objects ────────────────────────────
$deadline = new DateTime('2025-03-31');
$today    = new DateTime('2025-04-02');

if ($today > $deadline) {
    echo "\nDeadline has passed.\n";
} else {
    echo "\nDeadline is still ahead.\n";
}
Output
Current time (NY): 2025-04-02 10:45:33 EDT
Same moment in Tokyo: 2025-04-02 23:45:33 JST
Event date: Friday, July 4th 2025 at 6:00 PM
Invoice due: 2025-05-02
Subscription start: 2025-01-15
Subscription end: 2026-01-15
Project duration: 3 months and 1 days
Total days: 91 days
Deadline has passed.
Interview Gold: DateTime vs date()/time()
Interviewers love to ask why you'd use DateTime over date(). The answer: DateTime is timezone-aware by design, supports object-oriented method chaining, allows direct object comparison with > and <, and its date arithmetic never breaks across DST (Daylight Saving Time) boundaries the way manual second-maths can. Always reach for DateTime in real projects.
Production Insight
DateTime objects are mutable by default. Calling modify() or add() changes the original object, which can cause subtle bugs when you reuse a date for multiple calculations. Use clone before mutating, or switch to DateTimeImmutable.
In production, prefer DateTimeImmutable for entity fields that should not change. For example, an order's created_at should never be modified after creation.
Rule: Use DateTime for calculations you own; use DateTimeImmutable for dates that come from external sources or should remain constant.
Key Takeaway
DateTime is timezone-aware and object-oriented.
Always set timezone explicitly at script start.
Use clone before modify() or switch to DateTimeImmutable.
DateTime comparison and diff() handle DST transitions automatically.

Date Validation and Robust Input Handling

One of the most common production issues with PHP dates is invalid input causing silent failures. strtotime() returns false when it can't parse a string, and passing false to date() treats it as timestamp 0 — giving you January 1st, 1970. Not a helpful error.

Always validate date strings before using them. For user input, use DateTime::createFromFormat() with a strict format — that way you control exactly what the input should look like. For database dates, store them in ISO 8601 format (Y-m-d) which is unambiguous and sortable.

Also watch out for impossible dates like February 30th. PHP's checkdate() function validates month, day, and year combinations. Use it before creating timestamps from user-provided components.

DateValidation.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php

// ── Method 1: Validate strtotime() return value ──────────────────────
$input = '32-15-2025'; // completely invalid
$ts = strtotime($input);
if ($ts === false) {
    echo "Invalid date string: cannot parse.";
    // Handle error: log, return user feedback, etc.
} else {
    echo date('Y-m-d', $ts);
}

// ── Method 2: Use createFromFormat for strict parsing ─────────────────
$userInput = '2025-02-30'; // February 30th doesn't exist
$date = DateTime::createFromFormat('Y-m-d', $userInput);
// Check if the date exists AND the input exactly matched the format
if (!$date || $date->format('Y-m-d') !== $userInput) {
    echo "Invalid date: does not exist.\n";
} else {
    echo "Valid date: " . $date->format('Y-m-d');
}

// ── Method 3: checkdate() for component parts ─────────────────────────
echo checkdate(2, 29, 2024) ? 'Valid' : 'Invalid'; // 2024 is leap year → Valid
echo checkdate(2, 29, 2025) ? 'Valid' : 'Invalid'; // 2025 is not → Invalid

// ── Real-world: parsing a CSV with mixed date formats ─────────────────
$csvDate = '04/02/2025'; // ambiguous: US vs EU?
$formats = ['m/d/Y', 'd/m/Y', 'Y-m-d'];
$parsed = null;
foreach ($formats as $fmt) {
    $d = DateTime::createFromFormat($fmt, $csvDate);
    if ($d && $d->format($fmt) === $csvDate) {
        $parsed = $d;
        break;
    }
}
if ($parsed) {
    echo "Parsed as: " . $parsed->format('Y-m-d');
} else {
    echo "Unable to parse date.";
}
Output
Invalid date string: cannot parse.
Invalid date: does not exist.
Valid
Invalid
Parsed as: 2025-04-02
The Silent 1970 Bug
Passing false to date() because strtotime() failed is the most common beginner mistake. The output is '1970-01-01' which looks like a real date but is completely wrong. Always guard against false: $ts = strtotime($input); if ($ts === false) { / handle error / }.
Production Insight
In production, date input comes from many sources: APIs (ISO 8601), forms (user-submitted), CSV imports (various formats), and databases (usually Y-m-d). Each needs different parsing logic. Never use a single strtotime() call for all inputs.
When importing data, log every failed parse with the original string and source. A single malformed date in a batch import can corrupt thousands of records if you ignore the failure.
Rule: Use DateTime::createFromFormat() for known formats, checkdate() for components, and always verify strtotime() returned !== false.
Key Takeaway
Always validate date strings before use.
createFromFormat() gives you strict control over input format.
checkdate() handles month/day/year validity.
Never assume strtotime() succeeded — check for false.
● Production incidentPOST-MORTEMseverity: high

Double-Booked Slots from a Timezone Blind Spot

Symptom
Users in London and Paris saw conflicting slots at the same local time. Client-side conversion showed correct times, but server-side stored UTC timestamps that didn't match the expected local hour.
Assumption
The old server had date.timezone set in php.ini, and the new one didn't. The team assumed timezone was handled by the application code, but no explicit set was present.
Root cause
When no timezone is set, PHP uses the system default (UTC in most containers). All date() calls returned UTC, while the booking algorithm compared timestamps without adjusting for the user's zone. The result: 10:00 AM in London and 10:00 AM in Paris were treated as the same moment.
Fix
Added date_default_timezone_set('Europe/London') at the top of the entry script. Also stored the user's timezone in their profile and applied it per-request using DateTime objects.
Key lesson
  • Always set a default timezone explicitly in every entry point, even if php.ini seems correct.
  • Never compare or display times without knowing both the server timezone and the user's timezone.
  • Validate that your dev, staging, and prod environments all enforce the same timezone logic.
Production debug guideCommon symptoms, root causes, and actions to fix them fast.4 entries
Symptom · 01
Dates showing as 1970-01-01
Fix
Check if strtotime() returned false. Run var_dump($result); on the parsed input. Validate date string before use with if ($ts = strtotime($input)).
Symptom · 02
Time offset by exactly N hours (e.g., 5 hours off)
Fix
Compare server timezone (date_default_timezone_get()) against expected zone. Check if php.ini has date.timezone set. Run echo date('e'); at the top of the script.
Symptom · 03
Date arithmetic returns wrong day (e.g., +1 month on Jan 31 gives March 3)
Fix
Use DateTime::modify('+1 month') with caution. For month arithmetic, consider adding days instead: new DateInterval('P30D'). Better: use strtotime('+1 month') and then check if the month advanced correctly.
Symptom · 04
DateTime comparison fails unexpectedly
Fix
Convert both dates to UTC before comparing. Use $date->setTimezone(new DateTimeZone('UTC')) and then get timestamp with ->getTimestamp(). Compare integers, not objects, to avoid DST surprises.
★ Quick Debug Cheat Sheet for PHP DatesFive common date/time failures and the exact commands to diagnose and fix them.
All dates show 1970-01-01
Immediate action
Check the input string to strtotime() or DateTime constructor
Commands
echo 'Input: ' . var_export($input, true) . "\n"; $ts = strtotime($input); var_dump($ts);
if ($ts === false) { echo 'Parse failure'; } else { echo date('Y-m-d', $ts); }
Fix now
Validate input: if (!$ts = strtotime($input)) { / handle error / }
Time is off by several hours+
Immediate action
Check the current timezone setting
Commands
echo 'Default timezone: ' . date_default_timezone_get() . "\n"; echo 'Current time: ' . date('Y-m-d H:i:s T');
// If wrong, set it: date_default_timezone_set('America/New_York');
Fix now
Add date_default_timezone_set('Your/Timezone'); at the top of every script that uses dates
Adding +1 month gives March 3 instead of Feb 28+
Immediate action
Use strtotime with a base timestamp and check the result
Commands
$base = strtotime('2025-01-31'); $next = strtotime('+1 month', $base); echo date('Y-m-d', $next);
// Alternative: use DateTime and then correct $d = new DateTime('2025-01-31'); $d->modify('last day of next month'); echo $d->format('Y-m-d');
Fix now
'+1 month' adds 31 days, not 1 calendar month. Use 'last day of next month' or add days explicitly.
Date comparison returns wrong ordering+
Immediate action
Convert both dates to timestamps in UTC
Commands
$a = (new DateTime('2025-01-15'))->setTimezone(new DateTimeZone('UTC'))->getTimestamp(); $b = (new DateTime('2025-01-16'))->setTimezone(new DateTimeZone('UTC'))->getTimestamp(); if ($a < $b) { echo 'a is earlier'; }
// Or use Diff(): $diff = $date1->diff($date2); if ($diff->invert) { echo 'date1 is earlier'; }
Fix now
Always compare timestamps (integers) or use DateTime::diff() to avoid DST issues
Error: 'It is not safe to rely on the system's timezone settings'+
Immediate action
Set timezone in PHP code
Commands
date_default_timezone_set('UTC'); // or your preferred zone
// Also set in php.ini: date.timezone = 'Europe/London'
Fix now
Add date_default_timezone_set('UTC'); to your bootstrap file before any date operations
date()/time() vs DateTime
Featuredate() / time() FunctionsDateTime Class
Syntax styleProcedural (functions)Object-oriented (methods)
Timezone awarenessGlobal setting only via date_default_timezone_set()Per-object via DateTimeZone — explicit and safe
Date arithmeticManual maths on seconds — error-proneadd(), sub(), modify() — readable and reliable
Comparing datesCompare raw integer timestampsUse > < == directly on objects
DST handlingCan break — e.g. '+1 day' in seconds crosses a DST boundaryHandles DST transitions automatically
Best forQuick formatting in simple scriptsProduction apps, APIs, scheduling systems
Parse English stringsstrtotime() as companion functionnew DateTime('next Monday') built-in
Immutable optionNot availableDateTimeImmutable — returns new object, never mutates

Key takeaways

1
PHP stores all moments in time as a Unix timestamp
an integer counting seconds since 1 Jan 1970. Everything else is formatting that number for humans.
2
date('Y-m-d H:i:s') is your go-to format for databases and logs. date('l, F jS Y') is for human-facing output. Backslash-escape any literal letters in the format string.
3
strtotime() understands plain English like '+30 days' and 'next Monday'
but always pass a base timestamp as the second argument when calculating from a known date, not just 'now'.
4
Use the DateTime class in real projects
it handles timezones per object, makes date arithmetic readable, lets you compare dates with > and <, and never silently breaks across Daylight Saving Time boundaries.
5
Always validate strtotime() return value (false check) and use DateTime::createFromFormat() for strict parsing of user input to avoid the 1970 bug.
6
Prefer DateTimeImmutable over DateTime when dealing with dates that should not change (like creation timestamps).

Common mistakes to avoid

4 patterns
×

Forgetting to set a timezone

Symptom
PHP shows a warning ('It is not safe to rely on the system's timezone settings') and silently falls back to UTC. Users see times hours off from their local time.
Fix
Add date_default_timezone_set('America/New_York') at the top of every script that uses dates, or set date.timezone = 'Europe/London' in your php.ini file once for the whole server.
×

Mutating a DateTime object you meant to keep

Symptom
When you call $date->add(new DateInterval('P1M')) it changes $date itself. If you needed the original date for comparison later, it's gone.
Fix
Either use clone $date before adding, or better yet switch to DateTimeImmutable — it returns a new object on every operation and never touches the original.
×

Using strtotime() without checking for failure

Symptom
If strtotime() can't parse the string you gave it (e.g. a garbled date from user input like '32-15-2025'), it returns false, not a timestamp. Passing false to date() gives you the date for timestamp 0 — January 1st 1970 — which is a very confusing bug to track down.
Fix
Always check: $timestamp = strtotime($input); if ($timestamp === false) { / handle error / } before using the result.
×

Assuming date('Y-m-d', strtotime($input)) always returns the same time

Symptom
When parsing a date without time, strtotime uses current time, so the resulting timestamp may vary hour by hour. Consecutive calls return different timestamps for the same date string.
Fix
Set time to midnight when you only care about the date: $date = new DateTime($input); $date->setTime(0, 0); or use strtotime('midnight ' . $input).
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is a Unix timestamp, and why does PHP's time() function return the ...
Q02SENIOR
What's the difference between using date() with strtotime() versus using...
Q03SENIOR
If a user in London books a meeting for '3:00 PM' on your platform and t...
Q01 of 03JUNIOR

What is a Unix timestamp, and why does PHP's time() function return the number of seconds since January 1st 1970 specifically?

ANSWER
A Unix timestamp is an integer representing the number of seconds elapsed since midnight UTC on January 1st, 1970 — the Unix Epoch. This date was chosen because it's the approximate birth of the Unix operating system. The timestamp is a universal reference point that PHP shares with most other systems. time() returns this integer, making it easy to store, compare, and manipulate times using simple integer arithmetic. For example, adding 86400 (seconds in a day) gives tomorrow's timestamp. The downside is that it doesn't handle timezones or calendar quirks like leap years or months — you need DateTime for that.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
How do I get the current date and time in PHP?
02
What is the difference between date() and DateTime in PHP?
03
Why does PHP show a wrong date or a date in 1970?
04
How do I handle timezones correctly in PHP?
🔥

That's PHP Basics. Mark it forged?

4 min read · try the examples if you haven't

Previous
PHP Math Functions
12 / 14 · PHP Basics
Next
PHP Regular Expressions