Определите, является ли String действительной датой перед анализом - PullRequest
13 голосов
/ 08 июня 2009

У меня такая ситуация, когда я читаю около 130 тыс. Записей, содержащих даты, хранящиеся в виде строковых полей. Некоторые записи содержат пробелы (нули), некоторые содержат строки вроде этого: '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
   }

} 

Так что вы, возможно, уже заметили проблему. Я использую попытку .. поймать как часть моей логики . Было бы лучше, если бы я мог заранее определить, что строка действительно содержит анализируемую дату в каком-либо формате, а затем попытаться ее проанализировать.

Итак, есть ли какой-нибудь API или библиотека, которая может помочь с этим? Я не против написать несколько разных классов Parse для обработки разных форматов, а затем создать фабрику для выбора правильного6, но как мне определить, какой именно?

Спасибо.

Ответы [ 11 ]

7 голосов
/ 08 июня 2009

См. Обработка отложенных ошибок в Java , чтобы узнать, как устранить блоки try / catch, используя тип Option.

Функциональная 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 ... , это ленивый список . Это гарантирует, что вы проигнорируете шаблоны после первого успешного. Не нужно делать слишком много работы.

Вызовите свою функцию так:

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()
}
7 голосов
/ 08 июня 2009

Не будьте слишком осторожны в использовании try-catch в логике: это одна из тех ситуаций, когда Java заставляет вас делать это, поэтому с этим ничего не поделаешь.

Но в этом случае вы можете использовать DateFormat.parse(String, ParsePosition).

6 голосов
/ 08 июня 2009

Вы можете воспользоваться регулярными выражениями, чтобы определить, в каком формате находится строка, и соответствует ли она любому допустимому формату. Как то так (не проверено):

(К сожалению, я написал это на 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 голосов
/ 08 июня 2009

В этой ограниченной ситуации лучшим (и самым быстрым) методом, безусловно, является разбор дня, а затем на основе следующего символа «/» или «-» попытаться разобрать остальные. и если в какой-то момент появятся неожиданные данные, верните NULL затем.

3 голосов
/ 08 июня 2009

Если вы используете точные форматы (7 июня 1999 г. будет 07-июн-99 или 07/06/1999: вы уверены, что у вас начальные нули), тогда вы можете просто проверить длину строка перед попыткой разбора.

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

Но если ваши данные поступают из одной базы данных, я бы просто конвертировал все даты в общий формат (он одноразовый, но тогда вы контролируете данные и их формат).

3 голосов
/ 08 июня 2009

Похоже на три варианта, если у вас есть только два известных формата:

  • сначала проверьте наличие - или / и начните с этого анализа для этого формата.
  • проверить длину, так как "dd-MMM-yy" и "dd / MM / yyyy" различны
  • использовать предварительно скомпилированные регулярные выражения

Последнее кажется ненужным.

2 голосов
/ 08 июня 2009

Альтернативой созданию SimpleDateFormat (или двух) для каждой итерации было бы лениво заполнять контейнер ThreadLocal для этих форматов. Это решит как проблемы безопасности потоков, так и проблемы, связанные с производительностью создания объектов.

2 голосов
/ 08 июня 2009

Предполагая, что шаблоны, которые вы дали, являются единственно вероятным выбором, я бы посмотрел на переданную строку, чтобы увидеть, какой формат применить.

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 с многопоточным образом, вы можете создавать экземпляры на уровне класса или статические экземпляры. .

2 голосов
/ 08 июня 2009

вы можете использовать split, чтобы определить, какой формат использовать

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

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

2 голосов
/ 08 июня 2009

Используйте регулярные выражения для разбора вашей строки. Убедитесь, что вы сохранили оба скомпилированных регулярных выражения (не создавайте новые при каждом вызове метода, но сохраняйте их как константы) и сравнивайте, действительно ли это быстрее, чем try-catch, который вы используете.

Мне все еще странно, что ваш метод возвращает null, если обе версии дают сбой, а не выдают исключение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...