Как анализировать даты в нескольких форматах, используя SimpleDateFormat - PullRequest
48 голосов
/ 26 октября 2010

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

вот форматы:

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

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

Ответы [ 10 ]

73 голосов
/ 26 октября 2010

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

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

Итак, вам понадобятся следующие форматы:

  • "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;
}
19 голосов
/ 18 июля 2013

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

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;
12 голосов
/ 12 декабря 2012

Подход Мэтта, описанный выше, подойдет, но учтите, что у вас возникнут проблемы, если вы будете использовать его для различения дат в формате 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;
    }


}
8 голосов
/ 15 февраля 2013

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

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

4 голосов
/ 07 июня 2013

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

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.");

}
2 голосов
/ 16 февраля 2018

Вот полный пример (с методом 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 голос
/ 26 июля 2017

Для современного ответа я игнорирую требование использовать 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 голос
/ 28 сентября 2016

Если вы работаете в 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 DateTimeFormatterBuilder с несколькими форматами синтаксического анализатора?

0 голосов
/ 24 мая 2019

Используя DateTimeFormatter, это может быть достигнуто следующим образом:


import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
import java.util.TimeZone;

public class DateTimeFormatTest {

    public static void main(String[] args) {

        String pattern = "[yyyy-MM-dd[['T'][ ]HH:mm:ss[.SSSSSSSz][.SSS[XXX][X]]]]";
        String timeSample = "2018-05-04T13:49:01.7047141Z";
        SimpleDateFormat simpleDateFormatter = new SimpleDateFormat("dd/MM/yy HH:mm:ss");
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        TemporalAccessor accessor = formatter.parse(timeSample);
        ZonedDateTime zTime = LocalDateTime.from(accessor).atZone(ZoneOffset.UTC);

        Date date=new Date(zTime.toEpochSecond()*1000);
        simpleDateFormatter.setTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC));
        System.out.println(simpleDateFormatter.format(date));       
    }
}

Обратите внимание на String pattern, это комбинация нескольких шаблонов.В открытых [ и закрытых ] квадратных скобках можно указать любые виды рисунков.

0 голосов
/ 05 октября 2018

Реализовано то же самое в 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) )
  }
}

}

...