When I format a LocalDate to dd.MMM.YYYY I get 01.Jan..2000 with two dots
Asked Answered
H

3

6

I am trying to format LocalDate variables to dd.MMM.YYYY with:

DateTimeFormatter.ofPattern("dd.MMM.yyyy")

The problem is that more half the time I get two dots. For example 01-01-2000 goes to 01.Jan..2000.

I know why I have this problem, because of the three Ms. When I use dd.MM.yyyy I get to 01.01.2000 without issue. The third M is the problem.

How can I fix this?

Hellenize answered 6/11, 2021 at 2:22 Comment(5)
LocalDate has not format associated with, so "They are all in the format dd-MM-yyyy" is actually a invalid statement. You're getting .. because the MMM format is using Jan., which is probably coming from the localisationGeminius
Okay and how i could fix that? When I make dd.MMMyyyy some of my variables got a point and are for example 01.Jan.2000 and other are for example 02.Feb2000 they dont got a Point :(Hellenize
System.out.println(DateTimeFormatter.ofPattern("dd.MMM.yyyy").format(LocalDate.now())); prints 06.Nov.2021 so I suspect that it's coming from the default localisationGeminius
DateTimeFormatter.ofPattern("dd.MMM.yyyy", Locale.CANADA).format(LocalDate.now()) will print 06.Nov..2021 is it's coming from the localisation been used by the JVM. I found that using Locale.ENGLISH will print 06.Nov.2021, but this might not be desirable depending what you're trying to achieve, as you're locking yourself intoGeminius
Never use SimpleDateFormat or DateTimeFormatter without a Locale.Amphibolite
C
8

The cause of your problem is that the abbreviations for months are locale specific:

  • In some locales there is a dot (period) to indicate abbreviations1; Locale.CANADA for example. In others there isn't; Locale.ENGLISH for example.
  • In the locales where a dot indicates abbreviation, you may or may not find that there is dot when the name of months doesn't need abbreviating. For example the name of the month May is only three letters, so May. indicating that this is an abbreviation would be illogical.

There are various ways to deal with this, including:

  1. Don't fix it. The output with doubled dots in some cases and not others is logically correct (by a certain logic)2, even though it looks odd.

  2. My preferred way would be to use a different output format. Don't use dot as a separator.

    Using dot characters as separators is ... unconventional ... and when you combine this with abbreviated month names, you get this awkward edge-case.

    Sure there are ways to deal with this, but consider that other people might then run into an equivalent problem if they need to parse your dates in their code-base.

  3. Hard wire your DateTimeFormatter to an existing Locale where there are no dots in the abbreviated names.

    There is a theoretical risk that they may decide to change the abbreviations in a standard Locale. But doubt that they would, because such a change is liable to break customer code which is implicitly locale dependent ... like yours would be.

  4. Create a custom Locale and use that when creating the DateTimeFormatter.

  5. Use DateTimeFormatterBuilder for create the formatter. To deal with the month, use appendText(TemporalField field, Map<Long,String> textLookup) with a lookup table that contains exactly the abbreviations that you want to use.

    Depending on how you "append" the other fields, your formatter can be totally or partially locale independent.


Of these, 2. and 5. are the most "correct", in my opinion. Ole's answer illustrates these options with code.


1 - See this article on American English grammar - When you need periods after abbreviations.
2 - The problem would be convincing people that "looks odd but is logical" is better than "looks nice but is illogical". I don't think you would win this argument ...

Conjugation answered 6/11, 2021 at 3:20 Comment(0)
O
4

Stephen C. has written an answer that covers your options really well. As a supplement, since I agree that options 2 and 5 are the most correct, I would like to spell those two out.

Option 2: Use a different format

Localized date formats for most available locales are built into Java. These are generally under-used. We can save ourselves a lot if trouble by relying on Java to know how to format dates for our audience and their locale. I am using German as an example because it’s one of those locales that consistently includes dots both between the parts of the date and for abbreviation. The following should work for your locale too even if it’s not German (if you substitute Locale.getDefault(Locale.Category.FORMAT) or your users’ locale).

private static final Locale LOCALE = Locale.GERMAN;

private static final DateTimeFormatter DATE_FORMATTER
        = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
                .withLocale(LOCALE);

For demonstration I am formatting a day of each month of the current year:

    LocalDate date = LocalDate.of(2021, Month.JANUARY, 16);
    for (int i = 0; i < 12; i++) {
        System.out.println(date.format(DATE_FORMATTER));
        date = date.plusMonths(1).minusDays(1);
    }

Output is:

16.01.2021
15.02.2021
14.03.2021
13.04.2021
12.05.2021
11.06.2021
10.07.2021
09.08.2021
08.09.2021
07.10.2021
06.11.2021
05.12.2021

For German locale we got numeric months here. Other locales may give other results, for example month abbreviations.

If you want a longer format that doesn’t use numeric months, specify for example FormatStyle.LONG instead of FormatStyle.MEDIUM:

private static final DateTimeFormatter DATE_FORMATTER
        = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)
                .withLocale(LOCALE);

