DateTimeFormattingTests.java 28.4 KB
Newer Older
1
/*
2
 * Copyright 2002-2021 the original author or authors.
3 4 5 6 7
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
S
Spring Operator 已提交
8
 *      https://www.apache.org/licenses/LICENSE-2.0
9 10 11 12 13 14 15 16 17 18
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.format.datetime.standard;

19
import java.time.DateTimeException;
20
import java.time.Duration;
21 22 23 24
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
25
import java.time.Month;
26
import java.time.MonthDay;
27
import java.time.Period;
28
import java.time.Year;
29
import java.time.YearMonth;
30 31
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
32
import java.time.format.DateTimeParseException;
33 34
import java.time.format.FormatStyle;
import java.util.ArrayList;
35
import java.util.Date;
36
import java.util.GregorianCalendar;
37 38
import java.util.List;
import java.util.Locale;
39
import java.util.TimeZone;
40

41 42
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
43
import org.junit.jupiter.api.Nested;
44
import org.junit.jupiter.api.Test;
45 46
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
47 48

import org.springframework.beans.MutablePropertyValues;
49
import org.springframework.beans.TypeMismatchException;
50
import org.springframework.context.i18n.LocaleContextHolder;
51
import org.springframework.core.convert.ConversionFailedException;
52 53 54 55
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.format.support.FormattingConversionService;
56
import org.springframework.validation.BindingResult;
57
import org.springframework.validation.DataBinder;
58
import org.springframework.validation.FieldError;
59

60
import static org.assertj.core.api.Assertions.assertThat;
61 62 63 64 65

/**
 * @author Keith Donald
 * @author Juergen Hoeller
 * @author Phillip Webb
66
 * @author Sam Brannen
67
 */
68
class DateTimeFormattingTests {
69

70
	private final FormattingConversionService conversionService = new FormattingConversionService();
71 72 73

	private DataBinder binder;

74

75
	@BeforeEach
76
	void setup() {
77
		DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
78
		setup(registrar);
79 80
	}

81
	private void setup(DateTimeFormatterRegistrar registrar) {
82 83 84 85 86 87 88 89 90 91 92 93 94 95
		DefaultConversionService.addDefaultConverters(conversionService);
		registrar.registerFormatters(conversionService);

		DateTimeBean bean = new DateTimeBean();
		bean.getChildren().add(new DateTimeBean());
		binder = new DataBinder(bean);
		binder.setConversionService(conversionService);

		LocaleContextHolder.setLocale(Locale.US);
		DateTimeContext context = new DateTimeContext();
		context.setTimeZone(ZoneId.of("-05:00"));
		DateTimeContextHolder.setDateTimeContext(context);
	}

96
	@AfterEach
97
	void cleanup() {
98 99 100 101
		LocaleContextHolder.setLocale(null);
		DateTimeContextHolder.setDateTimeContext(null);
	}

102

103
	@Test
104
	void testBindLocalDate() {
105 106 107
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.add("localDate", "10/31/09");
		binder.bind(propertyValues);
108 109
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
		assertThat(binder.getBindingResult().getFieldValue("localDate")).isEqualTo("10/31/09");
110 111 112
	}

	@Test
113
	void testBindLocalDateWithSpecificStyle() {
114 115
		DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
		registrar.setDateStyle(FormatStyle.LONG);
116
		setup(registrar);
117 118 119
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.add("localDate", "October 31, 2009");
		binder.bind(propertyValues);
120 121
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
		assertThat(binder.getBindingResult().getFieldValue("localDate")).isEqualTo("October 31, 2009");
122 123 124
	}

	@Test
125
	void testBindLocalDateWithSpecificFormatter() {
126 127
		DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
		registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
128
		setup(registrar);
129 130 131
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.add("localDate", "20091031");
		binder.bind(propertyValues);
132 133
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
		assertThat(binder.getBindingResult().getFieldValue("localDate")).isEqualTo("20091031");
134 135 136
	}

