web-dev-qa-db-fra.com

Pièces facultatives dans SimpleDateFormat

Je lis des chaînes de date qui pourraient être avec ou sans ajustement de fuseau horaire: yyyyMMddHHmmssz ou yyyyMMddHHmmss. Quand une chaîne manque une zone, je la traiterai comme GMT. Je ne vois aucun moyen de créer des sections facultatives dans une SimpleDateFormat, mais peut-être qu'il me manque quelque chose. Existe-t-il un moyen de faire cela avec une SimpleDateFormat ou devrais-je simplement écrire un nouveau DateFormat concret pour gérer cela?

36
traffichazard

Je créerais deux SimpleDateFormat, un avec un fuseau horaire et un sans. Vous pouvez regarder la longueur de la chaîne pour déterminer laquelle utiliser.


On dirait que vous avez besoin d'un DateFormat qui délègue à deux SDF différents.

DateFormat df = new DateFormat() {
    static final String FORMAT1 = "yyyyMMddHHmmss";
    static final String FORMAT2 = "yyyyMMddHHmmssz";
    final SimpleDateFormat sdf1 = new SimpleDateFormat(FORMAT1);
    final SimpleDateFormat sdf2 = new SimpleDateFormat(FORMAT2);
    @Override
    public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Date parse(String source, ParsePosition pos) {
        if (source.length() - pos.getIndex() == FORMAT1.length())
            return sdf1.parse(source, pos);
        return sdf2.parse(source, pos);
    }
};
System.out.println(df.parse("20110102030405"));
System.out.println(df.parse("20110102030405PST"));
20
Peter Lawrey

JSR-310 est livré avec Java 8, qui offre une prise en charge améliorée de l'analyse des valeurs temporelles, les composants pouvant désormais être optionnels. Vous pouvez non seulement rendre la zone facultative, mais vous pouvez également rendre le composant heure facultatif et renvoyer l'unité temporelle correcte pour la chaîne donnée.

Considérez les cas de test suivants.

public class DateFormatTest {

    private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(
            "yyyy-MM-dd[[ ]['T']HH:mm[:ss][XXX]]");

    private TemporalAccessor parse(String v) {
        return formatter.parseBest(v,
                                   ZonedDateTime::from,
                                   LocalDateTime::from,
                                   LocalDate::from);
    }

    @Test public void testDateTime1() {
        assertEquals(LocalDateTime.of(2014, 9, 23, 14, 20, 59),
                     parse("2014-09-23T14:20:59"));
    }

    @Test public void testDateTime2() {
        assertEquals(LocalDateTime.of(2014, 9, 23, 14, 20),
                     parse("2014-09-23 14:20"));
    }

    @Test public void testDateOnly() {
        assertEquals(LocalDate.of(2014, 9, 23), parse("2014-09-23"));
    }

    @Test public void testZonedDateTime() {
        assertEquals(ZonedDateTime.of(2014, 9, 23, 14, 20, 59, 0,
                                      ZoneOffset.ofHoursMinutes(10, 30)),
                     parse("2014-09-23T14:20:59+10:30"));
    }

}

Ici, le motif DateTimeFormatter de "yyyy-MM-dd[[ ]['T']HH:mm[:ss][XXX]]" autorise les options dans les parenthèses carrées, qui peuvent également être imbriquées. Les modèles peuvent également être construits à partir de DateTimeFormatterBuilder , dont le modèle ci-dessus est démontré ici:

private final DateTimeFormatter formatter = new DateTimeFormatterBuilder()
        .parseCaseInsensitive()
        .append(DateTimeFormatter.ISO_LOCAL_DATE)
        .optionalStart()
        .optionalStart()
        .appendLiteral(' ')
        .optionalEnd()
        .optionalStart()
        .appendLiteral('T')
        .optionalEnd()
        .appendOptional(DateTimeFormatter.ISO_TIME)
        .toFormatter();

Cela se traduirait par une expression qui ressemble à ceci:

yyyy-MM-dd[[' ']['T']HH:mm[':'ss[.SSS]]].

Les valeurs facultatives peuvent être imbriquées et sont également automatiquement fermées à la fin si elles sont encore ouvertes. Notez cependant qu’il n’existe aucun moyen de fournir un OR exclusif sur les pièces facultatives. Le format ci-dessus analyserait donc la valeur suivante de manière assez précise:

2018-03-08 T11:12

Notez la capacité très pratique de réutiliser les formateurs existants en tant que parties de notre format actuel.

43
Brett Ryan

Je sais que c'est un ancien post mais juste pour l'enregistrement ...

La classe Apache DateUtils peut vous aider.

String[] acceptedFormats = {"dd/MM/yyyy","dd/MM/yyyy HH:mm","dd/MM/yyyy HH:mm:ss"};
Date date1 = DateUtils.parseDate("12/07/2012", acceptedFormats);
Date date2 = DateUtils.parseDate("12/07/2012 23:59:59", acceptedFormats);

Lien vers la bibliothèque Apache Commons Lang dans le référentiel Maven, vous pouvez vérifier quelle est la dernière version:
http://mvnrepository.com/artifact/org.Apache.commons/commons-lang3

