Custom DateTime format (#616)

* - goto/jumpmark

* + generate numberformats

* + `FormatId`

* ~ first working shot

* ~ assign responsibilities correctly

* ~ fix last test issues

* + extend tests

* ~ clean up

* ~ simplify `DateOnly` handling

* + `DateOnly` to Tests

* Update ExcelOpenXmlSheetWriter.cs

Update GenerateSheetByIDataReader

* + datetime format for async part

---------

Co-authored-by: Gary Jia <35099424+jiaguangli@users.noreply.github.com>
This commit is contained in:
DancePanda42 2024-06-13 15:26:26 +02:00 committed by GitHub
parent 12bb1c0028
commit 00a445c6bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 340 additions and 99 deletions

View File

@ -9,6 +9,8 @@ namespace MiniExcelLibs.Attributes
private int _index = -1; private int _index = -1;
private string _xName; private string _xName;
internal int FormatId { get; set; } = -1;
public string Name { get; set; } public string Name { get; set; }
public string[] Aliases { get; set; } public string[] Aliases { get; set; }
@ -52,6 +54,7 @@ namespace MiniExcelLibs.Attributes
public class DynamicExcelColumn : ExcelColumnAttribute public class DynamicExcelColumn : ExcelColumnAttribute
{ {
public string Key { get; set; } public string Key { get; set; }
public DynamicExcelColumn(string key) public DynamicExcelColumn(string key)
{ {
Key = key; Key = key;

View File

@ -1,4 +1,9 @@
using MiniExcelLibs.OpenXml.Models; using MiniExcelLibs.Attributes;
using MiniExcelLibs.OpenXml.Models;
using MiniExcelLibs.Utils;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MiniExcelLibs.OpenXml.Constants namespace MiniExcelLibs.OpenXml.Constants
{ {
@ -52,10 +57,21 @@ namespace MiniExcelLibs.OpenXml.Constants
</x:cellXfs> </x:cellXfs>
</x:styleSheet>"; </x:styleSheet>";
internal static readonly string DefaultStylesXml = @"<?xml version=""1.0"" encoding=""utf-8""?> #region StyleSheet
private const int startUpNumFmts = 1;
private const string NumFmtsToken = "{{numFmts}}";
private const string NumFmtsCountToken = "{{numFmtCount}}";
private const int startUpCellXfs = 5;
private const string cellXfsToken = "{{cellXfs}}";
private const string cellXfsCountToken = "{{cellXfsCount}}";
internal static readonly string DefaultStylesXml = $@"<?xml version=""1.0"" encoding=""utf-8""?>
<x:styleSheet xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main""> <x:styleSheet xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"">
<x:numFmts count=""1""> <x:numFmts count=""{NumFmtsCountToken}"">
<x:numFmt numFmtId=""0"" formatCode="""" /> <x:numFmt numFmtId=""0"" formatCode="""" />
{NumFmtsToken}
</x:numFmts> </x:numFmts>
<x:fonts count=""2""> <x:fonts count=""2"">
<x:font> <x:font>
@ -133,7 +149,7 @@ namespace MiniExcelLibs.OpenXml.Constants
<x:protection locked=""1"" hidden=""0"" /> <x:protection locked=""1"" hidden=""0"" />
</x:xf> </x:xf>
</x:cellStyleXfs> </x:cellStyleXfs>
<x:cellXfs count=""4""> <x:cellXfs count=""{cellXfsCountToken}"">
<x:xf></x:xf> <x:xf></x:xf>
<x:xf numFmtId=""0"" fontId=""1"" fillId=""2"" borderId=""1"" xfId=""0"" applyNumberFormat=""1"" applyFill=""0"" applyBorder=""1"" applyAlignment=""1"" applyProtection=""1""> <x:xf numFmtId=""0"" fontId=""1"" fillId=""2"" borderId=""1"" xfId=""0"" applyNumberFormat=""1"" applyFill=""0"" applyBorder=""1"" applyAlignment=""1"" applyProtection=""1"">
<x:alignment horizontal=""left"" vertical=""bottom"" textRotation=""0"" wrapText=""0"" indent=""0"" relativeIndent=""0"" justifyLastLine=""0"" shrinkToFit=""0"" readingOrder=""0"" /> <x:alignment horizontal=""left"" vertical=""bottom"" textRotation=""0"" wrapText=""0"" indent=""0"" relativeIndent=""0"" justifyLastLine=""0"" shrinkToFit=""0"" readingOrder=""0"" />
@ -150,12 +166,15 @@ namespace MiniExcelLibs.OpenXml.Constants
<x:xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""1"" xfId=""0"" applyBorder=""1"" applyAlignment=""1""> <x:xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""1"" xfId=""0"" applyBorder=""1"" applyAlignment=""1"">
<x:alignment horizontal=""fill""/> <x:alignment horizontal=""fill""/>
</x:xf> </x:xf>
{cellXfsToken}
</x:cellXfs> </x:cellXfs>
<x:cellStyles count=""1""> <x:cellStyles count=""1"">
<x:cellStyle name=""Normal"" xfId=""0"" builtinId=""0"" /> <x:cellStyle name=""Normal"" xfId=""0"" builtinId=""0"" />
</x:cellStyles> </x:cellStyles>
</x:styleSheet>"; </x:styleSheet>";
#endregion
internal static readonly string DefaultWorkbookXml = @"<?xml version=""1.0"" encoding=""utf-8""?> internal static readonly string DefaultWorkbookXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
<x:workbook xmlns:r=""http://schemas.openxmlformats.org/officeDocument/2006/relationships"" <x:workbook xmlns:r=""http://schemas.openxmlformats.org/officeDocument/2006/relationships""
xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main""> xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"">
@ -231,5 +250,52 @@ namespace MiniExcelLibs.OpenXml.Constants
internal static string Sheet(SheetDto sheetDto, int sheetId) internal static string Sheet(SheetDto sheetDto, int sheetId)
=> $@"<x:sheet name=""{ExcelOpenXmlUtils.EncodeXML(sheetDto.Name)}"" sheetId=""{sheetId}""{(string.IsNullOrWhiteSpace(sheetDto.State) ? string.Empty : $" state=\"{sheetDto.State}\"")} r:id=""{sheetDto.ID}"" />"; => $@"<x:sheet name=""{ExcelOpenXmlUtils.EncodeXML(sheetDto.Name)}"" sheetId=""{sheetId}""{(string.IsNullOrWhiteSpace(sheetDto.State) ? string.Empty : $" state=\"{sheetDto.State}\"")} r:id=""{sheetDto.ID}"" />";
internal static string SetupStyleXml(string styleXml, ICollection<ExcelColumnAttribute> columns)
{
const int numFmtIndex = 166;
var sb = new StringBuilder(styleXml);
var columnsToApply = GenerateStyleIds(columns);
var numFmts = columnsToApply.Select((x, i) =>
{
return new
{
numFmt =
$@"<x:numFmt numFmtId=""{numFmtIndex + i}"" formatCode=""{x.Format}"" />",
cellXfs =
$@"<x:xf numFmtId=""{numFmtIndex + i}"" fontId=""0"" fillId=""0"" borderId=""1"" xfId=""0"" applyNumberFormat=""1"" applyFill=""1"" applyBorder=""1"" applyAlignment=""1"" applyProtection=""1"">
<x:alignment horizontal=""general"" vertical=""bottom"" textRotation=""0"" wrapText=""0"" indent=""0"" relativeIndent=""0"" justifyLastLine=""0"" shrinkToFit=""0"" readingOrder=""0"" />
<x:protection locked=""1"" hidden=""0"" />
</x:xf>"
};
}).ToArray();
sb.Replace(NumFmtsToken, string.Join(string.Empty, numFmts.Select(x => x.numFmt)));
sb.Replace(NumFmtsCountToken, (startUpNumFmts + numFmts.Length).ToString());
sb.Replace(cellXfsToken, string.Join(string.Empty, numFmts.Select(x => x.cellXfs)));
sb.Replace(cellXfsCountToken, (5 + numFmts.Length).ToString());
return sb.ToString();
}
private static IEnumerable<ExcelColumnAttribute> GenerateStyleIds(ICollection<ExcelColumnAttribute> dynamicColumns)
{
if (dynamicColumns == null)
yield break;
int index = 0;
foreach (var g in dynamicColumns?.Where(x => !string.IsNullOrWhiteSpace(x.Format) && new ExcelNumberFormat(x.Format).IsValid).GroupBy(x => x.Format))
{
foreach (var col in g)
col.FormatId = startUpCellXfs + index;
yield return g.First();
index++;
}
}
} }
} }

View File

@ -36,6 +36,7 @@ namespace MiniExcelLibs.OpenXml
{ {
await CreateZipEntryAsync(ExcelFileNames.Rels, ExcelContentTypes.Relationships, ExcelXml.DefaultRels, cancellationToken); await CreateZipEntryAsync(ExcelFileNames.Rels, ExcelContentTypes.Relationships, ExcelXml.DefaultRels, cancellationToken);
await CreateZipEntryAsync(ExcelFileNames.SharedStrings, ExcelContentTypes.SharedStrings, ExcelXml.DefaultSharedString, cancellationToken); await CreateZipEntryAsync(ExcelFileNames.SharedStrings, ExcelContentTypes.SharedStrings, ExcelXml.DefaultSharedString, cancellationToken);
await GenerateStylesXmlAsync(cancellationToken);
} }
private async Task CreateSheetXmlAsync(object value, string sheetPath, CancellationToken cancellationToken) private async Task CreateSheetXmlAsync(object value, string sheetPath, CancellationToken cancellationToken)
@ -47,9 +48,9 @@ namespace MiniExcelLibs.OpenXml
if (value == null) if (value == null)
{ {
await WriteEmptySheetAsync(writer); await WriteEmptySheetAsync(writer);
goto End; //for re-using code
} }
else
{
//DapperRow //DapperRow
switch (value) switch (value)
@ -67,7 +68,7 @@ namespace MiniExcelLibs.OpenXml
throw new NotImplementedException($"Type {value.GetType().FullName} is not implemented. Please open an issue."); throw new NotImplementedException($"Type {value.GetType().FullName} is not implemented. Please open an issue.");
} }
} }
End: //for re-using code }
_zipDictionary.Add(sheetPath, new ZipPackageInfo(entry, ExcelContentTypes.Worksheet)); _zipDictionary.Add(sheetPath, new ZipPackageInfo(entry, ExcelContentTypes.Worksheet));
} }
@ -455,8 +456,6 @@ namespace MiniExcelLibs.OpenXml
{ {
await AddFilesToZipAsync(cancellationToken); await AddFilesToZipAsync(cancellationToken);
await GenerateStylesXmlAsync(cancellationToken);
await GenerateDrawinRelXmlAsync(cancellationToken); await GenerateDrawinRelXmlAsync(cancellationToken);
await GenerateDrawingXmlAsync(cancellationToken); await GenerateDrawingXmlAsync(cancellationToken);
@ -479,7 +478,7 @@ namespace MiniExcelLibs.OpenXml
/// </summary> /// </summary>
private async Task GenerateStylesXmlAsync(CancellationToken cancellationToken) private async Task GenerateStylesXmlAsync(CancellationToken cancellationToken)
{ {
var styleXml = GetStylesXml(); var styleXml = GetStylesXml(_configuration.DynamicColumns);
await CreateZipEntryAsync( await CreateZipEntryAsync(
ExcelFileNames.Styles, ExcelFileNames.Styles,

View File

@ -1,4 +1,5 @@
using MiniExcelLibs.OpenXml.Constants; using MiniExcelLibs.Attributes;
using MiniExcelLibs.OpenXml.Constants;
using MiniExcelLibs.OpenXml.Models; using MiniExcelLibs.OpenXml.Models;
using MiniExcelLibs.Utils; using MiniExcelLibs.Utils;
using MiniExcelLibs.Zip; using MiniExcelLibs.Zip;
@ -108,6 +109,7 @@ namespace MiniExcelLibs.OpenXml
if (dynamicColumn.Format != null) if (dynamicColumn.Format != null)
{ {
prop.ExcelFormat = dynamicColumn.Format; prop.ExcelFormat = dynamicColumn.Format;
prop.ExcelFormatId = dynamicColumn.FormatId;
} }
if (dynamicColumn.Aliases != null) if (dynamicColumn.Aliases != null)
@ -158,14 +160,26 @@ namespace MiniExcelLibs.OpenXml
return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXML(str)); return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXML(str));
} }
if (columnInfo?.ExcelFormat != null && value is IFormattable formattableValue) var type = GetValueType(value, columnInfo);
if (columnInfo?.ExcelFormat != null && columnInfo?.ExcelFormatId == -1 && value is IFormattable formattableValue)
{ {
var formattedStr = formattableValue.ToString(columnInfo.ExcelFormat, _configuration.Culture); var formattedStr = formattableValue.ToString(columnInfo.ExcelFormat, _configuration.Culture);
return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXML(formattedStr)); return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXML(formattedStr));
} }
var type = GetValueType(value, columnInfo); if (type == typeof(DateTime))
{
return GetDateTimeValue((DateTime)value, columnInfo);
}
#if NET6_0_OR_GREATER
if (type == typeof(DateOnly))
{
return GetDateTimeValue(((DateOnly)value).ToDateTime(new TimeOnly()), columnInfo);
}
#endif
if (type.IsEnum) if (type.IsEnum)
{ {
var description = CustomPropertyHelper.DescriptionAttr(type, value); var description = CustomPropertyHelper.DescriptionAttr(type, value);
@ -193,33 +207,6 @@ namespace MiniExcelLibs.OpenXml
return Tuple.Create("4", "str", ExcelOpenXmlUtils.EncodeXML(base64)); return Tuple.Create("4", "str", ExcelOpenXmlUtils.EncodeXML(base64));
} }
if (type == typeof(DateTime))
{
return GetDateTimeValue(value, columnInfo);
}
#if NET6_0_OR_GREATER
if (type == typeof(DateOnly))
{
if (_configuration.Culture != CultureInfo.InvariantCulture)
{
var cellValue = ((DateOnly)value).ToString(_configuration.Culture);
return Tuple.Create("2", "str", cellValue);
}
if (columnInfo == null || columnInfo.ExcelFormat == null)
{
var oaDate = CorrectDateTimeValue((DateTime)value);
var cellValue = oaDate.ToString(CultureInfo.InvariantCulture);
return Tuple.Create<string, string, string>("3", null, cellValue);
}
// TODO: now it'll lose date type information
var formattedCellValue = ((DateOnly)value).ToString(columnInfo.ExcelFormat, _configuration.Culture);
return Tuple.Create("2", "str", formattedCellValue);
}
#endif
return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXML(value.ToString())); return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXML(value.ToString()));
} }
@ -325,24 +312,21 @@ namespace MiniExcelLibs.OpenXml
return base64; return base64;
} }
private Tuple<string, string, string> GetDateTimeValue(object value, ExcelColumnInfo columnInfo) private Tuple<string, string, string> GetDateTimeValue(DateTime value, ExcelColumnInfo columnInfo)
{ {
string cellValue = null;
if (_configuration.Culture != CultureInfo.InvariantCulture) if (_configuration.Culture != CultureInfo.InvariantCulture)
{ {
var cellValue = ((DateTime)value).ToString(_configuration.Culture); cellValue = (value).ToString(_configuration.Culture);
return Tuple.Create("2", "str", cellValue); return Tuple.Create("2", "str", cellValue);
} }
var oaDate = CorrectDateTimeValue(value);
cellValue = oaDate.ToString(CultureInfo.InvariantCulture);
if (columnInfo == null || columnInfo.ExcelFormat == null) if (columnInfo == null || columnInfo.ExcelFormat == null)
{
var oaDate = CorrectDateTimeValue((DateTime)value);
var cellValue = oaDate.ToString(CultureInfo.InvariantCulture);
return Tuple.Create<string, string, string>("3", null, cellValue); return Tuple.Create<string, string, string>("3", null, cellValue);
} else
return Tuple.Create(columnInfo.ExcelFormatId.ToString(), (string)null, cellValue);
// TODO: now it'll lose date type information
var formattedCellValue = ((DateTime)value).ToString(columnInfo.ExcelFormat, _configuration.Culture);
return Tuple.Create("2", "str", formattedCellValue);
} }
private static double CorrectDateTimeValue(DateTime value) private static double CorrectDateTimeValue(DateTime value)
@ -375,14 +359,14 @@ namespace MiniExcelLibs.OpenXml
return dimensionRef; return dimensionRef;
} }
private string GetStylesXml() private string GetStylesXml(ICollection<ExcelColumnAttribute> columns)
{ {
switch (_configuration.TableStyles) switch (_configuration.TableStyles)
{ {
case TableStyles.None: case TableStyles.None:
return ExcelXml.NoneStylesXml; return ExcelXml.SetupStyleXml(ExcelXml.NoneStylesXml, columns);
case TableStyles.Default: case TableStyles.Default:
return ExcelXml.DefaultStylesXml; return ExcelXml.SetupStyleXml(ExcelXml.DefaultStylesXml, columns);
default: default:
return string.Empty; return string.Empty;
} }

View File

@ -1,4 +1,5 @@
using MiniExcelLibs.OpenXml.Constants; using MiniExcelLibs.Attributes;
using MiniExcelLibs.OpenXml.Constants;
using MiniExcelLibs.OpenXml.Models; using MiniExcelLibs.OpenXml.Models;
using MiniExcelLibs.Utils; using MiniExcelLibs.Utils;
using MiniExcelLibs.Zip; using MiniExcelLibs.Zip;
@ -68,6 +69,7 @@ namespace MiniExcelLibs.OpenXml
{ {
CreateZipEntry(ExcelFileNames.Rels, ExcelContentTypes.Relationships, ExcelXml.DefaultRels); CreateZipEntry(ExcelFileNames.Rels, ExcelContentTypes.Relationships, ExcelXml.DefaultRels);
CreateZipEntry(ExcelFileNames.SharedStrings, ExcelContentTypes.SharedStrings, ExcelXml.DefaultSharedString); CreateZipEntry(ExcelFileNames.SharedStrings, ExcelContentTypes.SharedStrings, ExcelXml.DefaultSharedString);
GenerateStylesXml();
} }
private void CreateSheetXml(object value, string sheetPath) private void CreateSheetXml(object value, string sheetPath)
@ -79,9 +81,9 @@ namespace MiniExcelLibs.OpenXml
if (value == null) if (value == null)
{ {
WriteEmptySheet(writer); WriteEmptySheet(writer);
goto End; //for re-using code
} }
else
{
//DapperRow //DapperRow
if (value is IDataReader) if (value is IDataReader)
@ -101,7 +103,7 @@ namespace MiniExcelLibs.OpenXml
throw new NotImplementedException($"Type {value.GetType().FullName} is not implemented. Please open an issue."); throw new NotImplementedException($"Type {value.GetType().FullName} is not implemented. Please open an issue.");
} }
} }
End: //for re-using code }
_zipDictionary.Add(sheetPath, new ZipPackageInfo(entry, ExcelContentTypes.Worksheet)); _zipDictionary.Add(sheetPath, new ZipPackageInfo(entry, ExcelContentTypes.Worksheet));
} }
@ -151,7 +153,7 @@ namespace MiniExcelLibs.OpenXml
for (int i = 0; i < fieldCount; i++) for (int i = 0; i < fieldCount; i++)
{ {
var cellValue = reader.GetValue(i); var cellValue = reader.GetValue(i);
WriteCell(writer, yIndex, xIndex, cellValue, columnInfo: null); WriteCell(writer, yIndex, xIndex, cellValue, columnInfo: props?.FirstOrDefault(x => x?.ExcelColumnIndex == xIndex - 1));
xIndex++; xIndex++;
} }
writer.Write(WorksheetXml.EndRow); writer.Write(WorksheetXml.EndRow);
@ -356,7 +358,7 @@ namespace MiniExcelLibs.OpenXml
for (int j = 0; j < value.Columns.Count; j++) for (int j = 0; j < value.Columns.Count; j++)
{ {
var cellValue = value.Rows[i][j]; var cellValue = value.Rows[i][j];
WriteCell(writer, yIndex, xIndex, cellValue, columnInfo: null); WriteCell(writer, yIndex, xIndex, cellValue, columnInfo: props?.FirstOrDefault(x => x?.ExcelColumnIndex == xIndex - 1));
xIndex++; xIndex++;
} }
writer.Write(WorksheetXml.EndRow); writer.Write(WorksheetXml.EndRow);
@ -484,8 +486,6 @@ namespace MiniExcelLibs.OpenXml
{ {
AddFilesToZip(); AddFilesToZip();
GenerateStylesXml();
GenerateDrawinRelXml(); GenerateDrawinRelXml();
GenerateDrawingXml(); GenerateDrawingXml();
@ -508,7 +508,7 @@ namespace MiniExcelLibs.OpenXml
/// </summary> /// </summary>
private void GenerateStylesXml() private void GenerateStylesXml()
{ {
var styleXml = GetStylesXml(); var styleXml = GetStylesXml(_configuration.DynamicColumns);
CreateZipEntry(ExcelFileNames.Styles, ExcelContentTypes.Styles, styleXml); CreateZipEntry(ExcelFileNames.Styles, ExcelContentTypes.Styles, styleXml);
} }

View File

@ -23,6 +23,7 @@
public double? ExcelColumnWidth { get; internal set; } public double? ExcelColumnWidth { get; internal set; }
public string ExcelIndexName { get; internal set; } public string ExcelIndexName { get; internal set; }
public bool ExcelIgnore { get; internal set; } public bool ExcelIgnore { get; internal set; }
public int ExcelFormatId { get; internal set; }
} }
internal class ExcellSheetInfo internal class ExcellSheetInfo
@ -205,6 +206,7 @@
ExcelIndexName = p.GetAttribute<ExcelColumnIndexAttribute>()?.ExcelXName ?? excelColumn?.IndexName, ExcelIndexName = p.GetAttribute<ExcelColumnIndexAttribute>()?.ExcelXName ?? excelColumn?.IndexName,
ExcelColumnWidth = p.GetAttribute<ExcelColumnWidthAttribute>()?.ExcelColumnWidth ?? excelColumn?.Width, ExcelColumnWidth = p.GetAttribute<ExcelColumnWidthAttribute>()?.ExcelColumnWidth ?? excelColumn?.Width,
ExcelFormat = excelFormat ?? excelColumn?.Format, ExcelFormat = excelFormat ?? excelColumn?.Format,
ExcelFormatId = excelColumn?.FormatId ?? -1
}; };
}).Where(_ => _ != null); }).Where(_ => _ != null);
} }
@ -292,7 +294,10 @@
p.Nullable = true; p.Nullable = true;
//p.ExcludeNullableType = item2[key]?.GetType(); //p.ExcludeNullableType = item2[key]?.GetType();
if (dynamicColumn.Format != null) if (dynamicColumn.Format != null)
{
p.ExcelFormat = dynamicColumn.Format; p.ExcelFormat = dynamicColumn.Format;
p.ExcelFormatId = dynamicColumn.FormatId;
}
if (dynamicColumn.Aliases != null) if (dynamicColumn.Aliases != null)
p.ExcelColumnAliases = dynamicColumn.Aliases; p.ExcelColumnAliases = dynamicColumn.Aliases;
if (dynamicColumn.IndexName != null) if (dynamicColumn.IndexName != null)

