Como analisar datas em vários formatos usando SimpleDateFormat

Eu estou tentando analisar algumas datas que estão saindo de um documento. Parece que os usuários inseriram essas datas em um formato semelhante, mas não exato.

aqui estão os formatos:

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

Qual é a melhor maneira de tentar analisar tudo isso? Estes parecem ser os mais comuns, mas eu acho que o que está me deixando de fora é que, se eu tenho um padrão de “M / yyyy” costuma pegar antes “MM / aaaa” Eu tenho que configurar meus blocos try / catch nested de forma menos restritiva para a forma mais restritiva? Parece que vai ter muita duplicação de código para fazer isso direito.

Você precisará usar um object SimpleDateFormat diferente para cada padrão diferente. Dito isto, você não precisa de muitos diferentes, graças a isso :

Número: para formatação, o número de letras padrão é o número mínimo de dígitos e os números mais curtos são preenchidos com zero com esse valor. Para análise, o número de letras padrão é ignorado, a menos que seja necessário separar dois campos adjacentes.

Então, você precisará desses formatos:

  • "M/y" (que abrange 9/09 , 9/2009 e 09/2009 )
  • "M/d/y" (que abrange 9/1/2009 )
  • "Mdy" (que cobre 9-1-2009 )

Então, meu conselho seria escrever um método que funciona algo assim ( não testado ):

 // ... List formatStrings = Arrays.asList("M/y", "M/d/y", "Mdy"); // ... Date tryParse(String dateString) { for (String formatString : formatStrings) { try { return new SimpleDateFormat(formatString).parse(dateString); } catch (ParseException e) {} } return null; } 

Que tal apenas definir vários padrões? Eles podem vir de um arquivo de configuração contendo padrões conhecidos, codificados como:

 List knownPatterns = new ArrayList(); 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; 

A abordagem de Matt acima está bem, mas esteja ciente de que você terá problemas se você usá-lo para diferenciar as datas do formato y/M/d d/M/y . Por exemplo, um formatador inicializado com y/M/d aceitará uma data como 01/01/2009 e retornará uma data que claramente não é o que você queria. Corrigi o problema da seguinte forma, mas tenho tempo limitado e não estou satisfeito com a solução por duas razões principais:

  1. Ele viola uma das diretrizes de Josh Bloch, especificamente “não use exceções para lidar com o stream do programa”.
  2. Eu posso ver o método getDateFormat() se tornando um pesadelo se você precisasse lidar com muitos outros formatos de data.

Se eu tivesse que fazer algo que pudesse lidar com muitos e diferentes formatos de data e precisasse ter alto desempenho, então eu acho que usaria a abordagem de criar um enum que vinculasse cada data regex diferente ao seu formato. Em seguida, use MyEnum.values() para percorrer o enum e testar com if(myEnum.getPattern().matches(date)) vez de capturar um dateformatexception.

Anualmente, dito isto, o seguinte pode lidar com datas dos formatos 'y/M/d' 'yMd' 'y M d' 'd/M/y' 'dMy' 'd M y' e todas as outras variações daqueles que incluem também os formatos de hora:

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

Na class Apache commons lang, DateUtils , temos um método chamado parseDate. Podemos usar isso para analisar a data.

Também outra biblioteca Joda-time também tem o método para analisar a data.

Esta solução verifica todos os formatos possíveis antes de lançar uma exceção. Essa solução é mais conveniente se você estiver tentando testar vários formatos de data.

 Date extractTimestampInput(String strDate){ final List 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."); } 

Se estiver trabalhando no Java 1.8, você poderá aproveitar o 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; } } 

Veja a postagem: Java 8 Data equivalente ao DateTimeFormatterBuilder do Joda com vários formatos de analisador?

Para a resposta moderna, estou ignorando o requisito de usar SimpleDateFormat . Embora o uso dessa class para análise tenha sido uma boa ideia em 2010, quando essa pergunta foi feita, agora ela está muito desatualizada. A substituição, DateTimeFormatter , saiu em 2014. A ideia a seguir é praticamente a mesma que na resposta aceita.

 private static DateTimeFormatter[] parseFormatters = Stream.of("M/yy", "M/y", "M/d/y", "Mdy") .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); } 

Isso analisa cada uma das seqüências de input da pergunta em um ano-mês de 2009-09 . É importante tentar o primeiro ano com dois dígitos, pois "M/y" também poderia analisar 9/09 , mas em 0009-09 .

Uma limitação do código acima é que ele ignora o dia do mês das strings que possuem um, como 9/1/2009 . Talvez esteja tudo bem, desde que a maioria dos formatos tenha apenas um mês e um ano. Para pegá-lo, precisaríamos tentar LocalDate.parse() vez de YearMonth.parse() para os formatos que incluem d na string padrão. Certamente isso pode ser feito.

Aqui está o exemplo completo (com o método principal) que pode ser adicionado como uma class de utilitário em seu projeto. Todo o formato mencionado na API SimpleDateFormate é suportado no método abaixo.

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