Как интерпретировать строку формата чисел Excel, чтобы определить, следует ли анализировать значение с помощью DateTime.FromOADate - PullRequest
2 голосов
/ 02 августа 2011

Как я могу создать функцию "bool IsDateTime", которая будет надежно определять, будет ли строка формата чисел Excel, такая как "[$ -409] ч: мм: сс AM / PM; @", указывать, что числовое значение является DateTime, который должно быть передано в DateTime.FromOADate?

Я выяснил, что такое [$ -409]: Числовой формат Excel: Что такое "[$ -409]"? . Это просто код локали.

Я также прочитал немного о строке формата числа, разделенной точками с запятой на четыре секции формата: http://office.microsoft.com/en-us/excel-help/create-or-delete-a-custom-number-format-HP005199500.aspx?CTT=5&origin=HP005198679 и здесь http://www.ozgrid.com/Excel/excel-custom-number-formats.htm

Например, будет ли надежным просто искать вхождения символов формата даты / времени, таких как h, m, s, y, d? Как Excel может интерпретировать это?

В случае, если вопрос не ясен ... когда вы читаете файл Excel и смотрите значение даты / времени, вы на самом деле смотрите обычное старое значение двойной точности, потому что это как это хранится в Excel. Чтобы выяснить, является ли это обычное двойное или двойное число, которое должно быть передано в DateTime.FromOADate, вы должны интерпретировать строку пользовательского формата чисел. Поэтому я спрашиваю, как интерпретировать такую ​​строку, которая может относиться или не относиться к значению даты / времени, чтобы определить, следует ли преобразовать значение двойной точности в значение DateTime через DateTime.FromOADate. Кроме того, в случае успешного преобразования в значение DateTime мне потребуется преобразовать строку числового формата Excel в эквивалентную строку формата .NET DateTime, чтобы можно было отобразить значение даты / времени, как в Excel, через DateTime.ToString (convert_format_string).

Ответы [ 2 ]

1 голос
/ 04 августа 2011

Я реализовал класс для разбора строки числового формата Excel.Он просматривает первый раздел (из четырех возможных разделов в строке формата) и использует регулярное выражение для захвата определенных пользовательских символов формата даты / времени, таких как «y», «m», «d», «h», «s»."," AM / PM "и возвращает ноль, если ничего не найдено.Этот первый шаг просто решает, предназначена ли строка формата для значения даты / времени, и оставляет нас с объектно-ориентированным упорядоченным списком логических спецификаторов формата даты / времени для дальнейшей обработки.

При условии, что было решено, чтострока формата предназначена для значения даты / времени, захваченные и классифицированные значения сортируются в порядке, в котором они были найдены в исходной строке формата.

Далее, он применяет специфические для Excel особенности форматирования, например, решая,«m» означает месяц или минуту, интерпретируя его как «минуту», только если он появляется сразу после «h» или перед «s» (между ними допускается буквальный текст, поэтому он не совсем «сразу» до / после).В Excel также используется 24-часовое время для символа «h», если «AM / PM» также не указано, поэтому, если «AM / PM» не найдено, используется строчная буква m (24-часовое время в .NET),в противном случае он преобразует его в заглавную М (12-часовое время в .NET).Он также преобразует «AM / PM» в .NET-эквивалент «tt» и исключает условные выражения, которые нельзя включить в обычную строку формата .NET DateTime.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;

namespace utilities.data
{
    public enum NumberFormatCaptureType
    {
        Condition,
        LiteralText,
        Year,
        Month,
        Day,
        Hour,
        Minute,
        Second,
        AMPM
    }

    public class NumberFormatTypedCapture
    {
        private class ClassificationPair
        {
            public string Name;
            public NumberFormatCaptureType Type;
            public bool IndicatesDateTimeValue;
        }

        private static readonly Regex regex = new Regex( @"(?<c>\[[^]]*])*((?<y>yyyy|yy)|(?<m>mmmm|mmm|mm|m)|(?<d>dddd|ddd|dd|d)|(?<h>hh|h)|(?<s>ss|s)|(?<t>AM/PM)|(?<t>am/pm)|(?<l>.))*", RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.Compiled );
        private static readonly ClassificationPair[] classifications = new ClassificationPair[] {
            new ClassificationPair() {Name="c", Type=NumberFormatCaptureType.Condition, IndicatesDateTimeValue=false},
            new ClassificationPair() {Name="y", Type=NumberFormatCaptureType.Year, IndicatesDateTimeValue=true},
            new ClassificationPair() {Name="m", Type=NumberFormatCaptureType.Month, IndicatesDateTimeValue=true},
            new ClassificationPair() {Name="d", Type=NumberFormatCaptureType.Day, IndicatesDateTimeValue=true},
            new ClassificationPair() {Name="h", Type=NumberFormatCaptureType.Hour, IndicatesDateTimeValue=true},
            new ClassificationPair() {Name="s", Type=NumberFormatCaptureType.Second, IndicatesDateTimeValue=true},
            new ClassificationPair() {Name="t", Type=NumberFormatCaptureType.AMPM, IndicatesDateTimeValue=true},
            new ClassificationPair() {Name="l", Type=NumberFormatCaptureType.LiteralText, IndicatesDateTimeValue=false}
        };
        private Capture Capture;
        private string mutable_value;
        public NumberFormatCaptureType Type;

