Признать произвольную строку даты

Мне нужно знать строки дат. Неважно, не могу ли я различать месяц и дату (например, 12/12/10), мне просто нужно классифицировать строку как дату, а не преобразовывать ее в объект Date. Итак, это действительно классификация, а не проблема синтаксического анализа.

У меня будут фрагменты текста, например:

"bla bla bla bla 12 января 09 bla bla bla 01/04/10 bla bla bla"

и я должен иметь возможность распознавать начальную и конечную границы для каждой строки даты внутри.

Мне было интересно, знает ли кто-нибудь о каких-либо java-библиотеках, которые могут это сделать. Мой google-fu пока ничего не придумал.

ОБНОВЛЕНИЕ: Мне нужно уметь распознавать как можно более широкий набор способов представления дат. Конечно, наивным решением может быть запись оператора if для каждого мыслимого формата, но подход распознавания образов с обученной моделью идеально подходит для меня.

Ответ 1

Используйте JChronic

Вы можете использовать DateParser2 из пакета edu.mit.broad.genome.utils.

Ответ 2

Вы можете закодировать все доступные форматы дат в 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
        }
    }
}

Это, однако, не учитывает какие-либо пользовательские форматы даты.

Ответ 3

Правила, которые могут помочь вам в вашем квесте:

  • Создайте или найдите какую-то базу данных с известными словами, которые соответствуют месяцам. Сокращенные и полные имена, например Jan или January. Во время поиска он должен быть нечувствительным к регистру, потому что fEBruaRy также является месяцем, хотя человек, печатающий его, должен был быть пьян. Если вы планируете искать неанглийские месяцы, необходима база данных, потому что никакая эвристика не обнаружит, что "Wrzesień" польский для сентября.
  • Только на английском языке порядковые номера, а также создайте базу данных для чисел от 1 до 31. Они будут полезны в течение нескольких дней и месяцев. Если вы хотите использовать этот подход для других языков, вам нужно будет провести собственное исследование.
  • Еще раз, только на английском языке, проверьте "Anno Domini" и "Before Christ", то есть AD и BC соответственно. Они также могут быть в форме A.D. и B.C.
  • Что касается самих номеров, которые будут представлять дни, месяцы и годы, вы должны знать, где ваш лимит. Это 0-9999 или более? То есть, вы хотите найти даты, которые представляют годы за 9999 год? Если нет, строки, имеющие 1-4 последовательных цифры, являются хорошими догадками для действительного дня, месяца или года.
  • Дни и месяцы имеют одну или две цифры. Ведущие нули приемлемы, поэтому допустимы строки с форматом 0*, где * может быть 1-9.
  • Сепараторы могут быть сложными, но если вы не допускаете противоречивого форматирования, например, 10/20\1999, то вы сэкономите много горя. Это связано с тем, что 10 * 20 * 1999 может быть допустимой датой, причем * обычно является одним из элементов набора {-,_, ,:,/,\,.,','}, но возможно, что * представляет собой комбинацию из 2 или 3 элементов упомянутого множества. Еще раз, вы должны выбрать приемлемые разделители. 10? 20? 1999 может быть подходящей датой для кого-то с странным чувством элегантности. 10/20/1999 также может быть действительной датой, но 10_/20_/1999 будет очень странным.
  • Есть случаи без разделителя. Например: 10J1988. В этих случаях используются слова из 1.
  • Существуют специальные случаи, например, 28 или 29 февраля, в зависимости от високосного года. Кроме того, месяцы с 30 или 31 днями.

Я думаю, этого достаточно для "наивной" классификации, специалист по лингвистам может помочь вам больше.

Теперь, идея для вашего алгоритма. Скорость не имеет значения. Могут быть несколько проходов по одной и той же строке. Оптимизируйте, когда это начнет иметь значение. Если вы сомневаетесь в том, что вы нашли строку даты, сохраните ее где-нибудь "безопасно" в ListOfPossibleDates и выполните экзамен еще раз, с более жесткими правилами с использованием комбинаций от 1. до 8. Когда вы считаете, что строка даты действительна, отправьте его в класс Date, чтобы убедиться, что он действительно действителен. 32 марта 1999 года недействительно, когда вы конвертируете его в формат, который будет понимать Date.

Один важный повторяющийся шаблон - lookbehind и lookaround. Когда вы считаете, что действительная сущность (день, месяц, год) найдена, вам нужно будет увидеть, что лежит за и после. Здесь может помочь механизм или рекурсия на основе стека.

