DateTimeFormatter:包括基于另一个字段值的可选字段

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

我想格式化一个 java.time.LocalTime,但格式会根据它的值而变化:

我当然可以这样做:

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

但为此我需要创建 2 个不同的格式化程序。

我只想使用 一个 可以重复使用多次的格式化程序:

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

我正在尝试使用 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();

我尝试使用 DateTimeFormatterBuilder.optionalStart()optionalEnd() 方法,也尝试使用 appendOptional(),但这些方法仅检查所附加的字段是否存在。 它们没有基于另一个字段值的可选行为。

我怎样才能只使用一个格式化程序(如果可能的话)?

尝试以下操作:

SimpleDateFormatter("HH:mm" + (((t.getHour() % 12) == 0) ? "" : ":ss");

不知道你为什么要这样做,但你快到了:

DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder()
    .appendPattern("HH:mm");
if (t.getHour() % 12 != 0) {
    dtfb.appendLiteral(":").appendValue(ChronoField.SECOND_OF_MINUTE, 2);
}
DateTimeFormatter fmt = dtfb.toFormatter();

您可以像您尝试的那样使用 DateTimeFormatterBuilder.optionalStart(),但您还必须创建一个包装器 class 来包装一个 LocalTime,其中 isSupported(SECONDS_OF_MINUTE) returns false 如果小时是 0 或 12.

在我看来,这太麻烦了,而且并没有真正提高清晰度(反正提高不了多少)或性能。使用两个单独的格式化程序更容易。

我可以按照@Klitos 的建议(创建一个包装器)来做到这一点。 但是重写 isSupported(正如他所建议的) 没有用

实际上我不得不重写 getLong 方法并在不应格式化字段时抛出 UnsupportedTemporalTypeException。我的解决方案的简短版本:

public class LocalTimeWrapper
    implements Temporal, TemporalAdjuster, Comparable<LocalTimeWrapper>, Serializable {

    private LocalTime time;

    public static LocalTimeWrapper of(int hour, int minute) {
        return new LocalTimeWrapper(LocalTime.of(hour, minute));
    }

    private LocalTimeWrapper(LocalTime time) {
        this.time = time;
    }

    // implement Temporal and TemporalAdjuster
    // (interface methods like isSupported() and until() delegate to "time" field)

    @Override
    public long getLong(TemporalField field) {
        if (field == ChronoField.SECOND_OF_MINUTE) {
            if (this.time.getHour() % 12 == 0) {
                // don't format seconds when hour is 12 or 0
                throw new UnsupportedTemporalTypeException("some message");
            }
        }

        return this.time.getLong(field);
    }
}

使用这段代码,它起作用了:

DateTimeFormatter f = new DateTimeFormatterBuilder().appendPattern("HH:mm")
    .optionalStart()
    .appendLiteral(":")
    .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
    .optionalEnd()
    .toFormatter();

f.format(LocalTimeWrapper.of(14, 0));  // 14:00:00
f.format(LocalTimeWrapper.of(12, 0));  // 12:00
f.format(LocalTimeWrapper.of(0, 15));  // 00:15
f.format(LocalTimeWrapper.of(15, 30)); // 15:30:00

但是正如你们中的许多人告诉我的那样,这似乎工作量太大,使用许多不同的格式化程序(或 )可能更好 - 我必须承认我试图完成这段代码,只是想知道是否可行.

我用的是jdk1.8.0_111这个测试

参见 DateTimeFormatterBuilder 和方法 appendText(TemporalField,Map)。它允许您将每个数字转换为一段特定的文本。但是,由于您希望秒数基于小时,因此您必须设置一个包含 86400 个条目的映射,一天中的每一秒对应一个条目。但它将作为一个单一的格式化程序工作。

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();

一个包含 86400 个条目的映射当然很愚蠢,但它是您可以用 API 原样做的最好的。确实,需要在 DateTimeFormatterBuilder 中添加一个 appendText(TemporalField, LongFunction<String>) 方法(这将导致格式化程序无法用于解析)。

首先要说的是,我真的不确定仅使用两个格式化程序的简单解决方法是否更适合您的问题。

与简单方法(伪代码)相比,每一种只使用一个格式化程序的方法都不可避免地会导致一些额外的性能损失:

if (time.getHour() == 0 || time.getHour() == 12) {
  formatterHHMM.format(time);
} else {
  formatterHHMMSS.format(time);
}

这只是一个额外的格式化程序。 请记住,将两个不可变格式化程序存储为静态常量是个好主意。否则,如果您坚持定义一个格式化程序,请进一步阅读:


正如 S. Colebourne 和 Jesper 所指出的,在 JSR-310 的上下文中没有特殊的方法来做你想做的事。但是,我的图书馆 Time4J 会 通过以下代码启用它。我什至给出了两种略有不同的方法,因为我不太清楚你的问题是如何处理亚小时精度。

如果小时是 0 或 12,第一种方法总是打印 "HH:mm",而不考虑可能的分钟或秒部分。

    ChronoFormatter<LocalTime> f1 =
        ChronoFormatter.setUp(PlainTime.axis(TemporalType.LOCAL_TIME), Locale.ROOT)
            .startOptionalSection((t) -> t.getInt(PlainTime.CLOCK_HOUR_OF_AMPM) == 12)
            .addCustomized(
                PlainTime.COMPONENT,
                ChronoFormatter.ofTimePattern("HH:mm", PatternType.CLDR, Locale.ROOT)
            )
            .endSection()
            .startOptionalSection((t) -> t.getInt(PlainTime.CLOCK_HOUR_OF_AMPM) != 12)
            .addCustomized(
                PlainTime.COMPONENT,
                ChronoFormatter.ofTimePattern("HH:mm:ss", PatternType.CLDR, Locale.ROOT)
            )
            .endSection()
        .build();
    System.out.println(f1.format(LocalTime.now())); // 16:35:29
    System.out.println(f1.format(LocalTime.of(12, 15))); // 12:15
    System.out.println(f1.format(LocalTime.of(12, 0))); // 12:00

如果时间恰好等于中午或午夜,第二种方式只打印 "HH:mm"。当然,print condition 也可以指定为 t.equals(PlainTime.of(0)) || t.equals(PlainTime.of(12)).

    ChronoFormatter<LocalTime> f2 =
        ChronoFormatter.setUp(PlainTime.axis(TemporalType.LOCAL_TIME), Locale.ROOT)
            .startOptionalSection(
                (t) -> t.getInt(PlainTime.CLOCK_HOUR_OF_AMPM) == 12
                    && t.get(PlainTime.PRECISION) == ClockUnit.HOURS)
            .addCustomized(
                PlainTime.COMPONENT,
                ChronoFormatter.ofTimePattern("HH:mm", PatternType.CLDR, Locale.ROOT)
            )
            .endSection()
            .startOptionalSection(
                (t) -> t.getInt(PlainTime.CLOCK_HOUR_OF_AMPM) != 12
                    || t.get(PlainTime.PRECISION) != ClockUnit.HOURS)
            .addCustomized(
                PlainTime.COMPONENT,
                ChronoFormatter.ofTimePattern("HH:mm:ss", PatternType.CLDR, Locale.ROOT)
            )
            .endSection()
            .build();
    System.out.println(f2.format(LocalTime.now())); // 16:35:29
    System.out.println(f2.format(LocalTime.of(12, 15))); // 12:15:00
    System.out.println(f2.format(LocalTime.of(12, 0))); // 12:00

此解决方案不需要包含 86400 个或更多条目的地图。

appendText(TemporalField, LongFunction<String>) 背后建议的想法相反,这种格式化程序也将启用解析(尽管这里单个模式 "HH:mm[:ss]" 就足够了)。这也是我拒绝这个想法作为对 Time4J 的增强的原因(我不想阻止解析)。

Time4J的独立格式引擎是为填补Java-8的空白而开发的。顺便说一句,我建议将 Time4J 的格式化程序也存储为静态常量(它是不可变的)。