        public NumberFormatTypedCapture( Capture c, NumberFormatCaptureType t )
        {
            this.Capture = c;
            this.Type = t;
            mutable_value = c.Value;
        }

        public int Index
        {
            get {return Capture.Index;}
        }

        public string Value
        {
            get {return mutable_value;}
            set {mutable_value = value;}
        }

        public int Length
        {
            get {return mutable_value.Length;}
        }

        public static string ConvertToDotNetDateTimeFormat( string number_format )
        {
            string[] number_formats = number_format.Split( ';' );
            Match m = regex.Match( number_formats[0] );
            bool date_time_formatting_encountered = false;
            bool am_pm_encountered = false;

            //Classify the catured values into typed NumberFormatTypedCapture instances
            List<NumberFormatTypedCapture> segments = new List<NumberFormatTypedCapture>();
            foreach (ClassificationPair classification in classifications)
            {
                CaptureCollection captures = m.Groups[classification.Name].Captures;
                if (classification.IndicatesDateTimeValue && captures.Count > 0)
                {
                    date_time_formatting_encountered = true;
                    if (classification.Type == NumberFormatCaptureType.AMPM)
                        am_pm_encountered = true;
                }
                segments.AddRange( captures.Cast<Capture>().Select<Capture,NumberFormatTypedCapture>( (capture) => new NumberFormatTypedCapture( capture, classification.Type ) ) );
            }

            //Not considered a date time format unless it has at least one instance of a date/time format character
            if (!date_time_formatting_encountered)
                return null;

            //Sort the captured values in the order they were found in the original string.
            Comparison<NumberFormatTypedCapture> comparison = (x,y) => (x.Index < y.Index) ? -1 : ((x.Index > y.Index) ? 1 : 0);
            segments.Sort( comparison );

            //Begin conversion of the captured Excel format characters to .NET DateTime format characters
            StringComparer sc = StringComparer.CurrentCultureIgnoreCase;
            for (int i = 0; i < segments.Count; i++)
            {
                NumberFormatTypedCapture c = segments[i];
                switch (c.Type)
                {
                    case NumberFormatCaptureType.Hour: //In the absense of an the AM/PM, Excel forces hours to display in 24-hour time
                        if (am_pm_encountered)
                            c.Value = c.Value.ToLower(); //.NET lowercase "h" formats hourse in 24-hour time
                        else
                            c.Value = c.Value.ToUpper(); //.NET uppercase "H" formats hours in 12-hour time
                        break;
                    case NumberFormatCaptureType.Month: //The "m" (month) designator is interpretted as minutes by Excel when found after an Hours indicator or before a Seconds indicator.
                        NumberFormatTypedCapture prev_format_character = GetAdjacentDateTimeVariable( segments, i, -1 );
                        NumberFormatTypedCapture next_format_character = GetAdjacentDateTimeVariable( segments, i, 1 );
                        if ((prev_format_character != null && prev_format_character.Type == NumberFormatCaptureType.Hour) || (next_format_character != null && next_format_character.Type == NumberFormatCaptureType.Second))
                            c.Type = NumberFormatCaptureType.Minute; //Format string is already lowercase (Excel seems to force it to lowercase), so just leave it lowercase and set the type to Minute
                        else
                            c.Value = c.Value.ToUpper(); //Month indicator is uppercase in .NET framework
                        break;
                    case NumberFormatCaptureType.AMPM: //AM/PM indicator is "tt" in .NET framework
                        c.Value = "tt";
                        break;
                    case NumberFormatCaptureType.Condition: //Conditional formatting is not supported in .NET framework
                        c.Value = String.Empty;
                        break;
                    //case NumberFormatCaptureType.Text: //Merge adjacent text elements
                        //break;
                }
            }

            //Now that the individual captures have been blanked out or converted to the .NET DateTime format string, concatenate it all together than return the final format string.
            StringBuilder sb = new StringBuilder();
            foreach (NumberFormatTypedCapture c in segments)
                sb.Append( c.Value );
            return sb.ToString();
        }

