Every Date Was Wrong: Server, MySQL, PHP, and JavaScript All at Once
When AI Generates a PHP Script in One Shot
For simple PHP scripts — a handful of files, minimal class definitions, nothing that warrants a full framework — I’ve been having AI bang them out quickly. It’s fine for something that just needs to run; not for anything production-grade.
For work use I still have it build things by feature and write automated tests, but you have to actually review the output — it’ll sometimes lie and say “it works!” when it doesn’t.
This is about a time I had AI generate a script in one shot from a spec.
What Happened
The data submitted through a form looked wrong. Tokyo was showing 2 AM. And the system’s own timestamp said… wait, why is it reporting a future time? This is now.
If MySQL was auto-inserting a TIMESTAMP column it would store UTC, so it shouldn’t show a future time. If it was DATETIME it should store exactly what was inserted. Something was off (more on MySQL column types later).
Either way, when dates drift, the usual suspects are PHP itself — both the php.ini config and the source code.
PHP Timezone Configuration
Depending on the server, php.ini typically doesn’t have a timezone set. When no timezone is declared, PHP defaults to the server’s own timezone. So what if the server’s timezone wasn’t Japan?
Sure enough, the server was UTC, php.ini had no timezone setting, and the PHP script didn’t set one either.
AI Skips This Without Thinking
Humans make this mistake too, so that part’s fine. The real question is whether AI properly handles timezone configuration when you hand it a task.
AI does what it’s told. If you didn’t say “this is in Japan,” it has no way to know (or at least, it behaves that way).
In a Japan-only team, you’d typically decide the timezone in the spec and handle output formatting accordingly, or adjust it in frontend JS (though personally I strongly recommend against doing date math in plain JS).
Setting the Timezone
In php.ini
The lazy option — just throw it in php.ini.
date.timezone = Asia/Tokyo
The config file location varies by environment; run php --ini to find it.
At the Top of the Script
For throwaway local scripts, just set it at the top.
<?php
setlocale(LC_ALL, 'ja_JP.UTF-8');
date_default_timezone_set('Asia/Tokyo');
Or specify the timezone directly when creating a DateTime object:
<?php
$tz = new DateTimeZone('Asia/Tokyo');
$now = new DateTime('now', $tz);
echo $now->format('Y-m-d H:i:s');
Why You Should Use DateTimeImmutable
Personally, I prefer declaring it at the top. It’s less hassle.
Also, a lot of people use the DateTime class instead of date/time functions, but I prefer immutable design, so I’d recommend DateTimeImmutable. The name is annoyingly long, so aliasing it is worth it.
<?php
use DateTimeImmutable as DTI;
use DateTimeZone as DTZ;
date_default_timezone_set('Asia/Tokyo');
$now = new DTI('now', new DTZ('Asia/Tokyo'));
echo $now->format('Y-m-d H:i:s');
Why DateTimeImmutable Is Better
The benefit of immutability is that modifying a date doesn’t affect the original object. It’s just a cleaner way to work.
<?php
// DateTime (mutable)
$today = new DateTime('2025-12-06');
$tomorrow = $today->modify('+1 day');
echo $today->format('Y-m-d'); // 2025-12-07 (changed!)
echo $tomorrow->format('Y-m-d'); // 2025-12-07
// DateTimeImmutable (immutable)
$today = new DateTimeImmutable('2025-12-06');
$tomorrow = $today->modify('+1 day');
echo $today->format('Y-m-d'); // 2025-12-06 (unchanged)
echo $tomorrow->format('Y-m-d'); // 2025-12-07
Don’t Trust JavaScript’s Dates
JavaScript dates depend on the browser — which means the client. Whatever the user’s device is set to is what you get, so you can’t trust them.
On top of that, the Date object’s API is a disaster. Months are zero-indexed (January is 0), timezone handling is counterintuitive — the traps are endless.
// Trying to create January 1, 2025
const date = new Date(2025, 1, 1);
console.log(date); // 2025-02-01 ← what?
// Months are 0-indexed (this is terrible)
const correct = new Date(2025, 0, 1);
console.log(correct); // 2025-01-01
// Timezone is also client-dependent (also terrible)
const now = new Date();
console.log(now.getTimezoneOffset()); // Returns different values per environment
Avoid doing date calculations in plain JS. Use libraries like day.js or date-fns, or do the computation server-side and just pass the result to the frontend.
A Note on MySQL Column Types
While we’re here, MySQL date types differ in important ways:
| Type | Storage Format | Timezone | Range |
|---|---|---|---|
| TIMESTAMP | Stored as UTC | Converted using session timezone on retrieval | 1970–2038 |
| DATETIME | Stored as-is | No conversion | 1000–9999 |
TIMESTAMP stores in UTC and converts on retrieval based on the server or session timezone. DATETIME stores exactly what you put in, so if the application isn’t consistent, it’s chaos.
(TIMESTAMP has the year 2038 problem — curious how that’s going to play out…)
TIMESTAMP also has auto-population features:
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
DEFAULT CURRENT_TIMESTAMP auto-inserts on INSERT, ON UPDATE CURRENT_TIMESTAMP auto-updates on UPDATE. Handy, but given the 2038 problem, I’d avoid it. AI tends to suggest it a lot though.
Anyone working with PHP probably knows all this, but when you let AI do the work, it tends to slip through the cracks (though it’s gotten better at handling it than it used to be).
Put timezone requirements in the spec. Working with AI is harder than it looks.
Unrelated to the story, but “2 AM” made me think of this song. Nothing to do with the article.
