A glitch in February of the year 0

Hacker News Top News

Summary

A rare bug in PHP's DateTimeImmutable caused off-by-one-day errors for February of year 0; the fix is to use alternative timestamp conversion methods.

No content available
Original Article
View Cached Full Text

Cached at: 06/29/26, 05:01 AM

# A glitch in February of the year 0 Source: [https://28times.com/blog/2026-06-26-february-of-the-year-0](https://28times.com/blog/2026-06-26-february-of-the-year-0) [![28times icon](https://28times.com/i/28times-logo.svg)![28times logo](https://28times.com/i/28times.svg)](https://28times.com/)[Blog](https://28times.com/blog)\> Post A technical report about a rare correctness issue that we found and fixed\. Recently, as we were adding support for timestamps in the distant past, a team member noticed during testing that some timestamps weren’t handled correctly\. The issue could be easily reproduced with the timestamp`0000\-02\-03 04:00 Europe/Oslo`\. A first investigation showed that the problem affected all time zones, but only in February of the year 0 \(as well as the last few days of January\)\. Most time series don’t have timestamps that are two thousand years in the past\. But, of course, we want to parse all timestamps in the supported range correctly, even the rare cases dating back to antiquity\. ## Time for bug hunting We started looking for the bug, assuming that, surely, it would be in our own code\. We use the calendar logic provided by the PHP runtime \(the`DateTimeImmutable`class\), but we still have some non\-trivial processing on timestamps\. To deal with timestamps that are ambiguous because of a time zone transition, we compute a Unix time stamp internally, and then convert it to a PHP`DateTimeImmutable`\. The year 0 is a bit of an outlier in two ways\. First, it doesn’t exist in the traditional Julian calendar that historians use \(they would call it 1 BC instead\)\. Second, in the proleptic Gregorian calendar with astronomical year numbering \(which is the calendar that we use on 28times\), it’s a[century leap year](https://en.wikipedia.org/wiki/Century_leap_year)\. Century leap years are the exception to an exception: years divisible by 100 are not leap years, unless – like the year 0 – they are divisible by 400\. This gave us a vague idea of why the year 0 might be impacted by a bug\. But since other century leap years \(such as 2000\) were unaffected, this couldn’t be a full explanation\. So we went through our code step by step and found the problem\. To our surprise, it wasn’t in our code\. The problem was related to the idiom we used to convert a Unix time stamp to a`DateTimeImmutable`object\. ## The root cause Below are three ways of converting a Unix time stamp to a`DateTimeImmutable`in PHP\. 1. `\\DateTimeImmutable::createFromFormat\('U', '\-62164356180'\)` 2. `\(new \\DateTimeImmutable\('@0'\)\)\-\>setTimestamp\(\-62164356180\)` 3. `new \\DateTimeImmutable\('@\-62164356180'\) // incorrect return value` These three should be completely equivalent – and for most timestamps, they are\. But unlike the first two, the last variant gives a result that’s off by one day for February of the year 0\. At time of writing, this happens in all recent PHP releases\. As luck would have it, the last method is also the one we used in our code\. \(You can also substitute`DateTime`for`DateTimeImmutable`in these three snippets\.`DateTime`has the same problem with the last variant\.\) ## Fixing the issue For our own purposes, the fix was simply to use one of the first two methods\. This is also what I would recommend to other PHP programmers using`DateTime`or`DateTimeImmutable`\. I also opened a[pull request](https://github.com/derickr/timelib/pull/173)to fix the bug in the library`timelib`, which provides date/time functionality\. PHP’s`DateTimeImmutable`uses this library internally\. The problem ended up being that`timelib`has two implementations for converting a Unix timestamp to a date in the proleptic Gregorian calendar\. One of them has a range check that uses the wrong date – a date that falls in January of the year 0, around a month before the century leap day instead of after it\. This causes all results before the century leap day to be off by one day\. I proposed fixing it by making all callers use the correct algorithm\. This issue will hopefully be fixed in upcoming releases of`timelib`and PHP\. It certainly made for a satisfying bug fix – we have a clean workaround, and improving`timelib`and PHP is a nice bonus\.

Similar Articles

PHP's Oddities

Hacker News Top

A developer reflects on PHP's oddities after five years of use, focusing on the quirks of its array implementation and type system.

Y2K

Reddit r/singularity

An article discussing the Y2K bug and its historical significance.

Core dump epidemiology: fixing an 18-year-old bug

OpenAI Blog

OpenAI engineers detail the diagnosis of seemingly impossible crashes in Rockset's C++ data infrastructure, revealing both a silent hardware corruption bug on Azure and an 18-year-old race condition in GNU libunwind, resolved through epidemiological analysis of crash data.

My Favorite Bugs: Invalid Surrogate Pairs

Hacker News Top

A blog post recounting a bug where inserting adjacent multi-byte emoji caused a splice in a CRDT library, splitting a surrogate pair and silently breaking collaborative editor synchronization.

It's not me, it's the compiler

Lobsters Hottest

A Rust programmer discovers a compiler bug where casting 'bool as u32' produces incorrect results, leading to a parser error. The bug is reported and linked to GitHub issue #158206.