	@Test
137
	void testBindLocalDateArray() {
138
		MutablePropertyValues propertyValues = new MutablePropertyValues();
J
Juergen Hoeller 已提交
139
		propertyValues.add("localDate", new String[] {"10/31/09"});
140
		binder.bind(propertyValues);
141
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
142 143 144
	}

	@Test
145
	void testBindLocalDateAnnotated() {
146
		MutablePropertyValues propertyValues = new MutablePropertyValues();
147
		propertyValues.add("styleLocalDate", "Oct 31, 2009");
148
		binder.bind(propertyValues);
149
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
150
		assertThat(binder.getBindingResult().getFieldValue("styleLocalDate")).isEqualTo("Oct 31, 2009");
151 152 153
	}

	@Test
154
	void testBindLocalDateAnnotatedWithError() {
155
		MutablePropertyValues propertyValues = new MutablePropertyValues();
156
		propertyValues.add("styleLocalDate", "Oct -31, 2009");
157
		binder.bind(propertyValues);
158 159
		assertThat(binder.getBindingResult().getFieldErrorCount("styleLocalDate")).isEqualTo(1);
		assertThat(binder.getBindingResult().getFieldValue("styleLocalDate")).isEqualTo("Oct -31, 2009");
160 161 162
	}

	@Test
163
	void testBindNestedLocalDateAnnotated() {
164
		MutablePropertyValues propertyValues = new MutablePropertyValues();
165
		propertyValues.add("children[0].styleLocalDate", "Oct 31, 2009");
166
		binder.bind(propertyValues);
167
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
168
		assertThat(binder.getBindingResult().getFieldValue("children[0].styleLocalDate")).isEqualTo("Oct 31, 2009");
169 170 171
	}

	@Test
172
	void testBindLocalDateAnnotatedWithDirectFieldAccess() {
173 174
		binder.initDirectFieldAccess();
		MutablePropertyValues propertyValues = new MutablePropertyValues();
175
		propertyValues.add("styleLocalDate", "Oct 31, 2009");
176
		binder.bind(propertyValues);
177
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
178
		assertThat(binder.getBindingResult().getFieldValue("styleLocalDate")).isEqualTo("Oct 31, 2009");
179 180 181
	}

	@Test
182
	void testBindLocalDateAnnotatedWithDirectFieldAccessAndError() {
183 184
		binder.initDirectFieldAccess();
		MutablePropertyValues propertyValues = new MutablePropertyValues();
185
		propertyValues.add("styleLocalDate", "Oct -31, 2009");
186
		binder.bind(propertyValues);
187 188
		assertThat(binder.getBindingResult().getFieldErrorCount("styleLocalDate")).isEqualTo(1);
		assertThat(binder.getBindingResult().getFieldValue("styleLocalDate")).isEqualTo("Oct -31, 2009");
189 190
	}

191
	@Test
192
	void testBindLocalDateFromJavaUtilCalendar() {
193 194 195
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.add("localDate", new GregorianCalendar(2009, 9, 31, 0, 0));
		binder.bind(propertyValues);
196 197
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
		assertThat(binder.getBindingResult().getFieldValue("localDate")).isEqualTo("10/31/09");
198 199
	}

200
	@Test
201
	void testBindLocalTime() {
202 203 204
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.add("localTime", "12:00 PM");
		binder.bind(propertyValues);
205 206
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
		assertThat(binder.getBindingResult().getFieldValue("localTime")).isEqualTo("12:00 PM");
207 208 209
	}

	@Test
210
	void testBindLocalTimeWithSpecificStyle() {
211 212
		DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
		registrar.setTimeStyle(FormatStyle.MEDIUM);
213
		setup(registrar);
214 215 216
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.add("localTime", "12:00:00 PM");
		binder.bind(propertyValues);
217 218
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
		assertThat(binder.getBindingResult().getFieldValue("localTime")).isEqualTo("12:00:00 PM");
219 220 221
	}