16. Januar 2021
15. Februar 2021
14. März 2021
13. April 2021
12. Mai 2021
11. Juni 2021
10. Juli 2021
9. August 2021
8. September 2021
7. Oktober 2021
6. November 2021
5. Dezember 2021

I suggest that your users would be happy with one of the above.

Option 5: DateTimeFormatterBuilder.appendText(TemporalField, Map<Long, String>)

If your users tell you that they don’t want the localized formats above and they do want your format with month abbreviations and single dots — it’s getting longer, but the result is beautiful and everyone will be happy.

private static final DateTimeFormatter DATE_FORMATTER = new DateTimeFormatterBuilder()
        .appendPattern("dd.")
        .appendText(ChronoField.MONTH_OF_YEAR, getMonthAbbreviations())
        .appendPattern(".uuuu")
        .toFormatter(LOCALE);

private static Map<Long, String> getMonthAbbreviations() {
    return Arrays.stream(Month.values())
            .collect(Collectors.toMap(m -> Long.valueOf(m.getValue()),
                    MyClass::getDisplayNameWithoutDot));
}

private static String getDisplayNameWithoutDot(Month m) {
    return m.getDisplayName(TextStyle.SHORT, LOCALE)
            .replaceFirst("\\.$", "");
}

Output from the same loop as above:

16.Jan.2021
15.Feb.2021
14.März.2021
13.Apr.2021
12.Mai.2021
11.Juni.2021
10.Juli.2021
09.Aug.2021
08.Sep.2021
07.Okt.2021
06.Nov.2021
05.Dez.2021

One dot each time. The central trick is to use Java’s month abbreviation and remove the dot from it if there is one (Jan. becomes Jan) and use it as-is if there is no dot (Mai stays Mai). My getDisplayNameWithoutDot method does this. I am in turn using this method to build the map that the two-arg appendText(TemporalField, Map<Long, String>) method requires and uses for formatting.

Orbit answered 6/11, 2021 at 5:39 Comment(0)
K
2

Don't use MMM but LLL pattern letters to get the SHORT_STANDALONE text style:

M/L month-of-year number/text 7; 07; Jul; July; J

@Test
void formatLLL() {
    final DateTimeFormatter format = DateTimeFormatter.ofPattern("dd.LLL.yyyy")
            .withLocale(Locale.GERMAN);

    assertEquals(
            "03.Dez.2017",
            format.format(LocalDate.parse("2017-12-03")));
}

@Test
void formatMMM() {
    final DateTimeFormatter format = DateTimeFormatter.ofPattern("dd.MMM.yyyy")
            .withLocale(Locale.GERMAN);

    assertEquals(
            "03.Dez..2017",
            format.format(LocalDate.parse("2017-12-03")));
}

DateTimeFormatter Patterns for Formatting and Parsing:

Text: The text style is determined based on the number of pattern letters used. Less than 4 pattern letters will use the short form. Exactly 4 pattern letters will use the full form. Exactly 5 pattern letters will use the narrow form. Pattern letters 'L', 'c', and 'q' specify the stand-alone form of the text styles.

Number/Text: If the count of pattern letters is 3 or greater, use the Text rules above. Otherwise use the Number rules above.

Kermit answered 24/10, 2023 at 18:44 Comment(1)
I checked the code of DateTimeFormatter. It's pretty confusing but I found this: Apparently L triggers a "stand-alone" format, corresponding to TextStyle.SHORT_STANDALONE. The documentation says this: "The difference between the 'standard' and 'stand-alone' forms is trickier to describe as there is no difference in English. However, in other languages there is a difference in the word used when the text is used alone." Maybe the final clue about the dots lies in the FormatData_de_* classes...Autoxidation

© 2022 - 2024 — McMap. All rights reserved.