带有文字且没有分隔符的 DateTimeFormatter 模式不起作用
DateTimeFormatter pattern with liternal and no separator does not work
DateTimeFormatter.ofPattern
生成的解析器表现出以下有趣的行为,这使我无法编写模式来解析像 20150100
这样的字符串:
System.out.println(DateTimeFormatter.ofPattern("yyyyMM").parse("201501", YearMonth::from)); // works
System.out.println(DateTimeFormatter.ofPattern("yyyyMM'aa'").parse("201501aa", YearMonth::from)); // works
System.out.println(DateTimeFormatter.ofPattern("yyyyMM'00'").parse("20150100", YearMonth::from));
// java.time.format.DateTimeParseException: Text '20150100' could not be parsed at index 0
我调试了代码,问题似乎是年字段解析超出了字符串的末尾(三个 y 及以上的最大宽度始终为 19)。但是,我不明白它如何适用于末尾没有 '00'
文字的模式。
有什么方法可以解决这个问题而不必使用格式化程序生成器吗?
编辑:
由于下面的 Jarrod 确认它有问题,我进行了更多的谷歌搜索,最终找到了错误报告:
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8031085
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8032491
两者都仅在 Java 9 中得到修复......
DateTimePrinterParser
中存在错误:
我一路调试,显然你不能将数字作为文字。类似的测试代码证明了这一点,如果您一直逐步调试到 DateTimeFormatterBuilder.parse()
方法,您可以看到它做错了什么。
显然,Value(YearOfEra,4,19,EXCEEDS_PAD)
解析器会消耗 00
如果这些 不是 数字,则它们会停止,因为它正在寻找一个数字 4
到 19
位长。 DateTimeParseContext
中嵌入的 DateTimeFormatter
是错误的。
如果你输入像 xx
这样的非数字字符文字,它可以工作,数字文字则不行。
这两个都失败了:
final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM'00'");
System.out.println(sdf.parse("20150100"));
Exception in thread "main" java.text.ParseException: Unparseable date:
"20150100" at java.text.DateFormat.parse(DateFormat.java:366)
final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMM'00'");
System.out.println(dateTimeFormatter.parse("20150100", YearMonth::from));
Exception in thread "main" java.time.format.DateTimeParseException:
Text '20150100' 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)
这两个都成功了:
final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM'xx'");
System.out.println(sdf.parse("201501xx"));
Thu Jan 01 00:00:00 EST 2015
final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMM'xx'");
System.out.println(dateTimeFormatter.parse("201501xx", YearMonth::from));
2015-01
如果您不介意使用第 3 方库,那么您可以试试我的库 Time4J,其最新版本 v4.18 可以满足您的需求:
import net.time4j.Month;
import net.time4j.range.CalendarMonth;
import net.time4j.format.expert.ChronoFormatter;
import net.time4j.format.expert.PatternType;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.text.ParseException;
import java.util.Locale;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
@RunWith(JUnit4.class)
public class CalendarMonthTest {
@Test
public void parse2() throws ParseException {
assertThat(
ChronoFormatter.ofPattern(
"yyyyMM'00'",
PatternType.CLDR,
Locale.ROOT,
CalendarMonth.chronology()
).parse("20150100"),
is(CalendarMonth.of(2015, Month.JANUARY)));
}
}
顺便说一句,指向 JDK-bug-log 的链接与您的问题并没有真正的关系。这些问题仅描述了在小数秒的上下文中应用相邻数字解析时的问题。虽然 Java-9 会解决该问题,但您的问题不会。也许您想在那里开一个新问题?但我怀疑 Oracle 会将其视为错误。这是一个新特性,直到现在 Oracle 分发的任何库都不支持它。在 JSR-310(又名 java.time 包)中,带有(前导)数字的文字预计不会参与相邻值解析(在 SimpleDateFormat
中也不会)。
旁注:Time4J 不仅是对这个细节(数字文字)的回答,而且通常在解析方面提供更好的性能,并且由于有很多转换方法,可以与 JSR-310 并行使用。例如:要实现YearMonth
的实例,只需在解析结果上调用calendarMonth.toTemporalAccessor()
即可。
作为 user177800 回答的补充,您可以改用这个表格:
var formatter = new DateTimeFormatterBuilder()
.appendValue(ChronoField.YEAR, 4)
.appendValue(ChronoField.MONTH_OF_YEAR, 2)
.appendLiteral("00")
.toFormatter();
YearMonth.parse("20220200", formatter);
java.time
的所有部分。
DateTimeFormatter.ofPattern
生成的解析器表现出以下有趣的行为,这使我无法编写模式来解析像 20150100
这样的字符串:
System.out.println(DateTimeFormatter.ofPattern("yyyyMM").parse("201501", YearMonth::from)); // works
System.out.println(DateTimeFormatter.ofPattern("yyyyMM'aa'").parse("201501aa", YearMonth::from)); // works
System.out.println(DateTimeFormatter.ofPattern("yyyyMM'00'").parse("20150100", YearMonth::from));
// java.time.format.DateTimeParseException: Text '20150100' could not be parsed at index 0
我调试了代码,问题似乎是年字段解析超出了字符串的末尾(三个 y 及以上的最大宽度始终为 19)。但是,我不明白它如何适用于末尾没有 '00'
文字的模式。
有什么方法可以解决这个问题而不必使用格式化程序生成器吗?
编辑:
由于下面的 Jarrod 确认它有问题,我进行了更多的谷歌搜索,最终找到了错误报告:
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8031085
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8032491
两者都仅在 Java 9 中得到修复......
DateTimePrinterParser
中存在错误:
我一路调试,显然你不能将数字作为文字。类似的测试代码证明了这一点,如果您一直逐步调试到 DateTimeFormatterBuilder.parse()
方法,您可以看到它做错了什么。
显然,Value(YearOfEra,4,19,EXCEEDS_PAD)
解析器会消耗 00
如果这些 不是 数字,则它们会停止,因为它正在寻找一个数字 4
到 19
位长。 DateTimeParseContext
中嵌入的 DateTimeFormatter
是错误的。
如果你输入像 xx
这样的非数字字符文字,它可以工作,数字文字则不行。
这两个都失败了:
final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM'00'");
System.out.println(sdf.parse("20150100"));
Exception in thread "main" java.text.ParseException: Unparseable date: "20150100" at java.text.DateFormat.parse(DateFormat.java:366)
final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMM'00'");
System.out.println(dateTimeFormatter.parse("20150100", YearMonth::from));
Exception in thread "main" java.time.format.DateTimeParseException: Text '20150100' 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)
这两个都成功了:
final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM'xx'");
System.out.println(sdf.parse("201501xx"));
Thu Jan 01 00:00:00 EST 2015
final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMM'xx'");
System.out.println(dateTimeFormatter.parse("201501xx", YearMonth::from));
2015-01
如果您不介意使用第 3 方库,那么您可以试试我的库 Time4J,其最新版本 v4.18 可以满足您的需求:
import net.time4j.Month;
import net.time4j.range.CalendarMonth;
import net.time4j.format.expert.ChronoFormatter;
import net.time4j.format.expert.PatternType;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.text.ParseException;
import java.util.Locale;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
@RunWith(JUnit4.class)
public class CalendarMonthTest {
@Test
public void parse2() throws ParseException {
assertThat(
ChronoFormatter.ofPattern(
"yyyyMM'00'",
PatternType.CLDR,
Locale.ROOT,
CalendarMonth.chronology()
).parse("20150100"),
is(CalendarMonth.of(2015, Month.JANUARY)));
}
}
顺便说一句,指向 JDK-bug-log 的链接与您的问题并没有真正的关系。这些问题仅描述了在小数秒的上下文中应用相邻数字解析时的问题。虽然 Java-9 会解决该问题,但您的问题不会。也许您想在那里开一个新问题?但我怀疑 Oracle 会将其视为错误。这是一个新特性,直到现在 Oracle 分发的任何库都不支持它。在 JSR-310(又名 java.time 包)中,带有(前导)数字的文字预计不会参与相邻值解析(在 SimpleDateFormat
中也不会)。
旁注:Time4J 不仅是对这个细节(数字文字)的回答,而且通常在解析方面提供更好的性能,并且由于有很多转换方法,可以与 JSR-310 并行使用。例如:要实现YearMonth
的实例,只需在解析结果上调用calendarMonth.toTemporalAccessor()
即可。
作为 user177800 回答的补充,您可以改用这个表格:
var formatter = new DateTimeFormatterBuilder()
.appendValue(ChronoField.YEAR, 4)
.appendValue(ChronoField.MONTH_OF_YEAR, 2)
.appendLiteral("00")
.toFormatter();
YearMonth.parse("20220200", formatter);
java.time
的所有部分。