Come analizzare le date in più formati utilizzando SimpleDateFormat

Sto cercando di analizzare alcune date che stanno uscendo da un documento. Sembra che gli utenti abbiano inserito queste date in un formato simile ma non esatto.

ecco i formati:

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

Qual è il modo migliore per provare a analizzare tutti questi? Questi sembrano essere i più comuni, ma suppongo che quello che mi sta impiccando è che se avrò uno schema di “M / yyyy” non lo catturerò sempre prima di “MM / aaaa” Devo impostare i miei blocchi try / catch nidificati in un modo meno restrittivo per il più restrittivo? sembra che ci vorrà un sacco di duplicati di codice per ottenere questo giusto.

Dovrai utilizzare un object SimpleDateFormat diverso per ogni modello diverso. Detto questo, non ne hai bisogno di tanti, grazie a questo :

Numero: per la formattazione, il numero di lettere del modello è il numero minimo di cifre, mentre i numeri più brevi sono riempiti a zero per questo importo. Per l’analisi, il numero di lettere di pattern viene ignorato a meno che non sia necessario separare due campi adiacenti.

Quindi, avrai bisogno di questi formati:

  • "M/y" (che copre il 9/09 , il 9/2009 e il 09/2009 )
  • "M/d/y" (che copre il 9/1/2009 )
  • "Mdy" (che copre il 9-1-2009 )

Quindi, il mio consiglio sarebbe di scrivere un metodo che funzioni in modo simile a questo ( non testato ):

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

Che dire solo definendo più modelli? Potrebbero provenire da un file di configurazione contenente pattern noti, codificati in modo rigido come:

 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; 

L’approccio di Matt sopra è soddisfacente, ma tieni presente che ti imbatterai in problemi se lo utilizzi per distinguere tra le date del formato y/M/d e d/M/y . Ad esempio, un formattatore inizializzato con y/M/d accetterà una data come 01/01/2009 e ti restituirà una data che chiaramente non è ciò che volevi. Ho risolto il problema come segue, ma ho un tempo limitato e non sono soddisfatto della soluzione per 2 motivi principali:

  1. Violare una delle linee guida di Josh Bloch, in particolare “non usare le eccezioni per gestire il stream del programma”.
  2. Posso vedere il metodo getDateFormat() diventare un po ‘un incubo se ti servisse per gestire molti altri formati di data.

Se dovessi creare qualcosa che potesse gestire molti e diversi formati di date e dovessi essere altamente performante, allora penso che utilizzerei l’approccio per creare un enum che collegasse ogni data regolare al suo formato. Quindi utilizzare MyEnum.values() per scorrere l’enum e testare if(myEnum.getPattern().matches(date)) piuttosto che prendere una dataformatexception.

Anzi, detto questo, quanto segue può gestire date dei formati 'y/M/d' 'yMd' 'y M d' 'd/M/y' 'dMy' 'd M y' e tutte le altre varianti di quelle che includono anche i formati temporali:

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

In Apache commons lang, la class DateUtils abbiamo un metodo chiamato parseDate. Possiamo usarlo per analizzare la data.

Anche un’altra libreria Joda-time ha il metodo per analizzare la data.

Questa soluzione controlla tutti i possibili formati prima di lanciare un’eccezione. Questa soluzione è più conveniente se stai provando a testare più formati di 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 lavori in Java 1.8 puoi sfruttare 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; } } 

Vedi post: Java 8 Data equivalente a DateTimeFormatterBuilder di Joda con più formati di parser?

Per la risposta moderna, sto ignorando il requisito di utilizzare SimpleDateFormat . L’utilizzo di questa class per l’analisi è stata una buona idea nel 2010, quando è stata posta questa domanda, che è ormai obsoleta. Il sostituto, DateTimeFormatter , è uscito nel 2014. L’idea di seguito è praticamente la stessa della risposta accettata.

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

Questo analizza ciascuna delle stringhe di input dalla domanda in un anno-mese del 2009-09 . È importante provare prima l’anno a due cifre poiché "M/y" potrebbe anche analizzare 9/09 , ma in 0009-09 .

Una limitazione del codice precedente è che ignora il giorno del mese dalle stringhe che ne hanno uno, come il 9/1/2009 . Forse è OK fino a quando la maggior parte dei formati ha solo mese e anno. Per raccoglierlo, dovremmo provare LocalDate.parse() piuttosto che YearMonth.parse() per i formati che includono d nella stringa del pattern. Sicuramente può essere fatto.

Ecco l’esempio completo (con il metodo principale) che può essere aggiunto come class di utilità nel progetto. Tutto il formato menzionato nell’API SimpleDateFormate è supportato nel metodo seguente.

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