【问题标题】:Month name in genitive (Polish locale) with Joda-Time DateTimeFormatter带有 Joda-Time DateTimeFormatter 的属格(波兰语语言环境)月份名称
【发布时间】:2013-06-28 18:00:20
【问题描述】:

我有 LocalDate,其中包含日期 2012-12-28,我想用本地化月份名称(即波兰语中的 12 月)属格打印它,这在波兰语中与主格不同(grudnia 和 grudzień)。因为我还想使用自定义格式,所以我使用DateTimeFormatterBuilder 创建了自己的DateTimeFormatter(在 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<String> 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
    }
  }

}

对我来说似乎很草率而且不是防弹的,因为我必须实现许多魔术方法,而且我正在使用 DelegatedDateTimeField 并且只覆盖一种方法 (getAsText(ReadablePartial, Locale)),而其他方法同名:

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

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

编辑:

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

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_GENITIVE 是带有自定义月份名称的Map&lt;Long, String&gt;,因此非常易于使用。见DateTimeFormatterBuilder#appendText(TemporalField, Map)

有趣的是,在 JDK8 中,整个 Polish-month-name-genitive 播放是不必要的,因为 DateFormatSymbols.getInstance(new Locale("pl", "PL")).getMonths() 默认返回属格的月份名称... 虽然这个更改对我的用例是正确的(在波兰语中,我们说“今天是 2012 年 12 月 28 日”在属格中使用月份名称),在其他一些情况下可能会很棘手(我们说“现在是 2012 年 12 月”使用主格)并且向后不兼容。

【问题讨论】:

  • 我实际上有点惊讶,它不知道何时使用属格以及何时使用其他(主格?)版本 - 我们当然会尝试在 Noda Time 中做到这一点。
  • @JonSkeet 似乎在JDK8中通过TextStyle枚举常量FULL/FULL_STANDALONE支持属格/主格版本,其中FULL是默认值,对于波兰语,它是属格和FULL_STANDALONE是主格 - 另见this XML file with localized names。所以.appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL_STANDALONE)可以用作标准月份名称。

标签: java jodatime


【解决方案1】:

你有我的同情 - 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<String> 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);
    }
}

当然,您可以在一个类中完成所有这些操作,但我可能会拆分它以使基类在将来更可重用。

【讨论】:

  • 我看到了DateTimePrinter 接口,但它被记录为internal,有注释应用程序用户很少直接使用这个类并且没有公共实现所以我改用自定义DateTimeFieldType。但是,您的尝试更简洁,更简洁,所以我一定会使用它。顺便说一句,我想知道为什么 Joda-Time 核心中没有 SimpleDateTimePrinter,因为 DateTimePrinterDateTimeFormatterBuilder 公共 API 中。
  • @Xaerxess:我没有发现“内部”部分——但它似乎很容易正确实现,而且正如你所说,它显示为公共类型。我可以理解普通应用程序用户很少直接使用它 - 但当然这与它是一个问题这样做不同:)
  • 你说得对,这可能是 很少使用 ;) DateTimeParser 是另一个“内部但公开”的界面,出现在 Joda 的 DateTimeFormatterBuilder 中,经过比较使用来自 JSR-311 的新构建器(请参阅已编辑的问题),我怀疑“内部”类放在那里,因为格式化 API 不允许像这个问题中的一些高级内容。 Java 8 Time API 似乎解决了这个问题。
  • 有没有人让 append(DateTimePrinter) 工作?我似乎是第一个尝试过的人,它附加了两次文本。我用 joda-time 提交了一个错误,但是在这种时机下,谁知道它是否会被修复......
  • @Trejkaz:好吧,我在答案中测试了代码,结果很好,IIRC。我建议您使用minimal reproducible example 提出一个新问题。
【解决方案2】:

你真的需要使用 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()));

【讨论】:

  • 我宁愿使用 Joda(公司推荐),而且 SimpleDateFormat 不是线程安全的。由于有一种简单的方法可以在标准 JDK Date API 中更改月份名称,我想知道 Joda 中是否有一个...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-20
  • 1970-01-01
  • 1970-01-01
  • 2012-07-28
  • 1970-01-01
  • 2014-03-15
相关资源
最近更新 更多