What is the Standard way to Parse different Dates

2020-05-03 10:36发布

问题:

I am working on a REST API which supports Date as a query param. Since it is Query param it will be String. Now the Date can be sent in the following formats in the QueryParams:

yyyy-mm-dd[(T| )HH:MM:SS[.fff]][(+|-)NNNN] 

It means following are valid dates:

2017-05-05 00:00:00.000+0000
2017-05-05 00:00:00.000
2017-05-05T00:00:00
2017-05-05+0000
2017-05-05

Now to parse all these different date-times i am using Java8 datetime api. The code is as shown below:

DateTimeFormatter formatter = new DateTimeFormatterBuilder().parseCaseInsensitive()
    .append(DateTimeFormatter.ofPattern("yyyy-MM-dd[[ ][['T'][ ]HH:mm:ss[.SSS]][Z]"))
    .toFormatter(); 
LocalDateTime localDateTime = null;
LocalDate localDate = null;
ZoneId zoneId = ZoneId.of(ZoneOffset.UTC.getId());
Date date = null;

try {
    localDateTime = LocalDateTime.parse(datetime, formatter);
    date = Date.from(localDateTime.atZone(zoneId).toInstant());
} catch (Exception exception) {
    System.out.println("Inside Excpetion");
    localDate = LocalDate.parse(datetime, formatter);
    date = Date.from(localDate.atStartOfDay(zoneId).toInstant());
}

As can be seens from the code I am using DateTimeFormatter and appending a pattern. Now I am first trying to parse date as LocalDateTime in the try-block and if it throws an exception for cases like 2017-05-05 as no time is passed, I am using a LocalDate in the catch block.

The above approach is giving me the solution I am looking for but my questions are that is this the standard way to deal with date sent as String and is my approach is in line with those standards?

Also, If possible what is the other way I can parse the different kinds of date (shown as the Valid dates above) except some other straightforward solutions like using an Array list and putting all the possible formats and then using for-loop trying to parse the date?

回答1:

    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .append(DateTimeFormatter.ISO_LOCAL_DATE)
            // time is optional
            .optionalStart()
            .parseCaseInsensitive()
            .appendPattern("[ ]['T']")
            .append(DateTimeFormatter.ISO_LOCAL_TIME)
            .optionalEnd()
            // offset is optional
            .appendPattern("[xx]")
            .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
            .parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
            .toFormatter();
    for (String queryParam : new String[] {
            "2017-05-05 00:00:00.000+0000",
            "2017-05-05 00:00:00.000",
            "2017-05-05T00:00:00",
            "2017-05-05+0000",
            "2017-05-05",
            "2017-05-05T11:20:30.643+0000",
            "2017-05-05 16:25:09.897+0000",
            "2017-05-05 22:13:55.996",
            "2017-05-05t02:24:01"
    }) {
        Instant inst = OffsetDateTime.parse(queryParam, formatter).toInstant();
        System.out.println(inst);
    }

The output from this snippet is:

2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T11:20:30.643Z
2017-05-05T16:25:09.897Z
2017-05-05T22:13:55.996Z
2017-05-05T02:24:01Z

The tricks I am using include:

  • Optional parts may be included in either optionalStart/optionalEnd or in [] in a pattern. I use both, each where I find it easier to read, and you may prefer differently.
  • There are already predefined formatters for date and time of day, so I reuse those. In particular I take advantage of the fact that DateTimeFormatter.ISO_LOCAL_TIME already handles optional seconds and fraction of second.
  • For parsing into an OffsetDateTime to work we need to supply default values for the parts that may be missing in the query parameter. parseDefaulting does this.

In your code you are converting to a Date. The java.util.Date class is long outdated and has a number of design problems, so avoid it if you can. Instant will do fine. If you do need a Date for a legacy API that you cannot change or don’t want to change just now, convert in the same way as you do in the question.

EDIT: Now defaulting HOUR_OF_DAY, not MILLI_OF_DAY. The latter caused a conflict when only the millis were missing, but it seems the formatter is happy with just default hour of day when the time is missing.



回答2:

I usually use the DateUtils.parseDate which belongs to commons-lang.

This method looks like this:

public static Date parseDate(String str,
                         String... parsePatterns)
                  throws ParseException

Here is the description:

Parses a string representing a date by trying a variety of different parsers.

The parse will try each parse pattern in turn. A parse is only deemed successful if it parses the whole of the input string. If no parse patterns match, a ParseException is thrown.

The parser will be lenient toward the parsed date.