        private static NumberFormatTypedCapture GetAdjacentDateTimeVariable( List<NumberFormatTypedCapture> captures, int current, int direction )
        {
        check_next:
            current += direction;
            if (current >= 0 && current < captures.Count)
            {
                NumberFormatTypedCapture capture = captures[current];
                if (capture.Type == NumberFormatCaptureType.Condition || capture.Type == NumberFormatCaptureType.LiteralText)
                    goto check_next;
                return capture;
            }
            return null;
        }
    }
}

Приведенный выше класс может бытьиспользуется в следующем контексте для чтения строковых значений в DataTable из столбцов в файле Excel с ненулевыми заголовками.В частности, он пытается получить действительный экземпляр DateTime, и, если он найден, он пытается создать действительную строку формата .NET DateTime из строки числового формата Excel.Если оба предыдущих шага выполнены успешно, он сохраняет отформатированную строку даты и времени в таблице данных, а в противном случае он преобразует любое значение, присутствующее в строке (при условии, что сначала удаляется расширенное форматирование текста, если оно присутствует):

using (ExcelPackage package = new ExcelPackage( fileUpload.FileContent ))
{
    Dictionary<string,string> converted_dt_format_strings = new Dictionary<string,string>();
    ExcelWorksheet sheet = package.Workbook.Worksheets.First();
    int end_column = sheet.Dimension.End.Column;
    int end_row = sheet.Dimension.End.Row;

    DataTable datatable = new DataTable();

    //Construct columns
    int i_row = 1;
    List<int> valid_columns = new List<int>();
    for (int i_col = 1; i_col <= end_column; i_col++)
    {
        ExcelRange range = sheet.Cells[i_row, i_col];
        string field_name_text = range.IsRichText ? range.RichText.Text : (range.Value ?? String.Empty).ToString();
        if (field_name_text != null)
        {
            valid_columns.Add( i_col );
            datatable.Columns.Add( field_name_text, typeof(string) );
        }
    }

    int valid_column_count = valid_columns.Count;
    for (i_row = 2; i_row <= end_row; i_row++)
    {
        DataRow row = datatable.NewRow();
        for (int i_col = 0; i_col < valid_column_count; i_col++)
        {
            ExcelRange range = sheet.Cells[i_row, valid_columns[i_col]];

            //Attempt to acquire a DateTime value from the cell
            DateTime? d = null;
            try
            {
                if (range.Value is DateTime)
                    d = (DateTime)range.Value;
                else if (range.Value is double)
                    d = DateTime.FromOADate( (double)range.Value );
                else
                    d = null;
            }
            catch
            {
                d = null;
            }

            string field_value_text = range.IsRichText ? (range.RichText.Text ?? String.Empty) : (range.Value ?? String.Empty).ToString(); //Acquire plain text string version of the object, which will be used if a formatted DateTime string cannot be produced
            string field_value_dt_text = null;

            if (d.HasValue)
            {
                try
                {
                    string excel_number_format = range.Style.Numberformat.Format;
                    string date_time_format = null;
                    if (excel_number_format != null)
                    {
                        if (!converted_dt_format_strings.TryGetValue( excel_number_format, out date_time_format ))
                        {
                            date_time_format = NumberFormatTypedCapture.ConvertToDotNetDateTimeFormat( excel_number_format );
                            converted_dt_format_strings.Add( excel_number_format, date_time_format );
                        }
                        if (date_time_format != null) //Appears to have Date/Time formatting applied to it
                            field_value_dt_text = d.Value.ToString( date_time_format );
                    }   
                }
                catch
                {
                    field_value_dt_text = null;
                }
            }

            row[i_col] = (field_value_dt_text == null) ? field_value_text : field_value_dt_text;
        }
        datatable.Rows.Add( row );
    }
    return datatable;
}
1 голос
/ 03 августа 2011

Вы можете проверить, содержит ли ячейка какой-либо из встроенных форматов даты, используя функцию CELL и возвращая формат. Он вернет «D», за которым следует число, если он использует встроенный формат.

Например:

=IF(LEFT(CELL("format", A1),1)="D",TRUE,FALSE)

В более общем случае я бы сначала проверил, является ли ячейка числом (ISNUMBER()) и находится в диапазоне для даты (то есть между 0 и TODAY() - что сегодня составляет 39296). Затем я проверил бы числовой формат на наличие по крайней мере одного d, m, y, h, M или s, поскольку это должно означать, что у вас есть дата в ячейке.

Надеюсь, это поможет,

Dave

...