正如我稍后解释的那样,我总是喜欢TryParse 和TryParseExact 方法。因为它们使用起来有点笨重,所以我写了一个扩展方法,它使解析更容易:
var dtStr = "2011-03-21 13:26";
DateTime? dt = dtStr.ToDate("yyyy-MM-dd HH:mm");
或者更简单地说,如果您想隐式使用当前文化的日期模式,您可以像这样使用它:
DateTime? dt = dtStr.ToDate();
在这种情况下,不需要指定特定的模式。
与Parse、ParseExact 等不同,它不会抛出异常,并允许您检查通过
if (dt.HasValue) { // continue processing } else { // do error handling }
转换是否成功(在这种情况下,dt 有一个可以通过dt.Value 访问的值)或不成功(在这种情况下,它是null)。
这甚至允许使用优雅的快捷方式,例如“Elvis”运算符?.,例如:
int? year = dtStr?.ToDate("yyyy-MM-dd HH:mm")?.Year;
在这里您还可以使用year.HasValue 来检查转换是否成功,如果不成功则year 将包含null,否则日期的年份部分。转换失败不抛出异常。
解决方案: .ToDate() 扩展方法
Try it in .NetFiddle
public static class Extensions
{
/// Extension method parsing a date string to a DateTime? <para/>
/// <summary>
/// </summary>
/// <param name="dateTimeStr">The date string to parse</param>
/// <param name="dateFmt">dateFmt is optional and allows to pass
/// a parsing pattern array or one or more patterns passed
/// as string parameters</param>
/// <returns>Parsed DateTime or null</returns>
public static DateTime? ToDate(this string dateTimeStr, params string[] dateFmt)
{
// example: var dt = "2011-03-21 13:26".ToDate(new string[]{"yyyy-MM-dd HH:mm",
// "M/d/yyyy h:mm:ss tt"});
// or simpler:
// var dt = "2011-03-21 13:26".ToDate("yyyy-MM-dd HH:mm", "M/d/yyyy h:mm:ss tt");
const DateTimeStyles style = DateTimeStyles.AllowWhiteSpaces;
if (dateFmt == null)
{
var dateInfo = System.Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat;
dateFmt=dateInfo.GetAllDateTimePatterns();
}
var result = DateTime.TryParseExact(dateTimeStr, dateFmt, CultureInfo.InvariantCulture,
style, out var dt) ? dt : null as DateTime?;
return result;
}
}
关于代码的一些信息
您可能想知道,为什么我使用InvariantCulture 调用TryParseExact:这是为了强制函数始终以相同的方式处理格式模式(否则例如“。”可以解释为英文中的小数分隔符,而它是组分隔符或德语中的日期分隔符)。回想一下,我们已经在几行之前查询了基于文化的格式字符串,所以这里没问题。
更新: .ToDate()(不带参数)现在默认为线程当前文化的所有常见日期/时间模式。
注意我们需要result和dt一起使用,因为TryParseExact不允许使用DateTime?,我们打算返回。
在 C# 版本 7 中,您可以将 ToDate 函数简化如下:
// in C#7 only: "DateTime dt;" - no longer required, declare implicitly
if (DateTime.TryParseExact(dateTimeStr, dateFmt,
CultureInfo.InvariantCulture, style, out var dt)) result = dt;
或者,如果你喜欢更短的:
// in C#7 only: Declaration of result as a "one-liner" ;-)
var result = DateTime.TryParseExact(dateTimeStr, dateFmt, CultureInfo.InvariantCulture,
style, out var dt) ? dt : null as DateTime?;
在这种情况下,您根本不需要DateTime? result = null; 和DateTime dt; 这两个声明——您可以在一行代码中完成。
(如果您愿意,也可以写成out DateTime dt 而不是out var dt)。
旧的 C# 风格需要以下方式(我从上面的代码中删除了它):
// DateTime? result = null;
// DateTime dt;
// if (DateTime.TryParseExact(dateTimeStr, dateFmt,
// CultureInfo.InvariantCulture, style, out dt)) result = dt;
我通过使用params 关键字进一步简化了代码:现在您不再需要2nd 重载方法了。
使用示例
var dtStr="2011-03-21 13:26";
var dt=dtStr.ToDate("yyyy-MM-dd HH:mm");
if (dt.HasValue)
{
Console.WriteLine("Successful!");
// ... dt.Value now contains the converted DateTime ...
}
else
{
Console.WriteLine("Invalid date format!");
}
如您所见,此示例仅查询dt.HasValue 以查看转换是否成功。作为额外的奖励,TryParseExact 允许指定严格的DateTimeStyles,这样您就可以准确地知道是否传递了正确的日期/时间字符串。
更多使用示例
重载函数允许您传递一个有效格式数组,用于解析/转换日期,如here 所示(TryParseExact 直接支持这一点),例如
string[] dateFmt = {"M/d/yyyy h:mm:ss tt", "M/d/yyyy h:mm tt",
"MM/dd/yyyy hh:mm:ss", "M/d/yyyy h:mm:ss",
"M/d/yyyy hh:mm tt", "M/d/yyyy hh tt",
"M/d/yyyy h:mm", "M/d/yyyy h:mm",
"MM/dd/yyyy hh:mm", "M/dd/yyyy hh:mm"};
var dtStr="5/1/2009 6:32 PM";
var dt=dtStr.ToDate(dateFmt);
如果你只有几个模板模式,你也可以这样写:
var dateStr = "2011-03-21 13:26";
var dt = dateStr.ToDate("yyyy-MM-dd HH:mm", "M/d/yyyy h:mm:ss tt");
高级示例
您可以使用?? 运算符默认为故障安全格式,例如
var dtStr = "2017-12-30 11:37:00";
var dt = (dtStr.ToDate()) ?? dtStr.ToDate("yyyy-MM-dd HH:mm:ss");
在这种情况下,.ToDate() 将使用常见的本地文化日期格式,如果所有这些都失败,它将尝试使用 ISO standard 格式 "yyyy-MM-dd HH:mm:ss" 作为后备。这样,扩展功能可以轻松“链接”不同的后备格式。
你甚至可以在 LINQ 中使用扩展,试试这个(在上面的 .NetFiddle 中):
var strDateArray = new[] { "15-01-2019", "15.01.2021" };
var patterns=new[] { "dd-MM-yyyy", "dd.MM.yyyy" };
var dtRange = strDateArray.Select(s => s.ToDate(patterns));
dtRange.Dump();
这将通过使用模式即时转换数组中的日期并将它们转储到控制台。
关于 TryParseExact 的一些背景知识
最后,这里有一些关于背景的cmets(即我这样写的原因):
我更喜欢 TryParseExact 在这个扩展方法中,因为你避免异常处理 - 你可以read in Eric Lippert's article about exceptions 为什么你应该使用 TryParse 而不是 Parse,我引用他该主题:2)
这个不幸的设计决定1) [注释:to
让 Parse 方法抛出异常] 这当然令人烦恼
框架团队此后不久实施了 TryParse,它做了正确的事情。
确实如此,但 TryParse 和 TryParseExact 仍然难以使用:它们强制您使用未初始化的变量作为 out 参数,该参数不能为空,并且在您转换时您需要评估布尔返回值 - 您必须立即使用 ifstatement,或者必须将返回值存储在附加的布尔变量中,以便稍后进行检查。而且你不能只使用目标变量而不知道转换是否成功。
在大多数情况下,您只想知道转换是否成功(当然还有成功时的值),所以可为空的目标变量保留所有信息将是可取的并且更加优雅 - 因为整个信息只存储在一个地方:这是一致且易于使用的,而且更不容易出错。
我编写的扩展方法正是这样做的(它还向您展示了如果您不打算使用它,您每次都必须编写什么样的代码)。
我相信.ToDate(strDateFormat) 的好处是它看起来简单干净——就像原来的DateTime.Parse 应该的一样简单——但是能够检查转换是否成功,并且不会抛出异常。
1) 这里的意思是异常处理(即try { ... } catch(Exception ex) { ...} 块) - 当您使用 Parse 时这是必要的,因为它如果解析无效字符串,将引发异常 - 在这种情况下不仅没有必要,而且令人讨厌,并使您的代码复杂化。 TryParse 避免了这一切,正如我提供的代码示例所示。
2) Eric Lippert 是著名的StackOverflow fellow,曾在 Microsoft 担任 C# 编译器团队的首席开发人员几年。