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

时间:2009-06-07 22:20:47

标签: java date parsing

我遇到这种情况,我正在阅读包含存储为字符串字段的日期的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,但是,我该如何确定哪一个?

感谢。

12 个答案:

答案 0 :(得分:7)

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

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

答案 1 :(得分:7)

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

Functional Java是你的朋友。

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

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<String, F<String, Option<Date>>> parseDate =
  curry(new F2<String, String, Option<Date>>() {
    public Option<Date> 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<Date> parseWithPatterns(String s, Stream<String> patterns) { 
  return stream(s).apply(patterns.map(parseDate)).find(isSome_()); 
}

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

如果您想知道Stream是什么...... it's a lazy list。这可确保您在第一次成功之后忽略模式。不需要做太多工作。

按照以下方式调用您的函数:

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

或者...

Option<Date> 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()
}

答案 2 :(得分:6)

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

(哎呀,我在检查你正在使用的语言之前用C#编写过这个。)

Regex test = new Regex(@"^(?:(?<formatA>\d{2}-[a-zA-Z]{3}-\d{2})|(?<formatB>\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.
    }
    ...
}

答案 3 :(得分:3)

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

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

后者似乎没必要。

答案 4 :(得分:3)

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

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

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

答案 5 :(得分:3)

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

答案 6 :(得分:2)

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

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

答案 7 :(得分:2)

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

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

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

答案 8 :(得分:2)

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

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

正如其他人所提到的,如果你能保证你将永远不会以多线程方式访问DateFormat,你可以上课 - 级别或静态实例。

答案 9 :(得分:2)

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

答案 10 :(得分:1)

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

用法示例:

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<SimpleDateFormat> dateFormats = new ArrayList<SimpleDateFormat>();



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

答案 11 :(得分:0)

一方面,我认为您为此目的使用try / catch并没有错,这是我会使用的选项。另一方面,还有其他选择:

  1. 在决定如何解析字符串之前先品尝一下字符串。
  2. 使用格式模式字符串的可选部分。

在我的演示中,我使用的是现代Java日期和时间API java.time,因为在问题中使用的Date类的设计总是很差,现在已经过时了。对于没有时间的日期,我们需要一个java.time.LocalDate

try-catch

在java.time中使用try-catch看起来像这样:

    DateTimeFormatter ddmmmuuFormatter = DateTimeFormatter.ofPattern("dd-MMM-uu", Locale.ENGLISH);
    DateTimeFormatter ddmmuuuuFormatter = DateTimeFormatter.ofPattern("dd/MM/uuuu");

    String dateString = "07-Jun-09";

    LocalDate result;
    try {
        result = LocalDate.parse(dateString, ddmmmuuFormatter);
    } catch (DateTimeParseException dtpe) {
        result = LocalDate.parse(dateString, ddmmuuuuFormatter);
    }
    System.out.println("Date: " + result);

输出为:

  

日期:2009-06-07

假设我们将字符串定义为:

    String dateString = "07/06/2009";

然后输出仍然相同。

品尝

如果您希望避免使用try-catch构造,可以轻松地进行简单的检查来确定您的字符串符合哪种格式。例如:

    if (dateString.contains("-")) {
        result = LocalDate.parse(dateString, ddmmmuuFormatter);
    } else {
        result = LocalDate.parse(dateString, ddmmuuuuFormatter);
    }

结果与以前相同。

在格式模式字符串中使用可选部分

这是我最不喜欢的选项,但它简短易懂,旨在提供某种程度的完整性。

    DateTimeFormatter dateFormatter
            = DateTimeFormatter.ofPattern("[dd-MMM-uu][dd/MM/uuuu]", Locale.ENGLISH);
    LocalDate result = LocalDate.parse(dateString, dateFormatter);

方括号表示格式的可选部分。因此,Java首先尝试使用dd-MMM-uu进行解析。无论是否成功,它都会尝试使用dd/MM/uuuu解析字符串的其余部分。给定两种格式,其中一种尝试将成功,并且您已经解析了日期。结果仍然与上面相同。

链接

Oracle tutorial: Date Time解释了如何使用java.time。