it-roy-ru.com

Как анализировать даты в нескольких форматах, используя SimpleDateFormat

Я пытаюсь разобрать некоторые даты, которые выходят из документа. Похоже, что пользователи ввели эти даты в похожем, но не точном формате.

вот форматы:

9/09
9/2009
09/2009
9/1/2009
9-1-2009 

Как лучше всего попытаться разобрать все это? Похоже, они наиболее распространены, но я думаю, что меня вешает, так это то, что если у меня есть шаблон "M/yyyy", который всегда ловит перед "MM/yyyy", я должен настроить свои блоки try/catch вложенный в наименее ограничивающем в наиболее ограничивающий способ? Похоже, что для того, чтобы сделать это правильно, потребуется много дублирования кода. 

40
Derek

Вам нужно будет использовать разные объекты SimpleDateFormat для каждого шаблона. Тем не менее, вам не нужно так много разных, благодаря этому :

Number: Для форматирования количество букв шаблона - это минимальное количество цифр, а более короткие числа дополняются нулями до этой суммы. При синтаксическом анализе количество букв шаблона игнорируется, если только это не необходимо для разделения двух смежных полей.

Итак, вам понадобятся эти форматы:

  • "M/y" (охватывает 9/09, 9/2009 и 09/2009)
  • "M/d/y" (охватывает 9/1/2009)
  • "M-d-y" (охватывает 9-1-2009)

Итак, мой совет - написать метод, который работает примерно так (untested):

// ...
List<String> formatStrings = Arrays.asList("M/y", "M/d/y", "M-d-y");
// ...

Date tryParse(String dateString)
{
    for (String formatString : formatStrings)
    {
        try
        {
            return new SimpleDateFormat(formatString).parse(dateString);
        }
        catch (ParseException e) {}
    }

    return null;
}
66
Matt Ball

Как насчет определения нескольких шаблонов? Они могут исходить из конфигурационного файла, содержащего известные шаблоны, жестко запрограммированные так:

List<SimpleDateFormat> knownPatterns = new ArrayList<SimpleDateFormat>();
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"));
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm.ss'Z'"));
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"));
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss"));
knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"));

for (SimpleDateFormat pattern : knownPatterns) {
    try {
        // Take a try
        return new Date(pattern.parse(candidate).getTime());

    } catch (ParseException pe) {
        // Loop on
    }
}
System.err.println("No known Date format found: " + candidate);
return null;
17
xdjkx

Вышеуказанный подход Мэтта подходит, но имейте в виду, что у вас возникнут проблемы, если вы будете использовать его для различения дат в формате y/M/d и d/M/y. Например, средство форматирования, инициализированное с помощью y/M/d, примет такую ​​дату, как 01/01/2009, и вернет вам дату, которая явно не соответствует вашей. Я исправил проблему следующим образом, но у меня ограниченное время, и я не доволен решением по двум основным причинам:

  1. Это нарушает одно из требований Джоша Блоха, а именно: «Не используйте исключения для обработки потока программы».
  2. Я вижу, что метод getDateFormat() становится чем-то вроде кошмара, если он вам нужен для обработки множества других форматов дат.

Если бы мне нужно было создать что-то, что могло бы обрабатывать множество разных форматов дат и требовало высокой производительности, я думаю, я бы использовал подход создания перечисления, связывающего каждое другое регулярное выражение даты с его форматом. Затем используйте MyEnum.values(), чтобы пройтись по перечислению и проверить с помощью if(myEnum.getPattern().matches(date)), а не перехватывать dateformatexception.

В любом случае, следующие данные могут обрабатывать даты форматов 'y/M/d' 'y-M-d' 'y M d' 'd/M/y' 'd-M-y' 'd M y' и все другие варианты тех, которые также включают форматы времени:

import Java.text.ParseException;
import Java.text.SimpleDateFormat;
import Java.util.Date;

public class DateUtil {
    private static final String[] timeFormats = {"HH:mm:ss","HH:mm"};
    private static final String[] dateSeparators = {"/","-"," "};

    private static final String DMY_FORMAT = "dd{sep}MM{sep}yyyy";
    private static final String YMD_FORMAT = "yyyy{sep}MM{sep}dd";

    private static final String ymd_template = "\\d{4}{sep}\\d{2}{sep}\\d{2}.*";
    private static final String dmy_template = "\\d{2}{sep}\\d{2}{sep}\\d{4}.*";

