使用open xml sdk从xlsx读取日期

时间:2012-11-01 12:13:05

标签: c# date datetime openxml openxml-sdk

我的格式为“4/5/2011”(月/日/年)的日期位于其中一个单元格的xlsx文件中。我试图解析文件并在某些类中加载这些数据。

到目前为止,我解析单元格的部分如下所示:

string cellValue = cell.InnerText;
if (cell.DataType != null)
{
    switch (cell.DataType.Value)
    {
        case CellValues.SharedString:
            // get string from shared string table
            cellValue = this.GetStringFromSharedStringTable(int.Parse(cellValue));
            break;
    }
}

我希望date可以是cell.DataType。事实是,在使用日期“4/5/2011”解析单元格时,cell.DataType的值为null,单元格的值为“40638”,并且它不是共享字符串表的索引。 (我之前尝试过这种方法,结果是例外。)

有什么想法吗? 感谢

7 个答案:

答案 0 :(得分:32)

Open XML将日期存储为1900年1月1日的天数。好吧,将1900年2月29日的错误日期作为有效日期。您应该能够找到算法来帮助您计算正确的值。我相信一些开发人员使用DateTime.FromOADate()作为帮助。

此外,默认情况下,Cell类的DataType属性为Number。因此,如果它为null,则为数字,其中包括我们的日期。

当存储的日期早于纪元(1900年1月1日)时,您只前往共享字符串表。然后在这种情况下,Cell类的CellValue保存共享字符串表的索引。

答案 1 :(得分:10)

您可以使用DateTime.FromOADate(41690)

答案 2 :(得分:3)

我有同样的问题 - 切换到EPPlus http://epplus.codeplex.com/

请注意,它具有LGPL许可证。因此,如果您需要保护代码库免受GPL问题的影响,只需按原样使用该库,并且您的原始代码库许可证是安全的。

答案 3 :(得分:1)

似乎未为日期设置cell.DataType。

方法是查看单元格是否具有StyleIndex,该索引是文档中单元格格式数组的索引。

然后使用cellFormat.NumberFormatId查看这是否是日期数据类型。

以下是一些代码:

    public class ExcelCellWithType
    {
        public string Value { get; set; }
        public UInt32Value ExcelCellFormat { get; set; }
        public bool IsDateTimeType { get; set; }
    }  

    public class ExcelDocumentData
    {
        public ExcelXmlStatus Status { get; set; }
        public IList<Sheet> Sheets { get; set; }
        public IList<ExcelSheetData> SheetData { get; set; }

        public ExcelDocumentData()
        {
            Status = new ExcelXmlStatus();
            Sheets = new List<Sheet>();
            SheetData = new List<ExcelSheetData>();
        }
    } 

    ...

    public ExcelDocumentData ReadSpreadSheetDocument(SpreadsheetDocument mySpreadsheet, ExcelDocumentData data)
    {
        var workbookPart = mySpreadsheet.WorkbookPart;

        data.Sheets = workbookPart.Workbook.Descendants<Sheet>().ToList();

        foreach (var sheet in data.Sheets)
        {
            var sheetData = new ExcelSheetData { SheetName = sheet.Name };
            var workSheet = ((WorksheetPart)workbookPart.GetPartById(sheet.Id)).Worksheet;

            sheetData.ColumnConfigurations = workSheet.Descendants<Columns>().FirstOrDefault();
            var rows = workSheet.Elements<SheetData>().First().Elements<Row>().ToList();
            if (rows.Count > 1)
            {
                foreach (var row in rows)
                {
                    var dataRow = new List<ExcelCellWithType>();

                    var cellEnumerator = GetExcelCellEnumerator(row);
                    while (cellEnumerator.MoveNext())
                    {
                        var cell = cellEnumerator.Current;
                        var cellWithType = ReadExcelCell(cell, workbookPart);
                        dataRow.Add(cellWithType);
                    }

                    sheetData.DataRows.Add(dataRow);
                }
            }
            data.SheetData.Add(sheetData);
        }

        return data;
    }

    ...

    private ExcelCellWithType ReadExcelCell(Cell cell, WorkbookPart workbookPart)
    {
        var cellValue = cell.CellValue;
        var text = (cellValue == null) ? cell.InnerText : cellValue.Text;
        if (cell.DataType?.Value == CellValues.SharedString)
        {
            text = workbookPart.SharedStringTablePart.SharedStringTable
                .Elements<SharedStringItem>().ElementAt(
                    Convert.ToInt32(cell.CellValue.Text)).InnerText;
        }

        var cellText = (text ?? string.Empty).Trim();

        var cellWithType = new ExcelCellWithType();

        if (cell.StyleIndex != null)
        {
            var cellFormat = workbookPart.WorkbookStylesPart.Stylesheet.CellFormats.ChildElements[
                int.Parse(cell.StyleIndex.InnerText)] as CellFormat;

            if (cellFormat != null)
            {
                cellWithType.ExcelCellFormat = cellFormat.NumberFormatId;

                var dateFormat = GetDateTimeFormat(cellFormat.NumberFormatId);
                if (!string.IsNullOrEmpty(dateFormat))
                {
                    cellWithType.IsDateTimeType = true;

                    if (!string.IsNullOrEmpty(cellText))
                    {
                       if (double.TryParse(cellText, out var cellDouble))
                        {
                            var theDate = DateTime.FromOADate(cellDouble);
                            cellText = theDate.ToString(dateFormat);
                        }
                    }
                }
            }
        }

        cellWithType.Value = cellText;

        return cellWithType;
    }

    //// https://msdn.microsoft.com/en-GB/library/documentformat.openxml.spreadsheet.numberingformat(v=office.14).aspx
    private readonly Dictionary<uint, string> DateFormatDictionary = new Dictionary<uint, string>()
    {
        [14] = "dd/MM/yyyy",
        [15] = "d-MMM-yy",
        [16] = "d-MMM",
        [17] = "MMM-yy",
        [18] = "h:mm AM/PM",
        [19] = "h:mm:ss AM/PM",
        [20] = "h:mm",
        [21] = "h:mm:ss",
        [22] = "M/d/yy h:mm",
        [30] = "M/d/yy",
        [34] = "yyyy-MM-dd",
        [45] = "mm:ss",
        [46] = "[h]:mm:ss",
        [47] = "mmss.0",
        [51] = "MM-dd",
        [52] = "yyyy-MM-dd",
        [53] = "yyyy-MM-dd",
        [55] = "yyyy-MM-dd",
        [56] = "yyyy-MM-dd",
        [58] = "MM-dd",
        [165] = "M/d/yy",
        [166] = "dd MMMM yyyy",
        [167] = "dd/MM/yyyy",
        [168] = "dd/MM/yy",
        [169] = "d.M.yy",
        [170] = "yyyy-MM-dd",
        [171] = "dd MMMM yyyy",
        [172] = "d MMMM yyyy",
        [173] = "M/d",
        [174] = "M/d/yy",
        [175] = "MM/dd/yy",
        [176] = "d-MMM",
        [177] = "d-MMM-yy",
        [178] = "dd-MMM-yy",
        [179] = "MMM-yy",
        [180] = "MMMM-yy",
        [181] = "MMMM d, yyyy",
        [182] = "M/d/yy hh:mm t",
        [183] = "M/d/y HH:mm",
        [184] = "MMM",
        [185] = "MMM-dd",
        [186] = "M/d/yyyy",
        [187] = "d-MMM-yyyy"
    };

    private string GetDateTimeFormat(UInt32Value numberFormatId)
    {
        return DateFormatDictionary.ContainsKey(numberFormatId) ? DateFormatDictionary[numberFormatId] : string.Empty;
    }

