使用 DateTimeFormatter.ofLocalizedDate 格式化 MonthDay

Formatting MonthDay using DateTimeFormatter.ofLocalizedDate

我正在尝试格式化 MonthDay object in a way that I do not have to specify the order. I am trying to use a localized DateTimeFormatter

我有这个代码:

LocalDate datetime = LocalDate.parse("2017-08-11", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
MonthDay monthday = MonthDay.from(datetime);
System.out.println(monthday.format(DateTimeFormatter.ofPattern("MMMM dd").withLocale(Locale.ENGLISH)));
System.out.println(monthday.format(DateTimeFormatter.ofPattern("MMMM dd").withLocale(Locale.GERMANY)));
System.out.println(monthday.format(DateTimeFormatter.ofPattern("MMMM dd").withLocale(Locale.forLanguageTag("UK"))));

System.out.println(datetime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.ENGLISH)));
System.out.println(datetime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.forLanguageTag("UK"))));
// next line throws exception for java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: YearOfEra
System.out.println(monthday.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.forLanguageTag("UK"))));

前 3 次打印将按预期打印翻译后的月份和日期,但始终是先是月份再是日期。它不会更改顺序,因为我明确告诉它顺序。

接下来的两个(异常前)将分别打印:

Aug 11, 2017
11 серп. 2017

请注意日期是在月份之前还是之后,具体取决于传递给函数的语言环境。我如何使用 MonthDay 对象执行此操作,因为最后一行在以这种方式完成时抛出异常。

MonthDay 不存储年份信息,FormatStyle.MEDIUM 需要一个年份值,这就是格式化程序找不到字段的原因 YearOfEra 如果您使用 [=13,也会发生同样的情况=] 具有相同的 FormatStyle,但现在缺少字段 DayOfMonth

MonthDay API

This class does not store or represent a year, time or time-zone. For example, the value "December 3rd" can be stored in a MonthDay.

您可以将 monthday 转换为 Instant 或简单地使用 datetime.

ofLocalizedDate returns 日期的格式化程序,因此它格式化 daymonthyear 字段,因此被格式化的对象需要具有所有三个字段。 MonthDay 没有 year 字段,这就是它抛出 UnsupportedTemporalTypeException.

的原因

如果要打印整个日期( ),您必须将年份添加到 MonthDay 对象。您可以为此使用 atYear 方法:

System.out.println(monthday.atYear(2017).format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.forLanguageTag("UK"))));

这将输出:

11 серп. 2017

如果您想要当前年份,只需使用 Year.now().getValue() 而不是 2017

您也可以使用 datetime.getYear(),如果您想要 LocalDate 的同一年 - 或者使用 datetime 而不是 monthday


如果您只想打印 ,则必须采取一些变通办法。

由于本地化格式化程序内置于 JDK 中(而且似乎无法更改它们),您没有直接的方法来做,尽管有一些替代方法.

一个(丑陋的)解决方案是设置年份,然后将其从格式化的 String:

中删除
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.forLanguageTag("UK"));
// set year to 2017, then remove "2017" from the formatted string
System.out.println(monthday.atYear(2017).format(formatter).replace("2017", "").trim());

输出为:

11 серп.

无聊的部分是删除每个语言环境中可能存在的所有额外字符。对于英语语言环境,我还必须删除 ,:

DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.ENGLISH);
// remove the year (2017) and the "," from the output
System.out.println(monthday.atYear(2017).format(formatter).replace("2017", "").replace(",", "").trim());

输出为:

Aug 11

您必须检查所有区域设置的所有额外字符(例如 ,),并将它们从输出中删除。或者只是将 ofPattern 与固定的非语言环境特定模式一起使用(就像您在前 3 次测试中所做的那样)。

一样,我假设年份处于模式的开头或结尾。如果年份在中间,最后的结果会有一些多余的空格,可以用.replaceAll("\s{2,}", " ")去掉(2个以上的空格只换成1个)


另一种选择(如 所建议)是使用 DateTimeFormatterBuilder 来获取本地化的日期模式。

然后我从模式中删除年份,替换 yupatterns used for the year),并删除一些其他字符(如 , 和额外的空格)。

