Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.1k views
in Technique[技术] by (71.8m points)

datetime - Parsing with Java 8 DateTimeFormatter and Spanish month names

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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().


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...