SimpleDateFormat toPattern behaves differently in java 9
Asked Answered
B

1

7
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, new Locale("SV", "SE"));
((SimpleDateFormat) dateFormat).toPattern();

In Java 8 this line produces "yyyy-MM-dd" while in Java 9 it produces "y-MM-dd".

This has some serious issues for our legacy code, is there some way to revert the behaviour?

Burr answered 16/2, 2018 at 15:7 Comment(9)
that is not compilingAllotrope
@ΦXocę웃Пepeúpaツ ok it works in intellij evaluate expression but it doesnt compile without casting it, updated my example.Burr
Possibly because of the CLDR date time patterns? The point from migration guide being If your application starts successfully, look carefully at your tests and ensure that the behavior is the same as on JDK 8. For example, a few early adopters have noticed that their dates and currencies are formatted differently. See Use CLDR Locale Data by Default.Agglutinative
If you need the string "yyyy-MM-dd", just use the string "yyyy-MM-dd". If you ask a factory for the pattern for a locale and style, you accept that this is a moving target. I doubt that Sweden is willing to nail down their date format just for the sake of some legacy Java code…Lamina
When using Java 9, why on earth are you still using the long outdated and notoriously troublesome SimpleDateFormat? I can understand if you don’t want to remove it from your legacy code right now, but for the task at hand, use DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.SHORT, null, IsoChronology.INSTANCE, new Locale("SV", "SE")) (this doesn’t solve your problem, though, it too behaves differently in Java 8 and 9).Olid
@OleV.V. Because it's in a huge legacy monolith and there is plenty of stuff to fix. Is it safe to replace it in one place and leave SimpleDateFormat in another that shares code?Burr
@Lamina Well it's obviously not hardcoded to SV SE in our app, that just in the test but it was the easier way to ask the question.Burr
@Burr it doesn’t matter which locale you are querying, none of them guarantees to have never changing properties.Lamina
@Burr I rewrote my answer to try to cover that part too.Olid
O
8
    System.setProperty("java.locale.providers", "COMPAT,CLDR");
    DateFormat dateFormat
            = DateFormat.getDateInstance(DateFormat.SHORT, new Locale("sv", "SE"));
    System.out.println(((SimpleDateFormat) dateFormat).toPattern());

Running on my jdk-9.0.4 this prints

yyyy-MM-dd

You may want to set the property with -D on the command line instead, it shouldn’t make any difference.

In Java 9 the Common Locale Data Repository (CLDR) from the Unicode Consortium is used as the default source of locale data, which wasn’t the case in earlier Java versions. Setting the system property as above enables the Java 8 behaviour. As nullpointer said in a comment, you can read a bit more here: Use CLDR Locale Data by Default.

Basil Bourque is correct in his comment, it is customary to use lowercase abbrevation for language, so you should probably specify sv to make sure you don’t confuse your reader.

One might also wonder whether it makes any difference whether there’s one y or four in the pattern. The way I read the SimpleDateFormat documentation, one y will interpret a 2-digit year according to the 80-20 rule: it’s within the last 80 years or within the next 20. yyyy will interpret a 2-digit year as a year in the first century AD. Assuming your years are in the 4-digit range (1000 through 9999), I wouldn’t expect it to be a problem, though.

If this was only for Swedish locale, the modern version of the code would give the same result:

DateTimeFormatterBuilder.getLocalizedDateTimePattern(
        FormatStyle.SHORT, null, IsoChronology.INSTANCE, new Locale("SV", "SE")));

However, for a number of locales (en-CY, Cyprus English, for example) the result differs from DateFormat to DateTimeFormatter, so if your goal was maximal backward compatibility, stick with the outmoded DateFormat class.

Olid answered 16/2, 2018 at 17:6 Comment(8)
Perhaps it does not matter, but I believe the various locale-related standards use lowercase in the language code. So perhaps that should be new Locale( “sv” , “SE” )?Monica
I was thinking the same, @BasilBourque, but took the Locale verbatim from the question. I have run the code, it works.Olid
new Locale("SV", "SE").equals(new Locale("sv", "SE")) returns true, so I take it it doesn’t matter. @BasilBourqueOlid
"one y will interpret a 2-digit year according to the 80-20 rule", I rather think, TWO yy will cause this interpretation. ONE y versus yyyy is just about padding, and then the question arises why the OP might have issues with padding in legacy code. Is it sensible to have years like 768 (formatted as such or as "0768")? Probably a data error. That is the real question.Eelgrass
Both are true, @MenoHochschild. “For parsing with the abbreviated year pattern ("y" or "yy"), SimpleDateFormat must interpret the abbreviated year relative to some century.” From Java 9 SimpleDateFormat documentation.Olid
Oh I see. But this is a change totally incompatible with CLDR-standard. In Minguo calendar, we have actually the year 107. Here the old interpretation with only one y is relevant.Eelgrass
See also the conflict between CLDR and new SimpleDateFormat about the meaning of "y" (shaking my head).Eelgrass
Caution: The property "java.locale.providers" is only read at the Java runtime startup, so the later call to System.setProperty() won't have a effect. (See LocaleServiceProvider API ). So setting this property in-code (as in the given example by @OleV.V.) is not best-pratice and may lead to hard to find errors.Vandalize

© 2022 - 2024 — McMap. All rights reserved.