如何解释Excel数字格式string以确定值是否应由DateTime.FromOADateparsing

如何创build一个函数“bool IsDateTime”,它可以可靠地确定一个Excel数字格式string像“[$ -409] h:mm:ss AM / PM; @”是否表明数值是应该传递的DateTime到DateTime.FromOADate?

我已经知道[$ -409]是什么了: Excel数字格式:什么是“[$ -409]”? 。 这只是一个区域代码。

我还读了一些有关数字格式string被分号分成四个格式的部分: http : //office.microsoft.com/en-us/excel-help/create-or-delete-a-custom-number -format-HP005199500.aspx?CTT = 5&origin = HP005198679 and here http://www.ozgrid.com/Excel/excel-custom-number-formats.htm

例如,简单地searchdate/时间格式字符如h,m,s,y,d的出现是否可靠? Excel如何解释?

如果问题不清楚 ……当你阅读一个Excel文件并查看一个date/时间值时,你实际上是在寻找一个普通的旧双精度值,因为这就是它存储在Excel中的方式。 要确定它是一个普通的double还是double,应该传递给DateTime.FromOADate,您必须解释自定义数字格式string。 所以我问如何去解释这样一个string,它可能或不可能引用date/时间值,以确定双精度值是否应通过DateTime.FromOADate转换为DateTime值。 此外,如果成功转换为DateTime值,则需要将Excel数字格式string转换为等效的.NET DateTime格式string,以便可以通过DateTime.ToString(converted_format_string)显示Excel的date/时间值。

您可以通过使用CELL函数并返回格式来检查单元格是否包含任何内置的date格式。 如果使用内置格式,它将返回“D”后跟一个数字。

例如:

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

对于一个更一般的情况,我会首先检查的是单元格号( ISNUMBER() ),并在一个date的范围内(即在0和TODAY() – 今天是39296)。 然后我会检查至less有一个d,m,y,h,M或s的数字格式,因为这应该表示在单元格中有一个date。

希望这可以帮助,

戴夫

我实现了一个类来parsingExcel数字格式string。 它会查看第一部分(格式string中的四个可能部分),并使用正则expression式来捕获date/时间特定的自定义格式字符,如“y”,“m”,“d”,“h”,“s “,”AM / PM“,如果没有find,则返回null。 这第一步简单地决定了格式string是否意味着一个date/时间值,并留给我们一个逻辑date/时间格式说明符的面向对象有序列表进行进一步处理。

假设已经确定格式string是用于date/时间值的,则捕获的和分类的值按照它们在原始格式string中find的顺序sorting。

接下来,它应用Excel特定的格式化怪癖,例如决定“m”是指月份还是分钟,只有在“h”之后或“s”之前出现时才将其解释为“分钟” ,所以它不是“立即”之前/之后)。 如果“AM / PM”没有被指定,Excel也强制“h”字符24小时,所以如果没有find“AM / PM”,它将使用小写的m(.NET中的24小时)否则将其转换为大写字母M(在.NET中为12小时)。 它还将“AM / PM”转换为.NET等效的“tt”,并将条件expression式删除,这些条件expression式不能包含在纯.NET DateTime格式string中。

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

在以下上下文中可以使用上面的类来从Excel文件中具有非空标头的列中将string值读入DataTable。 具体来说,它试图获取一个有效的DateTime实例,如果find了它,它将尝试从Excel数字格式string构造一个有效的.NET DateTime格式string。 如果前面的两个步骤都成功,它将格式化的date时间string存储在数据表中,否则将任何存在的值转换为string(确保首先删除富文本格式,如果存在的话):

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