it-swarm.com.de

Optionale Teile in SimpleDateFormat

Ich lese Datumszeichenfolgen, die mit oder ohne Zeitzonenanpassung sein könnten: yyyyMMddHHmmssz oder yyyyMMddHHmmss. Wenn einer Zeichenfolge eine Zone fehlt, werde ich sie als GMT behandeln. Ich sehe keine Möglichkeit, optionale Abschnitte in einer SimpleDateFormat zu erstellen, aber vielleicht fehlt mir etwas. Gibt es eine Möglichkeit, dies mit einer SimpleDateFormat zu tun, oder sollte ich einfach eine neue konkrete DateFormat schreiben, um damit umzugehen?

36
traffichazard

Ich würde zwei SimpleDateFormat erstellen, eines mit einer Zeitzone und eines ohne. Sie können die Länge der Zeichenfolge überprüfen, um die zu verwendende Länge zu bestimmen.


Klingt nach einem DateFormat, der an zwei verschiedene SDF delegiert.

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

Der JSR-310 wurde mit Java 8 ausgeliefert, das erweiterte Unterstützung für die Analyse temporärer Werte bietet, wobei Komponenten jetzt optional sein können. Sie können die Zone nicht nur optional machen, sondern auch die Zeitkomponente als optional und die korrekte Zeiteinheit für die angegebene Zeichenfolge zurückgeben.

Betrachten Sie die folgenden Testfälle.

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

}

Das DateTimeFormatter - Muster von "yyyy-MM-dd[[ ]['T']HH:mm[:ss][XXX]]" erlaubt hier Optionale innerhalb der eckigen Klammern, die auch geschachtelt werden können. Muster können auch aus einem DateTimeFormatterBuilder erstellt werden, wobei das obige Muster hier gezeigt wird:

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

Dies würde zu einem Ausdruck führen, der wie folgt aussieht:

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

Optionale Werte können verschachtelt werden und werden am Ende automatisch geschlossen, wenn sie noch geöffnet sind. Beachten Sie jedoch, dass es keine Möglichkeit gibt, ein exklusives OR für optionale Teile bereitzustellen. Daher würde das obige Format den folgenden Wert tatsächlich in Ordnung bringen:

2018-03-08 T11:12

Beachten Sie die wirklich nette Funktion, mit der wir vorhandene Formatierer als Teile unseres aktuellen Formats wiederverwenden können.

43
Brett Ryan

Ich weiß, das ist ein alter Beitrag, aber nur fürs Protokoll ...

Die Apache DateUtils-Klasse kann Ihnen dabei helfen.

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

Verknüpfen Sie mit der Bibliothek Apache Commons Lang im Maven-Repository. Vielleicht möchten Sie die aktuelle Version überprüfen:
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

Wenn Sie Joda Datetime verwenden können, werden optionale Teile im Formatierungsprogramm unterstützt, z. B. "JJJJ-MM-TT [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

Ich würde die Liste der möglichen DateFormat-Objekte mit einer try-catch-Operation durchlaufen, um die Schleife bei der ersten erfolgreichen Analyse zu unterbrechen. 

2
Johan Sjöberg

Ich habe vor einiger Zeit ein ähnliches Problem durch die Erweiterung von SimpleDateFormat gelöst. Nachfolgend eine grobe Implementierung, um die Idee meiner Lösung zu zeigen. Sie ist möglicherweise nicht vollständig/optimiert.

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

Das Wichtigste ist, dass Sie die Eingabezeichenfolge überprüfen und "vermuten" müssen, dass das TZ-Feld fehlt. Fügen Sie es hinzu, und lassen Sie den Rest die Funktion SimpleDateFormat#parse(String, ParsePosition) übernehmen. Die obige Implementierung aktualisiert ParsePosition nicht gemäß dem Vertrag in Javadoc in SimpleDateFormat#parse(String, ParsePosition).

Die Klasse hat einen einzigen Standardwert, da nur ein Format zulässig ist.

Die Methode MySimpleDateFormat#parse(String, ParsePosition) wird von SimpleDateFormat#parse(String) aufgerufen, es reicht also aus, um beide Fälle abzudecken.

Ausführen des main () Dies ist die Ausgabe (wie erwartet)

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

Sie können zwei verschiedene SimpleDateFormats-Elemente erstellen

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



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