在解析之前确定String是否是有效日期

我有这种情况,我正在阅读包含存储为字符串字段的日期的130K记录。 有些记录包含空格(nulls),有些包含这样的字符串:’dd-MMM-yy’,有些包含’dd / MM / yyyy’。

我写了一个像这样的方法:

public Date parsedate(String date){ if(date !== null){ try{ 1. create a SimpleDateFormat object using 'dd-MMM-yy' as the pattern 2. parse the date 3. return the parsed date }catch(ParseException e){ try{ 1. create a SimpleDateFormat object using 'dd/MM/yyy' as the pattern 2. parse the date 3. return parsed date }catch(ParseException e){ return null } } }else{ return null } } 

所以你可能已经发现了这个问题。 我正在使用try .. catch作为我逻辑的一部分 。 最好是我可以事先确定String实际上包含某种格式的可解析日期然后尝试解析它。

那么,是否有一些API或库可以帮助解决这个问题? 我不介意写几个不同的Parse类来处理不同的格式,然后创建一个工厂来选择正确的6,但是,我该如何确定哪一个?

谢谢。

有关如何使用Option类型消除try / catch块的概述,请参阅Java中的Lazy Error Handling 。

functionJava是你的朋友。

本质上,您要做的是将日期解析包装在不抛出任何内容的函数中,但在其返回类型中指示解析是否成功。 例如:

 import fj.F; import fj.F2; import fj.data.Option; import java.text.SimpleDateFormat; import java.text.ParseException; import static fj.Function.curry; import static fj.Option.some; import static fj.Option.none; ... F>> parseDate = curry(new F2>() { public Option f(String pattern, String s) { try { return some(new SimpleDateFormat(pattern).parse(s)); } catch (ParseException e) { return none(); } } }); 

好的,现在你有一个可重用的日期解析器,它不会抛出任何东西,但通过返回Option.None类型的值来指示失败。 这是你如何使用它:

 import fj.data.List; import static fj.data.Stream.stream; import static fj.data.Option.isSome_; .... public Option parseWithPatterns(String s, Stream patterns) { return stream(s).apply(patterns.map(parseDate)).find(isSome_()); } 

这将为您提供使用匹配的第一个模式解析的日期,或者类型为Option.None的值,这是类型安全的,而null则不是。

如果你想知道Stream是什么…… 它是一个懒惰的列表 。 这可确保您在第一次成功之后忽略模式。 不需要做太多工作。

像这样调用你的函数:

 for (Date d: parseWithPatterns(someString, stream("dd/MM/yyyy", "dd-MM-yyyy")) { // Do something with the date here. } 

要么…

 Option d = parseWithPatterns(someString, stream("dd/MM/yyyy", "dd-MM-yyyy")); if (d.isNone()) { // Handle the case where neither pattern matches. } else { // Do something with d.some() } 

在逻辑中使用try-catch不要太费劲:这是Java强迫你这样做的情况之一,所以你可以做很多事情。

但在这种情况下,您可以改为使用DateFormat.parse(String, ParsePosition)

您可以利用正则表达式来确定字符串所处的格式,以及它是否与任何有效格式匹配。 像这样的东西(未经测试):

(糟糕,我在用C#编写了这个,然后检查你正在使用的语言。)

 Regex test = new Regex(@"^(?:(?\d{2}-[a-zA-Z]{3}-\d{2})|(?\d{2}/\d{2}/\d{3}))$", RegexOption.Compiled); Match match = test.Match(yourString); if (match.Success) { if (!string.IsNullOrEmpty(match.Groups["formatA"])) { // Use format A. } else if (!string.IsNullOrEmpty(match.Groups["formatB"])) { // Use format B. } ... } 

如果格式是准确的(1999年6月7日将是07年6月7日或07/06/1999:你确定你有前导零),那么你可以在尝试解析之前检查字符串的长度

请注意第一个版本中的短月份名称,因为Jun可能不是6月份的另一种语言。

但是,如果您的数据来自一个数据库,那么我只会将所有日期转换为通用格式(它是一次性的,但您可以控制数据及其格式)。

在这种有限的情况下,最好的(也是最快的方法)可以解析当天,然后根据下一个字母“/”或“ – ”尝试解析其余的问题。 如果在任何时候有意外数据,则返回NULL。

如果您只有两种已知格式,则看起来有三个选项:

  • 检查是否存在-/首先从该格式的解析开始。
  • 检查长度,因为“dd-MMM-yy”和“dd / MM / yyyy”不同
  • 使用预编译的正则表达式

后者似乎没必要。

使用正则表达式来解析字符串。 确保你保留两个正则表达式的预编译(不是在每个方法调用上创建新的,但将它们存储为常量),并比较它实际上是否比你使用的try-catch快。

我仍然觉得奇怪的是,如果两个版本都失败而不是抛出exception,则您的方法返回null

您可以使用拆分来确定要使用的格式

 String[] parts = date.split("-"); df = (parts.length==3 ? format1 : format2); 

假设它们都是一种或另一种格式,如果需要,可以改进检查

假设您提供的模式是唯一可能的选择,我会查看传入的字符串以查看要应用的格式。

 public Date parseDate(final String date) { if (date == null) { return null; } SimpleDateFormat format = (date.charAt(2) == '/') ? new SimpleDateFormat("dd/MMM/yyyy") : new SimpleDateFormat("dd-MMM-yy"); try { return format.parse(date); } catch (ParseException e) { // Log a complaint and include date in the complaint } return null; } 

正如其他人所提到的,如果您可以保证 永远不会以multithreading方式访问DateFormat ,则可以创建类级别或静态实例。

每次迭代创建SimpleDateFormat(或两个)的替代方法是为这些格式延迟填充ThreadLocal容器。 这将解决线程安全问题和对象创建性能方面的问题。

我为我的项目编写的一个简单的实用工具类。 希望这有助于某人。

用法示例:

 DateUtils.multiParse("1-12-12"); DateUtils.multiParse("2-24-2012"); DateUtils.multiParse("3/5/2012"); DateUtils.multiParse("2/16/12"); public class DateUtils { private static List dateFormats = new ArrayList(); private Utils() { dateFormats.add(new SimpleDateFormat("MM/dd/yy")); // must precede yyyy dateFormats.add(new SimpleDateFormat("MM/dd/yyyy")); dateFormats.add(new SimpleDateFormat("MM-dd-yy")); dateFormats.add(new SimpleDateFormat("MM-dd-yyyy")); } private static Date tryToParse(String input, SimpleDateFormat format) { Date date = null; try { date = format.parse(input); } catch (ParseException e) { } return date; } public static Date multiParse(String input) { Date date = null; for (SimpleDateFormat format : dateFormats) { date = tryToParse(input, format); if (date != null) break; } return date; } }