Подтвердить что ты не робот

Название месяца в родительном падеже (польский язык) с Joda-Time DateTimeFormatter

У меня есть LocalDate, который содержит дату 2012-12-28, и я хочу напечатать его с локализованным именем месяца (т.е. декабрь на польском языке) в родительном падеже, который по-польски отличается от именительного (grudnia и grudzień соответственно). Поскольку я также хочу использовать пользовательский формат, я создал свой собственный DateTimeFormatter с помощью DateTimeFormatterBuilder (который AFAIK - правильный путь к нему в Joda-Time):

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) или мой подход правильный, но очень подробный?

EDIT:

Я попытался добиться того же, что и новый 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<Long, String> с пользовательскими именами месяцев, поэтому он очень прост в использовании. См. DateTimeFormatterBuilder#appendText(TemporalField, Map).

Интересно, что в JDK8 вся польза-месяц-родительный падеж не нужна, потому что DateFormatSymbols.getInstance(new Locale("pl", "PL")).getMonths() возвращает имена месяцев в родительном падеже по умолчанию... Пока это изменение верно для моего варианта использования (по-польски, мы говорим "сегодня 28 декабря 2012 года", используя месячные имена в родительном падеже), это может быть сложно в некоторых других случаях (мы говорим "это декабрь 2012 года" с использованием именительного имени), и это обратно несовместимо.

4b9b3361

Ответ 1

У вас есть мои симпатии: полевая система в Йода-Вэне несколько сложна.

Однако я предлагаю, чтобы в этом случае самым простым подходом было бы использовать DateTimeFormatterBuilder.append(DateTimePrinter printer). Обратите внимание, что этот подход будет работать только в том случае, если вас интересует только печать - если вам нужно также анализировать, жизнь становится более сложной.

В этот момент вам нужно реализовать DateTimePrinter, что относительно просто, особенно если вы с удовольствием проигнорируете Locale, поскольку вас интересует только одна культура. Вы можете поместить всю логику (не то, чтобы ее было много) в одном методе, а остальные методы просто делегировать этому методу. Для перегрузок, которые принимают long и a DateTimeZone, просто создайте DateTime и вызовите toLocalDateTime, после чего вы можете делегировать другие методы.

EDIT: Фактически, одним из вариантов было бы написать абстрактный базовый класс, если бы вы знали, что вам будут нужны только локальные значения:

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

Конечно, вы могли бы сделать все это в одном классе, но я бы, вероятно, сломал его, чтобы сделать базовый класс более многоразовым в будущем.

Ответ 2

Вам действительно нужно использовать Джоду? Замена имен месяцев тривиально с использованием форматов даты в стандартном 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()));