The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
This vignette is the reference behind unexcel’s
decisions: what a serial is, why there are two of them, and how a
workbook records which cells are dates.
A spreadsheet stores a date as the number of days since an origin.
The stored value is a plain number; the calendar appearance comes
entirely from the cell’s number format. Change the format to
General and the date shows its serial.
That is why date auto-conversion is destructive in a specific way:
the typed text 30.3 is gone, replaced by
45746, and only the formatting records that the number is
meant to be read as a date.
There are two systems in circulation.
The 1900 system is what Excel writes on every current platform. Serial 1 is 1900-01-01. Excel also carries 1900-02-29 — a date that never existed, retained for compatibility with Lotus 1-2-3, which had the bug first. R has no such day, so an origin of 1899-12-30 absorbs the extra day and reproduces what Excel displays for every serial from 61 onward:
excel_origin("1900")
#> [1] "1899-12-30"
as.Date(45746, origin = excel_origin("1900"))
#> [1] "2025-03-30"The 1904 system came from early Macintosh Excel, which sidestepped the Lotus bug by starting later. Serial 0 is 1904-01-01. Workbooks created in it still circulate, and the option survives in current Excel:
The two are 1462 days apart — four years and a day:
That gap is the reason to read the date system rather than guess it. Both readings of a serial are plausible dates in living memory, so no test of reasonableness separates them:
serial_to_day_month(44284, date_system = "1900")
#> [1] 29.3
serial_to_day_month(44284, date_system = "1904")
#> [1] 30.3Both answers look entirely ordinary. Only the workbook knows which is
right, and it says so in the date1904 attribute of
xl/workbook.xml:
xl/styles.xml holds a table of cell formats
(cellXfs); each cell’s s attribute indexes
into it, and each entry names a number format id. Ids 14 to 22 and 45 to
47 are Excel’s built-in date and time formats; ids from 164 up are
custom, and the workbook stores their format codes alongside.
unexcel treats a cell as a date when its format is one
of the built-in date formats — the time-only ones, 18 to 21 and
45 to 47, are excluded, since a duration is not a mistyped
day.month — or when a custom format code contains date
tokens. Quoted literals, escaped characters and bracketed sections
([Red], [<100], [$-409]) are
stripped first, so a currency format reading "day "0.0 is
not mistaken for a date, and only the positive-number section of a
multi-part code is considered.
The remaining ambiguity is m, which means months in
d/m/yyyy and minutes in h:mm. It is read as a
month unless hours or seconds appear alongside.
path <- system.file("extdata", "typed-numbers.xlsx", package = "unexcel")
excel_date_columns(path)
#> col name n_date n_values prop_date field_order format_code
#> 1 2 dose 5 5 1 dm d/m/yyyy
#> 2 5 visit 5 5 1 md mm-dd-yyThe same format code also settles the field order. A
d/m/yyyy column restores day-first, an
mm-dd-yy column month-first, and neither has to be
assumed:
Reading the file recovers the number that was typed, not the keystrokes.
A value typed as 3.10 becomes the 3rd of October, which
restores to the number 3.1. The trailing zero is not
recoverable numerically — the file records a day and a month, and
10 is the month either way.
output = "character" keeps the printed form instead:
A value that Excel converted to a date and that carried more
than two components — 30.3.2025 — leaves the
day.month idea behind entirely; the restored value is still
30.3, and the year is discarded by design.
Finally, if a column was auto-converted and then re-formatted as
General by a later editor, the workbook no longer describes
it as a date. The serial is still there, but the evidence is not, and
restore_day_month() with its guardrails is all that
remains.
numFmt) — the number format
grammar.These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.