clock, designs, time, desk accessory, glob shape desk clock, car clock, desk clock. Excel date serials and the 1900 and 1904 epochs: spreadsheet and API date handling
Photo by dipakthumar on Pixabay

Costs

Part of Spreadsheet or API: where the calendar should actually live

Excel date serials and the 1900 and 1904 epochs: spreadsheet and API date handling

Excel date serials, the 1900 and 1904 epochs, text date traps, and spreadsheet and API tools that move dates without shifting or corrupting them.

What to take away

  • A spreadsheet and API tool for dates is judged by one testdoes the value survive the trip. The whole part of a serial is the day, the fraction is the time, so noon is exactly 0.5.
  • Two epochs are in use. The same stored number means dates 1,462 days apart.
  • A date that arrived as text looks right, sorts wrong, and returns an error in any date function.
  • Excel, Google Sheets, Power Query and Python's pandas each handle serials and epochs differently. Test the boundary, do not reason about it.
  • Excel, Google Sheets and LibreOffice Calc hold the serial. Power Query, pandas, openpyxl and SheetJS move it between files. Nager.Date and the Python holidays package supply the closure tables.
  • Send 2027-03-05 over an API, never the raw serial. Nothing on the wire carries the epoch.

What the number means

Astronomers have counted days as consecutive integers for centuries. The Julian day number exists because subtracting two integers is easier to get right than subtracting two calendars. A sheet does the same thing from a different starting point.

Expression / Returns

INT(A2)
The date part, time discarded
MOD(A2,1)
The time part, as a fraction of a day
B2-A2
Whole days between two dates
(B2-A2)*24
Elapsed hours between two timestamps

Subtracting two dates is exact and carries no zone. A difference computed this way cannot be moved by a clock change, because the numbers never knew about one.

The two epochs

Excel's default date system counts from the start of 1900. A second system, inherited from early Macintosh versions, counts from 1904. The offset is 1,462 days. Excel's toggle sits at File > Options > Advanced, in the workbook calculation settings.

1900 vs 1904 date systems

1900 system

Epoch start
Start of 1900
Offset
0 days
Toggle location
File > Options > Advanced
Leap bug
Phantom 29 Feb 1900
Safe from
March 1900 onward

1904 system

Epoch start
Start of 1904
Offset
1,462 days
Toggle location
File > Options > Advanced
Leap bug
No phantom day
Safe from
March 1900 onward

A workbook on one system and a workbook on the other show different dates for the same stored number. Copy values between them without converting and everything shifts by just over four years. Copying cells usually carries the format and the conversion; pasting raw numbers does not.

The 1900 system keeps a 29 February that never existed, so serials below March 1900 run one day out against a true count. That is the best known case of the general leap year problem, and it is a compatibility decision nobody intends to fix.

Treat serials as interchangeable only from March 1900 onward. Test the boundary rather than assuming another tool reproduces the same defect. LibreOffice Calc defaults to an 1899-12-30 epoch and offers 1900 or 1904 under Tools > Options > Calc > Calculate.

The spreadsheet and API tools, reviewed

Prices below are list prices as of 2026. Per-seat plans move with region, billing term and currency, so treat them as typical.

Date handling across tools

Excel

Epoch
1900 or 1904
Epoch toggle
Yes
Cost
$9.99/mo
Date unit
Serial days

Google Sheets

Epoch
1899-12-30
Epoch toggle
No
Cost
Free
Date unit
Serial days

LibreOffice Calc

Epoch
1899-12-30 default
Epoch toggle
Yes
Cost
Free
Date unit
Serial days

pandas

Epoch
1970-01-01
Epoch toggle
No
Cost
Free
Date unit
Nanoseconds

What it does with dates

Microsoft Excel (Microsoft 365)
Stores a date as a serial and switches between the 1900 and 1904 systems at File > Options > Advanced
Google Sheets
Serial from 1899-12-30, no epoch toggle, and WORKDAY and NETWORKDAYS without the INTL suffix
LibreOffice Calc
Defaults to an 1899-12-30 epoch and sets 1900 or 1904 in its calculate options
Power Query
Date.From and DateTime.From convert text under the import locale
pandas
datetime64 counts nanoseconds from 1970-01-01, with no 1900 or 1904 epoch
openpyxl
Converts a serial with from_excel; wb.epoch reports the workbook's date system
SheetJS (xlsx)
Reads workbook cells in JavaScript and Node; cellDates: true returns date values
Nager.Date
Publishes country and subdivision holiday tables
Python holidays
Country and subdivision holiday tables for a WORKDAY.INTL holiday range
Zapier
Passes dates between Sheets or Excel and web APIs over ISO strings
Google Apps Script
Automates Sheets dates on the same 1899-12-30 epoch as the sheet