	@Test
222
	void testBindLocalTimeWithSpecificFormatter() {
223 224
		DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
		registrar.setTimeFormatter(DateTimeFormatter.ofPattern("HHmmss"));
225
		setup(registrar);
226 227 228
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.add("localTime", "130000");
		binder.bind(propertyValues);
229 230
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
		assertThat(binder.getBindingResult().getFieldValue("localTime")).isEqualTo("130000");
231 232 233
	}

	@Test
234
	void testBindLocalTimeAnnotated() {
235
		MutablePropertyValues propertyValues = new MutablePropertyValues();
236
		propertyValues.add("styleLocalTime", "12:00:00 PM");
237
		binder.bind(propertyValues);
238
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
239
		assertThat(binder.getBindingResult().getFieldValue("styleLocalTime")).isEqualTo("12:00:00 PM");
240 241
	}

242
	@Test
243
	void testBindLocalTimeFromJavaUtilCalendar() {
244 245 246
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.add("localTime", new GregorianCalendar(1970, 0, 0, 12, 0));
		binder.bind(propertyValues);
247 248
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
		assertThat(binder.getBindingResult().getFieldValue("localTime")).isEqualTo("12:00 PM");
249 250
	}

251
	@Test
252
	void testBindLocalDateTime() {
253
		MutablePropertyValues propertyValues = new MutablePropertyValues();
254
		propertyValues.add("localDateTime", LocalDateTime.of(2009, 10, 31, 12, 0));
255
		binder.bind(propertyValues);
256
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
257
		String value = binder.getBindingResult().getFieldValue("localDateTime").toString();
258 259
		assertThat(value.startsWith("10/31/09")).isTrue();
		assertThat(value.endsWith("12:00 PM")).isTrue();
260 261 262
	}

	@Test
263
	void testBindLocalDateTimeAnnotated() {
264
		MutablePropertyValues propertyValues = new MutablePropertyValues();
265
		propertyValues.add("styleLocalDateTime", LocalDateTime.of(2009, 10, 31, 12, 0));
266
		binder.bind(propertyValues);
267
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
268
		String value = binder.getBindingResult().getFieldValue("styleLocalDateTime").toString();
269 270
		assertThat(value.startsWith("Oct 31, 2009")).isTrue();
		assertThat(value.endsWith("12:00:00 PM")).isTrue();
271 272
	}

273
	@Test
274
	void testBindLocalDateTimeFromJavaUtilCalendar() {
275 276 277
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.add("localDateTime", new GregorianCalendar(2009, 9, 31, 12, 0));
		binder.bind(propertyValues);
278
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
279
		String value = binder.getBindingResult().getFieldValue("localDateTime").toString();
280 281
		assertThat(value.startsWith("10/31/09")).isTrue();
		assertThat(value.endsWith("12:00 PM")).isTrue();
282 283
	}

284
	@Test
285
	void testBindDateTimeWithSpecificStyle() {
286 287
		DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
		registrar.setDateTimeStyle(FormatStyle.MEDIUM);
288
		setup(registrar);
289
		MutablePropertyValues propertyValues = new MutablePropertyValues();
290
		propertyValues.add("localDateTime", LocalDateTime.of(2009, 10, 31, 12, 0));
291
		binder.bind(propertyValues);
292
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
293
		String value = binder.getBindingResult().getFieldValue("localDateTime").toString();
294 295
		assertThat(value.startsWith("Oct 31, 2009")).isTrue();
		assertThat(value.endsWith("12:00:00 PM")).isTrue();
296 297 298
	}