    public static Date stringToDate(String input){
    Date date = null;
    String dateFormat = getDateFormat(input);
    if(dateFormat == null){
        throw new IllegalArgumentException("Date is not in an accepted format " + input);
    }

    for(String sep : dateSeparators){
        String actualDateFormat = patternForSeparator(dateFormat, sep);
        //try first with the time
        for(String time : timeFormats){
        date = tryParse(input,actualDateFormat + " " + time);
        if(date != null){
            return date;
        }
        }
        //didn't work, try without the time formats
        date = tryParse(input,actualDateFormat);
        if(date != null){
        return date;
        }
    }

    return date;
    }

    private static String getDateFormat(String date){
    for(String sep : dateSeparators){
        String ymdPattern = patternForSeparator(ymd_template, sep);
        String dmyPattern = patternForSeparator(dmy_template, sep);
        if(date.matches(ymdPattern)){
        return YMD_FORMAT;
        }
        if(date.matches(dmyPattern)){
        return DMY_FORMAT;
        }
    }
    return null;
    }

    private static String patternForSeparator(String template, String sep){
    return template.replace("{sep}", sep);
    }

    private static Date tryParse(String input, String pattern){
    try{
        return new SimpleDateFormat(pattern).parse(input);
    }
    catch (ParseException e) {}
    return null;
    }


}
12
ChrisR

В Apache commons lang, DateUtils class у нас есть метод parseDate. Мы можем использовать это для разбора даты.

Также в другой библиотеке Joda-time также есть метод parse date.

8
SANN3

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

Date extractTimestampInput(String strDate){
    final List<String> dateFormats = Arrays.asList("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd");    

    for(String format: dateFormats){
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        try{
            return sdf.parse(strDate);
        } catch (ParseException e) {
             //intentionally empty
        }
    }
        throw new IllegalArgumentException("Invalid input for date. Given '"+strDate+"', expecting format yyyy-MM-dd HH:mm:ss.SSS or yyyy-MM-dd.");

}
4
locorecto

Для современного ответа я игнорирую требование использовать SimpleDateFormat. Хотя использование этого класса для синтаксического анализа было хорошей идеей в 2010 году, когда был задан этот вопрос, сейчас оно давно устарело. Замена DateTimeFormatter появилась в 2014 году. Идея в следующем почти такая же, как в принятом ответе.

private static DateTimeFormatter[] parseFormatters = Stream.of("M/yy", "M/y", "M/d/y", "M-d-y")
        .map(DateTimeFormatter::ofPattern)
        .toArray(DateTimeFormatter[]::new);

public static YearMonth parseYearMonth(String input) {
    for (DateTimeFormatter formatter : parseFormatters) {
        try {
            return YearMonth.parse(input, formatter);
        } catch (DateTimeParseException dtpe) {
            // ignore, try next format
        }
    }
    throw new IllegalArgumentException("Could not parse " + input);
}

Это анализирует каждую из входных строк из вопроса в год-месяц 2009-09. Важно сначала попробовать год, состоящий из двух цифр, поскольку "M/y" может также анализировать 9/09, но вместо этого 0009-09.

Ограничением вышеприведенного кода является то, что он игнорирует день месяца из строк, в которых он есть, например 9/1/2009. Может быть, это нормально, если большинство форматов имеют только месяц и год. Чтобы подобрать его, мы должны попробовать LocalDate.parse() вместо YearMonth.parse() для форматов, которые включают d в строку шаблона. Конечно, это может быть сделано.

1
Ole V.V.

Вот полный пример (с методом main), который можно добавить в виде служебного класса в вашем проекте. Весь формат, упомянутый в SimpleDateFormate API, поддерживается в следующем методе. 

import Java.text.ParseException;
import Java.text.SimpleDateFormat;
import Java.util.Date;

import org.Apache.commons.lang.time.DateUtils;

public class DateUtility {