View File

@ -309,7 +309,7 @@ namespace MiniExcelLibs.Tests
var rows = MiniExcel.Query(path, false).ToList(); var rows = MiniExcel.Query(path, false).ToList();
Assert.Equal("createdate", rows[0].A); Assert.Equal("createdate", rows[0].A);
Assert.Equal("2022-04-12", rows[1].A); Assert.Equal(new DateTime(2022, 04, 12), rows[1].A);
Assert.Equal("name", rows[0].B); Assert.Equal("name", rows[0].B);
Assert.Equal("Jack", rows[1].B); Assert.Equal("Jack", rows[1].B);
Assert.Equal("Account Point", rows[0].C); Assert.Equal("Account Point", rows[0].C);
@ -334,7 +334,7 @@ namespace MiniExcelLibs.Tests
var rows = MiniExcel.Query(path, false).ToList(); var rows = MiniExcel.Query(path, false).ToList();
Assert.Equal("createdate", rows[0].A); Assert.Equal("createdate", rows[0].A);
Assert.Equal("2022-04-12", rows[1].A); Assert.Equal(new DateTime(2022, 04, 12), rows[1].A);
Assert.Equal("name", rows[0].B); Assert.Equal("name", rows[0].B);
Assert.Equal("Jack", rows[1].B); Assert.Equal("Jack", rows[1].B);
Assert.Equal("Account Point", rows[0].C); Assert.Equal("Account Point", rows[0].C);

