24

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

我将有一些文本,例如:

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

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

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

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

4

15 回答 15

6

使用JChronic

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

于 2010-11-08T18:35:02.547 回答
5

可能对您的任务有所帮助的规则:

  1. 使用与月份匹配的已知单词制作或查找某种数据库。缩写和全名,如Janor January。搜索时,它必须不区分大小写,因为 fEBruaRy 也是一个月,尽管输入它的人一定是喝醉了。如果您打算搜索非英语月份,则还需要一个数据库,因为没有任何启发式方法会发现“Wrzesień”是 9 月的波兰语。
  2. 仅适用于英语,请查看序数并为数字 1 到 31 创建一个数据库。这些将在几天和几个月内很有用。如果您想将此方法用于其他语言,那么您将必须自己进行研究。
  3. 再一次,只有英语,检查“Anno Domini”和“Before Christ”,即分别是 AD 和 BC。它们也可以是 AD 和 BC 的形式
  4. 关于代表日、月和年的数字本身,你必须知道你的极限在哪里。是 0-9999 还是更多?也就是说,您是否要搜索代表 9999 年之后年份的日期?如果否,则具有 1-4 个连续数字的字符串是有效日、月或年的良好猜测。
  5. 日期和月份有一位或两位数字。前导零是可以接受的,因此格式为 的字符串是可以接受的0*,其中 * 可以是 1-9。
  6. 分隔符可能很棘手,但如果您不允许像 10/20\1999 这样的不一致格式,那么您将为自己省去很多麻烦。这是因为 10*20*1999 可以是有效日期,其中 * 通常是 set 的一个元素{-,_, ,:,/,\,.,','},但 * 可能是上述 set 的 2 个或 3 个元素的组合。再一次,您必须选择可接受的分隔符。10?20?1999 对有着怪异优雅感的人来说可能是一个有效的日期。10 / 20 / 1999 也可以是有效日期,但 10_/20_/1999 会是一个非常奇怪的日期。
  7. 有没有分隔符的情况。例如:1988 年 1 月 10 日。这些案例使用 1 中的单词。
  8. 有特殊情况,例如 2 月 28 日或 29 日,具体取决于闰年。此外,有 30 天或 31 天的月份。

我认为这些对于“幼稚”的分类已经足够了,语言专家可能会为您提供更多帮助。

现在,您的算法的一个想法。速度无所谓。同一字符串可能有多次传递。当它开始变得重要时进行优化。当您怀疑是否找到了日期字符串时,将其存储在 a 中“安全”的某个地方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") 并再次应用上述所有步骤。这样你就可以确保你没有错过任何东西。

我希望这些建议至少会有所帮助。如果不存在为您执行所有这些肮脏(以及更多)步骤的库,那么您将面临一条艰难的道路。祝你好运!

于 2010-11-10T21:25:35.227 回答
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
        }
    }
}

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

于 2010-10-19T13:25:16.907 回答
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

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

于 2010-11-07T14:30:47.603 回答
3

这是一个简单的例子:

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
于 2013-12-02T10:14:31.463 回答
2

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

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

于 2010-10-17T08:27:52.830 回答
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 的更多信息。

于 2021-04-01T12:58:24.837 回答
1

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

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

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

于 2010-10-04T11:52:18.610 回答
1

我要做的是寻找日期特征,而不是日期本身。例如,您可以搜索斜杠(以获取 1/1/1001 形式的日期)、破折号 (1 - 1 - 1001)、月份名称和缩写(1001 年 1 月 1 日或 1001 年 1 月 1 日)。当您对这些内容感兴趣时,请收集附近的单词(每边 2 个应该没问题)并将其存储在字符串数组中。扫描完所有输入后,使用此处找到的方法,使用更深入并提取实际日期字符串的函数检查此字符串数组。重要的是将一般日期降低到可管理的水平。

于 2010-11-10T08:28:51.883 回答
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匹配年份。

字段可以用破折号、斜线或点分隔。

问候, 塞尔吉

于 2010-10-03T17:33:52.587 回答
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

PS:我是这个项目的开发者之一

于 2021-04-01T11:01:00.180 回答
0

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

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

于 2010-10-03T17:32:40.703 回答
0

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

2010 年 12 月 30 日

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

于 2010-10-03T17:33:05.820 回答
0

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

于 2010-10-03T17:50:15.283 回答
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;
}

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

于 2010-10-03T17:52:52.890 回答