Cost and requirement

Microsoft Excel (Microsoft 365)
Microsoft 365 Personal lists at $9.99 a month or $99.99 a year; Office Home 2024 is a one-time license around $150
Google Sheets
Free with a Google account; Workspace Business Starter typically runs $7 to $9 per user a month
LibreOffice Calc
Free and open source, no account needed
Power Query
Included with Excel for Windows and Microsoft 365
pandas
Free, open source under the BSD-3 license
openpyxl
Free, MIT license, installed with pip
SheetJS (xlsx)
Free community edition under Apache-2.0
Nager.Date
Free, no API key
Python holidays
Free, MIT license, installed with pip
Zapier
Free tier covers 100 tasks a month; Starter typically runs about $20 a month billed annually
Google Apps Script
Free with a Google account

Excel and Google Sheets. Both store a date as a serial. Excel offers the 1900 and 1904 systems; Sheets uses 1899-12-30 and offers no toggle.

Workday arithmetic runs through WORKDAY.INTL and NETWORKDAYS.INTL in both, which accept a weekend code and a holiday range. Sheets also has WORKDAY and NETWORKDAYS without the suffix. The INTL variants are the ones to use when a closure table matters.

Power Query. Reads a column as a type you choose. Date.From and DateTime.From convert text under the file's locale setting, so the same eight characters can land on two different dates in two files. Set the locale explicitly on import rather than trusting the machine.

pandas. read_csv with parse_dates returns datetime64, a count from 1970-01-01 in nanoseconds. It has no 1900 epoch and no 1904 epoch. A serial pasted from Excel arrives as a number near 45,000 and needs pd.to_datetime(serial, unit='D', origin='1899-12-30') to become a date.

openpyxl and SheetJS. openpyxl reads and writes xlsx in Python, and its from_excel function converts a serial once you know the workbook's epoch. SheetJS is the JavaScript and Node reader behind many browser upload tools. Set cellDates: true and it hands back date values instead of raw numbers.

Nager.Date and the Python holidays package. Both publish country and subdivision holiday tables you can feed into a WORKDAY.INTL holiday range. Both carry a version date. Neither is authoritative for a filing deadline; the CRA, the IRS or the state board is.

Days-between-dates APIs. Most take two ISO dates and return an integer, and most count the difference rather than the span. A spreadsheet counting function includes both endpoints. That is a difference of one, and it survives every test built on long spans. The conventions are set out under counting between dates.

Text dates

A text date displays exactly like a date cell. It differs in three ways. It sorts alphabetically, so the 10th lands before the 9th. It is invisible to date functions, which error or treat it as zero. And it converts on import by locale.

Import in ISO 8601 order, largest component first. It parses unambiguously under every locale and sorts correctly while it is still text. Then test the column rather than the eye: a check that every populated cell is numeric finds text dates no amount of looking will.

The failure surfaces late. A closure range with one text date in it does not break until a count reaches that part of the range, which can be months after the import.

What travels, and what does not

Send text, not the serial. The serial is a number whose meaning depends on the workbook's epoch, and nothing on the wire carries the epoch. 2027-03-05 needs no agreement about epochs at all.

Send a date and a moment as different things. A date has no zone. A moment has one and needs an offset. A sheet cannot tell them apart because both are numbers, so the distinction has to be made at the boundary, and that boundary contract is set out under spreadsheet and interface tools.

Precision

The time fraction is binary floating point, so a time written as a decimal rarely lands exactly on the second you meant. Formatting hides it. An equality test against a window boundary does not.

Compare with clamping rather than equality. Take the median of a value and the two window bounds instead of asking whether the value equals a bound, and the question never arises. The window arithmetic is under working hours, and the day-level counting it supports is under counting a month.

Common questions

Why does my date show as a five-figure number?

The cell is formatted as a number and the value is a serial. Format it as a date. The value is correct.

Can I store a date as text on purpose?

Yes, in ISO order, when the sheet is a transport rather than a calculator. Convert it to a serial before any date function touches it.

How do I tell which date system a workbook uses?

Enter a known date and read the serial, or check the workbook's settings. The known-date test is quicker and works where the setting is not obvious.

Which of these tools honors the 1904 epoch?

Excel and LibreOffice Calc switch epochs in their settings. Sheets and Google Apps Script stay on 1899-12-30, and pandas counts from 1970-01-01. Convert a 1904 serial before handing it to any of them.

More in Costs

Latest from Planning Desk