问题描述
您好,我来这里是为了解决 Locale 格式化程序语言的 DateTimeFormatter 问题,让我向您介绍:
- 我正在读取具有以下字符串格式的 CSV 文件:“dd-MMM-yyyy”“28-dic-2017”
- 然后我必须将列表中的每个字符串解析为 LocalDate 以使用 timeAPI 方法处理每个字符串。
- 请注意,我的月份使用的是西班牙语缩写。真是一团糟!!
- 执行后返回:
观察到的堆栈跟踪错误:
Exception in thread "main" java.time.format.DateTimeParseException: Text '28-dic-2017' Could not be parsed at index 3
at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2051)
at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1953)
at java.base/java.time.LocalDate.parse(LocalDate.java:429)
at com.examen.Presentation.Principal.parseDate(Principal.java:28)
at com.examen.Presentation.Principal.main(Principal.java:19)
最小的、可重现的代码示例:
public static LocalDate parseDate(String fecha) {
Locale loc = new Locale("es","ES");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MMM-yyyy",loc);
LocalDate date = LocalDate.parse(fecha,formatter);
return date;
}
我在尝试不同语言环境中的不同事物和方法时遇到了同样的错误,DateTimeFormatter 甚至不知道我在做什么,但我认为这显然不会奏效。
解决方法
在另一个答案中,geocodezip 已经解释了您的尝试失败的原因。以下是我认为比该答案中的更优雅的两个解决方案建议。
Java 语言环境数据提供程序
Java 可以从多达四个来源获取其区域设置数据,包括不同语言的月份缩写。由于 Java 9 默认情况下,它更喜欢从 CLDR(Unicode 通用语言环境数据存储库)获取它们。在 CLDR 中,西班牙语月份的缩写带有点(或点),如另一个答案中所述,例如 dic.
表示 diciembre。另一个来源是自早期版本以来 Java 内置的区域设置数据(并且是 Java 8 之前的默认设置)。这里西班牙语月份的缩写没有点(你的数据可能是由运行在 Java 8 或更早版本上的程序生成的,我不知道)。这些语言环境数据称为 COMPAT
以与早期 Java 版本兼容。当您启动程序时,您可以在命令行中指定 Java 应该优先使用 COMPAT 而不是 CLDR,例如:
java -Djava.locale.providers=COMPAT,CLDR YourJavaMainClass
这将导致您现有的代码接受 dic
和其他不带点的月份缩写。
这是 JVM 的全局设置,因此它还会导致您的程序和在同一 JVM 中运行的其他程序中的任何区域设置敏感操作使用来自 Java 8 的旧区域设置数据。三思而后行。
DateTimeFormatterBuilder.appendText()
既然您知道您的格式总是 28-dic-2017
,您也可以让自己完全独立于任何区域设置数据,只需明确指定在您的输入数据中使用哪个月份的缩写即可。
private static Map<Long,String> getMonthAbbreviations() {
return Map.ofEntries(
Map.entry(1L,"ene"),Map.entry(2L,"feb"),Map.entry(3L,"mar"),Map.entry(4L,"abr"),Map.entry(5L,"may"),Map.entry(6L,"jun"),Map.entry(7L,"jul"),Map.entry(8L,"ago"),Map.entry(9L,"sep"),Map.entry(10L,"oct"),Map.entry(11L,"nov"),Map.entry(12L,"dic"));
}
private static final DateTimeFormatter inputDateFormatter = new DateTimeFormatterBuilder()
.appendPattern("dd-")
.appendText(ChronoField.MONTH_OF_YEAR,getMonthAbbreviations())
.appendPattern("-uuuu")
.toFormatter();
此格式化程序独立于语言环境并解析您的日期字符串:
String fecha = "28-dic-2017";
LocalDate date = LocalDate.parse(fecha,inputDateFormatter);
System.out.println(date);
输出为:
2017-12-28
解析问题的提示:先尝试格式化
提示:当解析不起作用时,您可以先尝试格式化以了解要解析的字符串应该是什么样子,并与您尝试解析的字符串进行比较。例如:
Locale loc = new Locale("es","ES");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MMM-yyyy",loc);
System.out.println("Want to parse: 28-dic-2017");
System.out.println("Formatted: "
+ LocalDate.of(2017,Month.DECEMBER,28).format(formatter));
System.out.println("Want to parse: 29-sep-2017");
System.out.println("Formatted: "
+ LocalDate.of(2017,Month.SEPTEMBER,29).format(formatter));
Want to parse: 28-dic-2017
Formatted: 28-dic.-2017
Want to parse: 29-sep-2017
Formatted: 29-sept.-2017
现在我们至少可以看到一开始有什么问题。
文档链接
-
LocaleServiceProvider
列出了 Java 语言环境数据的可能来源 DateTimeFormatterBuilder.appendText(TemporalField,Map)
对于西班牙语,DateTimeFormatter
期望缩写的月份以句点结束。像这样:28-dic.-2017
如果所有您的日期都是这种格式,您可以解析输入字符串以添加句点,然后将其发送到DateTimeFormatter
:
public static LocalDate parseDate(String fecha) {
int firstDash = fecha.indexOf('-')+1;
int secondDash = fecha.indexOf('-',fecha.indexOf('-')+1);
String month = fecha.substring(firstDash,secondDash);
String dateStr = fecha.substring(0,firstDash)+month+"."+fecha.substring(secondDash);
Locale loc = new Locale("es",loc);
LocalDate date = LocalDate.parse(dateStr,formatter);
return date;
}
如果格式有时有句点,如果它不存在,则需要添加它:
public static LocalDate parseDate(String fecha) {
String dateStr;
if (fecha.indexOf(".")!=-1) {
dateStr = fecha;
} else {
int firstDash = fecha.indexOf('-')+1;
int secondDash = fecha.indexOf('-',fecha.indexOf('-')+1);
String month = fecha.substring(firstDash,secondDash);
dateStr = fecha.substring(0,firstDash)+month+"."+fecha.substring(secondDash);
}
System.out.println(dateStr);
Locale loc = new Locale("es",formatter);
return date;
}
输出:
2017-12-28