web-dev-qa-db-ja.com

open xml sdkを使用してxlsxから日付を読み取る

セルの1つにある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」であり、共有文字列テーブルへのインデックスではありません。 (私は以前にそれを試しました、そしてそれは例外で終わりました。)

何か案は?ありがとう

29
Santhos

Open XMLは、日付を1900年1月1日からの日数として格納します。まあ、1900年2月29日という誤った日を有効な日としてスキップします。正しい値を計算するのに役立つアルゴリズムを見つけることができるはずです。一部の開発者はDateTime.FromOADate()をヘルパーとして使用していると思います。

また、Cellクラスには、デフォルトでNumberとしてDataTypeプロパティがあります。つまり、nullの場合は数値であり、この場合は日付も含まれます。

保存された日付がエポック(この場合は1900年1月1日)より前の場合にのみ、共有文字列テーブルに移動します。そしてその場合、CellクラスのCellValueは共有文字列テーブルへのインデックスを保持します。

32
Vincent Tan

あなたはDateTime.FromOADate(41690)を使うことができます

10
mzoabi

同じ問題が発生しました-EPPlusに切り替えました http://epplus.codeplex.com/

LGPLライセンスがあることに注意してください。したがって、GPLの問題からコードベースを保護する必要がある場合は、ライブラリをそのまま使用するだけで、元のコードベースライセンスは安全です。

3
CKmum

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

私の2ペンス相当を追加します。テンプレートを処理しているので、特定のセルがDateTimeであることを知っています。したがって、このメソッドでは、セル値を含む文字列パラメーターexcelDateTimeが最終的に得られます。これは通常、「42540.041666666664」のようなOADate番号になります。

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

私の問題は、エンドユーザーがドイツにいることです。これはWebサイトなので、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ドキュメントには、デフォルトのロケールでフォーマットされた数値が含まれます(いずれにせよUS?は不変?、小数点は小数点として使用されます)。私は検索しましたが、この仕様は見つかりませんでした。
  2. 潜在的なOADate文字列に対して_double.TryParse_を実行する場合は、double.TryParse(excelDateTimeAsString, NumberStyles.Any, CultureInfo.InvariantCulture, out oaDateAsDouble))を使用して実行する必要があります。私はCultureInfo.InvariantCultureを使用していますが、1が何であるかはわかりませんが、確かではありません。
0
Thierry_S