	@Test
299
	void testBindPatternLocalDateTime() {
300
		MutablePropertyValues propertyValues = new MutablePropertyValues();
301
		propertyValues.add("patternLocalDateTime", "10/31/09 12:00 PM");
302
		binder.bind(propertyValues);
303
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
304
		assertThat(binder.getBindingResult().getFieldValue("patternLocalDateTime")).isEqualTo("10/31/09 12:00 PM");
305 306
	}

307
	@Test
308
	void testBindDateTimeOverflow() {
309
		MutablePropertyValues propertyValues = new MutablePropertyValues();
310
		propertyValues.add("patternLocalDateTime", "02/29/09 12:00 PM");
311
		binder.bind(propertyValues);
312
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(1);
313 314
	}

315
	@Test
316
	void testBindISODate() {
317
		MutablePropertyValues propertyValues = new MutablePropertyValues();
318
		propertyValues.add("isoLocalDate", "2009-10-31");
319
		binder.bind(propertyValues);
320
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
321
		assertThat(binder.getBindingResult().getFieldValue("isoLocalDate")).isEqualTo("2009-10-31");
322 323
	}

324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
	@Test
	void isoLocalDateWithInvalidFormat() {
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		String propertyName = "isoLocalDate";
		propertyValues.add(propertyName, "2009-31-10");
		binder.bind(propertyValues);
		BindingResult bindingResult = binder.getBindingResult();
		assertThat(bindingResult.getErrorCount()).isEqualTo(1);
		FieldError fieldError = bindingResult.getFieldError(propertyName);
		assertThat(fieldError.unwrap(TypeMismatchException.class))
			.hasMessageContaining("for property 'isoLocalDate'")
			.hasCauseInstanceOf(ConversionFailedException.class).getCause()
				.hasMessageContaining("for value '2009-31-10'")
				.hasCauseInstanceOf(IllegalArgumentException.class).getCause()
					.hasMessageContaining("Parse attempt failed for value [2009-31-10]")
					.hasCauseInstanceOf(DateTimeParseException.class).getCause()
						// Unable to parse date time value "2009-31-10" using configuration from
						// @org.springframework.format.annotation.DateTimeFormat(pattern=, style=SS, iso=DATE, fallbackPatterns=[])
						.hasMessageContainingAll(
							"Unable to parse date time value \"2009-31-10\" using configuration from",
							"@org.springframework.format.annotation.DateTimeFormat",
							"iso=DATE", "fallbackPatterns=[]")
						.hasCauseInstanceOf(DateTimeParseException.class).getCause()
							.hasMessageStartingWith("Text '2009-31-10'")
							.hasCauseInstanceOf(DateTimeException.class).getCause()
								.hasMessageContaining("Invalid value for MonthOfYear (valid values 1 - 12): 31")
								.hasNoCause();
	}

353
	@Test
354
	void testBindISOTime() {
355
		MutablePropertyValues propertyValues = new MutablePropertyValues();
356
		propertyValues.add("isoLocalTime", "12:00:00");
357
		binder.bind(propertyValues);
358
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
359
		assertThat(binder.getBindingResult().getFieldValue("isoLocalTime")).isEqualTo("12:00:00");
360 361 362
	}

	@Test
363
	void testBindISOTimeWithZone() {
364
		MutablePropertyValues propertyValues = new MutablePropertyValues();
365
		propertyValues.add("isoLocalTime", "12:00:00.000-05:00");
366
		binder.bind(propertyValues);
367
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
368
		assertThat(binder.getBindingResult().getFieldValue("isoLocalTime")).isEqualTo("12:00:00");
369 370 371
	}

	@Test
372
	void testBindISODateTime() {
373
		MutablePropertyValues propertyValues = new MutablePropertyValues();
374
		propertyValues.add("isoLocalDateTime", "2009-10-31T12:00:00");
375
		binder.bind(propertyValues);
376
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
377
		assertThat(binder.getBindingResult().getFieldValue("isoLocalDateTime")).isEqualTo("2009-10-31T12:00:00");
378 379 380
	}

