提问人:hooknc 提问时间:9/16/2023 最后编辑:hooknc 更新时间:9/18/2023 访问量:97
现有年份、可选月份和可选日期的 DateTimeFormatter 验证
DateTimeFormatter Validation for Existing Year, Optional Month, and Optional Day
问:
我们正在尝试编写一个 DateTimeFormatter,以帮助我们验证 ISO 8601,该 ISO 8601 允许最终用户仅输入一年、一年和一个月,或一年、一个月和一天。我们还想验证输入的日期是否确实存在。
在下面的代码中,有两个日期和日期验证的示例,它们表现得很时髦。第一种是仅针对一年的验证测试,并带有可选的月份。这不能正确验证一个月的“00”。
第二个示例显示测试 (NOT) 因不正确的可选月份值而失败。
任何友好的指导将不胜感激。
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
import java.time.Year;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
import java.time.temporal.TemporalQuery;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class DateTimeFormatterForStackOverflowTest {
@Test
public void test_year_or_year_and_month_not_valid() {
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("uuuu[-MM]")
.toFormatter()
.withResolverStyle(ResolverStyle.STRICT);
this.expectException("1984-0", formatter, YearMonth::from, Year::from);
// Doesn't throw exception.
this.expectException("1984-00", formatter, YearMonth::from, Year::from);
// Doesn't throw exception.
this.expectException("1984-13", formatter, YearMonth::from, Year::from);
// Doesn't throw exception.
this.expectException("1984-99", formatter, YearMonth::from, Year::from);
}
@Test
public void test_year_or_year_month_or_year_month_day_not_valid() {
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("[uuuu-MM-dd][uuuu-MM][uuuu]")
.toFormatter()
.withResolverStyle(ResolverStyle.STRICT);
this.expectException("1984-0", formatter, LocalDate::from, YearMonth::from, Year::from);
// Doesn't throw exception.
this.expectException("1984-00", formatter, LocalDate::from, YearMonth::from, Year::from);
// Doesn't throw exception.
this.expectException("1984-13", formatter, LocalDate::from, YearMonth::from, Year::from);
// Doesn't throw exception.
this.expectException("1984-99", formatter, LocalDate::from, YearMonth::from, Year::from);
this.expectException("1984-00-01", formatter, LocalDate::from, YearMonth::from, Year::from);
this.expectException("1984-13-01", formatter, LocalDate::from, YearMonth::from, Year::from);
this.expectException("1984-01-0", formatter, LocalDate::from, YearMonth::from, Year::from);
this.expectException("1984-01-00", formatter, LocalDate::from, YearMonth::from, Year::from);
this.expectException("1984-01-32", formatter, LocalDate::from, YearMonth::from, Year::from);
this.expectException("1984-12-00", formatter, LocalDate::from, YearMonth::from, Year::from);
this.expectException("1984-12-32", formatter, LocalDate::from, YearMonth::from, Year::from);
}
private void expectException(String value, DateTimeFormatter formatter, TemporalQuery<?>... queries) {
assertThrows(DateTimeParseException.class,
() -> formatter.parseBest(value, queries));
}
}
测试输出:
org.opentest4j.AssertionFailedError: Expected java.time.format.DateTimeParseException to be thrown, but nothing was thrown.
at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:152)
at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:73)
at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:35)
at org.junit.jupiter.api.Assertions.assertThrows(Assertions.java:3083)
at DateTimeFormatterForStackOverflowTest.expectException(DateTimeFormatterForStackOverflowTest.java:59)
at DateTimeFormatterForStackOverflowTest.test_year_or_year_month_or_year_month_day_not_valid(DateTimeFormatterForStackOverflowTest.java:43)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:217)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService$ExclusiveTask.compute(ForkJoinPoolHierarchicalTestExecutorService.java:185)
at java.base/java.util.concurrent.RecursiveAction.exec(RecursiveAction.java:189)
at java.base/java.util.concurrent.ForkJoinTask.doExec$$$capture(ForkJoinTask.java:290)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)
答:
4赞
VGR
9/16/2023
#1
您可以通过打印以下结果的类型来了解正在发生的事情:parseBest
assertThrows(DateTimeParseException.class,
() -> System.out.println(
formatter.parseBest(value, queries).getClass()));
结果是:
java.time.Year
正如您预期的那样,“1984-00”不能解析为 YearMonth。但是您作为 TemporalQuery 传递,因此在 parseBest 发现无法使用 YearMonth.from 解析字符串后,它会再次尝试使用 Year.from,结果成功了。YearMonth::from, Year::from
为什么会成功?因为 Year 只需要 year 部分(在 DateTimeFormatter 中)即可解析。从未尝试解析月份,因此会忽略 。对于Year.from来说,该部分是否可以解析为有效月份并不重要。uuuu
00
如果我想解析多个类型,并且想要确保整个字符串始终有效,我将使用一个简单的方法:
public Temporal parse(String value) {
String len = value.length();
if (len == 4) {
return Year.parse(value);
} else if (len == 7) {
return YearMonth.parse(value);
} else {
return LocalDate.parse(value);
}
}
评论
0赞
hooknc
9/16/2023
感谢您的回答/评论。我确实注意到我们在其他测试中获得了一年的时间,但您的评论解释了为什么会发生这种情况。我想我现在已经知道如何真正解决这个问题了,但这需要一些工作......首先,我认为我们必须弄清楚实际格式是否正确(使用非严格的 DateTimeFormatter),如果格式正确,则使用严格的 DateTimeFormatter 来验证日期是否确实存在。你觉得这合适吗?感谢您抽出宝贵时间接受采访。
1赞
VGR
9/16/2023
@hooknc 您必须使用单个 DateTimeFormatter 完成所有操作吗?我只想选择直接的方法:if (length == 4) temporal = Year.parse(value); else if (length == 7) temporal = YearMonth.parse(value); else temporal = LocalDate.parse(value);
0赞
hooknc
9/16/2023
否,单个 DateTimeFormatter 不是必需的。我们只是喜欢简单。但看起来我们将使用多个格式化程序来完成这项工作。非常感谢您的回答和反馈。当我们开始工作时,我会发布我们的答案。
评论