Java: DateTimeFormatter fail to parse time string when seconds and milliseconds are all 0s?
Asked Answered
J

3

8

Basically, I am using the following code to parse string as LocalDateTime, which works fine most of the time.

DateTimeFormatter dtformatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");

However, I encounter cases where the seconds and millseconds are 00000 and this is when the parser fails and print a LocalDateTime 2018-03-01T09:16 instead of 2018-03-01T09:16:00.000.

System.out.println(LocalDateTime.parse("20180301091600000",dtformatter));

(Note that in my code, I have to parse string as LocalDateTime, do some comparison and then at the end, print LocalDateTime to csv)

How can I fix it to make it print 2018-03-01T09:16:00.000 instead of 2018-03-01T09:16 ?

FYI, I am using jdk10.

Jumpoff answered 22/4, 2018 at 14:40 Comment(9)
It was a bug in Java 8: bugs.openjdk.java.net/browse/JDK-8031085. But it should be fixed in Java 9, so seeing it in Java 10 is surprising.Crib
It seems the parser is fine, otherwise it would not print anything at all, other than a parse error. Your problem seems to be in the default format for printing a LocalDateTime. You should format your print as well.Alamo
Your code executes just fine on my Java 9.Crib
@OleV.V. I'm using Java 10 it return 2018-03-01T09:16Teacake
I get the same result on both Java 9 and 10, @YCF_L. It is also the result that I expected. BTW, mynameisJeFF, what makes you think you have a problem? 2018-03-01T09:16:00.000 is ISO 8601, but so is 2018-03-01T09:16, so any API that accepts the former, I would expect it to accept the latter too. Try and see if you’re not all fine.Crib
@OleV.V. why 20180301091600001 return 2018-03-01T09:16:00.001 but 20180301091600000 return 2018-03-01T00:00 why its not return 2018-03-01T00:00:00.000Teacake
@YCF_L For brevity, I suppose (I didn’t design it). In 2018-03-01T00:00 it’s understood that the seconds and fraction of seconds are 0, so printing them is not needed. I think Basil Bourque’s answer explains. BTW and for comparison, if you wanted to print the full precision of the LocalDateTime object, you would need 2018-03-01T00:00:00.000000000.Crib
@YCF_L Also remember that the LocalDateTime object doesn’t know (doesn’t remember) which string it was parsed from. It just holds a date and a time of day (with nanoseconds precision).Crib
@OleV.V. ok I agree it is not a bug, but the OP want to get 2018-03-01T09:16:00.000 instead of 2018-03-01T09:16 so I think my solution is correct no?Teacake
T
5

I'm not sure why it's not work, it seems it is a bug because when I use :

20180301091600001        result is      2018-03-01T09:16:00.001
----------------^                       -----------------^^^^^^

Also another test :

2018030100000000         result is      2018-03-01T00:00
--------^^^-----                        -----------^^^^^^^^^^^

It seems that the parser ignore the seconds and millisecond when it is zero, why?

The full explanation why?, is in the answer of Basil Bourque.


Solution

Here is a quick fix where you can use another formatter like this :

var result = LocalDateTime.parse("20180301091600000", dtformatter)
                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss:SSS"));

Output

2018-03-01T09:16:00:000
Teacake answered 22/4, 2018 at 14:54 Comment(7)
@Jumpoff no problem, I report it to open jdk so we can wait until they decide what is the problemTeacake
@YCF_L Can you link to your bug report?Ritualize
@BasilBourque I report it but I don't get any link, I think it take some time to be analyse and decide if it is a bug or notTeacake
@BasilBourque can you report it again please?Teacake
@YCF_L No, I do not believe this is a bug. See my Answer on this page.Ritualize
In other words, @YCF_L your “temporary solution” is correct and more than a temporary solution, it is the solution.Crib
Thank you @OleV.V. then I will remove that part which say it is a bug either the Temporary wordTeacake
R
5

tl;dr

where the seconds and millseconds are 00000 and this is when the parser fails

No, the parser succeeds. Your issue is with generating a String, not parsing.

The default DateTimeFormatter suppresses zero values in seconds and fractional second, as documented.

Feature, not a bug

