web-dev-qa-db-fra.com

Comment analyser des dates dans plusieurs formats à l'aide de SimpleDateFormat

J'essaie d'analyser certaines dates qui sortent d'un document. Il semblerait que les utilisateurs aient entré ces dates dans un format similaire mais pas exact.

voici les formats:

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

Quelle est la meilleure façon d’essayer d’analyser tout cela? Celles-ci semblent être les plus courantes, mais je suppose que ce qui me pèse, c’est que si j’ai un schéma de "M/aaaa", ce n’est pas toujours le cas avant "MM/aaaa" Dois-je configurer mes blocs try/catch imbriqué de la manière la moins restrictive à la plus restrictive? il semble qu'il va falloir beaucoup de duplication de code pour que cela fonctionne correctement. 

40
Derek

Vous devrez utiliser un objet SimpleDateFormat différent pour chaque modèle différent. Cela dit, vous n’avez pas besoin de tant de choses différentes, grâce à cela :

Number: Pour le formatage, le nombre de lettres de motif est le nombre minimum de chiffres, et les nombres les plus courts sont remplis de zéros. Pour l'analyse, le nombre de lettres de modèle est ignoré sauf s'il est nécessaire de séparer deux champs adjacents.

Donc, vous aurez besoin de ces formats:

  • "M/y" (qui couvre 9/09, 9/2009 et 09/2009)
  • "M/d/y" (qui couvre 9/1/2009)
  • "M-d-y" (qui couvre 9-1-2009)

Donc, mon conseil serait d'écrire une méthode qui fonctionne à peu près comme ceci (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

Pourquoi ne pas définir plusieurs modèles? Ils peuvent provenir d’un fichier de configuration contenant des modèles connus.

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

L'approche de Matt ci-dessus convient, mais sachez que vous rencontrerez des problèmes si vous l'utilisez pour différencier les dates du format y/M/d et d/M/y. Par exemple, un formateur initialisé avec y/M/d acceptera une date telle que 01/01/2009 et vous renverra une date qui n'est clairement pas celle que vous souhaitiez. J'ai résolu le problème comme suit, mais mon temps est limité et je ne suis pas satisfait de la solution pour deux raisons principales:

  1. Il enfreint l'une des lignes directrices de Josh Bloch, en particulier "n'utilisez pas d'exceptions pour gérer le flux de programmes".
  2. Je peux voir la méthode getDateFormat() devenir un peu cauchemard si vous en aviez besoin pour gérer de nombreux autres formats de date.

Si je devais créer quelque chose capable de gérer un grand nombre de formats de date différents et d’être très performant, je pense que j’utiliserais l’approche de création d’un enum liant chaque regex de date à son format. Utilisez ensuite MyEnum.values() pour parcourir l'énumération et tester avec if(myEnum.getPattern().matches(date)) plutôt que de capturer une exception dateformatexception.

Cela dit, les éléments suivants peuvent gérer les dates des formats 'y/M/d' 'y-M-d' 'y M d' 'd/M/y' 'd-M-y' 'd M y' et toutes les autres variantes de celles qui incluent les formats de l'heure:

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

Dans Apache commons lang, DateUtils class, nous avons une méthode appelée parseDate. Nous pouvons utiliser cela pour analyser la date.

De plus, une autre bibliothèque Joda-time a également la méthode pour analyser la date.

8
SANN3

Cette solution vérifie tous les formats possibles avant de lancer une exception. Cette solution est plus pratique si vous essayez de tester plusieurs formats de date.

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

Pour la réponse moderne, j'ignore l'obligation d'utiliser SimpleDateFormat. L'utilisation de cette classe pour l'analyse était une bonne idée en 2010 lorsque cette question a été posée, mais elle est maintenant obsolète. Le remplacement, DateTimeFormatter, est sorti en 2014. L'idée ci-dessous est quasiment la même chose que dans la réponse acceptée.

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

Cela analyse chacune des chaînes d'entrée de la question dans un mois-année de 2009-09. Il est important d’essayer d’abord l’année à deux chiffres car "M/y" pourrait également analyser 9/09, mais plutôt dans 0009-09.

Une limitation du code ci-dessus est qu'il ignore le jour du mois parmi les chaînes qui en ont un, comme 9/1/2009. C’est peut-être acceptable tant que la plupart des formats n’ont que le mois et l’année. Pour le récupérer, nous devrions essayer LocalDate.parse() plutôt que YearMonth.parse() pour les formats qui incluent d dans la chaîne de modèle. Cela peut sûrement être fait.

1
Ole V.V.

Voici l'exemple complet (avec la méthode main) que vous pouvez ajouter en tant que classe d'utilitaires dans votre projet. Tous les formats mentionnés dans SimpleDateFormate API sont pris en charge dans la méthode ci-dessous. 

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

Si vous travaillez en Java 1.8, vous pouvez utiliser le 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;
    }
}

Voir le message: Java 8 Date équivalente à DateTimeFormatterBuilder de Joda avec plusieurs formats d’analyseur?

1
Aaron G.

J'avais plusieurs formats de date dans json et extrayais le CSV avec le format universel. J'ai regardé plusieurs endroits, essayé différentes manières, mais à la fin je suis capable de convertir avec le code simple suivant.

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

Mis en œuvre de la même manière dans scala, aidez-vous à convertir Java, la logique et les fonctions principales restent les mêmes 

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

En utilisant DateTimeFormatter, vous pouvez obtenir les résultats suivants:


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

Faites attention à String pattern, c'est la combinaison de plusieurs motifs. Ouvrez [ et fermez ] entre crochets, vous pouvez mentionner tout type de motif.

0
Rakesh SKadam