diff --git a/samples/xlsx/TestIssue869/DateTimeMidnight.xlsx b/samples/xlsx/TestIssue869/DateTimeMidnight.xlsx new file mode 100644 index 00000000..19d84975 Binary files /dev/null and b/samples/xlsx/TestIssue869/DateTimeMidnight.xlsx differ diff --git a/samples/xlsx/TestIssue869/DateTimeNotMidnight.xlsx b/samples/xlsx/TestIssue869/DateTimeNotMidnight.xlsx new file mode 100644 index 00000000..8aa35a60 Binary files /dev/null and b/samples/xlsx/TestIssue869/DateTimeNotMidnight.xlsx differ diff --git a/src/MiniExcel.Core/Enums/DateOnlyConversionMode.cs b/src/MiniExcel.Core/Enums/DateOnlyConversionMode.cs new file mode 100644 index 00000000..0955a87b --- /dev/null +++ b/src/MiniExcel.Core/Enums/DateOnlyConversionMode.cs @@ -0,0 +1,20 @@ +namespace MiniExcelLib.Core.Enums; + + +public enum DateOnlyConversionMode +{ + /// + /// No conversion is applied and DateOnly values are not transformed. + /// + None, + + /// + /// Allows conversion from DateTime to DateOnly only if the time component is exactly midnight (00:00:00). + /// + RequireMidnight, + + /// + /// Converts DateTime to DateOnly by ignoring the time part completely, assuming the time component is not critical. + /// + IgnoreTimePart +} \ No newline at end of file diff --git a/src/MiniExcel.Core/MiniExcelConfiguration.cs b/src/MiniExcel.Core/MiniExcelConfiguration.cs index e5ec3ea0..fee2ac1e 100644 --- a/src/MiniExcel.Core/MiniExcelConfiguration.cs +++ b/src/MiniExcel.Core/MiniExcelConfiguration.cs @@ -1,4 +1,5 @@ using MiniExcelLib.Core.Attributes; +using MiniExcelLib.Core.Enums; namespace MiniExcelLib.Core; @@ -15,4 +16,9 @@ public abstract class MiniExcelBaseConfiguration : IMiniExcelConfiguration /// When exporting using DataReader, the data not in DynamicColumn will be filtered. /// public bool DynamicColumnFirst { get; set; } = false; + + /// + /// Specifies when and how DateTime values are converted to DateOnly values. + /// + public DateOnlyConversionMode DateOnlyConversionMode { get; set; } } \ No newline at end of file diff --git a/src/MiniExcel.Core/Reflection/MiniExcelMapper.cs b/src/MiniExcel.Core/Reflection/MiniExcelMapper.cs index 055da0c2..7e46339d 100644 --- a/src/MiniExcel.Core/Reflection/MiniExcelMapper.cs +++ b/src/MiniExcel.Core/Reflection/MiniExcelMapper.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using MiniExcelLib.Core.Enums; using MiniExcelLib.Core.Exceptions; namespace MiniExcelLib.Core.Reflection; @@ -150,6 +151,14 @@ public static partial class MiniExcelMapper return newValue; } + if (itemValue is DateTime dateTimeValue && config.DateOnlyConversionMode is not DateOnlyConversionMode.None) + { + if (config.DateOnlyConversionMode == DateOnlyConversionMode.RequireMidnight && dateTimeValue.TimeOfDay != TimeSpan.Zero) + throw new InvalidCastException($"Could not convert cell of type DateTime to DateOnly, because DateTime was not at midnight, but at {dateTimeValue:HH:mm:ss}."); + + return DateOnly.FromDateTime(dateTimeValue); + } + var vs = itemValue?.ToString(); if (pInfo.ExcelFormat is not null) { diff --git a/tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs b/tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs index c9a4e98e..6bab7c52 100644 --- a/tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs +++ b/tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs @@ -1,5 +1,6 @@ using System.ComponentModel; using System.Text.RegularExpressions; +using MiniExcelLib.Core.Enums; using MiniExcelLib.Core.Exceptions; using MiniExcelLib.Core.OpenXml.Picture; using MiniExcelLib.Core.OpenXml.Utils; @@ -16,9 +17,7 @@ public class MiniExcelIssueTests(ITestOutputHelper output) private readonly OpenXmlExporter _excelExporter = MiniExcel.Exporters.GetOpenXmlExporter(); private readonly OpenXmlTemplater _excelTemplater = MiniExcel.Templaters.GetOpenXmlTemplater(); - // private readonly OpenXmlImporter _csvImporter = MiniExcel.Importer.GetCsvImporter(); - // private readonly OpenXmlExporter _csvExporter = MiniExcel.Exporter.GetCsvExporter(); - + /// /// https://github.com/mini-software/MiniExcel/issues/549 /// @@ -3653,4 +3652,41 @@ public void TestIssue809() Assert.Equal(null, rows[0].A); Assert.Equal(2, rows[2].B); } + + + private class Issue869 + { + public string? Name { get; set; } + public DateOnly? Date { get; set; } + } + + [Theory] + [InlineData("DateTimeMidnight", DateOnlyConversionMode.None, true)] + [InlineData("DateTimeNotMidnight", DateOnlyConversionMode.None, true)] + [InlineData("DateTimeMidnight", DateOnlyConversionMode.RequireMidnight, false)] + [InlineData("DateTimeNotMidnight", DateOnlyConversionMode.RequireMidnight, true)] + [InlineData("DateTimeMidnight", DateOnlyConversionMode.IgnoreTimePart, false)] + [InlineData("DateTimeNotMidnight", DateOnlyConversionMode.IgnoreTimePart, false)] + public void TestIssue869(string fileName, DateOnlyConversionMode mode, bool throwsException) + { + var path = PathHelper.GetFile($"xlsx/TestIssue869/{fileName}.xlsx"); + var config = new OpenXmlConfiguration { DateOnlyConversionMode = mode }; + + var testFn = () => _excelImporter.Query(path, configuration: config).ToList(); + if (throwsException) + { + Assert.Throws(testFn); + } + else + { + try + { + _ = testFn(); + } + catch (Exception ex) + { + Assert.Fail($"No exception should be thrown, but one was still thrown: {ex}."); + } + } + } } \ No newline at end of file