10

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!

Tunaki
  • 132,869
  • 46
  • 340
  • 423
mkurz
  • 2,658
  • 1
  • 23
  • 35
  • 2
    I don't think there's an easy way out. At least for Oracle JDK, the locale specific patterns are [hard-coded here](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/sun/text/resources/FormatData.java?av=f#805) and there doesn't appear to be a format with only month / year, or only day / month. – Tunaki Jun 30 '16 at 15:23
  • Thanks, it's a pity formatting `YearMonth` and `MonthDay` isn't supported native by Oracle JDK... :( – mkurz Jun 30 '16 at 15:29
  • 1
    This would make a good feature-request. Unfortunately I believe Java 9 is feature-frozen now. – Basil Bourque Jun 30 '16 at 17:58
  • @BasilBourque do you know where do submit feature requests for Java? – mkurz Jun 30 '16 at 18:44
  • 2
    Oracle has [this general page about how to report a bug or request a feature](http://bugreport.java.com/). But I suspect it would be more productive to go through the working site of the [JSR 310](https://jcp.org/en/jsr/detail?id=310) experts: http://www.threeten.org/ – Basil Bourque Jun 30 '16 at 19:53
  • 1
    See also using [my own format engine](http://stackoverflow.com/a/39666326) for this purpose. Currently I am working on even better integration into java-time-package. – Meno Hochschild Oct 24 '16 at 11:22
  • Here is the Java feature-request: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8168532 – mkurz Nov 17 '16 at 16:49
  • Also see https://bugs.openjdk.java.net/browse/JDK-8168532 – mkurz Dec 19 '16 at 12:27
  • 1
    See [this Answer](http://stackoverflow.com/a/41850130/642706) on a duplicate Question, by the creator of java.time ('JodaStephen', Stephen Colbourne). See [feature request](https://bugs.openjdk.java.net/browse/JDK-8168532) mentioned in comment by mkurz. – Basil Bourque Jan 25 '17 at 17:44

3 Answers3

6

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)

Meno Hochschild
  • 42,708
  • 7
  • 104
  • 126
  • Thanks Meno Hochschild! I tried your `time4j` library and it works. Only one question: Your example shows how to format year and month, but how do you format month and day (see my original question)? Can you give also an example how to do that? Thanks! BTW: Looks like you work really hard and a lot on that library. You should advertise it a bit more maybe since I never heard of it before but looks quite promising. – mkurz Jan 13 '18 at 18:24
  • Oh I forgot one question: Does your library somehow interfere with the JDK locale data - I mean in a way that it would replace the underlying locale data from the JDK or something like this? Is it completely save to use your library in a way it doesn't change the behaviour of any existing JDK 8/9 date code (or whatever) that already exists? I know e.g. that Java 9 now uses "Unicode Common Locale Data Repository" - CLDR for locale data now as default - time4j doesn't replace that, does it? – mkurz Jan 13 '18 at 18:29
  • 1
    About your other questions related to Time4J: Indeed, I have invested a lot in that library. My family already complains sometimes ;-). but I have no commercial attitude and just do it for love for date and time. Unfortunately Time4J is not well known although its wealth of features deserves more attention. About i18n: Time4J and the JDK uses independent i18n-data which don't interfere at all. Not even in JDK 9. By the way, The CLDR-data of Time4J are still bigger because of extra support for types like `CalendarMonth`, `AnnualDate` and the many extra calendars. The JDK uses a subset of CLDR – Meno Hochschild Jan 13 '18 at 19:47
  • Month and day - can be formatted via a formatter for the class [AnnualDate](http://time4j.net/javadoc-en/net/time4j/AnnualDate.html). The javadoc contains an example how to do this. – Meno Hochschild Feb 19 '20 at 16:03
5

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
Julian
  • 192
  • 1
  • 12
  • I don't think this is working. For example for `FormatStyle.MEDIUM` I get for `Locale` German `dd.MM.yyyy`, for English: `MMM d, yyyy` and for Spanish: `dd-MMM-yyyy`. How should I figure out which parts to remove from here? I mean there is `MMM` and `MM` or `dd` and `dd`. I don't even know the parts I want to keep... – mkurz Jun 30 '16 at 13:36
  • 1
    This would be String handling, I'll add it in a second. – Julian Jun 30 '16 at 13:46
  • 1
    I've just added the requested function. It can probably be done several other ways and most likely better than I did. – Julian Jun 30 '16 at 14:06
  • I can not see how this should work. `DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.MEDIUM, null, IsoChronology.INSTANCE, Locale.ENGLISH)` gives me `MMM d, yyyy` not `MMM'x'd,'y'yyyy` or similiar. Why do you think it would be something like `MMM'x'dd'y'YYYY`? There are no `'` characters in the pattern - nor `'x'` or `'y'`.... – mkurz Jun 30 '16 at 14:14
  • 1
    It returns the pattern, for `FormatStyle.MEDIUM` and `Locale.ENGLISH` it does return `MMM d, yyyy`. But for `FormatStyle.LONG` and `new Locale("es","ES")` it returns `d' de 'MMMM' de 'yyyy`, so you need to be able to handle separators like '[whatever]'. – Julian Jun 30 '16 at 14:21
  • Yeah, so how should I know which seperator to use for each Locale/Style combination? There could be even differente seperators within one single pattern string... – mkurz Jun 30 '16 at 14:26
  • 2
    I've just added a full example that should work with any format and locale, please go ahead and try it. Sorry about the poor explanations. – Julian Jun 30 '16 at 14:42
  • I will test it.However in german `30.06.16` should become `30.06.` (with the trailing dot). – mkurz Jun 30 '16 at 15:18
  • Also you probably want to remove any leading separators as well. E.g try with `keep.add('y'); keep.add('M');` – mkurz Jun 30 '16 at 15:18
  • 1
    If you remove the while loop you will keep the trailing separator, although the I wouldn't keep it. – Julian Jun 30 '16 at 15:19
  • Also it could happen that you remove a part beetween two separators - so you end up two separator next to each other. E.g. try the `YearMonth` code with `Locale.US` - in this locale you get patterns for values like `6/30/16` so when removing the day you end up getting `6//16` having two slashes in between. – mkurz Jun 30 '16 at 15:25
  • 1
    Please make sure you have the same code as the provided one removes the leading separator and doesn't keep two consecutive separators either. – Julian Jun 30 '16 at 15:32
  • Sorry, my bad. However you made one mistake: You want to use `keep.add('y')` instead of `keep.add('Y')` (small `y`). – mkurz Jun 30 '16 at 16:04
  • Thank you very much for your time and effort! I will go with this solution for now. I will let you know if I run into any issues. – mkurz Jun 30 '16 at 16:08
  • 2
    You are right, fixed. The approach I followed is to keep the separator after each kept field, and then remove trailing separators, as it makes most senses in the languages I know, but this may not apply to every language. For instance, would be good with a pattern like: "d ' day of ' MM ' month of 'YYYY" – Julian Jun 30 '16 at 16:10
2

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));
gevorg
  • 4,835
  • 4
  • 35
  • 52
  • 7
    I am aware of `ofPattern`. But, as in my examples above for `LocalDate`, I don't want to set the delimiter and ordering by myself. Java should decide for me what to use. It does for `LocalDate` - so why not for `YearMonth` and `MonthDay`? – mkurz Jun 30 '16 at 13:07