答案 4 :(得分:0)

加上我2便士的价值。 我正在处理一个模板,所以我知道给定的单元格是一个DateTime。 所以我最终在这个方法中使用包含单元格值的字符串参数excelDateTime,这通常是一个OADate数字,如&#34; 42540.041666666664&#34;。

public static bool TryParseExcelDateTime(string excelDateTimeAsString, out DateTime dateTime)
{
    double oaDateAsDouble;
    if (!double.TryParse(excelDateTimeAsString, out oaDateAsDouble)) //this line is Culture dependent!
        return false;
    //[...]
    dateTime = DateTime.FromOADate(oaDateAsDouble);

我的问题是最终用户在德国,因为这是一个网站,我们将Thread.CurrentThread.CurrentCulture和Thread.CurrentThread.CurrentUICulture设置为&#34; DE-de&#34; 。当您致电double.TryParse时,它会使用文化来解析数字。所以这一行:double.TryParse("42540.041666666664", out oaDate)确实有效,但它返回42540041666666664,因为在德国,dot是一个组分隔符。 DateTime.FromOADate然后失败,因为该数字超出了范围(minOaDate = -657435.0, maxOaDate = +2958465.99999999)。

这让我想到:

  1. 无论用户机器上的区域设置如何,OpenXML文档都包含以默认语言环境格式化的数字(在任何情况下,US?不变量?以点作为小数分隔符)。我已经搜索过,但没有找到相关的规格。
  2. 在对潜在的OADate字符串执行double.TryParse时,我们应该使用double.TryParse(excelDateTimeAsString, NumberStyles.Any, CultureInfo.InvariantCulture, out oaDateAsDouble))。我正在使用CultureInfo.InvariantCulture,但它应该是1点,我不确定。

答案 5 :(得分:0)

我在检索任何内联字符串之后执行此操作:

    private static object Convert(this DocumentFormat.OpenXml.Spreadsheet.CellValues value, string content)
    {
        switch (value)
        {
            case DocumentFormat.OpenXml.Spreadsheet.CellValues.Boolean:
                if (content.Length < 2)
                {
                    return content?.ToUpperInvariant() == "T" || content == "1";
                }
                return System.Convert.ToBoolean(content);
            case DocumentFormat.OpenXml.Spreadsheet.CellValues.Date:
                if (double.TryParse(content, out double result))
                {
                    return System.DateTime.FromOADate(result);
                }
                return null;
            case DocumentFormat.OpenXml.Spreadsheet.CellValues.Number:
                return System.Convert.ToDecimal(content);
            case DocumentFormat.OpenXml.Spreadsheet.CellValues.Error:
            case DocumentFormat.OpenXml.Spreadsheet.CellValues.String:
            case DocumentFormat.OpenXml.Spreadsheet.CellValues.InlineString:
            case DocumentFormat.OpenXml.Spreadsheet.CellValues.SharedString:
            default:
                return content;
        }
    }

答案 6 :(得分:-1)

每个单元格有2个属性r(CellReference)和s(StyleIndex)

数字的StyleIndex为2,日期为3

日期在ODate中,您可以转换为字符串格式

value = DateTime.FromOADate(double.Parse(value))。ToShortDateString();