Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SimpleDateFormat with German Locale - Java 8 vs Java 10+

I have code and a test-case in a legacy application, which can be summarized as follows:

@Test public void testParseDate() throws ParseException {     String toParse = "Mo Aug 18 11:25:26 MESZ +0200 2014";     String pattern = "EEE MMM dd HH:mm:ss z Z yyyy";      DateFormat dateFormatter = new SimpleDateFormat(pattern, Locale.GERMANY);     Date date = dateFormatter.parse(toParse);      //skipped assumptions } 

This test passes in Java 8 and below. However with Java 10 upwards this leads to a java.text.ParseException: Unparseable date: "Mo Aug 18 11:25:26 MESZ +0200 2014".

For the record: Besides de_DE, the exception is also thrown for the locales de_CH, de_AT, de_LU.

I am aware of the fact, that Date formatting was changed with JDK 9 (JEP 252). However, I consider this to be a disruptive change breaking backwards compatibility. Excerpted:

In JDK 9, the Unicode Consortium's Common Locale Data Repository (CLDR) data is enabled as the default locale data, so that you can use standard locale data without any further action.

In JDK 8, although CLDR locale data is bundled with the JRE, it isn’t enabled by default.

Code that uses locale-sensitive services such as date, time, and number formatting may produce different results with the CLDR locale data.

Adding a . for the day of the week (Mo.) compensates for this and test would pass. However, this is not a real solution for old data (in serialized form such as XML).

Checking this stackoverflow post, it seems that the behaviour is intentional for the German locale and can be mitigated by specifying java.locale.providers with COMPAT mode. However, I do not like the idea to rely on some system property value for two reasons as it might:

  1. change in the next releases of the JDK.
  2. be forgotten in different environments.

My question is:

  • How can I maintain backwards compatibility of legacy code with this particular date pattern, without re-writing / modifying existing serialized data or adding / changing system properties (like java.locale.providers), which may be forgotten in different environments (application servers, standalone jars, ...) ?
like image 525
rzo1 Avatar asked May 18 '18 12:05

rzo1


People also ask

What can I use instead of SimpleDateFormat?

DateTimeFormatter is a replacement for the old SimpleDateFormat that is thread-safe and provides additional functionality.

What is the format of SimpleDateFormat?

Class SimpleDateFormat. SimpleDateFormat is a concrete class for formatting and parsing dates in a locale-sensitive manner. It allows for formatting (date -> text), parsing (text -> date), and normalization. SimpleDateFormat allows you to start by choosing any user-defined patterns for date-time formatting.

What is locale date in Java?

It is mostly used where we need to display or utilize the date and time functionality of Java. Both of these classes are present in com. text package. DateFormat is used for formatting a date into String based on specific locale that is provided as input.


2 Answers

I don’t say it’s a nice solution, but it seems to be a way through.

    Map<Long, String> dayOfWeekTexts = Map.of(1L, "Mo", 2L, "Di",              3L, "Mi", 4L, "Do", 5L, "Fr", 6L, "Sa", 7L, "So");     Map<Long, String> monthTexts = Map.ofEntries(Map.entry(1L, "Jan"),              Map.entry(2L, "Feb"), Map.entry(3L, "Mär"), Map.entry(4L, "Apr"),             Map.entry(5L, "Mai"), Map.entry(6L, "Jun"), Map.entry(7L, "Jul"),             Map.entry(8L, "Aug"), Map.entry(9L, "Sep"), Map.entry(10L, "Okt"),             Map.entry(11L, "Nov"), Map.entry(12L, "Dez"));      DateTimeFormatter formatter = new DateTimeFormatterBuilder()             .appendText(ChronoField.DAY_OF_WEEK, dayOfWeekTexts)             .appendLiteral(' ')             .appendText(ChronoField.MONTH_OF_YEAR, monthTexts)             .appendPattern(" dd HH:mm:ss z Z yyyy")             .toFormatter(Locale.GERMANY);      String toParse = "Mo Aug 18 11:25:26 MESZ +0200 2014";     OffsetDateTime odt = OffsetDateTime.parse(toParse, formatter);     System.out.println(odt);     ZonedDateTime zdt = ZonedDateTime.parse(toParse, formatter);     System.out.println(zdt); 

Output running on my Oracle JDK 10.0.1:

2014-08-18T11:25:26+02:00 2014-08-18T11:25:26+02:00[Europe/Berlin] 

Then again, no nice solution may exist.

java.time, the modern Java date and time API, allows us to specify texts to use for fields for both formatting and parsing. So I exploit that for both day of week and for month, specifying the abbreviations without dot that were used with the old COMPAT or JRE locale data. I have used the Java 9 Map.of and Map.ofEntries for building the maps we need. If this is to work in Java 8 too, you must find some other way to populate the two maps, I trust you to do that.

If you do need an old-fashioned java.util.Date (likely in a legacy code base), convert like this:

    Date date = Date.from(odt.toInstant());     System.out.println("As legacy Date: " + date); 

Output in my time zone (Europe/Copenhagen, probably roughly agrees with yours):

As legacy Date: Mon Aug 18 11:25:26 CEST 2014 

Suggestion for a strategy

I am thinking that if that were me, I’d consider proceeding this way:

  1. Wait. Set the relevant system property from within Java: System.setProperty("java.locale.providers", "COMPAT,CLDR"); so it won’t be forgot in any environment. The COMPAT locale data have been around since 1.0 (I believe, at least close), so a lot of code out there depends on it (not only yours). The name was changed from JRE to COMPAT in Java 9. To me this may sound like a plan to keep the data around for quite a while still. According to the early access documentation it will still be available in Java 11 (the next “long term support” Java version) and with no deprecation warning or the like. And should it be removed in some future Java version, you will probably be able to find out soon enough that you can deal with the problem before upgrading.
  2. Use my solution above.
  3. Use the locale service provider interface that Basil Bourque linked to. There is no doubt that this is the nice solution in case the COMPAT data should be removed some unknown time in the future. You may even be able to copy the COMPAT locale data into your own files so they can’t take them away from you, only check if there are copyright issues before you do so. The reason why I mention the nice solution last is you said you aren’t happy with having to set a system property in every possible environment where your program may run. As far as I can tell, using your own locale data through the locale service provider interface will still require you to set the same system property (only to a different value).
like image 132
Ole V.V. Avatar answered Sep 24 '22 13:09

Ole V.V.


Just to mention: SimpleDateFormat is an old way to format dates which BTW is not thread safe. Since Java 8 there are new packages called java.time and java.time.format and you should use those to work with dates. For your purposes you should use class DateTimeFormatter.

like image 44
Michael Gantman Avatar answered Sep 21 '22 13:09

Michael Gantman