How to parse date-time with two or three milliseconds digits in java?
Asked Answered
B

6

28

Here is my method to parse String into LocalDateTime.

    public static String formatDate(final String date) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SS");
        LocalDateTime formatDateTime = LocalDateTime.parse(date, formatter);
        return formatDateTime.atZone(ZoneId.of("UTC")).toOffsetDateTime().toString();
    }

but this only works for input String like 2017-11-21 18:11:14.05 but fails for 2017-11-21 18:11:14.057 with DateTimeParseException.

How can I define a formatter that works for both .SS and .SSS?

Berte answered 17/1, 2018 at 0:7 Comment(3)
If I may ask - Why do you need BOTH 2 and 3 digits? I think you should stick with 3 digits. IMHO it's the most elegant way...Vines
thats not in my hands. my api accept date and we support dates in both formats/Berte
As an aside, your return statement can be written as just return formatDateTime.atOffset(ZoneOffset.UTC).toString();.Coquito
A
19

tl;dr

No need to define a formatter at all.

LocalDateTime.parse(
    "2017-11-21 18:11:14.05".replace( " " , "T" )
)

ISO 8601

The Answer by Sleiman Jneidi is especially clever and high-tech, but there is a simpler way.

Adjust your input string to comply with ISO 8601 format, the format used by default in the java.time classes. So no need to specify a formatting pattern at all. The default formatter can handle any number of decimal digits between zero (whole seconds) and nine (nanoseconds) for the fractional second.

Your input is nearly compliant. Just replace the SPACE in the middle with aT.

String input = "2017-11-21 18:11:14.05".replace( " " , "T" );
LocalDateTime ldt = LocalDateTime.parse( input );

ldt.toString(): 2017-11-21T18:11:14.050


enter image description here

Alister answered 17/1, 2018 at 4:57 Comment(5)
interesting solution. I will try thisBerte
I just want to note that ZonedDateTime also exists and is much more useful in my experience since it can parse the Z (or other time zones) at the end of strings into the correct time zoneLour
@Lour Actually, the Z on the end is an offset-from-UTC rather than a time zone. So Instant or OffsetDateTime is more appropriate than ZonedDateTime. And this Question involves neither an offset nor a time zone, so the only relevant class here is LocalDateTime. I added a graphic table showing all these classes labeling their purposes.Alister
No the Z on the end signifies that it's in UTC time zoneLour
@Lour The Z is an offset-from-UTC of zero hours-minutes-seconds. But it is not a time zone. A time zone is a history of the past, present, and future changes to the offset used by the people of particular region. For example, Atlantic/Reykjavik & Africa/Accra are time zones, zones which happen to currently use an offset-from-UTC of zero. The distinction between offset and zone, and between OffsetDateTime and ZonedDateTime is important because when adding/subtracting a span-of-time, the offset will never change with the first of each of those, but may change with the second.Alister
D
37

You would need to build a formatter with a specified fraction

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
  .appendPattern("yyyy-MM-dd HH:mm:ss")
  .appendFraction(ChronoField.MILLI_OF_SECOND, 2, 3, true) // min 2 max 3
  .toFormatter();

LocalDateTime formatDateTime = LocalDateTime.parse(date, formatter);
Devonna answered 17/1, 2018 at 0:20 Comment(1)
@JimGarrison thx for the edit, I got used for writing Scala :)Devonna
C
35

The answers by Basil Bourque and Sleiman Jneidi are excellent. I just wanted to point out that the answer by EMH333 has a point in it too: the following very simple modification of the code in the question solves your problem.

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

The square bracket in the format pattern string enclose optional parts, so this accepts 3 or 2 decimals in the fraction of seconds.

  • Potential advantage over Basil Bourque’s answer: gives better input validation, will object if there is only 1 or there are four decimals on the seconds (whether this is an advantage depends entirely on your situation).
  • Advantage over Sleiman Jneidi’s answer: You don’t need the builder.

Possible downside: it accepts no decimals at all (as long as the decimal point is there).

As I said, the other solutions are very good too. Which one you prefer is mostly a matter of taste.

Coquito answered 17/1, 2018 at 12:6 Comment(5)
About the possible downside you state you can include the dot inside the optional part: "yyyy-MM-dd HH:mm:ss[.SSS][.SS]"Yeseniayeshiva
Thanks alot mate. I have created like this DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSS][.SS][.S]['Z']"). As I am getting multi formats from serverOutturn
@kaibuki Are you sure you sometimes receive a string with Z at the end and sometimes without it? Doesn’t sound too comforting, but if it’s so, then it’s so. I’m also not happy about hardcoding Z; consider pattern letter X instead of literal 'Z'. Depending on circumstances other solutions to parsing those strings could be found too.Coquito
@OleV.V., yes I am getting date occasionally with the "Z", I have reported it to the related team, awaiting for their fix it to a specific format. Thanks for suggestion of using literal 'X'Outturn
@kaibuki If you end up knowing whether you get Z or not (which will be good), you may use DateTimeFormatter.ISO_OFFSET_DATE_TIME if you get the Z and DateTimeFormatter.ISO_LOCAL_DATE_TIME if you don’t. Both the mentioned formatters handle varying number of decimals, no problem.Coquito
A
19

