Название месяца в родительном падеже (польский язык) с 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 года" с использованием именительного имени), и это обратно несовместимо.
Ответы
Ответ 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()));