	@Test
381
	void testBindISODateTimeWithZone() {
382
		MutablePropertyValues propertyValues = new MutablePropertyValues();
383
		propertyValues.add("isoLocalDateTime", "2009-10-31T12:00:00.000Z");
384
		binder.bind(propertyValues);
385
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
386
		assertThat(binder.getBindingResult().getFieldValue("isoLocalDateTime")).isEqualTo("2009-10-31T12:00:00");
387 388 389
	}

	@Test
390
	void testBindInstant() {
391 392 393
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.add("instant", "2009-10-31T12:00:00.000Z");
		binder.bind(propertyValues);
394 395
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
		assertThat(binder.getBindingResult().getFieldValue("instant").toString().startsWith("2009-10-31T12:00")).isTrue();
396 397
	}

398
	@Test
399
	@SuppressWarnings("deprecation")
400
	void testBindInstantFromJavaUtilDate() {
401 402 403 404 405 406
		TimeZone defaultZone = TimeZone.getDefault();
		TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
		try {
			MutablePropertyValues propertyValues = new MutablePropertyValues();
			propertyValues.add("instant", new Date(109, 9, 31, 12, 0));
			binder.bind(propertyValues);
407 408
			assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
			assertThat(binder.getBindingResult().getFieldValue("instant").toString().startsWith("2009-10-31")).isTrue();
409 410 411 412
		}
		finally {
			TimeZone.setDefault(defaultZone);
		}
413 414
	}

415
	@Test
416
	void testBindPeriod() {
417 418 419
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.add("period", "P6Y3M1D");
		binder.bind(propertyValues);
420 421
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
		assertThat(binder.getBindingResult().getFieldValue("period").toString().equals("P6Y3M1D")).isTrue();
422 423 424
	}

	@Test
425
	void testBindDuration() {
426 427 428
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.add("duration", "PT8H6M12.345S");
		binder.bind(propertyValues);
429 430
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
		assertThat(binder.getBindingResult().getFieldValue("duration").toString().equals("PT8H6M12.345S")).isTrue();
431 432
	}

433
	@Test
434
	void testBindYear() {
435 436 437
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.add("year", "2007");
		binder.bind(propertyValues);
438 439
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
		assertThat(binder.getBindingResult().getFieldValue("year").toString().equals("2007")).isTrue();
440 441 442
	}

	@Test
443
	void testBindMonth() {
444 445 446
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.add("month", "JULY");
		binder.bind(propertyValues);
447 448
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
		assertThat(binder.getBindingResult().getFieldValue("month").toString().equals("JULY")).isTrue();
449 450 451
	}

	@Test
452
	void testBindMonthInAnyCase() {
453 454 455
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.add("month", "July");
		binder.bind(propertyValues);
456 457
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
		assertThat(binder.getBindingResult().getFieldValue("month").toString().equals("JULY")).isTrue();
458 459
	}

460
	@Test
461
	void testBindYearMonth() {
462 463 464
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.add("yearMonth", "2007-12");
		binder.bind(propertyValues);
465 466
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
		assertThat(binder.getBindingResult().getFieldValue("yearMonth").toString().equals("2007-12")).isTrue();
467 468 469
	}

	@Test
470
	void testBindMonthDay() {
471 472 473
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.add("monthDay", "--12-03");
		binder.bind(propertyValues);
474 475
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
		assertThat(binder.getBindingResult().getFieldValue("monthDay").toString().equals("--12-03")).isTrue();
476 477
	}

478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551
	@Nested
	class FallbackPatternTests {

		@ParameterizedTest(name = "input date: {0}")
		@ValueSource(strings = {"2021-03-02", "2021.03.02", "20210302", "3/2/21"})
		void styleLocalDate(String propertyValue) {
			String propertyName = "styleLocalDateWithFallbackPatterns";
			MutablePropertyValues propertyValues = new MutablePropertyValues();
			propertyValues.add(propertyName, propertyValue);
			binder.bind(propertyValues);
			BindingResult bindingResult = binder.getBindingResult();
			assertThat(bindingResult.getErrorCount()).isEqualTo(0);
			assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("3/2/21");
		}

