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.
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.xlsxandstrict/LocaleTime.xlsxare the same workbook saved inthe transitional and strict formats:
The first value is the one
ExcelOpenXmlReaderLocaleTestalready asserts:The strict twin of that file is not covered, and it returns a different instant.
Tomorrow it will return another one.
Reproduction
Run from
src/TestData. Observed on 2026-08-08:strict/Open.xlsxshows the same thing in column 10, and because "today" isitself a function of the host, the value also changes with the machine's
timezone:
Cause
Core/OpenXmlFormat/XlsxWorksheet.cs:222:The cell arrives here as a string because the
t="d"branch inXmlWorksheetReader.ParseCellValueonly accepts the exactyyyy-MM-ddform andpasses anything else through unchanged.
DateTime.TryParsethen substitutes thecurrent 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:
Possible fixes
Adding
DateTimeStyles.NoCurrentDateDefaultis the one-line version, and makesthe result stable at
0001-01-01 01:34:00:CultureInfo.InvariantCulturebelongs there regardless. The value is ISO 8601,so parsing it with
CurrentCulturecannot be right even where it happens toagree.
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:00for both, which matches the existing assertion and makesthe 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
.xlsand.xlsxfiles 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.