使用Joda-Time DateTimeFormatter的genitive(波兰语语言环境)中的月份名称

我有LocalDate ,其中包含日期2012-12-28,我想用本地化的月份名称(即波兰语中的 12月)打印出来的格式 ,其中波兰语与nominative( grudniagrudzień分别)不同。 因为我也想使用自定义格式,所以我使用DateTimeFormatterBuilder创建了自己的DateTimeFormatterBuilder (在Joda-Time中,AFAIK是正确的方式):

 private static final DateTimeFormatter CUSTOM_DATE_FORMATTER = new DateTimeFormatterBuilder() .appendLiteral("z dnia ") .appendDayOfMonth(1) .appendLiteral(' ') .appendText(new MonthNameGenitive()) // <-- .appendLiteral(' ') .appendYear(4, 4) .appendLiteral(" r.") .toFormatter() .withLocale(new Locale("pl", "PL")); // not used in this case apparently 

输出应为“ z dnia 28 grudnia 2012 r。 ”。

我的问题是关于用箭头标记的行:我应该如何实现MonthNameGenitive ? 目前它扩展了DateTimeFieldType并且有很多代码:

 final class MonthNameGenitive extends DateTimeFieldType { private static final long serialVersionUID = 1L; MonthNameGenitive() { super("monthNameGenitive"); } @Override public DurationFieldType getDurationType() { return DurationFieldType.months(); } @Override public DurationFieldType getRangeDurationType() { return DurationFieldType.years(); } @Override public DateTimeField getField(final Chronology chronology) { return new MonthNameGenDateTimeField(chronology.monthOfYear()); } private static final class MonthNameGenDateTimeField extends DelegatedDateTimeField { private static final long serialVersionUID = 1L; private static final ImmutableList MONTH_NAMES = ImmutableList.of( "stycznia", "lutego", "marca", "kwietnia", "maja", "czerwca", "lipca", "sierpnia", "września", "października", "listopada", "grudnia"); private MonthNameGenDateTimeField(final DateTimeField field) { super(field); } @Override public String getAsText(final ReadablePartial partial, final Locale locale) { return MONTH_NAMES.get( partial.get(this.getType()) - 1); // months are 1-based } } } 

看起来很邋and而不是防弹,因为我必须实现许多魔术方法,而且我使用DelegatedDateTimeField并且只覆盖一个方法( getAsText(ReadablePartial, Locale) ),而其他方法具有相同的名称:

  • getAsText(long, Locale)
  • getAsText(long)
  • getAsText(ReadablePartial, int, Locale)
  • getAsText(int, Locale)

是否有更好的方法来获得所需的输出(使用DateTimeFormatter )或我的方法正确但非常冗长?

编辑:

我试图用新的JDK8 Time API(类似于基于JSR-310的Joda)实现相同的function,并且可以轻松完成:

 private static final java.time.format.DateTimeFormatter JDK8_DATE_FORMATTER = new java.time.format.DateTimeFormatterBuilder() .appendLiteral("z dnia ") .appendValue(ChronoField.DAY_OF_MONTH, 1, 2, SignStyle.NORMAL) .appendLiteral(' ') .appendText(ChronoField.MONTH_OF_YEAR, MONTH_NAMES_GENITIVE) // <-- .appendLiteral(' ') .appendValue(ChronoField.YEAR, 4) .appendLiteral(" r.") .toFormatter() .withLocale(new Locale("pl", "PL")); 

其中MONTH_NAMES_GENITIVEMap带有自定义月份名称,因此它非常易于使用。 请参阅DateTimeFormatterBuilder#appendText(TemporalField, Map)

有趣的是,在JDK8中,这个整个波兰月名称的游戏都不是必需的,因为DateFormatSymbols.getInstance(new Locale("pl", "PL")).getMonths()默认返回genitive中的月份名称…虽然这个改变对我的用例是正确的(在波兰语中,我们说“今天是2012年12月28日”使用月份名称),在其他一些情况下它们可能很棘手(我们说“2012年12月”使用主格)并且它是向后不相容。

你有我的同情 – Joda Time的现场系统有点复杂。

但是,我建议在这种情况下,最简单的方法实际上是使用DateTimeFormatterBuilder.append(DateTimePrinter printer) 。 请注意,这种方法只有在您只对打印感兴趣时才有效 – 如果您需要解析,生活也会变得更加复杂。

此时,您只需要实现DateTimePrinter ,这是相对简单的,特别是如果您乐于忽略Locale因为您只对单一文化感兴趣。 您可以将所有逻辑(而不是它的大部分内容)放在一个方法中,并使其余的方法只委托给该方法。 对于带有longDateTimeZone的重载,只需构造一个DateTime并调用toLocalDateTime ,此时您可以委托其他方法。

编辑:事实上,一个选项是编写一个抽象基类,如果你知道你只关心本地值:

 public abstract class SimpleDateTimePrinter implements DateTimePrinter { protected abstract String getText(ReadablePartial partial, Locale locale); @Override public void printTo(StringBuffer buf, long instant, Chronology chrono, int displayOffset, DateTimeZone displayZone, Locale locale) { DateTime dateTime = new DateTime(instant, chrono.withZone(displayZone)); String text = getText(dateTime.toLocalDateTime(), locale); buf.append(text); } @Override public void printTo(Writer out, long instant, Chronology chrono, int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { DateTime dateTime = new DateTime(instant, chrono.withZone(displayZone)); String text = getText(dateTime.toLocalDateTime(), locale); out.write(text); } @Override public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { buf.append(getText(partial, locale)); } @Override public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { out.write(getText(partial, locale)); } } 

然后,您可以轻松编写一个忽略区域设置的具体子类,并返回月份:

 public class PolishGenitiveMonthPrinter extends SimpleDateTimePrinter { private static final ImmutableList MONTH_NAMES = ImmutableList.of( "stycznia", "lutego", "marca", "kwietnia", "maja", "czerwca", "lipca", "sierpnia", "września", "października", "listopada", "grudnia"); private static final int MAX_MONTH_LENGTH; static { int max = 0; for (String month : MONTH_NAMES) { if (month.length() > max) { max = month.length(); } } MAX_MONTH_LENGTH = max; } @Override public int estimatePrintedLength() { return MAX_MONTH_LENGTH; } @Override protected String getText(ReadablePartial partial, Locale locale) { int month = partial.get(DateTimeFieldType.monthOfYear()); return MONTH_NAMES.get(month - 1); } } 

当然你可以在一个类中完成所有这些,但我可能会将其分解出来以使基类在将来更具可重用性。

你真的需要使用Joda吗? 使用标准Java API中的日期格式化器来替换月份名称是微不足道的:

 SimpleDateFormat sdf = new SimpleDateFormat("'z dnia' dd MMMM yyyy 'r.'"); DateFormatSymbols dfs = sdf.getDateFormatSymbols(); dfs.setMonths( new String[] { "stycznia", "lutego", "marca", "kwietnia", "maja", "czerwca", "lipca", "sierpnia", "września", "października", "listopada", "grudnia" }); sdf.setDateFormatSymbols(dfs); System.out.println( sdf.format(new GregorianCalendar(2012, Calendar.DECEMBER, 28).getTime()));