Подтвердить что ты не робот

Определите, является ли строка правильной датой перед синтаксическим анализом

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

} 

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

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

Спасибо.

4b9b3361

Ответ 1

См. Lazy Error Handling в 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()
}

Ответ 2

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

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

Ответ 3

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

(К сожалению, я написал это на С#, прежде чем проверять, какой язык вы использовали.)

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

Ответ 4

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

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

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

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

Ответ 8

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

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

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

Ответ 9

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

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

Ответ 10

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

Ответ 11

Простой класс утилиты, который я написал для моего проекта. Надеюсь, это поможет кому-то.

Примеры использования:

DateUtils.multiParse("1-12-12");
DateUtils.multiParse("2-24-2012");
DateUtils.multiParse("3/5/2012");
DateUtils.multiParse("2/16/12");




public class DateUtils {

    private static List<SimpleDateFormat> dateFormats = new ArrayList<SimpleDateFormat>();



    private Utils() {
        dateFormats.add(new SimpleDateFormat("MM/dd/yy")); // must precede yyyy
        dateFormats.add(new SimpleDateFormat("MM/dd/yyyy"));
        dateFormats.add(new SimpleDateFormat("MM-dd-yy"));
        dateFormats.add(new SimpleDateFormat("MM-dd-yyyy"));            

    }
        private static Date tryToParse(String input, SimpleDateFormat format) {
        Date date  = null;
        try {
            date = format.parse(input);
        } catch (ParseException e) {

        }

        return date;
    }

        public static Date multiParse(String input)  {
        Date date = null;
        for (SimpleDateFormat format : dateFormats) {
            date = tryToParse(input, format);
            if (date != null) break;
        }
        return date;
    }
}