Maven v3.4

<dependency>
    <groupId>org.Apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.4</version>
</dependency>

Gradle v3.4

'org.Apache.commons:commons-lang3:3.4'
30
tbraun

Si vous pouvez utiliser Joda Datetime, il prend en charge des pièces facultatives dans le formateur, par exemple, "aaaa-MM-jj [hh: mm: ss]"

private DateTimeFormatter fmt = new DateTimeFormatterBuilder()
.append(DateTimeFormat.forPattern("yyyy-MM-dd"))                                            
.appendOptional(
    new DateTimeFormatterBuilder()
    .appendLiteral(' ')
    .append(DateTimeFormat.forPattern("HH:mm:ss"))
    .toParser()
.toFormatter();
3
hgh

Je voudrais parcourir la liste d'objets DateFormat potentiels en utilisant une opération try-catch pour rompre la boucle lors de la première analyse réussie. 

2
Johan Sjöberg

J'ai résolu un problème similaire il y a quelque temps en étendant SimpleDateFormat. Ci-dessous une implémentation grossière pour montrer l'idée de ma solution. Il peut ne pas être complètement complet/optimisé.

public class MySimpleDateFormat extends SimpleDateFormat {
    private static final long serialVersionUID = 1L;
    private static String FORMAT = "        
    private static int FORMAT_LEN = "yyyyMMddHHmmss".length();
    private static String TZ_ID = "GMT";

    public MySimpleDateFormat() {
            this(TimeZone.getTimeZone(TZ_ID));
    }

    public MySimpleDateFormat(TimeZone tz) {
            super(FORMAT);
            setTimeZone(tz);
    }

    @Override
    public Date parse(String source, ParsePosition pos) {
            // TODO: args validation
            int offset = pos.getIndex() + FORMAT_LEN;
            Date result;
            if (offset < source.length()) {
                    // there maybe is a timezone
                    result = super.parse(source, pos);
                    if (result != null) {
                            return result;
                    }
                    if (pos.getErrorIndex() >= offset) {
                            // there isn't a TZ after all
                            String part0 = source.substring(0, offset);
                            String part1 = source.substring(offset);
                            ParsePosition anotherPos = new ParsePosition(pos.getIndex());
                            result = super.parse(part0 + TZ_ID + part1, anotherPos);
                            if(result == null) {
                                    pos.setErrorIndex(anotherPos.getErrorIndex());
                            } else {
                                    // check SimpleDateFormat#parse javadoc to implement correctly the pos updates
                                    pos.setErrorIndex(-1);
                                    pos.setIndex(offset);
                            }
                            return result;
                    }
                    // there's something wrong with the first FORMAT_LEN chars
                    return null;
            }
            result = super.parse(source + TZ_ID, pos);
            if(result != null) {
                    pos.setIndex(pos.getIndex() - TZ_ID.length());
            }
            return result;
    }

    public static void main(String [] args) {
            ParsePosition pos = new ParsePosition(0);
            MySimpleDateFormat mySdf = new MySimpleDateFormat();
            System.out.println(mySdf.parse("20120622131415", pos) + " -- " + pos);
            pos = new ParsePosition(0);
            System.out.println(mySdf.parse("20120622131415GMT", pos) + " -- " + pos);
            pos = new ParsePosition(0);
            System.out.println(mySdf.parse("20120622131415xxx", pos) + " -- " + pos);
            pos = new ParsePosition(0);
            System.out.println(mySdf.parse("20120x22131415xxx", pos) + " -- " + pos);
    }
}

En résumé, vous devez vérifier la chaîne d'entrée et "deviner" que le champ TZ est manquant, ajoutez-le si c'est le cas, puis laissez la fonction SimpleDateFormat#parse(String, ParsePosition) faire le reste. L'implémentation ci-dessus ne met pas à jour ParsePosition conformément au contrat sous javadoc dans SimpleDateFormat#parse(String, ParsePosition)

La classe a un seul ctor par défaut car un seul format est autorisé.

La méthode MySimpleDateFormat#parse(String, ParsePosition) est invoquée par SimpleDateFormat#parse(String) et est donc suffisante pour couvrir les deux cas.

Exécuter le main () ceci est la sortie (comme prévu)

Fri Jun 22 14:14:15 BST 2012 -- Java.text.ParsePosition[index=14,errorIndex=-1]
Fri Jun 22 14:14:15 BST 2012 -- Java.text.ParsePosition[index=17,errorIndex=-1]
Fri Jun 22 14:14:15 BST 2012 -- Java.text.ParsePosition[index=14,errorIndex=-1]
null -- Java.text.ParsePosition[index=0,errorIndex=5]
0
smartrics

Vous pouvez créer deux SimpleDateFormats différents comme

 public PWMDateTimeFormatter(String aPatternStr)
    {
        _formatter = DateTimeFormat.forPattern(aPatternStr);
    }



    public PWMDateTimeFormatter(String aPatternStr, TimeZone aZone)
    {
        _formatter =   DateTimeFormat.forPattern(aPatternStr).withZone(XXDateTime._getTimeZone(aZone));
    }
0
GustyWind