25

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?

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
brain storm
  • 30,124
  • 69
  • 225
  • 393
  • 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... – zlakad Jan 17 '18 at 00:17
  • thats not in my hands. my api accept date and we support dates in both formats/ – brain storm Jan 17 '18 at 00:18
  • 1
    As an aside, your return statement can be written as just `return formatDateTime.atOffset(ZoneOffset.UTC).toString();`. – Ole V.V. Jan 17 '18 at 09:09

6 Answers6

34

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);
Community
  • 1
  • 1
Sleiman Jneidi
  • 22,907
  • 14
  • 56
  • 77
32

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.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • 1
    About the possible downside you state you can include the dot inside the optional part: `"yyyy-MM-dd HH:mm:ss[.SSS][.SS]"` – juanluisrp Nov 23 '20 at 10:19
  • 1
    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 server – Adil Bhatty Sep 27 '21 at 02:51
  • 1
    @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. – Ole V.V. Sep 27 '21 at 06:59
  • 1
    @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' – Adil Bhatty Sep 29 '21 at 22:47
  • @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. – Ole V.V. Sep 30 '21 at 08:38
18

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

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • 1
    interesting solution. I will try this – brain storm Jan 17 '18 at 05:13
  • 1
    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 zone – Dasmowenator May 14 '20 at 21:17
  • @Dasmowenator 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. – Basil Bourque May 14 '20 at 22:57
  • 1
    No the `Z` on the end signifies that it's in UTC time zone – Dasmowenator May 16 '20 at 02:16
  • @Dasmowenator 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. – Basil Bourque May 16 '20 at 04:15
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 "";
  }
}
EMH333
  • 125
  • 2
  • 10
  • 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. – Ole V.V. Jan 17 '18 at 08:58
  • 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]")? – Harry Jones Feb 07 '22 at 11:48
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");
Patch
  • 21
  • 3
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.

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110