5

I am developing using Java 8 a function that must handle the conversion from String to LocalDateTime of the following dates:

  • 2019-06-20 12:18:07.207 +0000 UTC
  • 2019-06-20 12:18:07.20 +0000 UTC
  • 2019-06-20 12:18:07.2 +0000 UTC
  • 2019-06-20 12:18:07 +0000 UTC

The strings are produced from an external library that I cannot change.

Following the suggestions given in the SO answer Optional parts in SimpleDateFormat, I tried using the optional formatting offered by the type DateTimeFormatter, using the characters [ and ]. I tried the following patterns:

  • yyyy-MM-dd HH:mm:ss[.S[S[S]]] Z z
  • yyyy-MM-dd HH:mm:ss[.S[S][S]] Z z

However, neither of them works.

Any suggestion?

Hassam Abdelillah
  • 2,246
  • 3
  • 16
  • 37
riccardo.cardin
  • 7,971
  • 5
  • 57
  • 106

3 Answers3

8

You can build the pattern using DateTimeFormatterBuilder and reuse ISO_LOCAL_DATE and ISO_LOCAL_TIME constants:

    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .append(DateTimeFormatter.ISO_LOCAL_DATE)
            .appendLiteral(" ")
            .append(DateTimeFormatter.ISO_LOCAL_TIME)
            .appendPattern("[ Z z]")
            .toFormatter();

    ZonedDateTime dt = ZonedDateTime.parse(date, formatter);

The trick is that DateTimeFormatter.ISO_LOCAL_TIME handles the different number of digit used to represent milliseconds its own. From DateTimeFormatter.ISO_LOCAL_TIME JavaDoc:

This returns an immutable formatter capable of formatting and parsing the ISO-8601 extended local time format. The format consists of:
[..]
One to nine digits for the nano-of-second. As many digits will be output as required.

riccardo.cardin
  • 7,971
  • 5
  • 57
  • 106
Jaywalker
  • 3,079
  • 3
  • 28
  • 44
  • Sorry, but your answer did not respond to my question. The optional and variable part is the milliseconds' block, not the offset. – riccardo.cardin Aug 19 '19 at 11:51
  • Doesn't ISO_LOCAL_TIME itself take care of that optional part of milliseconds? Try running the examples with this code. – Jaywalker Aug 19 '19 at 12:04
  • 1
    Yep, you're right. Sorry. I added some useful information to your answer. – riccardo.cardin Aug 19 '19 at 14:54
  • 1
    Thanks. You OK with ZonedDateTime, right? I think the requirement of having LocalDateTime in the question conflicts with zone specific text that needs to be parsed. – Jaywalker Aug 19 '19 at 14:59
  • Don't worry. The problem was with the optional part. – riccardo.cardin Aug 19 '19 at 15:06
  • Your `DateTimeFormatter` serves parsing to `ZonedDateTime` as well as `LocalDateTime` e.g. you can also use it as `LocalDateTime dt = LocalDateTime.parse("2019-06-20 12:18:07.207 +0000 UTC", formatter);` – Arvind Kumar Avinash Jan 02 '23 at 12:45
4

I think it is better to use a DateTimeFormatterBuilder for that purpose. For the optional parts just use one of the follwing methods :

  1. OptionalStart() & OptionalEnd()
  2. Append your whole optional pattern with appendOptional()

Here is an example :

DateTimeFormatter formatter = DateTimeFormatter.ofPattern(""
    + "[yyyy-MM-dd HH:mm:ss.SSS Z z]"
    + "[yyyy-MM-dd HH:mm:ss.SS Z z]"
    + "[yyyy-MM-dd HH:mm:ss.S Z z]"
    + "[yyyy-MM-dd HH:mm:ss Z z]"
);

Also, you can create a dtf for each optional and append them with appendOptional() and the the DateTimeFormatterBuilder

for example :

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
    .appendValue(HOUR_OF_DAY,2)
    .optionalStart()
    .appendValue(MINUTE_OF_HOUR,2)
    .optionalEnd()
    .optionalStart()
    .appendValue(SECOND_OF_MINUTE,2)
    .optionalEnd()
    .toFormatter();

This code is not tested but try to build your optional pattern each time in a start/end optional blocks.

Andrei Petrenko
  • 3,922
  • 3
  • 31
  • 53
Hassam Abdelillah
  • 2,246
  • 3
  • 16
  • 37
1

Here is my example that I use in one app that takes different formats as you can see millisecond is optional and also 'Z' at end.

new DateTimeFormatterBuilder()
      .appendPattern("yyyy-MM-dd'T'HH:mm:ss")
      .optionalStart()
      .appendLiteral('.')
      .appendValue(ChronoField.MILLI_OF_SECOND, 1, 3, SignStyle.NORMAL)
      .optionalEnd()
      .optionalStart()
      .appendLiteral('Z')
      .optionalEnd()
      .toFormatter

appendPattern is static value, other at inside optional start and end, very simple to build such pattern ( if you know of course how to do that ;) )

FrancMo
  • 2,459
  • 2
  • 19
  • 39