View File

@ -1281,5 +1281,150 @@ namespace MiniExcelLibs.Tests
} }
}); });
} }
[Fact]
public async Task DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingIDataReader()
{
var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
var dateTime = DateTime.Now;
var onlyDate = DateOnly.FromDateTime(dateTime);
var table = new DataTable();
{
table.Columns.Add("Column1", typeof(string));
table.Columns.Add("Column2", typeof(int));
table.Columns.Add("Column3", typeof(DateTime));
table.Columns.Add("Column4", typeof(DateOnly));
table.Rows.Add("MiniExcel", 1, dateTime, onlyDate);
table.Rows.Add("Github", 2, dateTime, onlyDate);
}
var configuration = new OpenXmlConfiguration
{
DynamicColumns = new[]
{
new DynamicExcelColumn("Column1")
{
Name = "Name of something",
Index = 0,
Width = 150
},
new DynamicExcelColumn("Column2")
{
Name = "Its value",
Index = 1,
Width = 150
},
new DynamicExcelColumn("Column3")
{
Name = "Its Date",
Index = 2,
Width = 150,
Format = "dd.mm.yyyy hh:mm:ss",
}
}
};
var reader = table.CreateDataReader();
await MiniExcel.SaveAsAsync(path, reader, configuration: configuration);
using (var stream = File.OpenRead(path))
{
var rows = stream.Query(useHeaderRow: true)
.Select(x => (IDictionary<string, object>)x)
.ToList();
Assert.Contains("Name of something", rows[0]);
Assert.Contains("Its value", rows[0]);
Assert.Contains("Its Date", rows[0]);
Assert.Contains("Column4", rows[0]);
Assert.Contains("Name of something", rows[1]);
Assert.Contains("Its value", rows[1]);
Assert.Contains("Its Date", rows[1]);
Assert.Contains("Column4", rows[1]);
Assert.Equal("MiniExcel", rows[0]["Name of something"]);
Assert.Equal(1D, rows[0]["Its value"]);
Assert.Equal(dateTime, (DateTime)rows[0]["Its Date"], TimeSpan.FromMilliseconds(10d));
Assert.Equal(onlyDate.ToDateTime(TimeOnly.MinValue), (DateTime)rows[0]["Column4"]);
Assert.Equal("Github", rows[1]["Name of something"]);
Assert.Equal(2D, rows[1]["Its value"]);
Assert.Equal(dateTime, (DateTime)rows[1]["Its Date"], TimeSpan.FromMilliseconds(10d));
Assert.Equal(onlyDate.ToDateTime(TimeOnly.MinValue), (DateTime)rows[1]["Column4"]);
}
}
[Fact]
public async Task DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingDataTable()
{
var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
var dateTime = DateTime.Now;
var onlyDate = DateOnly.FromDateTime(dateTime);
var table = new DataTable();
{
table.Columns.Add("Column1", typeof(string));
table.Columns.Add("Column2", typeof(int));
table.Columns.Add("Column3", typeof(DateTime));
table.Columns.Add("Column4", typeof(DateOnly));
table.Rows.Add("MiniExcel", 1, dateTime, onlyDate);
table.Rows.Add("Github", 2, dateTime, onlyDate);
}
var configuration = new OpenXmlConfiguration
{
DynamicColumns = new[]
{
new DynamicExcelColumn("Column1")
{
Name = "Name of something",
Index = 0,
Width = 150
},
new DynamicExcelColumn("Column2")
{
Name = "Its value",
Index = 1,
Width = 150
},
new DynamicExcelColumn("Column3")
{
Name = "Its Date",
Index = 2,
Width = 150,
Format = "dd.mm.yyyy hh:mm:ss"
}
}
};
await MiniExcel.SaveAsAsync(path, table, configuration: configuration);
using (var stream = File.OpenRead(path))
{
var rows = stream.Query(useHeaderRow: true)
.Select(x => (IDictionary<string, object>)x)
.Select(x => (IDictionary<string, object>)x)
.ToList();
Assert.Contains("Name of something", rows[0]);
Assert.Contains("Its value", rows[0]);
Assert.Contains("Its Date", rows[0]);
Assert.Contains("Column4", rows[0]);
Assert.Contains("Name of something", rows[1]);
Assert.Contains("Its value", rows[1]);
Assert.Contains("Its Date", rows[1]);
Assert.Contains("Column4", rows[1]);
Assert.Equal("MiniExcel", rows[0]["Name of something"]);
Assert.Equal(1D, rows[0]["Its value"]);
Assert.Equal(dateTime, (DateTime)rows[0]["Its Date"], TimeSpan.FromMilliseconds(10d));
Assert.Equal(onlyDate.ToDateTime(TimeOnly.MinValue), (DateTime)rows[0]["Column4"]);
Assert.Equal("Github", rows[1]["Name of something"]);
Assert.Equal(2D, rows[1]["Its value"]);
Assert.Equal(dateTime, (DateTime)rows[1]["Its Date"], TimeSpan.FromMilliseconds(10d));
Assert.Equal(onlyDate.ToDateTime(TimeOnly.MinValue), (DateTime)rows[1]["Column4"]);
}
}
} }
} }