    public static Date parseDate(String inputDate) {

        Date outputDate = null;
        String[] possibleDateFormats =
              {
                    "yyyy.MM.dd G 'at' HH:mm:ss z",
                    "EEE, MMM d, ''yy",
                    "h:mm a",
                    "hh 'o''clock' a, zzzz",
                    "K:mm a, z",
                    "yyyyy.MMMMM.dd GGG hh:mm aaa",
                    "EEE, d MMM yyyy HH:mm:ss Z",
                    "yyMMddHHmmssZ",
                    "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
                    "yyyy-MM-dd'T'HH:mm:ss.SSSXXX",
                    "YYYY-'W'ww-u",
                    "EEE, dd MMM yyyy HH:mm:ss z", 
                    "EEE, dd MMM yyyy HH:mm zzzz",
                    "yyyy-MM-dd'T'HH:mm:ssZ",
                    "yyyy-MM-dd'T'HH:mm:ss.SSSzzzz", 
                    "yyyy-MM-dd'T'HH:mm:sszzzz",
                    "yyyy-MM-dd'T'HH:mm:ss z",
                    "yyyy-MM-dd'T'HH:mm:ssz", 
                    "yyyy-MM-dd'T'HH:mm:ss",
                    "yyyy-MM-dd'T'HHmmss.SSSz",
                    "yyyy-MM-dd",
                    "yyyyMMdd",
                    "dd/MM/yy",
                    "dd/MM/yyyy"
              };

        try {

            outputDate = DateUtils.parseDate(inputDate, possibleDateFormats);
            System.out.println("inputDate ==> " + inputDate + ", outputDate ==> " + outputDate);

        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return outputDate;

    }

    public static String formatDate(Date date, String requiredDateFormat) {
        SimpleDateFormat df = new SimpleDateFormat(requiredDateFormat);
        String outputDateFormatted = df.format(date);
        return outputDateFormatted;
    }

    public static void main(String[] args) {

        DateUtility.parseDate("20181118");
        DateUtility.parseDate("2018-11-18");
        DateUtility.parseDate("18/11/18");
        DateUtility.parseDate("18/11/2018");
        DateUtility.parseDate("2018.11.18 AD at 12:08:56 PDT");
        System.out.println("");
        DateUtility.parseDate("Wed, Nov 18, '18");
        DateUtility.parseDate("12:08 PM");
        DateUtility.parseDate("12 o'clock PM, Pacific Daylight Time");
        DateUtility.parseDate("0:08 PM, PDT");
        DateUtility.parseDate("02018.Nov.18 AD 12:08 PM");
        System.out.println("");
        DateUtility.parseDate("Wed, 18 Nov 2018 12:08:56 -0700");
        DateUtility.parseDate("181118120856-0700");
        DateUtility.parseDate("2018-11-18T12:08:56.235-0700");
        DateUtility.parseDate("2018-11-18T12:08:56.235-07:00");
        DateUtility.parseDate("2018-W27-3");
    }

}
1
Vinayak Dornala

При работе в Java 1.8 вы можете использовать DateTimeFormatterBuilder

public static boolean isTimeStampValid(String inputString)
{
    DateTimeFormatterBuilder dateTimeFormatterBuilder = new DateTimeFormatterBuilder()
            .append(DateTimeFormatter.ofPattern("" + "[yyyy-MM-dd'T'HH:mm:ss.SSSZ]" + "[yyyy-MM-dd]"));

    DateTimeFormatter dateTimeFormatter = dateTimeFormatterBuilder.toFormatter();

    try {
        dateTimeFormatter.parse(inputString);
        return true;
    } catch (DateTimeParseException e) {
        return false;
    }
}

Смотрите пост: Java 8 Дата эквивалентна Joda's DateTimeFormatterBuilder с несколькими форматами синтаксического анализатора?

1
Aaron G.

В scala реализовано то же самое. Пожалуйста, помогите себе с преобразованием в Java, основная логика и используемые функции остаются прежними. 

import Java.text.SimpleDateFormat
import org.Apache.commons.lang.time.DateUtils

object MultiDataFormat {
  def main(args: Array[String]) {

val dates =Array("2015-10-31","26/12/2015","19-10-2016")

val possibleDateFormats:Array[String] = Array("yyyy-MM-dd","dd/MM/yyyy","dd-MM-yyyy")

val sdf =  new SimpleDateFormat("yyyy-MM-dd") //change it as per the requirement
  for (date<-dates) {
    val outputDate = DateUtils.parseDateStrictly(date, possibleDateFormats)
    System.out.println("inputDate ==> " + date + ", outputDate ==> " +outputDate + " " + sdf.format(outputDate) )
  }
}

}

0
Sairam Asapu