Your problem is not in the parsing, but in the generating of a string after parsing. Keep in mind that the textual representation of a date-time object is distinct and separate from the object. In other words, a date-time object does not have a “format”.

[String] --> parse --> [LocalDateTime] --> toString --> [String]

The documentation for LocalDateTime::toString clearly says that the shortest possible formatting variation will be used when encountering zero values in the least-significant parts. To quote:

The output will be one of the following ISO-8601 formats:

uuuu-MM-dd'T'HH:mm

uuuu-MM-dd'T'HH:mm:ss

uuuu-MM-dd'T'HH:mm:ss.SSS

uuuu-MM-dd'T'HH:mm:ss.SSSSSS

uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS

The format used will be the shortest that outputs the full value of the time where the omitted parts are implied to be zero.

Examples

Regarding two examples shown in the accepted Answer by YCF_L

20180301091600001 result is 2018-03-01T09:16:00.001

In that example, the least-significant part (millisecond) has a non-zero value, so it is represented in the result.

2018030100000000 result is 2018-03-01T00:00

In that example, the least significant parts of hour, minute, second, milliseconds, microseconds, and nanoseconds are all zero. So their display is suppressed, except for hours and minutes as the documentation promises that year-minute is always displayed.

So both of your examples work as documented; feature, not a bug.

Solution

The solution is to not use the default formatter provided in the toString method. Instead, use another formatter. For example, use the same custom formatter you defined for parsing.

DateTimeFormatter f = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSSS" );
LocalDateTime ldt = LocalDateTime.parse( "20180301091600000" , f );

String outputDefault = ldt.toString();
String outputCustom = ldt.format( f );

Dump to console.

System.out.println( "outputDefault: " + outputDefault );
System.out.println( "outputCustom: " + outputCustom );

outputDefault: 2018-03-01T09:16

outputCustom: 20180301091600000

The Question asks:

How can I fix it to make it print 2018-03-01T09:16:00.000 instead of 2018-03-01T09:16 ?

Specify a custom formatter instead of the default formatter.

DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss:SSS") ; 
String output = ldt.format( f ) ;

But keep in mind your generated String will suppress display of any microseconds or nanoseconds in the LocalDateTime object.

Ritualize answered 22/4, 2018 at 23:33 Comment(3)
Hi @Basil Bourque but the OP want to produce 2018-03-01T09:16:00:000 so i think your solution is not what the OP want exactly no?Teacake
@YCF_L The desired output is irrelevant - The point of the Question (see the title) is thinking that the parser failed to parse and omitted data. But no, the code in Question implicitly calls toString which uses a formatter that suppresses zero values in seconds and fractional-second. So the premise of the Question is wrong. As for the desired format, that is a side-issue, and has been handled in hundreds, if not thousands, of existing Questions and Answers. I suppose I could show such formatting code, but that is beside the point, and you already did a good job of that.Ritualize
You are a hero @Basil read the report I just receive it bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8202144 it is the same as you already mention in your answer :)Teacake
H
0

@BasilBourque I have a further query on you above answer

I have a date "2023-12-10T00:00:00.000" when we parse this in OffsetDateTime The parsed OffsetDateTime object never have second and nanoseconds. can we somehow add seconds and nanos into OffsetDateTime object as well? Please see the attached code snippet and debug info

private fun parse3(date:String) {
    val ldt: LocalDateTime = LocalDateTime.parse("2023-12-10T00:00:00.000", df)
    val odt = OffsetDateTime.of(ldt, ZoneOffset.of("+07:00"))
    val outputDefault = odt.toString()
    val outputCustom = odt.format(df)

    println( "outputDefault: " + outputDefault)
    println( "outputCustom: " + outputCustom)
}

enter image description here

Hebrews answered 27/11, 2023 at 11:38 Comment(1)
What you see is the value of OffsetDateTime#toString and the documentation clearly mentions: The format used will be the shortest that outputs the full value of the time where the omitted parts are implied to be zero. If you want to show zeros for seconds and nanoseconds, you need to get another string using odt.format(DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS")). It has been discussed several times on StackOverflow. I recommend you open a few of my or Basil's answers and study them.Excurvate

© 2022 - 2024 — McMap. All rights reserved.