识别任意日期字符串

我需要能够识别日期字符串。 如果我无法区分月份和日期(例如12/12/10)并不重要,我只需要将字符串分类为日期,而不是将其转换为Date对象。 所以,这实际上是一个分类而不是解析问题。

我会有一些文字,例如:

“bla bla bla bla 12 Jan 09 bla bla bla 01/04/10 bla bla bla”

我需要能够识别每个日期字符串的开始和结束边界。

我想知道是否有人知道任何可以做到这一点的java库。 到目前为止,我的google-fu还没有拿到任何东西。

更新:我需要能够识别出最广泛的代表日期的方法。 当然,天真的解决方案可能是为每种可能的格式编写if语句,但是模式识别方法与训练有素的模型一样,理想情况下我正在追求的。

使用JChronic

您可能希望使用edu.mit.broad.genome.utils包中的DateParser2 。

您可以在Java中循环所有可用的日期格式:

for (Locale locale : DateFormat.getAvailableLocales()) { for (int style = DateFormat.FULL; style <= DateFormat.SHORT; style ++) { DateFormat df = DateFormat.getDateInstance(style, locale); try { df.parse(dateString); // either return "true", or return the Date obtained Date object } catch (ParseException ex) { continue; // unperasable, try the next one } } } 

但是,这不会考虑任何自定义日期格式。

可能有助于您完成任务的规则:

  1. 制作或查找某种具有匹配月份的已知单词的数据库。 缩写和全名,如JanJanuary 。 搜索时,它必须不区分大小写,因为fEBruaRy也是一个月,虽然输入它的人一定是醉了。 如果你计划搜索非英语月份,也需要一个数据库,因为没有启发式的工具会发现“Wrzesień”在9月份很好。
  2. 仅限英语,查看序号,并为数字1到31建立数据库。这些数据将在数天和数月内有用。 如果您想将此方法用于其他语言,那么您将不得不进行自己的研究。
  3. 再一次,仅限英语,分别检查“Anno Domini”和“Before Christ”,即AD和BC。 它们也可以是AD和BCforms
  4. 关于代表天,月和年的数字本身,您必须知道您的限制在哪里。 是0-9999还是更多? 也就是说,您想搜索代表9999年以后年份的日期吗? 如果不是,那么具有1-4个连续数字的字符串对于有效的日,月或年是好的猜测。
  5. 天和月有一个或两个数字。 前导零是可以接受的,因此格式为0*字符串,其中*可以是1-9是可以接受的。
  6. 分隔符可能很棘手,但如果你不允许像10/20 \ 1999这样的格式不一致,那么你将为自己省去很多悲伤。 这是因为10 * 20 * 1999可以是一个有效的日期,*通常是集{-,_, ,:,/,\,.,','}一个元素,但*可能是*的组合提到的2或3个元素集。 再次,您必须选择可接受的分隔符。 10?20?1999可能是一个有着奇怪优雅感的人的有效日期。 10/20 / 1999也可以是一个有效的日期,但是10_ / 20_ / 1999将是一个非常奇怪的日期。
  7. 有些情况下没有分隔符。 例如:10Jan1988。 这些案例使用1中的单词。
  8. 根据闰年,有特殊情况,如2月28日或29日。 此外,30或31天的月份。

我认为这些对于“天真”的分类来说已经足够了,语言专家可能会帮助你更多。

现在,您的算法的想法。 速度无关紧要。 同一个字符串可能有多个传递。 优化何时开始重要。 如果您怀疑自己找到了日期字符串,请将其存储在ListOfPossibleDates “安全”位置并再次进行检查,使用从1到8的组合使用更严格的规则。当您认为日期字符串有效时,请将其输入到Date类,看看它是否真的有效。 1999年3月32日无效,当您将其转换为Date将理解的格式时。

一个重要的反复出现的模式是lookbehind和lookaround。 当您认为找到有效的实体(日,月,年)时,您将不得不看到背后的内容。 基于堆栈的机制或递归可能对此有所帮助。

脚步:

  1. 在字符串中搜索规则1中的单词。如果找到任何单词,请记下该位置。 注意这个月。 现在,在后面跟几个角色和几个角色看看等待你的是什么。 如果您的月份之前和之后没有空格,并且有数字,例如规则7,请检查它们的有效性。 如果其中一个代表一天(必须是0-31)和其他一年(必须是0-9999,可能是AD或BC),则您有一个候选人。 如果之前和之后有相同的分隔符,请查看6中的规则。始终记住您必须确保存在有效组合。 所以,32Jan1999不会这样做。
  2. 从规则2和3中搜索您的字符串中的其他英语单词。重复类似于步骤1。
  3. 搜索分隔符。 空的空间将是最棘手的。 尝试成对找到它们。 所以,如果你的字符串中有一个“/”,找到另一个,看看它们之间有什么。 如果你发现了分离器的组合,那就是同样的事情。 此外,使用步骤2中的算法。
  4. 搜索数字。 有效的是0-9999,允许前导零。 如果找到一个,请查找步骤3中的分隔符。

由于确实有无数的可能性,你将无法捕捉到它们。 一旦找到了您认为可能再次发生的模式,将其存储在某处,您可以将其用作传递其他字符串的正则表达式。

让我们举个例子, "bla bla bla bla 12 Jan 09 bla bla bla 01/04/10 bla bla bla" 。 在提取第一个日期, 12 Jan 09 ,然后使用该字符串的其余部分( "bla bla bla 01/04/10 bla bla bla" )并再次应用上述所有步骤。 这样你就可以确定你没有错过任何东西。

我希望这些建议至少可以提供一些帮助。 如果没有一个库可以为你做所有这些肮脏(和更多)的步骤,那么你就有了一条艰难的道路。 祝你好运!

我用一个巨大的正则表达式(自己创建)做到了:

 public static final String DATE_REGEX = "\b([0-9]{1,2} ?([\\-/\\\\] ?[0-9]{1,2} ?| (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ?)([\\-/\\\\]? ?('?[0-9]{2}|[0-9]{4}))?)\b"; public static final Pattern DATE_PATTERN = Pattern.compile(DATE_REGEX, Pattern.CASE_INSENSITIVE); // Case insensitive is to match also "mar" and not only "Mar" for March public static boolean containsDate(String str) { Matcher matcher = pattern.matcher(str); return matcher.matches(); } 

这符合以下日期:

 06 Sep 2010 12-5-2005 07 Mar 95 30 DEC '99 11\9\2001 

而不是这个:

 444/11/11 bla11/11/11 11/11/11blah 

它还匹配[]() ,等符号之间的日期:

 Yesterday (6 nov 2010) 

它匹配没有年份的日期:

 Yesterday, 6 nov, was a rainy day... 

但它匹配

 86-44/1234 00-00-0000 11\11/11 

这看起来不像是约会。 但这可以通过检查数字是否是一个月,一天,一年的可能值来解决。

java中非常好的日期解析器是Natty ,你可以在这里试试

我相信信息提取的研究人员已经研究过这个问题,但我找不到论文。

您可以尝试的一件事是将其作为两个步骤来完成。 (1)收集尽可能多的数据后,提取特征,想到的一些特征:字符串中出现的数字的数量,字符串中出现的1-31的数字,1-的数字的数量12出现在字符串中,出现在字符串中的月份名称,依此类推。 (2)使用某种类型的二进制分类方法(例如SVM)学习特征,最后(3)在新字符串到来时,提取特征并查询SVM以进行预测。

这是一个简单的例子:

 import com.joestelmach.natty.*; List dates =new Parser().parse("Start date 11/30/2013 , end date Friday, Sept. 7, 2013").get(0).getDates(); System.out.println(dates.get(0)); System.out.println(dates.get(1)); //output: //Sat Nov 30 11:14:30 BDT 2013 //Sat Sep 07 11:14:30 BDT 2013 

也许你应该使用正则表达式?

希望这个适用于mm-dd-yyyy格式:

^(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)\d\d$

这里(0[1-9]|1[012])匹配月份00..12, (0[1-9]|[12][0-9]|3[01])匹配日期00 .. 31和(19|20)\d\d匹配一年。

字段可以用破折号,斜杠或点来表示。

问候,塞尔

使用“标准”算法几乎不可能将所有可能的日期格式识别为日期。 那只是因为有这么多。

我们人类能够做到这一点只是因为我们了解到像2010-03-31这样的东西类似于约会。 换句话说,我建议使用机器学习算法并教你的程序识别有效的日期序列。 使用Google Prediction API应该是可行的。

或者您可以使用上面建议的正则表达式来检测一些但不是所有日期格式。

我要做的是寻找日期特征,而不是日期本身。 例如,您可以搜索斜杠((获取格式为1/1/1001的日期),短划线(1 – 1 – 1001),月份名称和缩写(1月1日1001或1月1日1001)。 当你受到这些打击时,收集附近的单词(每边2个应该没问题)并将其存储在一个字符串数组中。 扫描完所有输入后,使用此处找到的方法检查此字符串数组是否具有更深入的function并拉出实际日期字符串。 重要的是将一般日期降低到可管理的水平。

通常日期是由后退/正斜杠或短划线分隔的字符。 你考虑过正则表达式吗?

我假设您不打算对2010年10月3日星期日等类型的日期进行分类

我不知道任何可以做到这一点的图书馆,但写自己的图书馆并不难以置信。 假设您的日期都使用12/12/12这样的斜杠格式化,那么您可以validation您有三个’s’。 您可以获得更多技术,并检查斜杠之间的值。 例如,如果你有:

30/12/10

然后你知道30天是天,12天是月。 但是,如果你得到30/30/10,你知道即使ti具有正确的格式,也不能是约会,因为没有’30’个月。

我也不知道有没有这样做的图书馆。 我建议混合使用嵌套递归函数和正则表达式(很多)来匹配字符串,并尝试提出最佳猜测,看它是否可以成为日期。 日期可以用很多不同的方式写成,有些人可能会把它们写成“2010年10月3日星期日”或“2010年10月3日星期日”或“2010年10月3日”或“10/3/2010”和一大堆不同的方式(如果你考虑其他语言/文化的日期,甚至更多)。

您可以随时检查字符串中是否有两个“/”字符。

 public static boolean isDate(){ String date = "12/25/2010"; int counter = 0; for(int i=0; i 

您可以执行类似的操作来检查其他所有内容是否为整数。