34

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

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

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;
    }
}

我希望那个日期是一个 cell.DataType。事实是,在解析日期为“4/5/2011”的单元格时,cell.DataType 的值为 null,单元格的值为“40638”,它不是共享字符串表的索引。(我之前尝试过,但结果是异常。)

有任何想法吗?谢谢

4

8 回答 8

43

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

此外,Cell该类DataType默认具有 Number 属性。因此,如果它为空,则它是一个数字,在我们的例子中包括日期。

仅当存储的日期在纪元之前(在这种情况下为 1900 年 1 月 1 日)时,您才转到共享字符串表。然后在这种情况下,Cell 类的 CellValue 保存共享字符串表的索引。

于 2012-11-01T13:23:49.493 回答
13

似乎未为日期设置 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;
    }
于 2019-03-29T21:33:07.133 回答
12

你可以使用 DateTime.FromOADate(41690)

于 2014-03-01T01:54:22.473 回答
2

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

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

于 2012-11-01T12:18:55.450 回答
1

加上我的 2 便士价值。我正在处理一个模板,所以我知道给定的单元格是一个日期时间。所以我最终在这个方法中使用了一个包含单元格值的字符串参数 excelDateTime,它通常是一个 OADate 数字,如“42540.041666666664”。

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 设置为“DE-de”。当你打电话时double.TryParse,它会使用文化来解析数字。所以这条线:double.TryParse("42540.041666666664", out oaDate)确实有效,但它返回42540041666666664,因为在德国,点是组分隔符。DateTime.FromOADate然后失败,因为数字超出范围(minOaDate = -657435.0, maxOaDate = +2958465.99999999)。

这让我觉得:

  1. 无论用户机器上的语言环境如何,OpenXML 文档都包含在默认语言环境中格式化的数字(美国?不变?在任何情况下,点作为小数分隔符)。我已经搜索过,但没有找到这个规范。
  2. double.TryParse处理潜在的 OADate 字符串时,我们应该使用double.TryParse(excelDateTimeAsString, NumberStyles.Any, CultureInfo.InvariantCulture, out oaDateAsDouble)). 我正在使用 CultureInfo.InvariantCulture,但它应该是第 1 点,我不确定。
于 2016-11-23T14:31:31.323 回答
1

我们需要采用不同的策略来使用 OpenXML 解析不同类型的列。

要解析字符串和布尔值 - 我们可以使用单元格的 DataType 属性,如下所示 -

        switch (cell.DataType.Value)
        {
            case CellValues.SharedString:
                // Fetch value from SharedStrings array
                break;
            case CellValues.Boolean:
                text = cell.InnerText;
                switch (text)
                {
                    case "0": text = "false"; break;
                    default: text = "true"; break;
                }
                break;
        }

解析日期/时间/日期时间值(应用任何内置或任何自定义格式) -DataType属性返回为 null,因此可以如下所示 -

    if (cell.DataType == null)
        DateTime.FromOADate(double.Parse(cell.InnerText))

返回的上述值将采用默认格式,具体取决于您机器上的区域设置。但是,如果您需要获取 excel 中实际格式的值并且您不确定格式,那么您可以访问StyleIndex与此类单元格关联的属性。

StyleIndex属性将为您提供应用于单元格的样式的索引,可以在styles.xml文件(标签下方)中找到 -

    <cellXfs count="3">
        <xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>          
        <xf numFmtId="168" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1"/>
        <xf numFmtId="169" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1"/>
    </cellXfs>

在上述情况下,该StyleIndex值可以是 0、1 或 2 - 因为应用了 3 种样式。样式numFmtId in (0, 163)对应于 Excel 提供的内置格式 &numFmtId >= 164对应于自定义格式。

StyleIndex上面获得的值中,您将获得- 映射到部分(文件中)下存在numFmtId的特定<numFmt>标签,以获取应用于单元格的实际日期格式。<numFmts>styles.xml

    <numFmts count="2">
       <numFmt numFmtId="168" formatCode="[$£-809]#,##0.00"/>
       <numFmt numFmtId="169" formatCode="dd\-mmm\-yyyy\ hh:mm:ss"/>
    </numFmts>

也可以使用 OpenXML API 获取应用于单元格的日期格式 -

      CellFormat cellFmt = cellFormats.ChildElements[int.Parse(cell.StyleIndex.InnerText)] as CellFormat;
      string format = numberingFormats.Elements<NumberingFormat>()
                .Where(i => i.NumberFormatId.Value == cellFmt .NumberFormatId.Value)
                .First().FormatCode;
于 2022-01-13T06:46:48.360 回答
-1

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

数字的 StyleIndex 为 2,日期为 3

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

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

于 2016-12-28T17:18:40.387 回答
-1

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

    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;
        }
    }
于 2020-07-23T17:38:45.133 回答