Issue for ExcelDataReader/ExcelDataReader
Type: bug report, with an offer to write the fix
Status: FILED 2026-08-08 as
#759
Verified on: develop @ c4acb4b (4.0.0) and the released 3.9.0 package
Kept here as the source of what was sent, so the claims stay checkable against the harness that produced them.
This was nearly two issues. The second one, that their output depends on the machine's timezone, did not survive being written up: the reproduction that killed it is in the second afterword to chapter 9. Verifying a finding against the other project rather than against your own rendering of it is the whole difference between a contribution and a waste of a maintainer's evening.
Title
OOXML strict: a cell holding a time with no date is resolved against the current date, so its value changes daily
Body
In the strict flavour of OOXML, a cell can carry an ISO 8601 time with no date component. ExcelDataReader resolves those against today's date, so reading the same file on two different days returns two different values.
The clearest way to see it is with two files already in the repository.
LocaleTime.xlsx and strict/LocaleTime.xlsx are the same workbook saved in
the transitional and strict formats:
LocaleTime.xlsx row 1, column 1 -> 1899-12-31 01:34:00
strict/LocaleTime.xlsx row 1, column 1 -> 2026-08-08 01:34:00 (today's date)
The first value is the one ExcelOpenXmlReaderLocaleTest already asserts:
Assert.That(dataSet.Tables[0].Rows[1][1], Is.EqualTo(new System.DateTime(1899, 12, 31, 1, 34, 0)));
The strict twin of that file is not covered, and it returns a different instant. Tomorrow it will return another one.
Reproduction
using System.Globalization;
using System.Text;
using ExcelDataReader;
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
foreach (var path in new[] { "LocaleTime.xlsx", "strict/LocaleTime.xlsx" })
{
using var stream = File.Open(path, FileMode.Open, FileAccess.Read);
using var reader = ExcelReaderFactory.CreateReader(stream);
reader.Read(); // header row
reader.Read(); // first data row
var v = (DateTime)reader.GetValue(1);
Console.WriteLine($"{path,-24} {v.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)}");
}
Run from src/TestData. Observed on 2026-08-08:
LocaleTime.xlsx 1899-12-31 01:34:00
strict/LocaleTime.xlsx 2026-08-08 01:34:00
strict/Open.xlsx shows the same thing in column 10, and because "today" is
itself a function of the host, the value also changes with the machine's
timezone:
TZ=UTC 2026-08-08 11:00:00
TZ=Asia/Tokyo 2026-08-09 11:00:00
Cause
Core/OpenXmlFormat/XlsxWorksheet.cs:222:
if (numberFormat.IsDateTimeFormat && DateTime.TryParse(s, out DateTime dateTime))
The cell arrives here as a string because the t="d" branch in
XmlWorksheetReader.ParseCellValue only accepts the exact yyyy-MM-dd form and
passes anything else through unchanged. DateTime.TryParse then substitutes the
current date when the input has no date component, which is documented .NET
behaviour rather than a surprise.
The values in these files look like this, complete with the float error left over from converting a serial to text:
<c r="B2" s="4" t="d"><v>01:34:00.00000000000154875</v></c>
Possible fixes
Adding DateTimeStyles.NoCurrentDateDefault is the one-line version, and makes
the result stable at 0001-01-01 01:34:00:
DateTime.TryParse(s, CultureInfo.InvariantCulture,
DateTimeStyles.NoCurrentDateDefault | DateTimeStyles.AllowWhiteSpaces, out var dateTime)
CultureInfo.InvariantCulture belongs there regardless. The value is ISO 8601,
so parsing it with CurrentCulture cannot be right even where it happens to
agree.
That leaves strict and transitional disagreeing, though. Routing a time with no
date through the same OADate conversion the transitional path uses would return
1899-12-31 01:34:00 for both, which matches the existing assertion and makes
the two spellings of one workbook decode alike. That is a design call rather
than a bug fix, so I have not assumed it.
Happy to open a PR for whichever you prefer.
Context
Found while building a differential harness for a Go port of this library. It
compares the two implementations cell by cell across the 303 .xls and .xlsx
files in src/TestData, which is also how I found several bugs in my own code.
The corpus in this repository is the reason that was possible at all.
Writing is AI assisted. Thoughts and publishing are human-gated.
Rendered from jloor/go-excel-reader at 18e4904. The markdown in that repository is the source of truth; if this page disagrees with it, this page is stale.