From 7b1fcfc7c3ead828dc55a305598da75c781b8dee Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 23 Feb 2016 16:12:26 +0100 Subject: [PATCH] Consistently strict parsing of date overflows (using java.time's strict resolution style) Issue: SPR-13567 --- .../format/annotation/DateTimeFormat.java | 8 +++++++- .../datetime/standard/DateTimeFormatterFactory.java | 9 +++++++-- .../format/datetime/DateFormattingTests.java | 10 +++++++++- .../format/datetime/joda/JodaTimeFormattingTests.java | 10 +++++++++- .../datetime/standard/DateTimeFormattingTests.java | 10 +++++++++- 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java b/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java index 5feb6ee8b0e..e0e79e54c69 100644 --- a/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java +++ b/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -75,6 +75,12 @@ public @interface DateTimeFormat { *

Defaults to empty String, indicating no custom pattern String has been specified. * Set this attribute when you wish to format your field in accordance with a custom * date time pattern not represented by a style or ISO format. + *

Note: This pattern follows the original {@link java.text.SimpleDateFormat} style, + * as also supported by Joda-Time, with strict parsing semantics towards overflows + * (e.g. rejecting a Feb 29 value for a non-leap-year). As a consequence, 'yy' + * characters indicate a year in the traditional style, not a "year-of-era" as in the + * {@link java.time.format.DateTimeFormatter} specification (i.e. 'yy' turns into 'uu' + * when going through that {@code DateTimeFormatter} with strict resolution mode). */ String pattern() default ""; diff --git a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterFactory.java b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterFactory.java index ca895d855e5..fb4f50b3832 100644 --- a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterFactory.java +++ b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.format.datetime.standard; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; +import java.time.format.ResolverStyle; import java.util.TimeZone; import org.springframework.format.annotation.DateTimeFormat.ISO; @@ -174,7 +175,11 @@ public class DateTimeFormatterFactory { public DateTimeFormatter createDateTimeFormatter(DateTimeFormatter fallbackFormatter) { DateTimeFormatter dateTimeFormatter = null; if (StringUtils.hasLength(this.pattern)) { - dateTimeFormatter = DateTimeFormatter.ofPattern(this.pattern); + // Using strict parsing to align with Joda-Time and standard DateFormat behavior: + // otherwise, an overflow like e.g. Feb 29 for a non-leap-year wouldn't get rejected. + // However, with strict parsing, a year digit needs to be specified as 'u'... + String patternToUse = this.pattern.replace("yy", "uu"); + dateTimeFormatter = DateTimeFormatter.ofPattern(patternToUse).withResolverStyle(ResolverStyle.STRICT); } else if (this.iso != null && this.iso != ISO.NONE) { switch (this.iso) { diff --git a/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java b/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java index ffbd422ee3c..0681d4ece1b 100644 --- a/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java +++ b/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -148,6 +148,14 @@ public class DateFormattingTests { assertEquals("10/31/09 1:05", binder.getBindingResult().getFieldValue("dateAnnotatedPattern")); } + @Test + public void testBindDateTimeOverflow() { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("dateAnnotatedPattern", "02/29/09 12:00 PM"); + binder.bind(propertyValues); + assertEquals(1, binder.getBindingResult().getErrorCount()); + } + @Test public void testBindISODate() { MutablePropertyValues propertyValues = new MutablePropertyValues(); diff --git a/spring-context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java b/spring-context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java index 1e96b13c075..b0de33ec7b6 100644 --- a/spring-context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java +++ b/spring-context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -317,6 +317,14 @@ public class JodaTimeFormattingTests { assertEquals("10/31/09 12:00 PM", binder.getBindingResult().getFieldValue("dateTimeAnnotatedPattern")); } + @Test + public void testBindDateTimeOverflow() { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("dateTimeAnnotatedPattern", "02/29/09 12:00 PM"); + binder.bind(propertyValues); + assertEquals(1, binder.getBindingResult().getErrorCount()); + } + @Test public void testBindDateTimeAnnotatedDefault() { MutablePropertyValues propertyValues = new MutablePropertyValues(); diff --git a/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java b/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java index a60fbd69db3..f112d4462c5 100644 --- a/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java +++ b/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -292,6 +292,14 @@ public class DateTimeFormattingTests { assertEquals("10/31/09 12:00 PM", binder.getBindingResult().getFieldValue("dateTimeAnnotatedPattern")); } + @Test + public void testBindDateTimeOverflow() { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("dateTimeAnnotatedPattern", "02/29/09 12:00 PM"); + binder.bind(propertyValues); + assertEquals(1, binder.getBindingResult().getErrorCount()); + } + @Test public void testBindISODate() { MutablePropertyValues propertyValues = new MutablePropertyValues();