CURRENT EPOCH · EPOCHTIME.TOOLS · A PRECISION INSTRUMENT FOR TIME
Converter Batch Difference Blog
Languages
JavaScript Python TypeScript Go Rust Java PHP SQL Bash
Specialty
LDAP Timestamp .NET Ticks Chrome/WebKit Cocoa / Core Data Discord Timestamp Excel OADate Unix Hex
Standards
ISO 8601 Guide Year 2038 NTP Timestamp GPS Time Julian Day

The bug

Open Excel. In any cell, type =DATE(1900,2,29). Press Enter. Excel happily displays "2/29/1900" or "29-Feb-1900" depending on your locale.

February 29, 1900 didn't exist. 1900 wasn't a leap year. By the Gregorian rule, century years (those divisible by 100) are not leap years — unless they're also divisible by 400. 1900 is divisible by 100 and not by 400. So it's not a leap year. February in 1900 had 28 days, the same as in 1901, 1902, 1903.

Excel doesn't know this. As far as Excel is concerned, February 29, 1900 is a valid date. So is March 1, March 2, and every date after — but each one is off by a day from reality.

This is on purpose

Microsoft knows. They've known for at least 30 years. The bug is intentional.

The reason goes back to Lotus 1-2-3, which dominated spreadsheets in the early 1980s and which had this same bug. When Microsoft built Excel for Macintosh in 1985 and then Excel for Windows in 1987, they wanted Lotus 1-2-3 users to be able to import their files into Excel with date columns intact. The 1900 leap year bug was already baked into Lotus 1-2-3's date serial system. To match it exactly, Microsoft built the same bug into Excel.

40 years later, the bug is still there. Microsoft has explained their reasoning in the official Microsoft Support article "Excel incorrectly assumes that the year 1900 is a leap year" — which has been updated several times but still concludes that fixing the bug would break decades of compatibility with existing spreadsheets, and is therefore not worth the cost.

What this means for serial dates

Excel stores dates internally as serial numbers. The value 1 represents January 1, 1900. The value 2 represents January 2. And so on.

Because of the bug, 60 represents February 29, 1900 (which didn't exist). After that point, every serial number is off by one from a correct calendar. 61 is March 1, 1900 — which is correctly the 61st day of 1900 only because the phantom February 29 makes the math line up.

The practical implications:

The 1904 system: a parallel timeline

Excel for Macintosh originally shipped with a different date system: days since 1904-01-01. No phantom leap day. No off-by-one bug. This is called the "1904 date system" and it's still selectable in modern Excel via File → Options → Advanced → "Use 1904 date system."

If you encounter a workbook that shows dates 4 years and 1 day off from what you expected, it's probably using the 1904 system. The offset between the two systems is exactly 1462 days.

The 1904 system has its own problems: dates before 1904 can't be represented. But it has the appealing property of correct calendar arithmetic.

What this means for your code

If you're parsing Excel files programmatically (via openpyxl, ExcelJS, pandas, etc.), the libraries handle the 1900 leap year bug for you. They use the same formula Excel does, which produces correct dates for everything past March 1, 1900.

Where this trips people up is when they implement Excel date parsing themselves — say, processing a CSV export that has serial numbers — and use a naive "days since 1900-01-01" formula. They get a date one day off. The fix is either to use one of the well-tested libraries, or to use the same compensation Excel does (treat the epoch as 1899-12-30 instead of 1900-01-01).

For interactive conversion, the Excel OADate converter on epochtime.tools handles this correctly — the formula it uses is unix_sec = (excel_serial - 25569) × 86400, which already includes the offset compensation.

The Lotus origin

So how did the bug get into Lotus 1-2-3 in the first place? The official Lotus team explanation has been lost to time, but the most plausible theory is that the developers used a simpler algorithm: "every year divisible by 4 is a leap year." This is correct for the Julian calendar and approximately correct for the Gregorian calendar — the century-rule exception only matters for years divisible by 100, which most code never has to handle. They got away with it for years before anyone noticed.

By the time anyone wanted to fix it, the bug had been preserved in tens of thousands of customer spreadsheets, and changing it would have broken compatibility. Same pattern as Microsoft's later decision: the bug becomes the standard, and the standard outlives the original software.

Other "wrong leap year" stories

Excel isn't alone here. Several other systems have had quietly-wrong leap year handling:

The lesson: leap year math seems trivial. It's not. The "divisible by 4" rule is right 75% of the time, "divisible by 4 except 100" is right 99% of the time, "divisible by 4 except 100 except 400" is the actual rule. Most code uses one of the first two and gets lucky for decades.

The takeaway

For interactive conversion you can spot-check with the Excel OADate converter.


Published April 21, 2026. Tagged: excel, history, bugs.

← Back to blog  ·  Try the converter