View File

@ -1217,12 +1217,16 @@ namespace MiniExcelLibs.Tests
public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingIDataReader() public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingIDataReader()
{ {
var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
var dateTime = DateTime.Now;
var onlyDate = DateOnly.FromDateTime(dateTime);
var table = new DataTable(); var table = new DataTable();
{ {
table.Columns.Add("Column1", typeof(string)); table.Columns.Add("Column1", typeof(string));
table.Columns.Add("Column2", typeof(int)); table.Columns.Add("Column2", typeof(int));
table.Rows.Add("MiniExcel", 1); table.Columns.Add("Column3", typeof(DateTime));
table.Rows.Add("Github", 2); table.Columns.Add("Column4", typeof(DateOnly));
table.Rows.Add("MiniExcel", 1, dateTime, onlyDate);
table.Rows.Add("Github", 2, dateTime, onlyDate);
} }
var configuration = new OpenXmlConfiguration var configuration = new OpenXmlConfiguration
@ -1240,7 +1244,15 @@ namespace MiniExcelLibs.Tests
Name = "Its value", Name = "Its value",
Index = 1, Index = 1,
Width = 150 Width = 150
},
new DynamicExcelColumn("Column3")
{
Name = "Its Date",
Index = 2,
Width = 150,
Format = "dd.mm.yyyy hh:mm:ss",
} }
} }
}; };
var reader = table.CreateDataReader(); var reader = table.CreateDataReader();
@ -1255,13 +1267,21 @@ namespace MiniExcelLibs.Tests
Assert.Contains("Name of something", rows[0]); Assert.Contains("Name of something", rows[0]);
Assert.Contains("Its value", rows[0]); Assert.Contains("Its value", rows[0]);
Assert.Contains("Its Date", rows[0]);
Assert.Contains("Column4", rows[0]);
Assert.Contains("Name of something", rows[1]); Assert.Contains("Name of something", rows[1]);
Assert.Contains("Its value", rows[1]); Assert.Contains("Its value", rows[1]);
Assert.Contains("Its Date", rows[1]);
Assert.Contains("Column4", rows[1]);
Assert.Equal("MiniExcel", rows[0]["Name of something"]); Assert.Equal("MiniExcel", rows[0]["Name of something"]);
Assert.Equal(1D, rows[0]["Its value"]); Assert.Equal(1D, rows[0]["Its value"]);
Assert.Equal(dateTime, (DateTime)rows[0]["Its Date"], TimeSpan.FromMilliseconds(10d));
Assert.Equal(onlyDate.ToDateTime(TimeOnly.MinValue), (DateTime)rows[0]["Column4"]);
Assert.Equal("Github", rows[1]["Name of something"]); Assert.Equal("Github", rows[1]["Name of something"]);
Assert.Equal(2D, rows[1]["Its value"]); Assert.Equal(2D, rows[1]["Its value"]);
Assert.Equal(dateTime, (DateTime)rows[1]["Its Date"], TimeSpan.FromMilliseconds(10d));
Assert.Equal(onlyDate.ToDateTime(TimeOnly.MinValue), (DateTime)rows[1]["Column4"]);
} }
} }
@ -1269,12 +1289,16 @@ namespace MiniExcelLibs.Tests
public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingDataTable() public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingDataTable()
{ {
var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
var dateTime = DateTime.Now;
var onlyDate = DateOnly.FromDateTime(dateTime);
var table = new DataTable(); var table = new DataTable();
{ {
table.Columns.Add("Column1", typeof(string)); table.Columns.Add("Column1", typeof(string));
table.Columns.Add("Column2", typeof(int)); table.Columns.Add("Column2", typeof(int));
table.Rows.Add("MiniExcel", 1); table.Columns.Add("Column3", typeof(DateTime));
table.Rows.Add("Github", 2); table.Columns.Add("Column4", typeof(DateOnly));
table.Rows.Add("MiniExcel", 1, dateTime, onlyDate);
table.Rows.Add("Github", 2, dateTime, onlyDate);
} }
var configuration = new OpenXmlConfiguration var configuration = new OpenXmlConfiguration
@ -1292,6 +1316,13 @@ namespace MiniExcelLibs.Tests
Name = "Its value", Name = "Its value",
Index = 1, Index = 1,
Width = 150 Width = 150
},
new DynamicExcelColumn("Column3")
{
Name = "Its Date",
Index = 2,
Width = 150,
Format = "dd.mm.yyyy hh:mm:ss"
} }
} }
}; };
@ -1307,14 +1338,22 @@ namespace MiniExcelLibs.Tests
Assert.Contains("Name of something", rows[0]); Assert.Contains("Name of something", rows[0]);
Assert.Contains("Its value", rows[0]); Assert.Contains("Its value", rows[0]);
Assert.Contains("Its Date", rows[0]);
Assert.Contains("Column4", rows[0]);
Assert.Contains("Name of something", rows[1]); Assert.Contains("Name of something", rows[1]);
Assert.Contains("Its value", rows[1]); Assert.Contains("Its value", rows[1]);
Assert.Contains("Its Date", rows[1]);
Assert.Contains("Column4", rows[1]);
Assert.Equal("MiniExcel", rows[0]["Name of something"]); Assert.Equal("MiniExcel", rows[0]["Name of something"]);
Assert.Equal(1D, rows[0]["Its value"]); Assert.Equal(1D, rows[0]["Its value"]);
Assert.Equal(dateTime, (DateTime)rows[0]["Its Date"], TimeSpan.FromMilliseconds(10d));
Assert.Equal(onlyDate.ToDateTime(TimeOnly.MinValue), (DateTime)rows[0]["Column4"]);
Assert.Equal("Github", rows[1]["Name of something"]); Assert.Equal("Github", rows[1]["Name of something"]);
Assert.Equal(2D, rows[1]["Its value"]); Assert.Equal(2D, rows[1]["Its value"]);
Assert.Equal(dateTime, (DateTime)rows[1]["Its Date"], TimeSpan.FromMilliseconds(10d));
Assert.Equal(onlyDate.ToDateTime(TimeOnly.MinValue), (DateTime)rows[1]["Column4"]);
} }
} }
} }