web-dev-qa-db-de.com

Wie analysiert man Datumsangaben in mehreren Formaten mit SimpleDateFormat?

Ich versuche, einige Daten zu analysieren, die aus einem Dokument stammen. Anscheinend haben Benutzer diese Daten in einem ähnlichen, aber nicht exakten Format eingegeben.

hier sind die Formate:

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

Was ist der beste Weg, um zu versuchen, all diese zu analysieren? Diese scheinen am häufigsten zu sein, aber ich denke, was mich aufhängt, ist, dass wenn ich ein Muster von "M/jjjj" habe, das nicht immer vor "MM/jjjj" greift. Muss ich meine try/catch-Blöcke einrichten auf wenig restriktive bis restriktive Weise verschachtelt? Es scheint, als würde es sicher eine Menge Code-Duplizierung erfordern, um dies richtig zu machen. 

40
Derek

Sie müssen für jedes Muster ein anderes SimpleDateFormat-Objekt verwenden. Das heißt, Sie brauchen nicht so viele verschiedene, dank dieser :

Number: Bei der Formatierung ist die Anzahl der Musterbuchstaben die Mindestanzahl von Ziffern, und kürzere Zahlen werden auf diesen Wert mit Null aufgefüllt. Bei der Analyse wird die Anzahl der Musterbuchstaben ignoriert, sofern nicht zwei benachbarte Felder voneinander getrennt werden müssen.

Sie benötigen also folgende Formate:

  • "M/y" (deckt 9/09, 9/2009 und 09/2009 ab)
  • "M/d/y" (der 9/1/2009 abdeckt)
  • "M-d-y" (der 9-1-2009 abdeckt)

Mein Rat wäre also, eine Methode zu schreiben, die etwa so funktioniert (ungetested):

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

Wie wäre es, mehrere Muster zu definieren? Sie könnten aus einer Konfigurationsdatei stammen, die bekannte Muster enthält, die hartcodiert lauten:

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

Der oben beschriebene Ansatz von Matt ist in Ordnung, aber seien Sie sich bewusst, dass Sie auf Probleme stoßen werden, wenn Sie zwischen Datumsangaben des Formats y/M/d und d/M/y unterscheiden. Ein mit y/M/d initialisiertes Formatierungsprogramm akzeptiert beispielsweise ein Datum wie 01/01/2009 und gibt Ihnen ein Datum zurück, das eindeutig nicht Ihren Wünschen entspricht. Ich habe das Problem wie folgt behoben, aber ich habe wenig Zeit und bin aus zwei Gründen nicht zufrieden mit der Lösung:

  1. Es verstößt gegen eine der Regeln von Josh Bloch, insbesondere "Keine Ausnahmen verwenden, um den Programmfluss zu handhaben".
  2. Ich kann sehen, dass die getDateFormat()-Methode zu einem Albtraum wird, wenn Sie es für viele andere Datumsformate benötigen.

Wenn ich etwas erstellen musste, das mit vielen verschiedenen Datumsformaten umgehen konnte und hochperformant sein sollte, würde ich den Ansatz verwenden, ein Enum zu erstellen, das die verschiedenen Datumsregexen mit dem jeweiligen Format verknüpft. Verwenden Sie dann MyEnum.values(), um das Enum zu durchlaufen und testen Sie mit if(myEnum.getPattern().matches(date)), anstatt eine Dateformatexception zu erfassen.

Wie dem auch sei, das Folgende kann mit Datumsangaben der Formate 'y/M/d' 'y-M-d' 'y M d' 'd/M/y' 'd-M-y' 'd M y' und allen anderen Variationen derjenigen umgehen, die auch Zeitformate enthalten:

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

In der Klasse Apache commons lang, DateUtils haben wir eine Methode namens parseDate. Wir können dies zur Analyse des Datums verwenden.

Auch eine andere Bibliothek-Joda-Zeit hat die Methode, das Datum zu parse .

8
SANN3

Diese Lösung überprüft alle möglichen Formate, bevor eine Ausnahme ausgelöst wird. Diese Lösung ist praktischer, wenn Sie versuchen, mehrere Datumsformate zu testen.

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

Für die moderne Antwort ignoriere ich die Anforderung, SimpleDateFormat zu verwenden. Die Verwendung dieser Klasse für das Parsen war 2010 eine gute Idee, als diese Frage gestellt wurde, sie ist jedoch längst überholt. Der Ersatz, DateTimeFormatter, kam 2014 heraus. Die folgende Idee ist ziemlich dieselbe wie in der akzeptierten Antwort.

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

Dadurch wird jede der eingegebenen Zeichenfolgen aus der Frage in einen Jahr-Monat von 2009-09 analysiert. Es ist wichtig, zuerst das zweistellige Jahr auszuprobieren, da "M/y" auch 9/09 analysieren kann, stattdessen in 0009-09.

Eine Einschränkung des obigen Codes besteht darin, dass er den Tag des Monats aus den Zeichenfolgen ignoriert, die einen enthalten, wie 9/1/2009. Vielleicht ist es in Ordnung, solange die meisten Formate nur Monat und Jahr haben. Um es aufzugreifen, müssten wir LocalDate.parse() anstelle von YearMonth.parse() für die Formate versuchen, die d in der Musterzeichenfolge enthalten. Sicher kann es gemacht werden.

1
Ole V.V.

Hier ist das vollständige Beispiel (mit der Hauptmethode), das als Dienstklasse in Ihrem Projekt hinzugefügt werden kann. Alle in SimpleDateFormate API genannten Formate werden in der folgenden Methode unterstützt. 

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

Wenn Sie in Java 1.8 arbeiten, können Sie den DateTimeFormatterBuilder nutzen

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

Siehe post: Java 8 Datum äquivalent zu Jodas DateTimeFormatterBuilder mit mehreren Parserformaten?

1
Aaron G.

Ich hatte mehrere Datumsformate in JSON und extrahierte CSV mit Universal-Format. Ich habe an mehreren Stellen nachgeschaut, verschiedene Methoden ausprobiert, aber am Ende kann ich mit dem folgenden einfachen Code konvertieren.

private String getDate(String anyDateFormattedString) {
    @SuppressWarnings("deprecation")
    Date date = new Date(anyDateFormattedString);
    SimpleDateFormat dateFormat = new SimpleDateFormat(yourDesiredDateFormat);
        String convertedDate = dateFormat.format(date);
    return convertedDate;
}
0
Tushar Chavda

Gleiches in scala implementiert. Bitte helfen Sie sich bei der Konvertierung nach Java. Die verwendete Logik und Funktionen bleiben unverändert. 

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

Mit DateTimeFormatter kann Folgendes erreicht werden:


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

Achten Sie auf String pattern, dies ist die Kombination mehrerer Muster. In offenen [ und geschlossenen ] eckigen Klammern können Sie jede Art von Mustern erwähnen.

0
Rakesh SKadam