I have LocalDate
which contains date 2012-12-28 and I want to print it with localized month name (i.e. December in Polish) in genitive which in Polish is distinct from nominative (grudnia and grudzień respectively). Because I also want to use custom format I created my own DateTimeFormatter
using DateTimeFormatterBuilder
(which AFAIK is the right way to it in 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
The output should be "z dnia 28 grudnia 2012 r.".
My question is about line marked with an arrow: how should I implement MonthNameGenitive
? Currently it extends DateTimeFieldType
and has quite much code:
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
}
}
}
Seems sloppy and not bullet-proof to me, since I had to implement many magic methods plus I'm using DelegatedDateTimeField
and overriding only one method (getAsText(ReadablePartial, Locale)
) while there are others with the same name:
getAsText(long, Locale)
getAsText(long)
getAsText(ReadablePartial, int, Locale)
getAsText(int, Locale)
Is there better approach to get desired output (using DateTimeFormatter
) or my approach correct yet very verbose?
EDIT:
I've tried to achieve the same thing with new JDK8 Time API (which is similar to Joda, based on JSR-310) and it could be done easily:
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"));
where MONTH_NAMES_GENITIVE
is Map<Long, String>
with custom month names, so it's very easy to use. See DateTimeFormatterBuilder#appendText(TemporalField, Map)
.
Interestingly, in JDK8 this whole Polish-month-name-genitive play is not necessary because DateFormatSymbols.getInstance(new Locale("pl", "PL")).getMonths()
returns month names in genitive by default... While this change is correct for my use case (in Polish, we say "today is the 28th of December 2012" using month names in genitive), it can be tricky in some other cases (we say "it's December 2012" using nominative) and it's backwards incompatible.
You have my sympathies - the field system in Joda Time is somewhat complicated.
However, I suggest that in this case the simplest approach would actually be to use DateTimeFormatterBuilder.append(DateTimePrinter printer)
. Note that this approach will only work if you're only interested in printing - if you need to parse as well, life becomes more complicated.
At that point you only need to implement DateTimePrinter
, which is relatively straightforward, particularly if you're happy to ignore the Locale
as you're only interested in a single culture. You can put all the logic (not that there'll be much of it) in a single method, and make the rest of the methods just delegate to that method. For the overloads which take a long
and a DateTimeZone
, just construct a DateTime
and call toLocalDateTime
, at which point you can delegate to the other methods.
EDIT: In fact, one option would be to write an abstract base class, if you knew you'd only care about the local values:
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));
}
}
Then you can easily write a concrete subclass which ignores the locale, and just returns the month:
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);
}
}
Of course you could do this all in one class, but I'd probably break it out to make the base class more reusable in the future.
Do you really need to use Joda? Replacing the month names is trivial using the date formatters in the standard 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()));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With