使用生成的模式(没有年份),我使用指定的语言环境创建了一个 DateTimeFormatter 并格式化了 MonthDay:

// get date pattern for the specified locale
String pattern = DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.MEDIUM, null, IsoChronology.INSTANCE, Locale.forLanguageTag("UK"));
pattern = pattern
    // remove the year (1 or more occurrences of "y" or "u")
    .replaceAll("[yu]+", "")
    // replace "," (you can change this to remove any other characters you want)
    .replaceAll(",", "")
    // replace 2 or more spaces with just one space and trim to remove spaces in the start or end
    .replaceAll("\s{2,}", " ").trim();
// create formatter for the pattern and locale
DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pattern, Locale.forLanguageTag("UK"));
System.out.println(monthday.format(fmt));

输出将是:

11 серп.

提醒一下,这个例子可能不完整,因为有一些语言环境使用/-和其他字符作为分隔符,你必须从最终结果中删除它们。检查您正在使用的所有语言环境并相应地删除字符。

另一个未涵盖的极端情况是当您将 yu 作为文字(在 ' 内)时:在这种情况下,不应删除它们。无论如何,您必须检查您正在使用的所有语言环境的格式并相应地处理每种情况。

到目前为止给出的其他答案描述了标准的局限性DateTimeFormatter,另请参阅未解决的相关问题JDK-issue。建议的通过删除 "y" 等来编辑本地化日期模式的解决方法很棘手,并且由于模式中存在其他本地化文字,可能不适用于所有语言环境。

但是,您也可以考虑使用更注重国际化问题的外部库,并且能够仅使用语言环境信息来格式化月-日-对象。所以语言环境决定了字段组件以及点、空格或其他特殊文字(如中文)的顺序。

这里有两个选项,其中包含与您的系统时区相关的必要类型转换:

ICU4J

MonthDay md = MonthDay.now();

GregorianCalendar gcal =
    new GregorianCalendar(
        2000, // avoids possible leap year problems
        md.getMonthValue() - 1,
        md.getDayOfMonth()
    );

DateFormat df =
    DateFormat.getInstanceForSkeleton(
        DateFormat.ABBR_MONTH_DAY,
        Locale.forLanguageTag("en")
    );
System.out.println(df.format(gcal.getTime())); // Aug 15

DateFormat df2 =
    DateFormat.getInstanceForSkeleton(
        DateFormat.ABBR_MONTH_DAY,
        Locale.forLanguageTag("de")
    );
System.out.println(df2.format(gcal.getTime())); // 15. Aug.

DateFormat df3 =
    DateFormat.getInstanceForSkeleton(DateFormat.MONTH_DAY, Locale.forLanguageTag("zh"));
System.out.println(df3.format(gcal.getTime())); // 8月15日

Time4J

MonthDay md = MonthDay.now();

ChronoFormatter<AnnualDate> cf1 =
    ChronoFormatter.ofStyle(DisplayMode.SHORT, Locale.GERMAN, AnnualDate.chronology());
System.out.println(cf1.format(AnnualDate.from(md))); // 15.8.

ChronoFormatter<AnnualDate> cf2 =
    ChronoFormatter.ofStyle(DisplayMode.MEDIUM, Locale.GERMAN, AnnualDate.chronology());
System.out.println(cf2.format(AnnualDate.from(md))); // 15.08.

ChronoFormatter<AnnualDate> cf3 =
    ChronoFormatter.ofStyle(DisplayMode.LONG, Locale.ENGLISH, AnnualDate.chronology());
System.out.println(cf3.format(AnnualDate.from(md))); // Aug 15

ChronoFormatter<AnnualDate> cf4 =
    ChronoFormatter.ofStyle(DisplayMode.FULL, Locale.GERMAN, AnnualDate.chronology());
System.out.println(cf4.format(AnnualDate.from(md))); // 15. August

ChronoFormatter<AnnualDate> cf5 =
    ChronoFormatter.ofStyle(DisplayMode.FULL, Locale.CHINESE, AnnualDate.chronology());
System.out.println(cf5.format(AnnualDate.from(md))); // 8月15日

免责声明:我自己编写了 Time4J 以填补空白或改进 JSR-310(java.time-包)的其他功能。