Reputation: 5751
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:
My question is:
java.locale.providers
), which may be forgotten in different environments (application servers, standalone jars, ...) ?Upvotes: 51
Views: 11284
Reputation: 5392
Here is a working but ugly workaround for this. It is ugly because you have to redefine all words in an own map, but you still have all the benefits of the efficient and flexible default parser.
String dateString = "Mi Mai 09 09:17:24 2018";
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 dtf =
new DateTimeFormatterBuilder()
.appendText(ChronoField.DAY_OF_WEEK, dayOfWeekTexts)
.appendLiteral(' ')
.appendText(ChronoField.MONTH_OF_YEAR, monthTexts)
.appendPattern(" dd HH:mm:ss yyyy")
.toFormatter(Locale.GERMAN);
LocalDateTime dateTime = LocalDateTime.parse(dateString, dtf);
This is only a slightly modified answer from https://stackoverflow.com/a/50412644/1353930
Upvotes: 0
Reputation: 86399
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
I am thinking that if that were me, I’d consider proceeding this way:
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.Upvotes: 22
Reputation: 7808
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.
Upvotes: 3
Reputation: 111
The formatted value in java 8 was Fr Juni 15 00:20:21 MESZ +0900 2018 But it changed to Fr. Juni 15 00:20:21 MESZ +0900 2018 EEE includes . THIS IS COMPATIBILITY ISSUE and it does not matter that older versions of code do not work in newer versions.(Sorry for translator) If date string is yours, you should add dot for new version users. Or make users use Java 8 to use your software.
It can make the software slower, using substring method is also good.
String toParse = "Mo Aug 18 11:25:26 MESZ +0200 2014";
String str = toParse.substring(0, 2) + "." + toParse.substring(2);
String pattern = "EEE MMM dd HH:mm:ss z Z yyyy";
DateFormat dateFormatter = new SimpleDateFormat(pattern, Locale.GERMANY);
System.out.println(dateFormatter.format(System.currentTimeMillis()));
Date date = dateFormatter.parse(str);
Sorry again for my bad English.
Upvotes: 0