DateTimeFormattingTests.java 28.5 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
	@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=[])
342 343
						// We do not check "fallbackPatterns=[]", since the array representation in the toString()
						// implementation for annotations changed from [] to {} in Java 9.
344 345
						.hasMessageContainingAll(
							"Unable to parse date time value \"2009-31-10\" using configuration from",
346
							"@org.springframework.format.annotation.DateTimeFormat", "iso=DATE")
347 348 349 350 351 352 353
						.hasCauseInstanceOf(DateTimeParseException.class).getCause()
							.hasMessageStartingWith("Text '2009-31-10'")
							.hasCauseInstanceOf(DateTimeException.class).getCause()
								.hasMessageContaining("Invalid value for MonthOfYear (valid values 1 - 12): 31")
								.hasNoCause();
	}

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

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

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

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

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

399
	@Test
400
	@SuppressWarnings("deprecation")
401
	void testBindInstantFromJavaUtilDate() {
402 403 404 405 406 407
		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);
408 409
			assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
			assertThat(binder.getBindingResult().getFieldValue("instant").toString().startsWith("2009-10-31")).isTrue();
410 411 412 413
		}
		finally {
			TimeZone.setDefault(defaultZone);
		}
414 415
	}

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

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

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

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

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

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

	@Test
471
	void testBindMonthDay() {
472 473 474
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.add("monthDay", "--12-03");
		binder.bind(propertyValues);
475 476
		assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
		assertThat(binder.getBindingResult().getFieldValue("monthDay").toString().equals("--12-03")).isTrue();
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 552
	@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",
553 554 555 556
								"yyyy-MM-dd", "M/d/yy", "yyyyMMdd", "yyyy.MM.dd")
							.hasCauseInstanceOf(DateTimeParseException.class).getCause()
								.hasMessageStartingWith("Text '210302'")
								.hasNoCause();
557
		}
558
	}
559

560 561 562 563 564

	public static class DateTimeBean {

		private LocalDate localDate;

565
		@DateTimeFormat(style = "M-")
566 567 568 569 570 571 572
		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;
573 574 575

		private LocalTime localTime;

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

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

		private LocalDateTime localDateTime;

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

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

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

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

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

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

		private Instant instant;

604 605 606 607
		private Period period;

		private Duration duration;

608 609 610 611
		private Year year;

		private Month month;

612 613 614 615
		private YearMonth yearMonth;

		private MonthDay monthDay;

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

618

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

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

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

631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
		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;
648 649 650
		}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

}