Handling various timestamp with one formatter in java/scala?

Viewed 146

I am currently getting two version of timestamp format eg '2017-04-17 20:33:45.223+05:30' and '2017-04-17 20:33:45+05:30'.My parsing is failing due to dynamic timestamp .Is it possible to handle both of these time stamp with one DateTimeFormatter Pattern .Below is the example code what i tried

val myDate=LocalDateTime.parse("2017-04-17 20:33:45.223+05:30", DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSSZ")).toDateTime(DateTimeZone.UTC)//this will fail if  time stamp comes with '2017-04-17 20:33:45+05:30 

I had seen one way to achieve the same using optional part however I canot make it work

val pattern = "MM/dd/yyyy HH:mm:ss[.SSS]Z"
val fmt = DateTimeFormatter.ofPattern(pattern)
val temporalAccessor = fmt.parse("2017-04-17 20:33:45.223+05:30")

Ant help on this or any suggestion how to handle such cases will be helpful .Thanks in advance .

2 Answers

uuuu-MM-dd

Edit: This fixes it. I am using java.time, the modern Java date and time API, and Java syntax.

private static final DateTimeFormatter FORMATTER
        = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss[.SSS]xxx", Locale.ROOT);

Trying it out:

    String[] variants = {
            "2017-04-17 20:33:45.223+05:30",
            "2017-04-17 20:33:45+05:30",
            // Variants we don’t want to accept
            "2017-04-17 20:00+05:30",
            "2017-04-17 20:00:00.000000+05:30" };
    
    for (String inputString : variants) {
        try {
            OffsetDateTime dateTime = OffsetDateTime.parse(inputString, FORMATTER);
            System.out.println("Parsed: " + dateTime);
        } catch (DateTimeParseException dtpe) {
            System.out.println("Invalid: " + inputString);
        }
    }

Output:

Parsed: 2017-04-17T20:33:45.223+05:30
Parsed: 2017-04-17T20:33:45+05:30
Invalid: 2017-04-17 20:00+05:30
Invalid: 2017-04-17 20:00:00.000000+05:30

What went wrong in your code?

You had the right idea for your purpose.

You attempted using the outmoded Joda-Time library. Joda-Time can support optional parts when parsing, but not through the square bracket syntax. Instead its DateTimeFormatterBuilder has got an appendOptional method.

In your java.time code this part of your format pattern string doesn’t match any of your inputs: MM/dd/yyyy. Java parsed 20 as a 2 digit month number (postponing validation of the number) and threw the exception because no slash was found after 20.

Edit 2: why xxx works but Z doesn't:

With Joda-Time’s DateTimeFormat one Z is for offset without colon, for example +0530. ZZ should have worked for +05:30 with colon.

With java.time both x and Z (and also upper case X) are for zone offset. Here too Z is for offset without colon. Either xxx or ZZZZZ works for +05:30.

Use the built-in formatters

Original answer, likely useful for others: This one does it (using Java syntax):

private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
        .append(DateTimeFormatter.ISO_LOCAL_DATE)
        .appendLiteral(' ')
        .append(DateTimeFormatter.ISO_LOCAL_TIME)
        .appendOffsetId()
        .toFormatter();

Let’s try it out:

    String[] variants = {
            "2017-04-17 20:33:45.223+05:30",
            "2017-04-17 20:33:45+05:30",
            "2017-04-17 20:00+05:30",
            "2017-04-17 20:00:00.000000+05:30" };

    
    for (String inputString : variants) {
        OffsetDateTime dateTime = OffsetDateTime.parse(inputString, FORMATTER);
        System.out.println(dateTime);
    }

Output:

2017-04-17T20:33:45.223+05:30
2017-04-17T20:33:45+05:30
2017-04-17T20:00+05:30
2017-04-17T20:00+05:30

I am exploiting the fact that the built-in DateTimeFormatter.ISO_LOCAL_TIME accepts a time both with and without decimals on the seconds. We can reuse existing formatters in our own formatter through a DateTimeFormatterBuilder.

parseBest looks a good fit for this

public TemporalAccessor parseBest(CharSequence text,
                                  TemporalQuery<?>... queries)

Fully parses the text producing an object of one of the specified types.

This parse method is convenient for use when the parser can handle optional elements. For example, a pattern of 'uuuu-MM-dd HH.mm[ VV]' can be fully parsed to a ZonedDateTime, or partially parsed to a LocalDateTime. The queries must be specified in order, starting from the best matching full-parse option and ending with the worst matching minimal parse option. The query is typically a method reference to a from(TemporalAccessor) method.

The result is associated with the first type that successfully parses

Related