使用Java 8 DateTimeFormatter和西班牙语月份名称进行解析

Kri*_*ris 4 java datetime date-parsing java-8 java-time

使用'old',Java 8之前SimpleDateFormat我可以做到:

new java.text.SimpleDateFormat("MMM yyyy", new java.util.Locale("es", "ES")).parse("Mayo 2017")
Run Code Online (Sandbox Code Playgroud)

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

如何使用Java 8实现相同的功能DateTimeFormatter

我试过了:

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

但只得到一个java.time.format.DateTimeParseException.

小智 9

ofLocalizedDateTime()可以删除调用,因为最后你调用ofPattern(),创建另一个格式ofLocalizedDateTime(FormatStyle.FULL)函数与完全不同的模式(返回的模式与just非常不同month year,所以这不是你想要的).

另一个细节是Mayo完整的月份名称,因此模式必须是MMMM(检查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");
Run Code Online (Sandbox Code Playgroud)

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

YearMonth ym = YearMonth.parse("Mayo 2017", fmt);
System.out.println(ym); // 2017-05
Run Code Online (Sandbox Code Playgroud)

默认值

当输入没有所有字段时,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"));
Run Code Online (Sandbox Code Playgroud)

使用此功能,您可以将其解析为a LocalDateTime,并将缺少的字段分配给相应的默认值:

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

如果您需要获取java.util.Date与创建的值相同的值,则SimpleDateFormat可以将其转换LocalDateTime为JVM默认时区,然后将其转换为Date:

Date javaUtilDate = Date.from(dt.atZone(ZoneId.systemDefault()).toInstant());
Run Code Online (Sandbox Code Playgroud)

请注意,我必须明确使用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());
Run Code Online (Sandbox Code Playgroud)

即使在运行时,也可以在不事先通知的情况下更改默认时区,因此最好始终明确指出您正在使用的时区.

API使用IANA时区名称(始终采用格式Region/City,如America/New_YorkEurope/Berlin),因此您可以调用ZoneId.of("America/New_York")示例.避免使用3个字母的缩写(如CSTPST),因为它们不明确且不标准.

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

  • 另请注意,这可以解析为YearMonth,但不能解析为LocalDate,LocalDateTime或Instant(相当于Date),因为缺少必需的部分. (3认同)
  • @JBNizet确实,我刚刚更新了答案.谢谢! (2认同)