Search code examples
javadatetime-formatjava-timelocaltime

DateTimeFormatter: include optional field based on the value of another field


I want to format a java.time.LocalTime, but the format can vary according to its value:

  • if hour of day is 12 or 0, use the format HH:mm
  • else, use the format HH:mm:ss

Of course I could do it like this:

if (t.getHour() == 12 || t.getHour() == 0) {
    // use "HH:mm" formatter
} else {
    // use "HH:mm:ss" formatter
}

But for that I need to create 2 different formatters.

I want to use just one formatter that can be reused many times:

DateTimeFormatter fmt = // create this "conditional" formatter
fmt.format(LocalTime.of(14, 0))); // 14:00:00
fmt.format(LocalTime.of(12, 0))); // 12:00

I'm trying to do it with a DateTimeFormatterBuilder:

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    .appendPattern("HH:mm")
    // how to append seconds (and the ":" before it) only if hour of day is not (12 or 0)?
    .appendLiteral(":").appendValue(ChronoField.SECOND_OF_MINUTE, 2)
    .toFormatter();

I tried with DateTimeFormatterBuilder.optionalStart() and optionalEnd() methods, and also with appendOptional(), but these methods only check if the field being appended is present. They don't have an optional behaviour based on another field's value.

How can I do it with just one formatter (if possible)?


Solution

  • See DateTimeFormatterBuilder and the method appendText(TemporalField,Map). It allows you to convert each number to a specific piece of text. However, since you want to base the seconds on the hour, you'll have to setup a map of 86400 entries, one for every second in the day. But it will then work as a single formatter.

    Map<Long, String> map = new HashMap<>();
    map.put(0L, "00:00");
    map.put(1L, "00:00");  // would normally be 00:00:01
    map.put(2L, "00:00");  // would normally be 00:00:02
    map.put(60L, "00:01");
    // and so on
    map.put(3600L, "01:00:00");
    map.put(3601L, "01:00:01");
    // and so on to 86399
    DateTimeFormatter fmt = new DateTimeFormatterBuilder()
      .appendText(ChronoField.SECOND_OF_DAY, map)
      .toFormatter();
    

    A map of 86400 entries is of course silly, but its the best you can do with the API as is. Really, there needs to be an appendText(TemporalField, LongFunction<String>) method added to DateTimeFormatterBuilder (which would result in a formatter that could not be used for parsing).