Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to format YearMonth and MonthDay depending on a Locale?

Formatting a LocalDate in Java 8 using a specific Locale can be achieved like this:

DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(myLocale).format(value);
DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(myLocale).format(value);
DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).withLocale(myLocale).format(value);
DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(myLocale).format(value);

Assuming value = LocalDate.now() this would result into:

// myLocale = Locale.ENGLISH
6/30/16
Jun 30, 2016
June 30, 2016
Thursday, June 30, 2016

// myLocale = Locale.GERMAN
30.06.16
30.06.2016
30. Juni 2016
Donnerstag, 30. Juni 2016

// myLocale = new Locale("es", "ES")
30/06/16
30-jun-2016
30 de junio de 2016
jueves 30 de junio de 2016

As you can see Java decides which delimiter ("-", ".", "/", " ", etc.) to use and how to sort the date elements (e.g. month before day or vice versa, etc - in some locales it could be that year comes first, etc.).

My question is: How can I format a java.time.YearMonth and java.time.MonthDay depending on a Locale like the example above?

Based on the example I would expect results like this...

... for YearMonth:

// myLocale = Locale.ENGLISH
6/16
Jun, 2016
June, 2016
June, 2016

// myLocale = Locale.GERMAN
06.16
06.2016
Juni 2016
Juni 2016

// myLocale = new Locale("es", "ES")
06/16
jun-2016
de junio de 2016
de junio de 2016

... for MonthDay:

// myLocale = Locale.ENGLISH
6/30
Jun 30
June 30
June 30

// myLocale = Locale.GERMAN
30.06.
30.06.
30. Juni
30. Juni

// myLocale = new Locale("es", "ES")
30/06
30-jun
30 de junio de
30 de junio de

Of course there could be other Locales which use completely different delimiters and ordering.

Thank you!

like image 760
mkurz Avatar asked Jun 30 '16 12:06

mkurz


3 Answers

It seems that this Java-8-bug cannot be fixed in Java-9 because even the feature-extension-complete-date is already over. Let's see if it is going to be fixed in Java-10 which is still a long time away...

Of course, as one answer here suggests, you could try to process a given localized date pattern in order to remove irrelevant parts. But I still consider this approach as errorprone because there are still so many locales around. And indeed, the accepted answer is flawed for Chinese. Localized literals are here the main problem. Maybe the accepted answer can be fixed at least for this important language, but you might also consider two other libraries with good internationalization features which can solve your issue in a more reliable way.

a) ICU4J

DateFormat df = DateFormat.getInstanceForSkeleton(DateFormat.YEAR_MONTH, Locale.CHINESE);
String output = df.format(new Date());
System.out.println("ICU4J=" + output); // 2017年1月

However, one problem is lack of interoperability with Java-8-types, especially MonthDay and YearMonth. A solution requires something like Date.from(YearMonth.now().atDay(1).atStartOfDay(ZoneId.systemDefault()).toInstant()); Possible, but cumbersome.

b) my library Time4J (with the same data base as ICU4J)

ChronoFormatter<CalendarMonth> cf =
    ChronoFormatter.ofStyle(DisplayMode.FULL, Locale.CHINESE, CalendarMonth.chronology());
CalendarMonth cm = 
    CalendarMonth.from(YearMonth.now()); // or: CalendarMonth.nowInSystemTime()
System.out.println("Time4J=" + cf.format(cm)); // 2017年1月

Interoperability with Java-8 exists in the reverse direction, too. And the Time4J-counterpart for MonthDay is the class AnnualDate.


Side note: The accepted answer of @Julian yields for Chinese: 2017年1 (needs to be fixed)

like image 129
Meno Hochschild Avatar answered Sep 22 '22 01:09

Meno Hochschild


Solution provided by gevorg is probably the simplest solution if you will use a limited list of Locale.

If you want to make it work with any Locale I would suggest to get a locale pattern and then remove the parts you are not interested in, once you have this pattern you should remove the part you are not interested in and use the resulting pattern to create your own DateTimeFormatter.

This is a full example of the idea explained above for MonthDay. In order to use it for YearMonth replace keep.add('d') with keep.add('y'). (and of course MonthDay with YearMonth)

ArrayList<Locale> locales = new ArrayList<Locale>();
locales.add(Locale.ENGLISH);
locales.add(Locale.GERMAN);
locales.add(new Locale("es", "ES"));
locales.add(Locale.US);
ArrayList<FormatStyle> styles = new ArrayList<FormatStyle>();
styles.add(FormatStyle.SHORT);
styles.add(FormatStyle.MEDIUM);
styles.add(FormatStyle.LONG);
styles.add(FormatStyle.FULL);
ArrayList<Character> keep = new ArrayList<Character>();
keep.add('d');
keep.add('M');

for (FormatStyle style : styles) {
    for (Locale myLocale : locales) {
        String myPattern = DateTimeFormatterBuilder.getLocalizedDateTimePattern(style, null, IsoChronology.INSTANCE, myLocale);

        boolean separator = false;
        boolean copy = true;
        String newPattern = "";
        for (char c : myPattern.toCharArray()) {
            if (c == '\'') {
                separator = !separator;
            }
            if (!separator) {
                if (Character.isAlphabetic(c)) {
                    if (keep.contains(c)) {
                        copy = true;
                    } else {
                        copy = false;
                    }
                }
            }
            if (copy) {
                newPattern = newPattern + c;
            }
        }

        char lastChar = newPattern.charAt(newPattern.length() - 1);
        while (!keep.contains(lastChar)) {
            if (lastChar == '\'') {
                newPattern = newPattern.substring(0, newPattern.length() - 1);
                newPattern = newPattern.substring(0, newPattern.lastIndexOf('\''));
            } else {
                newPattern = newPattern.substring(0, newPattern.length() - 1);
            }
            lastChar = newPattern.charAt(newPattern.length() - 1);
        }

        System.out.println(DateTimeFormatter.ofPattern(newPattern, myLocale).format(YearMonth.now()));
    }
    System.out.println();
}

The output would be:

6/30
Jun 30
June 30
June 30

30.06
30.06
30. Juni
30. Juni

30/06
30-jun
30 de junio
30 de junio

6/30
Jun 30
June 30
June 30

And for YearMonth:

6/16
Jun 2016
June 2016
June 2016

06.16
06.2016
Juni 2016
Juni 2016

06/16
jun-2016
junio de 2016
junio de 2016

6/16
Jun 2016
June 2016
June 2016
like image 44
Julian Avatar answered Sep 21 '22 01:09

Julian


You need to use DateTimeFormatter#ofPattern

For YearMonth

YearMonth source = YearMonth.now();
DateTimeFormatter english = DateTimeFormatter.ofPattern("MMMM, yyyy", Locale.ENGLISH);
DateTimeFormatter german = DateTimeFormatter.ofPattern("MMMM yyyy", Locale.GERMAN);

System.out.println(source.format(english));
System.out.println(source.format(german));

For MonthDay

MonthDay source = MonthDay.now();
DateTimeFormatter english = DateTimeFormatter.ofPattern("MMMM dd", Locale.ENGLISH);
DateTimeFormatter german = DateTimeFormatter.ofPattern("dd. MMMM", Locale.GERMAN);

System.out.println(source.format(english));
System.out.println(source.format(german));
like image 34
gevorg Avatar answered Sep 22 '22 01:09

gevorg