为LocalDate实例提供灵活的解析器,可以使用以下格式之一处理输入:
以下类尝试处理第一个和第二个模式。解析工作年份输入,但年+月导致下面列出的例外情况。
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
public class DateTest {
public static void main(String[] args) {
DateTimeFormatter parser = new DateTimeFormatterBuilder()
.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
.parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
.appendPattern("yyyy")
.optionalStart().appendPattern("MM").optionalEnd().toFormatter();
System.out.println(parser.parse("2014", LocalDate::from)); // Works
System.out.println(parser.parse("201411", LocalDate::from)); // Fails
}
}
第二次解析()尝试会导致以下异常:
Exception in thread "main" java.time.format.DateTimeParseException: Text '201411' could not be parsed at index 0
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
我认为我对可选部分模式如何工作的理解是缺乏的。我的目标是一个灵活格式的解析器甚至可以实现,还是我需要检查输入长度并从解析器列表中选择?一如既往,感谢帮助。
答案 0 :(得分:2)
这是解决方案。您可以在appendPattern()中定义可能的模式。以及可选的默认值。
DateTimeFormatter parser = new DateTimeFormatterBuilder()
.appendPattern("[yyyy][yyyyMM][yyyyMMdd]")
.optionalStart()
.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
.parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
.optionalEnd()
.toFormatter();
System.out.println(parser.parse("2014",LocalDate::from)); // Works
System.out.println(parser.parse("201411",LocalDate::from)); // Works
System.out.println(parser.parse("20141102",LocalDate::from)); // Works
输出
2014-01-01
2014-11-01
2014-11-02
答案 1 :(得分:2)
问题的真正原因是签名处理。您的输入没有任何符号,但解析器元素“yyyy”贪婪地解析尽可能多的数字并且期望一个正号,因为找到了超过四位数。
我的分析以两种不同的方式完成:
调试(为了查看不明确的错误消息背后的内容)
基于我的lib Time4J模拟另一个解析引擎中的行为,以获得更好的错误消息:
ChronoFormatter<LocalDate> cf =
ChronoFormatter
.ofPattern(
"yyyy[MM]",
PatternType.THREETEN,
Locale.ROOT,
PlainDate.axis(TemporalType.LOCAL_DATE)
)
.withDefault(PlainDate.MONTH_AS_NUMBER, 1)
.withDefault(PlainDate.DAY_OF_MONTH, 1)
.with(Leniency.STRICT);
System.out.println(cf.parse("201411"));
// java.text.ParseException: Positive sign must be present for big number.
您可以通过指示构建者一年只使用四位数来解决问题:
DateTimeFormatter parser =
new DateTimeFormatterBuilder()
.appendValue(ChronoField.YEAR, 4)
.optionalStart()
.appendPattern("MM[dd]")
.optionalEnd()
.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
.parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
.toFormatter();
System.out.println(parser.parse("2014", LocalDate::from)); // 2014-01-01
System.out.println(parser.parse("201411", LocalDate::from)); // 2014-11-01
System.out.println(parser.parse("20141130", LocalDate::from)); // 2014-11-30
注意构建器中默认元素的位置。它们不是在开始时调用,而是在最后调用,因为不幸的是,java.time
中对默认元素的处理是位置敏感的。我还在第一个可选部分的内部添加了一个额外的可选部分。这个解决方案对我来说似乎更干净,而不是像Danila Zharenkov建议的那样使用3个可选部分的序列,因为后者也可以用更多的数字来解析相当不同的输入(可能滥用可选部分作为替换or-patterns特别是在宽松中解析)。
关于默认元素的位置敏感行为,这里引用API-documentation:
在解析期间,将检查解析的当前状态。如果 指定的字段没有关联值,因为它还没有 在该点成功解析,然后指定的值为 注入解析结果。注射是立即的,因此 字段值对将对任何后续元素可见 格式化。因此,这种方法通常在结束时调用 助洗剂。
顺便说一句:在我的lib Time4J中,我还可以使用符号“|”定义真实或模式然后创建此格式化程序:
ChronoFormatter<LocalDate> cf =
ChronoFormatter
.ofPattern(
"yyyyMMdd|yyyyMM|yyyy",
PatternType.CLDR,
Locale.ROOT,
PlainDate.axis(TemporalType.LOCAL_DATE)
)
.withDefault(PlainDate.MONTH_AS_NUMBER, 1)
.withDefault(PlainDate.DAY_OF_MONTH, 1)
.with(Leniency.STRICT);
答案 2 :(得分:1)
在这部分代码中,您已经设置了月和日的值
.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
.parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
然后,您尝试在代码中传递月份和年份的输入
System.out.println(parser.parse("201411", LocalDate::from));
你已经设定了。
答案 3 :(得分:1)
您可以设置月和日的值,但会传递一个月和一年。这就是问题所在。
您可能想要使用:
.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
.parseDefaulting(ChronoField.YEAR_OF_ERA, ZonedDateTime.now().getYear())