it-swarm.com.de

Lesen eines Datums aus xlsx mit open xml sdk

Ich habe ein Datum im Format "4/5/2011" (Monat/Tag/Jahr) in einer Xlsx-Datei in einer der Zellen. Ich versuche, die Datei zu analysieren und diese Daten in einigen Klassen zu laden.

Bisher sieht der Teil, in dem ich die Zelle parse, folgendermaßen aus:

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

Ich hoffte, dass dieses Datum ein cell.DataType sein würde. Die Wahrheit ist, wenn die Zelle mit dem Datum "4/5/2011" analysiert wird, der Wert von cell.DataType null ist und der Wert der Zelle "40638" ist und kein Index für die gemeinsam genutzte Stringtabelle ist. (Ich habe das schon einmal ausprobiert und es kam zu einer Ausnahme.)

Irgendwelche Ideen? Danke

25
Santhos

Open XML speichert Datumsangaben als Anzahl der Tage ab dem 1. Januar 1900. Nun, den falschen 29. Februar 1900 als gültigen Tag überspringen. Sie sollten in der Lage sein, Algorithmen herauszufinden, mit denen Sie den korrekten Wert berechnen können. Ich glaube, einige Entwickler verwenden DateTime.FromOADate() als Helfer.

Außerdem hat die Cell-Klasse standardmäßig die DataType-Eigenschaft als Number. Wenn es also null ist, ist es eine Zahl, die in unserem Fall Daten enthält.

Sie gelangen nur zur Tabelle der gemeinsam genutzten Strings, wenn das gespeicherte Datum vor der Epoche liegt (in diesem Fall der 1. Januar 1900). In diesem Fall enthält der CellValue der Cell-Klasse den Index für die gemeinsam genutzte Stringtabelle.

30
Vincent Tan

sie können DateTime.FromOADate (41690) verwenden.

10
mzoabi

Ich hatte dasselbe Problem - wechselte zu EPPlus http://epplus.codeplex.com/

Beachten Sie, dass es eine LGPL-Lizenz hat. Wenn Sie also benötigen, dass Ihre Codebasis vor GPL-Problemen geschützt ist, verwenden Sie einfach die Bibliothek wie sie ist und Ihre ursprüngliche Codebasislizenz ist sicher.

3
CKmum

Es scheint, dass cell.DataType nicht für Datumsangaben festgelegt ist.

Die Methode besteht darin, zu überprüfen, ob die Zelle über einen StyleIndex verfügt, bei dem es sich um einen Index in einem Array von Zellenformaten im Dokument handelt.

Anschließend verwenden Sie die cellFormat.NumberFormatId, um festzustellen, ob es sich um einen Datumsdatentyp handelt.

Hier ist ein Code:

    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;
    }
2
Philip Johnson

Ich addiere meinen Wert von 2 Pence. Ich verarbeite eine Vorlage, daher weiß ich, dass eine bestimmte Zelle eine DateTime sein soll. Ich ende also bei dieser Methode mit einem String-Parameter excelDateTime, der die Zelle enthält Wert, der normalerweise eine OADate-Nummer wie "42540.041666666664" ist.

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

Mein Problem ist, dass der Endbenutzer in Deutschland ist. Da dies eine Website ist, haben wir Thread.CurrentThread.CurrentCulture und Thread.CurrentThread.CurrentUICulture auf "DE-de" gesetzt. Wenn Sie double.TryParse aufrufen, verwendet es die Kultur, um die Nummer zu analysieren. Diese Zeile: double.TryParse("42540.041666666664", out oaDate) funktioniert zwar, gibt jedoch 42540041666666664 zurück, da der Punkt in Deutschland ein Gruppentrennzeichen ist. DateTime.FromOADate schlägt dann fehl, weil die Nummer außerhalb des gültigen Bereichs liegt ( minOaDate = -657435.0, maxOaDate = +2958465.99999999 ).

Das lässt mich denken:

  1. unabhängig vom Gebietsschema auf dem Computer eines Benutzers enthält das OpenXML-Dokument Zahlen, die in einem Standardgebietsschema formatiert sind (in jedem Fall "US invariant", wobei der Punkt als Dezimaltrennzeichen verwendet wird). Ich habe gesucht, aber die Spezifikation dafür nicht gefunden.
  2. wenn wir double.TryParse für eine potenzielle OADate-Zeichenfolge verwenden, sollten wir dies mit double.TryParse(excelDateTimeAsString, NumberStyles.Any, CultureInfo.InvariantCulture, out oaDateAsDouble)) tun. Ich verwende CultureInfo.InvariantCulture, aber es sollte der Punkt 1 sein, den ich nicht sicher kenne.
0
Thierry_S