PHP
In brief: PHP's integer is as wide as the machine it was built for. On 64-bit PHP, timestamps are fine for billions of years. On 32-bit PHP, every function that works with Unix timestamps (
time(),mktime(),strtotime(),date()) stops working on 19 January 2038, and date calculations that reach past 2038 already fail today. The fix is to run 64-bit PHP and to use theDateTimeImmutableclasses instead of raw timestamps.
| Risk level | High on 32-bit builds. Low on 64-bit builds |
| Status | No fix planned for 32-bit timestamps. 64-bit builds (PHP_INT_SIZE === 8) are safe. 64-bit Windows builds use 64-bit integers since PHP 7.0 |
| Last reviewed | 2026-10-21 |
2038 status at a glance
- At risk ConfirmedPHP 32-bit builds (incl. PHP 5 on Windows x64 and 32-bit dev stacks)When PHP_INT_SIZE is 4, timestamps are limited to 1901–2038.Do: Use a 64-bit PHP 7+ build.
Not affected or different deadline (1), and why
- Not affectedPHP 7+ 64-bit buildsPHP_INT_SIZE is 8, so timestamps are effectively unlimited.Do: Still check database columns and pack() formats.
Overview
PHP has one integer type, int, and its size "is platform-dependent… 64-bit platforms usually have a maximum value of about 9E18". You can check it at run time with PHP_INT_SIZE (4 or 8) and PHP_INT_MAX [1]. When an integer operation overflows, PHP "will return a float instead" rather than wrapping [1].
Unix-timestamp functions take and return int, so on a 32-bit build the PHP manual says: "The valid range of a timestamp is typically from Fri, 13 Dec 1901 20:45:54 UTC to Tue, 19 Jan 2038 03:14:07 UTC… For 64-bit versions of PHP, the valid range of a timestamp is effectively infinite" [2].
Before PHP 7.0, Windows x64 builds also had 32-bit integers, because PHP's integer was a C long, which is 32 bits on Windows. Derick Rethans, the author of PHP's date extension, noted this "got resolved with PHP 7.0, which now uses an int64_t-like type" [3]. A 64-bit Windows server running PHP 5.x is therefore effectively a 32-bit-timestamp system.
Technical details
How the timestamp functions fail on 32-bit
| Function | Behaviour with a post-2038 date on 32-bit PHP |
|---|---|
mktime() |
Returns false "if the timestamp doesn't fit in a PHP integer" [4] |
strtotime() |
Returns false [2] |
date('Y', $ts) |
Can't receive a post-2038 value, because $timestamp is typed int [6] |
time() |
Wraps or fails after 2038-01-19 03:14:07 UTC |
DateTime::getTimestamp() |
Throws ValueError: Epoch doesn't fit in a PHP integer (PHP 8) [5] |
<?php
// BUG on 32-bit PHP: false, then date() renders 1970-01-01
$ts = mktime(0, 0, 0, 1, 1, 2040);
echo date('Y-m-d', $ts); // "1970-01-01" because false is cast to 0
// BUG: arithmetic on timestamps; 25-year expiry is already past 2038
$expires = time() + 25 * 365 * 86400; // becomes float on 32-bit, then truncated when stored
// Always check the return value strictly
if ($ts === false) { throw new RuntimeException('date out of range'); }
false turning into 0 (1970-01-01) is the typical symptom: silent wrong dates, not crashes.
The fix: DateTime / DateTimeImmutable
The DateTime* classes are built on the timelib library and don't depend on PHP's int for their internal value. Reports from 32-bit users show format('U') returning correct values past the 32-bit range [7], so calendar arithmetic should work past 2038 even on 32-bit builds, provided you never convert to an int timestamp. (Internal representation not verified against timelib source; test on your build.)
<?php
$tz = new DateTimeZone('UTC');
$d = new DateTimeImmutable('2038-01-19 03:14:07', $tz);
$d2 = $d->modify('+1 second');
echo $d2->format('Y-m-d H:i:s'); // 2038-01-19 03:14:08
echo $d2->format('U'); // "2147483648" as a *string*; safe to store
// $d2->getTimestamp(); // 32-bit: ValueError; 64-bit: 2147483648
The manual itself recommends DateTimeImmutable over strtotime() for calculations, and DateTime::add()/sub() for arithmetic [2][6]. On 32-bit builds, format('U') is the reported workaround for getting the epoch value as a string. A Drupal issue notes that feeding it back through setTimestamp() still fails, so keep values as DateTime objects or strings end to end [7].
Detecting a 32-bit build
<?php
if (PHP_INT_SIZE < 8) {
trigger_error('32-bit PHP: Unix timestamps fail after 2038-01-19', E_USER_WARNING);
}
var_dump(PHP_INT_MAX + 1); // 32-bit: float(2147483648) 64-bit: float(9.2233720368548E+18)
Other PHP-specific pitfalls
- Databases. MySQL
TIMESTAMPcolumns andUNIX_TIMESTAMP()stop at 2038-01-19 on any MySQL before 8.0.28, and on 32-bit platforms in every version.INTcolumns that hold epoch seconds also stop there [8]. UseDATETIMEorBIGINT. See Databases. pack('N', $ts)/pack('l', $ts)produce 32-bit binary fields.'N'is unsigned (good to 2106).'l'is signed (2038).intval()/(int)casts of a float timestamp on 32-bit clamp or wrap.- Sessions and cookies.
setcookie()withexpiresset past 2038 on 32-bit can't be represented as anint. Pass a smaller relative lifetime instead. - Two-digit years.
mktime()maps years 0–69 to 2000–2069 and 70–100 to 1970–2000 [4]. That's a separate windowing hazard (see Year-2000-Problem).
Will 32-bit PHP be fixed?
In January 2021, Tyson Andre asked on the php-internals list whether PHP should drop or deprecate 32-bit builds before 2038. He noted that (new DateTime())->getTimestamp() would start throwing ValueError on 32-bit after the rollover. He also rejected widening zend_long to 64 bits on 32-bit platforms and returning int|float. Nikita Popov replied that usage data was lacking [5]. As of 2026-10-21 we found no adopted RFC that changes 32-bit timestamp behaviour (no RFC located). Assume 32-bit PHP will not be fixed.
Affected versions / products
| PHP build | PHP_INT_SIZE |
Timestamp range | 2038 exposure |
|---|---|---|---|
| Any version, 32-bit OS/build | 4 | 1901-12-13 to 2038-01-19 | High |
| PHP 5.x on Windows x64 | 4 | 1901–2038 | High |
| PHP 7.0+ on Windows x64 | 8 | effectively unlimited | Low |
| PHP on 64-bit Linux/BSD/macOS | 8 | effectively unlimited | Low (watch DB columns and pack()) |
| Bundled dev stacks shipping 32-bit PHP (some "starter kits") | 4 | 1901–2038 | High in dev/test [3] |
Risk & compliance notes
- Inventory:
php -r 'echo PHP_INT_SIZE;'on every host and container, including Raspberry Pi and armhf images, shared hosting and Windows IIS servers. - Search code for
mktime,strtotime,time() +,date(andgetTimestampin code paths that handle future dates (subscriptions, warranty, loan terms, certificate expiry). - Owners: platform team (runtime architecture), application team (API usage), DBA (column types).
Detection & mitigation
- Fail the deploy if
PHP_INT_SIZE !== 8. - Replace timestamp arithmetic with
DateTimeImmutableandDateInterval. Store dates asDATETIMEor ISO 8601 strings. - Add unit tests with dates such as
2038-01-19 03:14:08 UTCand2040-01-01, and run them in a 32-bit container (i386/php) if you still support one. - Static analysis: PHPStan and Psalm can flag unchecked
int|falsereturns frommktime()/strtotime()at stricter levels. - See Testing-and-Detection and Remediation-Guide.
See also
References
- Integers — PHP Manual — platform-dependent size,
PHP_INT_SIZE/PHP_INT_MAX, overflow to float. - strtotime — PHP Manual — valid range 1901-12-13 to 2038-01-19 on 32-bit, "effectively infinite" on 64-bit; recommends
DateTimeImmutable. - Re: [tz] The Y2038 problem — Matthew Donadio quoting Derick Rethans, tz mailing list, 2018-01-19 — Windows
longwas int32; resolved in PHP 7.0; 32-bit starter kits. - mktime — PHP Manual — returns
falseif the timestamp doesn't fit; year range 1901–2038 with 32-bittime_t; two-digit year mapping. - php-internals thread on 32-bit builds and 2038 — Tyson Andre, via externals.io, 2021-01-08 —
getTimestamp()ValueError on 32-bit; reply by Nikita Popov. - date — PHP Manual —
$timestampis anint; recommendsDateTimeImmutable. - Drupal issue #2795489 — drupal.org —
format('U')workaround on 32-bit;setTimestamp()round-trip problem. - Date and Time Functions — MySQL 8.0 Reference Manual —
UNIX_TIMESTAMP()range 2038 before 8.0.28 and on 32-bit.