【问题标题】:Reading a date from xlsx using open xml sdk使用 open xml sdk 从 xlsx 读取日期
【发布时间】:2012-10-22 00:37:31
【问题描述】:

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

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

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”,它不是共享字符串表的索引。 (我之前尝试过,结果出现异常。)

有什么想法吗? 谢谢

【问题讨论】:

    标签: c# date datetime openxml openxml-sdk


    【解决方案1】:

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

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

    【讨论】:

    • 我宁愿坚持打开xml sdk,但如果我没有找到解决方案,我会尝试这个。谢谢
    • 你在 openxml sdk 中有一个解决方案,但它太复杂了,无法在几个语句中实现。这是一个指针 - 样式文档部分告诉您数字格式。在其中迭代,选择匹配的元素
    • 我不明白。我希望数字格式类似于“数字:#.###”或“日期:m/d/y”。我从牢房里得到的那个号码一定意味着什么。你知道这意味着什么吗?
    • 否 - 您实际上必须获取样式文档部分。然后找到单元格的格式条目。它将有 numberformat="1" 或这样的数字。 14 到 18 是指日期格式。然后,您必须查找日期格式,然后加载数据 stackoverflow.com/questions/8929799/…
    • 我最近试用了这个库,它真的很容易使用,并且对于某些解决方案可能比原来的 open xml sdk 好得多
    【解决方案2】:

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

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

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

    【讨论】:

    • @Santhos,除了FromOADate,你还需要做些调整吗? @VincentTan 仅将其作为“辅助方法”提及。
    • 我有一个问题:如何区分内联数字和应该是日期的数字?在这两种情况下,cell.DataType 对我来说都是null,并且我在文本中有一个数字的字符串表示形式。
    • 我现在有使用 EPPlus 库和 Open SDK 的经验,对于所有未来的查看者,我想说使用 EPPlus 是一个非常好的选择(比使用 Open SDK 更好),除非你想做一些 EPPlus 不支持的事情
    • 要区分数字和日期,见评论stackoverflow.com/questions/13176832/…
    【解决方案3】:

    你可以使用 DateTime.FromOADate(41690)

    【讨论】:

      【解决方案4】:

      加上我的 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. 当对潜在的 OADate 字符串执行 double.TryParse 时,我们应该使用 double.TryParse(excelDateTimeAsString, NumberStyles.Any, CultureInfo.InvariantCulture, out oaDateAsDouble))。我正在使用 CultureInfo.InvariantCulture,但它应该是第 1 点,我不确定。

      【讨论】:

      • 我遇到了完全相同的问题。我用这个解决了它:Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
      【解决方案5】:

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

      数字的样式索引为 2,日期的样式索引为 3

      日期是ODate,可以转换成字符串格式

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

      【讨论】:

      • 我有一个 StyleIndex = 11 的日期字段
      【解决方案6】:

      cell.DataType 似乎没有设置日期。

      这样做的方法是查看单元格是否具有样式索引,它是文档中单元格格式数组的索引。

      然后您使用 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;
          }
      

      【讨论】:

      • 想一想,仅仅识别您的文化的日期格式数字可能就足够了(上面的示例有美国和英国的日期数字,我可以从 Excel 365 中找到这些数字)。有可能从文档中获取我猜不是这个问题的格式。
      • IMO 这个答案更正确,因为如果您正在读取一个随机的 XLSX 文件,您必须通过这个过程来确定单元格是否包含日期,而不仅仅是一个数字。
      【解决方案7】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-02-12
        • 2017-08-08
        • 1970-01-01
        相关资源
        最近更新 更多