Необязательные части в SimpleDateFormat - PullRequest
37 голосов
/ 05 мая 2011

Я читаю в строках даты, которые могут быть с настройкой часового пояса или без нее: yyyyMMddHHmmssz или yyyyMMddHHmmss. Когда в строке отсутствует зона, я буду рассматривать ее как GMT. Я не вижу никакого способа создания дополнительных разделов в SimpleDateFormat, но, возможно, я что-то упустил. Есть ли способ сделать это с SimpleDateFormat, или мне просто написать новый бетон DateFormat, чтобы справиться с этим?

Ответы [ 7 ]

47 голосов
/ 01 октября 2014

JSR-310 поставляется с Java 8, который обеспечивает расширенную поддержку синтаксического анализа временных значений, где компоненты теперь могут быть необязательными.Вы можете не только сделать зону необязательной, но вы также можете сделать компонент времени необязательным и вернуть правильную временную единицу для данной строки.

Рассмотрим следующие тестовые примеры.

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

}

Здесь шаблон DateTimeFormatter , равный "yyyy-MM-dd[[ ]['T']HH:mm[:ss][XXX]]", допускает дополнительные параметры в квадратных скобках, которые также могут быть вложенными.Шаблоны также могут быть построены из DateTimeFormatterBuilder , который здесь демонстрируется вышеупомянутым шаблоном:

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

Это будет преобразовано в выражение, которое выглядит следующим образом:

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

Дополнительные значения могут быть вложенными и автоматически закрываться в конце, если они все еще открыты.Тем не менее, обратите внимание, что не существует способа предоставить исключающее ИЛИ для необязательных частей, поэтому приведенный выше формат на самом деле будет достаточно хорошо разбирать следующее значение:

2018-03-08 T11:12

Обратите внимание на действительно полезную возможность, которую мы можем использовать в существующем форматере какчасти нашего текущего формата.

32 голосов
/ 04 сентября 2012

Я знаю, что это старый пост, но только для записи ...

Класс Apache DateUtils может помочь вам в этом.

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

Ссылка на библиотеку Apache Commons Lang в репозитории Maven, вы можете проверить, какая последняя версия:
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'
21 голосов
/ 05 мая 2011

Я бы создал два SimpleDateFormat, один с часовым поясом и один без. Вы можете посмотреть на длину строки, чтобы определить, какую использовать.


Похоже, вам нужен DateFormat, который делегирует два разных SDF.

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"));
3 голосов
/ 21 мая 2013

Если вы можете использовать Joda Datetime, он поддерживает дополнительные части в форматере, например, «гггг-мм-дд [чч: мм: сс]»

private DateTimeFormatter fmt = new DateTimeFormatterBuilder()
.append(DateTimeFormat.forPattern("yyyy-MM-dd"))                                            
.appendOptional(
    new DateTimeFormatterBuilder()
    .appendLiteral(' ')
    .append(DateTimeFormat.forPattern("HH:mm:ss"))
    .toParser()
.toFormatter();
2 голосов
/ 05 мая 2011

Я бы перебрал список потенциальных DateFormat объектов, используя операцию try-catch, чтобы разорвать цикл при первом успешном разборе.

0 голосов
/ 28 июня 2012

Некоторое время назад я решил похожую проблему, расширив SimpleDateFormat. Ниже грубая реализация, чтобы показать идею моего решения. Возможно, он не полностью / оптимизирован.

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

Суть в том, что вам нужно проверить строку ввода и как-то "угадать", что поле TZ отсутствует, добавьте его, если так, и затем позвольте SimpleDateFormat#parse(String, ParsePosition) сделать все остальное. Реализация выше не обновляет ParsePosition в соответствии с контрактом в Javadoc в SimpleDateFormat#parse(String, ParsePosition)

У класса есть один ctor по умолчанию, поскольку разрешен только один формат.

Метод MySimpleDateFormat#parse(String, ParsePosition) вызывается SimpleDateFormat#parse(String), поэтому достаточно охватить оба случая.

Запуск main () это вывод (как и ожидалось)

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 голосов
/ 05 мая 2011

Вы можете создать два разных SimpleDateFormats типа

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



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