Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions src/ExcelReader.Core/Reader/XlsxReader.Enumerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -726,8 +726,21 @@ private static bool TryParseIsoDate(ReadOnlySpan<byte> utf8, out DateTime value)
{
chars[i] = (char)utf8[i];
}
return DateTime.TryParse(chars[..utf8.Length], CultureInfo.InvariantCulture,
DateTimeStyles.RoundtripKind | DateTimeStyles.AllowWhiteSpaces, out value);
const DateTimeStyles dateParseFlag = DateTimeStyles.RoundtripKind | DateTimeStyles.AllowWhiteSpaces;
if (!DateTime.TryParse(chars[..utf8.Length], CultureInfo.InvariantCulture, dateParseFlag, out value))
{
return false;
}
// DateTime spans years 1..9999 but ToOADate only accepts 0100-01-01 and later — it
// throws OverflowException ("Not a legal OleAut date") below that. A hand-written
// t="d" like "0024-02-29" parses fine and would have taken down the whole read, so
// treat anything with no serial representation as unparseable and keep it as text.
if (value.Year < 100)
{
value = default;
return false;
}
return true;
}

private static ReadOnlySpan<byte> ElementText(ReadOnlySpan<byte> inner, ReadOnlySpan<byte> openTag, ReadOnlySpan<byte> closeTag)
Expand Down
1 change: 1 addition & 0 deletions tests/ExcelReader.Fuzz/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ the working corpus for both jobs.
|---|---|---|
| `xls-minifat-overflow.bin` | `xls` | `OverflowException` out of `XlsCompoundFile.ReadIntSectors`. `miniFatSectorCount` (16,777,215 in a 3.4 KB file) was the one header sector count not bounded against the container length, and `ReadIntSectors` multiplies it by `sectorSize` inside a `checked` block. Now rejected as `InvalidDataException`; regression test in `ExcelOpenAndOleErrorTests`. |
| `xlsx-truncated-cellxfs.bin` | `xlsx` | `ArgumentOutOfRangeException` out of `XlsxReader.ParseStyleDateFlags`. A styles part truncated mid-`<cellXfs` open tag left the `'>'` search at -1, which then anchored the search for `</cellXfs>` before being checked. Now returns no date flags; the `IdxOf` helpers also treat a negative anchor as "not found". Regression test in `XlsxReaderTests`. |
| `xlsx-isodate-year-below-100.bin` | `xlsx`, `xlsx-memory` | `OverflowException` ("Not a legal OleAut date") out of `XlsxReader.Enumerator.EmitIsoDate`. A `t="d"` cell holding `0024-02-29T21:00:00.000Z` parsed fine into a `DateTime` but has no OLE automation serial — `ToOADate` only accepts `0100-01-01` and later. Such values are now kept verbatim as text like any other unparseable `t="d"`. Regression test in `NamespacePrefixAndIsoDateTests`. |

## Seeds

Expand Down
Binary file not shown.
33 changes: 33 additions & 0 deletions tests/ExcelReader.Tests/NamespacePrefixAndIsoDateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,39 @@ public void UnparseableIsoDateCellIsKeptAsString()
Assert.Equal("not-a-date", e.Current[0].GetString());
}

// Regression: found by the xlsx-memory fuzz target. DateTime spans years 1..9999 but
// ToOADate only accepts 0100-01-01 and later, so a t="d" whose text parsed into an earlier
// year threw OverflowException ("Not a legal OleAut date") out of MoveNext. Such a value has
// no Excel serial at all, so it is kept verbatim as text like any other unparseable t="d".
[Theory]
[InlineData("0024-02-29T21:00:00.000Z")]
[InlineData("0001-01-01")]
[InlineData("0099-12-31T23:59:59")]
public void IsoDateBelowOaDateRangeIsKeptAsString(string text)
{
using MemoryStream ms = WorkbookBuilder.Build(
$"""<row r="1"><c r="A1" t="d"><v>{text}</v></c></row>""");
using XlsxReader reader = Excel.From(ms);
using XlsxReader.Enumerator e = reader.GetEnumerator();

Assert.True(e.MoveNext());
Assert.Equal(CellType.ExcelString, e.Current[0].Type);
Assert.Equal(text, e.Current[0].GetString());
}

// The first legal OADate value still round-trips as a date, so the guard is not off by a day.
[Fact]
public void IsoDateAtOaDateFloorIsStillParsedAsDate()
{
using MemoryStream ms = WorkbookBuilder.Build(
"""<row r="1"><c r="A1" t="d"><v>0100-01-01</v></c></row>""");
using XlsxReader reader = Excel.From(ms);
using XlsxReader.Enumerator e = reader.GetEnumerator();

Assert.True(e.MoveNext());
Assert.Equal(CellType.Date, e.Current[0].Type);
}

[Fact]
public async Task IsoDateCellIsParsedAsDateAsync()
{
Expand Down
Loading