		@ParameterizedTest(name = "input date: {0}")
		@ValueSource(strings = {"2021-03-02", "2021.03.02", "20210302", "3/2/21"})
		void patternLocalDate(String propertyValue) {
			String propertyName = "patternLocalDateWithFallbackPatterns";
			MutablePropertyValues propertyValues = new MutablePropertyValues();
			propertyValues.add(propertyName, propertyValue);
			binder.bind(propertyValues);
			BindingResult bindingResult = binder.getBindingResult();
			assertThat(bindingResult.getErrorCount()).isEqualTo(0);
			assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("2021-03-02");
		}

		@ParameterizedTest(name = "input date: {0}")
		@ValueSource(strings = {"12:00:00 PM", "12:00:00", "12:00"})
		void styleLocalTime(String propertyValue) {
			String propertyName = "styleLocalTimeWithFallbackPatterns";
			MutablePropertyValues propertyValues = new MutablePropertyValues();
			propertyValues.add(propertyName, propertyValue);
			binder.bind(propertyValues);
			BindingResult bindingResult = binder.getBindingResult();
			assertThat(bindingResult.getErrorCount()).isEqualTo(0);
			assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("12:00:00 PM");
		}

		@ParameterizedTest(name = "input date: {0}")
		@ValueSource(strings = {"2021-03-02T12:00:00", "2021-03-02 12:00:00", "3/2/21 12:00"})
		void isoLocalDateTime(String propertyValue) {
			String propertyName = "isoLocalDateTimeWithFallbackPatterns";
			MutablePropertyValues propertyValues = new MutablePropertyValues();
			propertyValues.add(propertyName, propertyValue);
			binder.bind(propertyValues);
			BindingResult bindingResult = binder.getBindingResult();
			assertThat(bindingResult.getErrorCount()).isEqualTo(0);
			assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("2021-03-02T12:00:00");
		}

		@Test
		void patternLocalDateWithUnsupportedPattern() {
			String propertyValue = "210302";
			String propertyName = "patternLocalDateWithFallbackPatterns";
			MutablePropertyValues propertyValues = new MutablePropertyValues();
			propertyValues.add(propertyName, propertyValue);
			binder.bind(propertyValues);
			BindingResult bindingResult = binder.getBindingResult();
			assertThat(bindingResult.getErrorCount()).isEqualTo(1);
			FieldError fieldError = bindingResult.getFieldError(propertyName);
			assertThat(fieldError.unwrap(TypeMismatchException.class))
				.hasMessageContaining("for property 'patternLocalDateWithFallbackPatterns'")
				.hasCauseInstanceOf(ConversionFailedException.class).getCause()
					.hasMessageContaining("for value '210302'")
					.hasCauseInstanceOf(IllegalArgumentException.class).getCause()
						.hasMessageContaining("Parse attempt failed for value [210302]")
						.hasCauseInstanceOf(DateTimeParseException.class).getCause()
							// Unable to parse date time value "210302" using configuration from
							// @org.springframework.format.annotation.DateTimeFormat(
							// pattern=yyyy-MM-dd, style=SS, iso=NONE, fallbackPatterns=[M/d/yy, yyyyMMdd, yyyy.MM.dd])
							.hasMessageContainingAll(
								"Unable to parse date time value \"210302\" using configuration from",
								"@org.springframework.format.annotation.DateTimeFormat",
552 553 554 555
								"yyyy-MM-dd", "M/d/yy", "yyyyMMdd", "yyyy.MM.dd")
							.hasCauseInstanceOf(DateTimeParseException.class).getCause()
								.hasMessageStartingWith("Text '210302'")
								.hasNoCause();
556
		}
557
	}
558

559 560 561 562 563

	public static class DateTimeBean {

