识别任意日期字符串

时间:2010-10-03 17:26:28

标签: java date classification

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

我会有一些文字,例如:

   bla bla bla bla 2009年1月12日 bla bla bla    01/04/10 bla bla bla“

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

我想知道是否有人知道任何可以执行此操作的java库。到目前为止,我的google-fu还没有提出任何建议。

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

16 个答案:

答案 0 :(得分:5)

您可以在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 :(得分:5)

使用JChronic

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

答案 2 :(得分:5)

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

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

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

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

    步骤:

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

      我们举个例子,"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")并再次应用上述所有步骤。这样你就可以确定你没有错过任何东西。

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

答案 3 :(得分:4)

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

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

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

答案 4 :(得分:4)

java中非常好的日期解析器是Natty,你可以尝试here

答案 5 :(得分:2)

我确信information extraction的研究人员已经研究过这个问题,但我找不到论文。

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

答案 6 :(得分:2)

这是一个简单的例子:

import com.joestelmach.natty.*;

List<Date> 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

答案 7 :(得分:2)

java.time

您可以使用 DateTimeFormatter 指定任意数量的自定义模式。您需要做的就是通过将模式括在方括号中来将模式指定为可选。 DateTimeFormatterBuilder 为您提供更多的东西,例如不区分大小写的解析,默认为缺少的单元(例如 HOUR_OF_DAY)等

演示:

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.util.Locale;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        // DateTimeFormatter parser = DateTimeFormatter.ofPattern("[M/d/uu[ H:m]][d MMM u][M.d.u][E MMM d, u]", Locale.ENGLISH);
        final DateTimeFormatter parser = new DateTimeFormatterBuilder()
                    .parseCaseInsensitive() // parse in case-insensitive manner
                    .appendPattern("[M/d/uu[ H:m]][d MMM u][M.d.u][E MMM d, u]")
                    .toFormatter(Locale.ENGLISH);
        
        // Test
        Stream.of(
                    "Thu Apr 1, 2021",
                    "THU Apr 1, 2021",
                    "01/06/10",
                    "1 Jan 2009",
                    "1.2.2010",
                    "asdf"
                ).forEach(s -> {
                    try {
                        LocalDate.parse(s, parser);
                        System.out.println(true);
                    } catch(DateTimeParseException e) {
                        System.out.println(false);
                    }
                });     
    }   
}

输出:

true
true
true
true
true
false

Trail: Date Time 了解有关现代日期时间 API 的更多信息。

答案 8 :(得分:1)

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

希望这个适用于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匹配一年。

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

此致 哔叽

答案 9 :(得分:1)

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

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

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

答案 10 :(得分:1)

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

答案 11 :(得分:1)

检查这个 https://github.com/zoho/hawking。 由 ZOHO ZIA 团队开发。

Hawking Parser 是一个基于 Java 的 NLP 解析器,用于解析日期和时间信息。最流行的解析器,如 Heidel Time、SuTime 和 Natty Date 时间解析器,显然是基于规则的。因此,他们往往难以解析日期/时间信息,因为需要考虑更复杂的因素,如上下文、时态、多个值等。

考虑到这一点,Hawking Parser 旨在解决许多这些挑战,并且与其他可用的日期/时间解析器相比具有许多明显的优势。

它是 GPL v3 下的一个开源库,也是最好的库。要了解为什么它是最好的,请查看这篇详细解释的博客:https://www.zoho.com/blog/general/zias-nlp-based-hawking-date-time-parser-is-now-open-source.html

P.S:我是这个项目的开发者之一

答案 12 :(得分:0)

通常日期是由后退/正斜杠或破折号分隔的字符。你有没有考虑过正则表达式?

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

答案 13 :(得分:0)

我不知道任何可以做到这一点的图书馆,但是编写自己的图书馆并不会非常困难。假设您的日期都使用12/12/12之类的斜杠进行格式化,那么您可以验证您有三个's'。您可以获得更多技术,并检查斜杠之间的值。例如,如果你有:

30/12/10

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

答案 14 :(得分:0)

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

答案 15 :(得分:0)

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

public static boolean isDate(){
     String date = "12/25/2010";
     int counter = 0;
     for(int i=0; i<date.length(); i++){
          if ("\/-.".indexOf(date.charAt(i)) != -1) //Any symbol can be used. 
               counter++;
     }
     if(counter == 2)    //If there are two symbols in the string,
          return true;   //Return true.
     else
          return false;
}

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