Alexander Terp
Alexander Terp

Reputation: 425

Java DateTimeFormatter: Print milliseconds to 3 places only if not 0

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:

  1. Always print at least HH:mm:ss (13:00:00 prints as 13:00:00).
  2. Only print milliseconds if they != 0 (13:45:20 and 13:45:20.000 both print as 13:45:20)
  3. If printing milliseconds, always print them to three places. (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

Answers (1)

Woodz
Woodz

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:

  1. Always display 3 decimal places and run .replace(".000", "") afterwards, or
  2. Remove the millisecond component of the timestamp if it is zero (I.e. set to null)

Upvotes: 5

Related Questions