【问题标题】:Parsing with Java 8 DateTimeFormatter and Spanish month names使用 Java 8 DateTimeFormatter 和西班牙月份名称进行解析
【发布时间】:2018-02-28 12:32:37
【问题描述】:

使用“旧”,Java 8 之前的 SimpleDateFormat 我可以做到:

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

获取带有西班牙月份名称的日期的Date 对象。

如何使用 Java 8 和 DateTimeFormatter 实现相同的目标?

我试过了:

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

但只能得到java.time.format.DateTimeParseException

【问题讨论】:

    标签: java datetime java-8 java-time date-parsing


    【解决方案1】:

    可以删除对ofLocalizedDateTime()的调用,因为最后你调用ofPattern(),创建另一个具有完全不同模式的格式化程序(ofLocalizedDateTime(FormatStyle.FULL)返回的模式与month year非常不同,所以这不是你真正想要的)。

    另一个细节是Mayo 是完整的月份名称,所以模式必须是MMMM(更多细节是check the javadoc)。此外,DateTimeFormatter 默认情况下只接受小写名称(至少在我使用西班牙语语言环境进行的测试中),因此您必须将格式化程序设置为不区分大小写。

    您可以使用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");
    

    或者,您可以直接将其解析为java.time.YearMonth 对象,因为它似乎是这种情况下的最佳选择(因为输入只有年份和月份):

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

    默认值

    当输入没有包含所有字段时,SimpleDateFormat 只是为它们使用一些默认值。在这种情况下,输入只有年和月,因此解析的Date 将等同于解析的月/年,但日将设置为 1,时间设置为午夜(在 JVM 默认时区)。

    新的 API 对此非常严格,并且不会创建默认值,除非您告诉它这样做。配置它的一种方法是将parseDefaultingjava.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"));
    

    有了这个,你可以将其解析为LocalDateTime,缺失的字段将被分配给各自的默认值:

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

    如果你需要得到一个与SimpleDateFormat创建的值相同的java.util.Date,你可以将这个LocalDateTime转换为JVM默认时区,然后再转换为Date

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

    请注意,我必须明确使用 JVM 默认时区 (ZoneId.systemDefault()),这是 SimpleDateFormat 隐式使用的。


    另一种选择是手动设置YearMonth 值中的值:

    // 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());
    

    默认时区can be changed without notice, even at runtime,因此最好始终明确说明您使用的是哪个时区。

    API 使用IANA timezones names(格式始终为Region/City,如America/New_YorkEurope/Berlin),因此您可以调用ZoneId.of("America/New_York")。 避免使用三个字母的缩写(如CSTPST),因为它们是ambiguous and not standard

    您可以致电ZoneId.getAvailableZoneIds() 获取可用时区列表(并选择最适合您系统的时区)。

    【讨论】:

    • 另请注意,这可以解析为 YearMonth,但不能解析为 LocalDate、LocalDateTime 或 Instant(相当于 Date),因为缺少所需的部分。
    • @JBNizet 事实上,我刚刚更新了答案。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2015-08-29
    • 1970-01-01
    • 1970-01-01
    • 2012-11-30
    • 2021-11-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多