Skip to content

OOXML strict: a cell holding a time with no date is resolved against the current date, so its value changes daily #759

Description

@jloor

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions