Parsing with Java 8 DateTimeFormatter and Spanish month names

前端 未结 1 1599
予麋鹿
予麋鹿 2021-01-05 21:37

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

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


        
1条回答
  •  醉梦人生
    2021-01-05 21:45

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

    0 讨论(0)
提交回复
热议问题