Чтение даты из xlsx с помощью открытого xml sdk
У меня есть дата в формате "4/5/2011" (месяц/день/год) в файле xlsx в одной из ячеек. Im пытается разобрать файл и загрузить эти данные в некоторых классах.
До сих пор часть, где я разбираю ячейку, выглядит так:
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", и это не индекс общей таблицы строк. (Я пробовал это раньше, и это закончилось исключением.)
Любые идеи?
Благодаря
Ответы
Ответ 1
Open XML хранит даты как количество дней с 1 января 1900 года. Ну, пропуская неверный 29 февраля 1900 года как действительный день. Вы должны уметь находить алгоритмы, которые помогут вам рассчитать правильное значение. Я считаю, что некоторые разработчики используют DateTime.FromOADate()
в качестве помощника.
Кроме того, класс Cell
имеет свойство DataType
как Number по умолчанию. Поэтому, если он null, это число, которое включает даты в нашем случае.
Вы переходите только в таблицу разделяемых строк, когда хранящаяся дата находится до эпохи (в этом случае 1 января 1900 года). И тогда в этом случае класс CellValue класса Cell удерживает индекс в общей таблице строк.
Ответ 2
вы можете использовать DateTime.FromOADate(41690)
Ответ 3
У меня была такая же проблема - переход на EPPlus http://epplus.codeplex.com/
Обратите внимание, что у него есть лицензия LGPL. Поэтому, если вам нужна ваша база кода, чтобы быть в безопасности от проблемы GPL, просто используйте библиотеку, как есть, и ваша исходная лицензия на базовую версию кода безопасна.
Ответ 4
Добавление моего 2 пенсов.
Я обрабатываю шаблон, поэтому я знаю, что данная ячейка предназначена для DateTime.
Поэтому я заканчиваю этот метод строковым параметром 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).
Это заставляет меня думать, что:
- независимо от языкового стандарта на пользовательском компьютере, документ OpenXML содержит номера, отформатированные в стандартном языковом стандарте (в любом случае, инвариант от US, с точкой в виде десятичного разделителя). Я искал, но не нашел спецификацию для этого.
- при выполнении
double.TryParse
в потенциальной строке OADate мы должны сделать это с помощью double.TryParse(excelDateTimeAsString, NumberStyles.Any, CultureInfo.InvariantCulture, out oaDateAsDouble))
. Я использую CultureInfo.InvariantCulture, но это должно быть то, что точка 1, что я не знаю точно.
Ответ 5
Похоже, что 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;
}
Ответ 6
Каждая ячейка имеет 2 свойства r (CellReference) и s (StyleIndex)
StyleIndex для чисел - 2, а для даты - 3
Дата в ODate и вы можете преобразовать в строковый формат
value = DateTime.FromOADate(double.Parse(значение)). ToShortDateString();