		private LocalDate localDate;

564
		@DateTimeFormat(style = "M-")
565 566 567 568 569 570 571
		private LocalDate styleLocalDate;

		@DateTimeFormat(style = "S-", fallbackPatterns = { "yyyy-MM-dd", "yyyyMMdd", "yyyy.MM.dd" })
		private LocalDate styleLocalDateWithFallbackPatterns;

		@DateTimeFormat(pattern = "yyyy-MM-dd", fallbackPatterns = { "M/d/yy", "yyyyMMdd", "yyyy.MM.dd" })
		private LocalDate patternLocalDateWithFallbackPatterns;
572 573 574

		private LocalTime localTime;

575
		@DateTimeFormat(style = "-M")
576 577 578 579
		private LocalTime styleLocalTime;

		@DateTimeFormat(style = "-M", fallbackPatterns = { "HH:mm:ss", "HH:mm"})
		private LocalTime styleLocalTimeWithFallbackPatterns;
580 581 582

		private LocalDateTime localDateTime;

583
		@DateTimeFormat(style = "MM")
584
		private LocalDateTime styleLocalDateTime;
585

586
		@DateTimeFormat(pattern = "M/d/yy h:mm a")
587
		private LocalDateTime patternLocalDateTime;
588

589
		@DateTimeFormat(iso = ISO.DATE)
590
		private LocalDate isoLocalDate;
591

592
		@DateTimeFormat(iso = ISO.TIME)
593
		private LocalTime isoLocalTime;
594

595
		@DateTimeFormat(iso = ISO.DATE_TIME)
596 597 598 599
		private LocalDateTime isoLocalDateTime;

		@DateTimeFormat(iso = ISO.DATE_TIME, fallbackPatterns = { "yyyy-MM-dd HH:mm:ss", "M/d/yy HH:mm"})
		private LocalDateTime isoLocalDateTimeWithFallbackPatterns;
600 601 602

		private Instant instant;

603 604 605 606
		private Period period;

		private Duration duration;

607 608 609 610
		private Year year;

		private Month month;

611 612 613 614
		private YearMonth yearMonth;

		private MonthDay monthDay;

615
		private final List<DateTimeBean> children = new ArrayList<>();
616

617

618
		public LocalDate getLocalDate() {
619
			return this.localDate;
620 621 622 623 624 625
		}

		public void setLocalDate(LocalDate localDate) {
			this.localDate = localDate;
		}

626 627
		public LocalDate getStyleLocalDate() {
			return this.styleLocalDate;
628 629
		}

630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646
		public void setStyleLocalDate(LocalDate styleLocalDate) {
			this.styleLocalDate = styleLocalDate;
		}

		public LocalDate getStyleLocalDateWithFallbackPatterns() {
			return this.styleLocalDateWithFallbackPatterns;
		}

		public void setStyleLocalDateWithFallbackPatterns(LocalDate styleLocalDateWithFallbackPatterns) {
			this.styleLocalDateWithFallbackPatterns = styleLocalDateWithFallbackPatterns;
		}
		public LocalDate getPatternLocalDateWithFallbackPatterns() {
			return this.patternLocalDateWithFallbackPatterns;
		}

		public void setPatternLocalDateWithFallbackPatterns(LocalDate patternLocalDateWithFallbackPatterns) {
			this.patternLocalDateWithFallbackPatterns = patternLocalDateWithFallbackPatterns;
647 648 649
		}

		public LocalTime getLocalTime() {
650
			return this.localTime;
651 652 653 654 655 656
		}

		public void setLocalTime(LocalTime localTime) {
			this.localTime = localTime;
		}

657 658 659 660 661 662 663 664 665 666
		public LocalTime getStyleLocalTime() {
			return this.styleLocalTime;
		}

		public void setStyleLocalTime(LocalTime styleLocalTime) {
			this.styleLocalTime = styleLocalTime;
		}

