Parsing with Java 8 DateTimeFormatter and Spanish month names

时光毁灭记忆、已成空白 提交于 2019-12-19 09:03:57

问题


With the 'old', pre-Java 8 SimpleDateFormat I can do:

new java.text.SimpleDateFormat("MMM yyyy", new java.util.Locale("es", "ES")).parse("Mayo 2017")

to get the Date object of a date with Spanish month names.

How can I achieve the same with Java 8 and DateTimeFormatter?

I tried:

DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).withLocale(new Locale("es", "ES")).ofPattern("MMM yyyy").parse("Mayo 2017")

but only get a java.time.format.DateTimeParseException.


回答1:


The call to ofLocalizedDateTime() can be removed, because in the end you call ofPattern(), creating another formatter with a totally different pattern (and the pattern returned by ofLocalizedDateTime(FormatStyle.FULL) is very different from just month year, so that's not really what you want).

Another detail is that Mayo is the full month name, so the pattern must be MMMM (check the javadoc for more details). Also, the DateTimeFormatter by default accepts only lowercase names (at least in the tests I've made with Spanish locale), so you must set the formatter to be case insensitive.

You can do that by using a java.time.format.DateTimeFormatterBuilder:

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // case insensitive
    .parseCaseInsensitive()
    // pattern with full month name (MMMM)
    .appendPattern("MMMM yyyy")
    // set locale
    .toFormatter(new Locale("es", "ES"));
// now it works
fmt.parse("Mayo 2017");

Optionally, you can directly parse it to a java.time.YearMonth object, as it seems to be the best choice for this case (because the input has only year and month):

YearMonth ym = YearMonth.parse("Mayo 2017", fmt);
System.out.println(ym); // 2017-05

Default values

When the input doesn't have all the fields, SimpleDateFormat simply uses some defaults for them. In this case, the input has only year and month, so the parsed Date will be equivalent to the parsed month/year, but the day will be set to 1 and the time to midnight (at the JVM default timezone).

The new API is very strict about that and doesn't create default values unless you tell it to do so. One way to configure it is to use parseDefaulting with a java.time.temporal.ChronoField:

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // case insensitive
    .parseCaseInsensitive()
    // pattern with full month name (MMMM)
    .appendPattern("MMMM yyyy")
    // default value for day of month
    .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
    // default value for hour
    .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
    // default value for minute
    .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
    // set locale
    .toFormatter(new Locale("es", "ES"));

With this, you can parse it to a LocalDateTime and the missing fields will be assigned to the respective default values:

LocalDateTime dt = LocalDateTime.parse("Mayo 2017", fmt);
System.out.println(dt); // 2017-05-01T00:00

If you need to get a java.util.Date with the same value as the one created by SimpleDateFormat, you can convert this LocalDateTime to the JVM default timezone and then convert it to Date:

Date javaUtilDate = Date.from(dt.atZone(ZoneId.systemDefault()).toInstant());

Note that I had to explicity use the JVM default timezone (ZoneId.systemDefault()), which is implicity used by SimpleDateFormat.


Another alternative is to manually set the values in the YearMonth value:

// in this case, the formatter doesn't need the default values
YearMonth ym = YearMonth.parse("Mayo 2017", fmt);
ZonedDateTime z = ym
    // set day of month to 1
    .atDay(1)
    // midnight at JVM default timezone
    .atStartOfDay(ZoneId.systemDefault());
Date javaUtilDate = date.from(z.toInstant());

The default timezone can be changed without notice, even at runtime, so it's better to always make it explicit which one you're using.

The API uses IANA timezones names (always in the format Region/City, like America/New_York or Europe/Berlin), so you can call ZoneId.of("America/New_York") for example. Avoid using the 3-letter abbreviations (like CST or PST) because they are ambiguous and not standard.

You can get a list of available timezones (and choose the one that fits best your system) by calling ZoneId.getAvailableZoneIds().



来源:https://stackoverflow.com/questions/46305245/parsing-with-java-8-datetimeformatter-and-spanish-month-names

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!