Шаги:

  • Найдите строку для слов из правила 1. Если вы найдете любой из них, обратите внимание на это местоположение. Обратите внимание на месяц. Теперь, пойдите несколько символов позади и несколько впереди, чтобы видеть то, что Вас ждет. Если перед месяцем и после месяца нет пробелов, и есть числа, как в правиле 7., проверьте их на достоверность. Если один из них представляет день (должен быть 0-31) и другой год (должен быть 0-9999, возможно, с AD или BC), у вас есть один кандидат. Если есть одинаковые разделители до и после, найдите правила из 6. Всегда помните, что вы должны быть уверены, что существует действительная комбинация. поэтому, 32Jan1999 не будет делать.
  • Найдите строку для других английских слов, из правил 2. и 3. Повторяйте аналогично, как на шаге 1.
  • Поиск разделителей. Пустое пространство будет самым сложным. Попытайтесь найти их в парах. Итак, если у вас есть одна "/" в вашей строке, найдите другую и посмотрите, что у них есть между ними. Если вы найдете комбинацию разделителей, то же самое. Кроме того, используйте алгоритм из шага 2.
  • Поиск цифр. Допустимыми являются 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") и повторите все вышеописанные шаги еще раз. Таким образом, вы будете уверены, что ничего не пропустите.

Я надеюсь, что эти предложения будут, по крайней мере, полезными. Если не существует библиотеки для всех этих грязных (и более) шагов для вас, то у вас впереди трудная дорога. Удачи!

Ответ 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

И это больше не похоже на дату. Но это то, что вы можете решить, проверяя, являются ли цифры возможными значениями за месяц, день, год.

Ответ 5

Очень хороший синтаксический анализатор даты в java Natty, вы можете попробовать его здесь

Ответ 6

Я уверен, что исследователи в извлечении информации рассматривали эту проблему, но я не мог найти бумагу.

Одна вещь, которую вы можете попробовать - сделать это как двухэтапный процесс. (1) после сбора как можно большего количества данных, извлеките функции, некоторые функции, которые приходят на ум: количество чисел, которые появляются в строке, количество чисел от 1-31, которые появляются в строке, количество чисел из 1- 12, которые появляются в строке, количество месяцев имен, которые появляются в строке, и так далее. (2) изучать функции с использованием какого-либо типа метода двоичной классификации (например, SVM) и, наконец, (3) при появлении новой строки, извлекать функции и запрашивать SVM для прогнозирования.

Ответ 7

Вот простой пример natty:

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

Ответ 8

Может быть, вы должны использовать регулярные выражения?

Надеюсь, этот будет работать в формате 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 соответствует году.

Поля могут быть отброшены тире, косой чертой или точкой.

С уважением, Serge

Ответ 9

Практически невозможно распознать все возможные форматы даты в виде дат, используя "стандартные" алгоритмы. Это просто потому, что их так много.

Мы, люди, способны это делать только потому, что узнали, что что-то вроде 2010-03-31 напоминает дату. Другими словами, я бы предложил использовать алгоритмы машинного обучения и научить вашу программу распознавать правильные последовательности дат. Google Prediction API, который должен быть осуществим.

Или вы можете использовать регулярные выражения, как было предложено выше, для обнаружения некоторых, но не всех форматов даты.

Ответ 10

Что бы я сделал, это искать характеристики даты, а не сами даты. Например, вы можете искать косые черты (для получения дат формы 1/1/1001), тире (1 - 1 - 1001), названиях месяцев и аббревиатурах (1 января 1001 или 1 января 1001 года). Когда вы получаете хит для них, соберите близлежащие слова (2 с каждой стороны должно быть хорошо) и сохраните это в массиве строк. После того, как вы проверили весь вход, проверьте этот массив строк с помощью функции, которая будет немного глубже и вытащить строки фактической даты, используя найденные здесь методы. Важно то, что общие даты доходят до уровня управления.

Ответ 11

Обычно даты являются символами, разделенными обратной или прямой косой чертой или тире. Вы считали регулярное выражение?

Я предполагаю, что вы не хотите классифицировать даты типа Sunday, October 3rd 2010 и т.д.

Ответ 12

Я не знаю ни одной библиотеки, которая могла бы это сделать, но написать собственное не было бы невероятно сложно. Предполагая, что ваши даты отформатированы с помощью слэшей типа 12/12/12, тогда вы можете убедиться, что у вас есть три "\". Вы можете получить еще больше технических средств и проверить его значения между косой чертой. Например, если у вас есть:

30/12/10

Тогда вы знаете, что 30 дней, а 12 - месяц. Однако, если вы получаете 30/30/10, вы знаете, что даже если ti имеет правильный формат, он не может быть датой, потому что нет "30" месяцев.

Ответ 13

Я не знаю ни одной библиотеки, которая тоже это делает. Я бы предложил сочетание вложенных рекурсивных функций и регулярных выражений (много), чтобы соответствовать строкам и попытаться придумать лучшее предположение, чтобы увидеть, может ли это быть датой. Даты могут быть написаны по-разному, некоторые могут написать их как "Воскресенье, 3 октября 2010" или "Воскресенье, 3 октября 2010" или "10/03/2010" или "10/3/2010" и целую кучу разных способов (даже если вы рассматриваете даты на других языках/культурах).

Ответ 14

Вы всегда можете проверить, есть ли в строке два символа '/'.

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

Вы можете сделать что-то похожее, чтобы проверить, является ли все остальное целым.