Reputation: 425
I'm trying to do something seemingly quite simple, but I cannot for the life of me get it to work.
I want to parse certain Strings as LocalTime and then print them in the desired format. What I want is:
HH:mm:ss
(13:00:00
prints as 13:00:00
).13:45:20
and 13:45:20.000
both print as 13:45:20
)13:45:20.01
prints as 13:45:20.010
)It seems like it should be possible using optionals in the DateTimeFormatter, as per the documentation of optionalStart
:
All elements in the optional section are treated as optional.
During formatting, the section is only output if data is available in the
{@code TemporalAccessor} for all the elements in the section.
During parsing, the whole section may be missing from the parsed string.
However, enforcing the 3 decimal places for millis seems to bypass the optional aspect i.e. .000
gets printed when millis == 0:
final DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendValue(HOUR_OF_DAY, 2)
.appendLiteral(':')
.appendValue(MINUTE_OF_HOUR, 2)
.appendLiteral(':')
.appendValue(SECOND_OF_MINUTE, 2)
.optionalStart()
.appendLiteral('.')
.appendValue(MILLI_OF_SECOND, 3)
.toFormatter();
System.out.println(formatter.format(LocalTime.parse("12:45:00"))); // Outputs 12:45:00.000, bad!
System.out.println(formatter.format(LocalTime.parse("12:45:00.000"))); // Outputs 12:45:00.000, bad!
System.out.println(formatter.format(LocalTime.parse("12:45:00.010"))); // Outputs 12:45:00.010, good!
It can of course be done via conditionals, manually checking if millis != 0, but what I want to know is if this is possible to achieve via less explicit means.
Thanks heaps!
Upvotes: 3
Views: 2012
Reputation: 1137
The confusion lies with the behaviour of optionalStart
. You expect it to truncate millisecond values that are zero (as you consider the millisecond value to not exist). However, optionalStart
only looks at the presence of date time components, not the value (therefore the "existance" of the millisecond component of the time is never missing). Think of it as the difference between a timestamp without milliseconds vs a timestamp with zero milliseconds.
DateTimeFormatterBuilder.appendValue
does not claim to truncate decimal places (https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatterBuilder.html#appendValue-java.time.temporal.TemporalField-int-), so to get the behaviour you seek, use https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatterBuilder.html#appendFraction-java.time.temporal.TemporalField-int-int-boolean-, e.g.
final DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendValue(HOUR_OF_DAY, 2)
.appendLiteral(':')
.appendValue(MINUTE_OF_HOUR, 2)
.appendLiteral(':')
.appendValue(SECOND_OF_MINUTE, 2)
.optionalStart()
.appendFraction(MILLI_OF_SECOND, 0, 3, true)
.toFormatter();
Note: You added the decimal place as a literal, which means that the formatter has no way of understanding that you want the milliseconds as fractions. In general, the library has to provide the decimal place if you want to treat values as fractions instead of whole numbers.
Edit: Apologies to @AMterp for not quite matching the expected behaviour. Specifically, 3 decimal places should be displayed unless the millisecond component is zero.
To achieve this, unfortunately I can't see a way to get java.time.DateTimeFormatter
to behave this way (none of the built in functions support this and the class is final
so you cannot override implementations). Instead, I can suggest two options:
.replace(".000", "")
afterwards, ornull
)Upvotes: 5