tl;dr

No need to define a formatter at all.

LocalDateTime.parse(
    "2017-11-21 18:11:14.05".replace( " " , "T" )
)

ISO 8601

The Answer by Sleiman Jneidi is especially clever and high-tech, but there is a simpler way.

Adjust your input string to comply with ISO 8601 format, the format used by default in the java.time classes. So no need to specify a formatting pattern at all. The default formatter can handle any number of decimal digits between zero (whole seconds) and nine (nanoseconds) for the fractional second.

Your input is nearly compliant. Just replace the SPACE in the middle with aT.

String input = "2017-11-21 18:11:14.05".replace( " " , "T" );
LocalDateTime ldt = LocalDateTime.parse( input );

ldt.toString(): 2017-11-21T18:11:14.050


enter image description here

Alister answered 17/1, 2018 at 4:57 Comment(5)
interesting solution. I will try thisBerte
I just want to note that ZonedDateTime also exists and is much more useful in my experience since it can parse the Z (or other time zones) at the end of strings into the correct time zoneLour
@Lour Actually, the Z on the end is an offset-from-UTC rather than a time zone. So Instant or OffsetDateTime is more appropriate than ZonedDateTime. And this Question involves neither an offset nor a time zone, so the only relevant class here is LocalDateTime. I added a graphic table showing all these classes labeling their purposes.Alister
No the Z on the end signifies that it's in UTC time zoneLour
@Lour The Z is an offset-from-UTC of zero hours-minutes-seconds. But it is not a time zone. A time zone is a history of the past, present, and future changes to the offset used by the people of particular region. For example, Atlantic/Reykjavik & Africa/Accra are time zones, zones which happen to currently use an offset-from-UTC of zero. The distinction between offset and zone, and between OffsetDateTime and ZonedDateTime is important because when adding/subtracting a span-of-time, the offset will never change with the first of each of those, but may change with the second.Alister
F
2

Using Java 8 you can use the DateTimeFormatterBuilder and a Pattern. See this answer for a little more information

public static String formatDate(final String date) {

  DateTimeFormatterBuilder dateTimeFormatterBuilder = new DateTimeFormatterBuilder()
        .append(DateTimeFormatter.ofPattern("" + "[yyyy-MM-dd HH:mm:ss.SSS]" 
         + "[yyyy-MM-dd HH:mm:ss.SS]"));

  DateTimeFormatter formatter = dateTimeFormatterBuilder.toFormatter();

  try {
    LocalDateTime formatDateTime = LocalDateTime.parse(date, formatter);
    return formatDateTime.atZone(ZoneId.of("UTC")).toOffsetDateTime().toString();
  } catch (DateTimeParseException e) {
    return "";
  }
}
Folks answered 17/1, 2018 at 1:26 Comment(2)
It works (if you omit the T), but is more complicated than necessary. I would at least use just "yyyy-MM-dd HH:mm:ss.[SSS][SS]". And not use a builder, I think DateTimeFormatter.of() suffices.Coquito
I really think the square brackets should be used for the actual optional parts, rather than the entire format as a whole. Also, this seems like a strange concatenation, is there any reason we can't use DateTimeFormatter.ofPattern("[yyyy-MM-dd HH:mm:ss.SSS][yyyy-MM-dd HH:mm:ss.SS]")?Moseley
K
2

Ideally, you would account for a time that has 0 nanoseconds as well. If the time so happens to land perfectly on 2021-02-28T12:00:15.000Z, it may actually be serialised to 2021-02-28T12:00:15Z (at least, for something like java.time.OffsetDateTime it would be). It would therefore be more appropriate to use the following:

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

... and if you require time zone, like I did, then it would look this:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss[.SSS][.SS][.S]z");
Karlie answered 1/3, 2021 at 11:5 Comment(0)
A
0

DateTimeFormatter allows specifying optional units using square brackets.

Demo:

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(
                "yyyy-MM-dd HH:mm:ss[.[SSSSSSSSS][SSSSSSSS][SSSSSSS][SSSSSS][SSSSS][SSSS][SSS][SS][S]]",
                Locale.ENGLISH);

        // Test
        Stream.of(
                "2015-05-04 12:34:56.123456789",
                "2015-05-04 12:34:56.123456",
                "2015-05-04 12:34:56.123",
                "2015-05-04 12:34:56"
        ).forEach(s -> System.out.println(LocalDateTime.parse(s, formatter)));
    }
}

Output:

2015-05-04T12:34:56.123456789
2015-05-04T12:34:56.123456
2015-05-04T12:34:56.123
2015-05-04T12:34:56

Learn more about the modern date-time API from Trail: Date Time.

Acetic answered 15/10, 2022 at 18:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.