		public LocalTime getStyleLocalTimeWithFallbackPatterns() {
			return this.styleLocalTimeWithFallbackPatterns;
667 668
		}

669 670
		public void setStyleLocalTimeWithFallbackPatterns(LocalTime styleLocalTimeWithFallbackPatterns) {
			this.styleLocalTimeWithFallbackPatterns = styleLocalTimeWithFallbackPatterns;
671 672 673
		}

		public LocalDateTime getLocalDateTime() {
674
			return this.localDateTime;
675 676 677 678 679 680
		}

		public void setLocalDateTime(LocalDateTime localDateTime) {
			this.localDateTime = localDateTime;
		}

681 682 683 684 685 686 687 688 689 690
		public LocalDateTime getStyleLocalDateTime() {
			return this.styleLocalDateTime;
		}

		public void setStyleLocalDateTime(LocalDateTime styleLocalDateTime) {
			this.styleLocalDateTime = styleLocalDateTime;
		}

		public LocalDateTime getPatternLocalDateTime() {
			return this.patternLocalDateTime;
691 692
		}

693 694
		public void setPatternLocalDateTime(LocalDateTime patternLocalDateTime) {
			this.patternLocalDateTime = patternLocalDateTime;
695 696
		}

697 698
		public LocalDate getIsoLocalDate() {
			return this.isoLocalDate;
699 700
		}

701 702
		public void setIsoLocalDate(LocalDate isoLocalDate) {
			this.isoLocalDate = isoLocalDate;
703 704
		}

705 706
		public LocalTime getIsoLocalTime() {
			return this.isoLocalTime;
707 708
		}

709 710
		public void setIsoLocalTime(LocalTime isoLocalTime) {
			this.isoLocalTime = isoLocalTime;
711 712
		}

713 714
		public LocalDateTime getIsoLocalDateTime() {
			return this.isoLocalDateTime;
715 716
		}

717 718
		public void setIsoLocalDateTime(LocalDateTime isoLocalDateTime) {
			this.isoLocalDateTime = isoLocalDateTime;
719 720
		}

721 722
		public LocalDateTime getIsoLocalDateTimeWithFallbackPatterns() {
			return this.isoLocalDateTimeWithFallbackPatterns;
723 724
		}

725 726
		public void setIsoLocalDateTimeWithFallbackPatterns(LocalDateTime isoLocalDateTimeWithFallbackPatterns) {
			this.isoLocalDateTimeWithFallbackPatterns = isoLocalDateTimeWithFallbackPatterns;
727 728 729
		}

		public Instant getInstant() {
730
			return this.instant;
731 732 733 734 735 736
		}

		public void setInstant(Instant instant) {
			this.instant = instant;
		}

737
		public Period getPeriod() {
738
			return this.period;
739 740 741 742 743 744 745
		}

		public void setPeriod(Period period) {
			this.period = period;
		}

		public Duration getDuration() {
746
			return this.duration;
747 748 749 750 751 752
		}

		public void setDuration(Duration duration) {
			this.duration = duration;
		}

753
		public Year getYear() {
754
			return this.year;
755 756 757 758 759 760 761
		}

		public void setYear(Year year) {
			this.year = year;
		}

		public Month getMonth() {
762
			return this.month;
763 764 765 766 767 768
		}

		public void setMonth(Month month) {
			this.month = month;
		}

769
		public YearMonth getYearMonth() {
770
			return this.yearMonth;
771 772 773 774 775 776 777
		}

		public void setYearMonth(YearMonth yearMonth) {
			this.yearMonth = yearMonth;
		}

		public MonthDay getMonthDay() {
778
			return this.monthDay;
779 780 781 782 783 784
		}

		public void setMonthDay(MonthDay monthDay) {
			this.monthDay = monthDay;
		}

785
		public List<DateTimeBean> getChildren() {
786
			return this.children;
787 788 789 790
		}
	}

}