Skip to content

Commit bbbb7c3

Browse files
committed
Update Date/Time formatting tests for pre/post JDK 20
This commit updates our Date/Time formatting/printing tests to demonstrate that the use of fallback patterns can help mitigate locale-based parsing/formatting issues beginning with JDK 20. The documentation within the tests is intentionally rather thorough for two reasons: 1. We need to understand exactly what it is we are testing and why the tests are written that way. 2. We may re-use parts of the documentation and examples in forthcoming documentation that we will provide to users. See gh-33151
1 parent d72c8b3 commit bbbb7c3

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed

Diff for: spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java

+91
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,70 @@ void isoDate(String propertyValue) {
339339
assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("2021-03-02");
340340
}
341341

342+
/**
343+
* {@link SimpleDateBean#styleDateTimeWithFallbackPatternsForPreAndPostJdk20}
344+
* configures "SS" as the date/time style to use. Thus, we have to be aware
345+
* of the following if we do not configure fallback patterns for parsing.
346+
*
347+
* <ul>
348+
* <li>JDK &le; 19 requires a standard space before the "PM".
349+
* <li>JDK &ge; 20 requires a narrow non-breaking space (NNBSP) before the "PM".
350+
* </ul>
351+
*
352+
* <p>To avoid compatibility issues between JDK versions, we have configured
353+
* two fallback patterns which emulate the "SS" style: <code>"MM/dd/yy h:mm a"</code>
354+
* matches against a standard space before the "PM", and <code>"MM/dd/yy h:mm&#92;u202Fa"</code>
355+
* matches against a narrow non-breaking space (NNBSP) before the "PM".
356+
*
357+
* <p>Thus, the following should theoretically be supported on any JDK (or at least
358+
* JDK 17 - 23, where we have tested it).
359+
*
360+
* @see #patternDateTime(String)
361+
*/
362+
@ParameterizedTest(name = "input date: {0}") // gh-33151
363+
@ValueSource(strings = {"10/31/09, 12:00 PM", "10/31/09, 12:00\u202FPM"})
364+
void styleDateTime_PreAndPostJdk20(String propertyValue) {
365+
String propertyName = "styleDateTimeWithFallbackPatternsForPreAndPostJdk20";
366+
MutablePropertyValues propertyValues = new MutablePropertyValues();
367+
propertyValues.add(propertyName, propertyValue);
368+
binder.bind(propertyValues);
369+
BindingResult bindingResult = binder.getBindingResult();
370+
assertThat(bindingResult.getErrorCount()).isEqualTo(0);
371+
String value = binder.getBindingResult().getFieldValue(propertyName).toString();
372+
// Since the "SS" style is always used for printing and the underlying format
373+
// changes depending on the JDK version, we cannot be certain that a normal
374+
// space is used before the "PM". Consequently we have to use a regular
375+
// expression to match against any Unicode space character (\p{Zs}).
376+
assertThat(value).startsWith("10/31/09").matches(".+?12:00\\p{Zs}PM");
377+
}
378+
379+
/**
380+
* To avoid the use of Locale-based styles (such as "MM") for
381+
* {@link SimpleDateBean#patternDateTimeWithFallbackPatternForPreAndPostJdk20}, we have configured a
382+
* primary pattern (<code>"MM/dd/yy h:mm a"</code>) that matches against a standard space
383+
* before the "PM" and a fallback pattern (<code>"MM/dd/yy h:mm&#92;u202Fa"</code> that matches
384+
* against a narrow non-breaking space (NNBSP) before the "PM".
385+
*
386+
* <p>Thus, the following should theoretically be supported on any JDK (or at least
387+
* JDK 17 - 23, where we have tested it).
388+
*
389+
* @see #styleDateTime(String)
390+
*/
391+
@ParameterizedTest(name = "input date: {0}") // gh-33151
392+
@ValueSource(strings = {"10/31/09 3:45 PM", "10/31/09 3:45\u202FPM"})
393+
void patternDateTime_PreAndPostJdk20(String propertyValue) {
394+
String propertyName = "patternDateTimeWithFallbackPatternForPreAndPostJdk20";
395+
MutablePropertyValues propertyValues = new MutablePropertyValues();
396+
propertyValues.add(propertyName, propertyValue);
397+
binder.bind(propertyValues);
398+
BindingResult bindingResult = binder.getBindingResult();
399+
assertThat(bindingResult.getErrorCount()).isEqualTo(0);
400+
String value = binder.getBindingResult().getFieldValue(propertyName).toString();
401+
// Since the "MM/dd/yy h:mm a" primary pattern is always used for printing, we
402+
// can be certain that a normal space is used before the "PM".
403+
assertThat(value).matches("10/31/09 3:45 PM");
404+
}
405+
342406
@Test
343407
void patternDateWithUnsupportedPattern() {
344408
String propertyValue = "210302";
@@ -389,12 +453,23 @@ private static class SimpleDateBean {
389453
@DateTimeFormat(style = "S-", fallbackPatterns = { "yyyy-MM-dd", "yyyyMMdd", "yyyy.MM.dd" })
390454
private Date styleDateWithFallbackPatterns;
391455

456+
// "SS" style matches either a standard space or a narrow non-breaking space (NNBSP) before AM/PM,
457+
// depending on the version of the JDK.
458+
// Fallback patterns match a standard space OR a narrow non-breaking space (NNBSP) before AM/PM.
459+
@DateTimeFormat(style = "SS", fallbackPatterns = { "M/d/yy, h:mm a", "M/d/yy, h:mm\u202Fa" })
460+
private Date styleDateTimeWithFallbackPatternsForPreAndPostJdk20;
461+
392462
@DateTimeFormat(pattern = "M/d/yy h:mm")
393463
private Date patternDate;
394464

395465
@DateTimeFormat(pattern = "yyyy-MM-dd", fallbackPatterns = { "M/d/yy", "yyyyMMdd", "yyyy.MM.dd" })
396466
private Date patternDateWithFallbackPatterns;
397467

468+
// Primary pattern matches a standard space before AM/PM.
469+
// Fallback pattern matches a narrow non-breaking space (NNBSP) before AM/PM.
470+
@DateTimeFormat(pattern = "MM/dd/yy h:mm a", fallbackPatterns = "MM/dd/yy h:mm\u202Fa")
471+
private Date patternDateTimeWithFallbackPatternForPreAndPostJdk20;
472+
398473
@DateTimeFormat(iso = ISO.DATE)
399474
private Date isoDate;
400475

@@ -459,6 +534,14 @@ public void setStyleDateWithFallbackPatterns(Date styleDateWithFallbackPatterns)
459534
this.styleDateWithFallbackPatterns = styleDateWithFallbackPatterns;
460535
}
461536

537+
public Date getStyleDateTimeWithFallbackPatternsForPreAndPostJdk20() {
538+
return this.styleDateTimeWithFallbackPatternsForPreAndPostJdk20;
539+
}
540+
541+
public void setStyleDateTimeWithFallbackPatternsForPreAndPostJdk20(Date styleDateTimeWithFallbackPatternsForPreAndPostJdk20) {
542+
this.styleDateTimeWithFallbackPatternsForPreAndPostJdk20 = styleDateTimeWithFallbackPatternsForPreAndPostJdk20;
543+
}
544+
462545
public Date getPatternDate() {
463546
return this.patternDate;
464547
}
@@ -475,6 +558,14 @@ public void setPatternDateWithFallbackPatterns(Date patternDateWithFallbackPatte
475558
this.patternDateWithFallbackPatterns = patternDateWithFallbackPatterns;
476559
}
477560

561+
public Date getPatternDateTimeWithFallbackPatternForPreAndPostJdk20() {
562+
return this.patternDateTimeWithFallbackPatternForPreAndPostJdk20;
563+
}
564+
565+
public void setPatternDateTimeWithFallbackPatternForPreAndPostJdk20(Date patternDateTimeWithFallbackPatternForPreAndPostJdk20) {
566+
this.patternDateTimeWithFallbackPatternForPreAndPostJdk20 = patternDateTimeWithFallbackPatternForPreAndPostJdk20;
567+
}
568+
478569
public Date getIsoDate() {
479570
return this.isoDate;
480571
}

Diff for: spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java

+50
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,41 @@ private void styleLocalTime(String propertyValue) {
605605
assertThat(bindingResult.getFieldValue(propertyName)).asString().matches("12:00:00\\p{Zs}PM");
606606
}
607607

608+
/**
609+
* {@link DateTimeBean#styleLocalTimeWithFallbackPatternsForPreAndPostJdk20}
610+
* configures "-M" as the time style to use. Thus, we have to be aware
611+
* of the following if we do not configure fallback patterns for parsing.
612+
*
613+
* <ul>
614+
* <li>JDK &le; 19 requires a standard space before the "PM".
615+
* <li>JDK &ge; 20 requires a narrow non-breaking space (NNBSP) before the "PM".
616+
* </ul>
617+
*
618+
* <p>To avoid compatibility issues between JDK versions, we have configured
619+
* two fallback patterns which emulate the "-M" style: <code>"HH:mm:ss a"</code>
620+
* matches against a standard space before the "PM", and <code>"HH:mm:ss&#92;u202Fa"</code>
621+
* matches against a narrow non-breaking space (NNBSP) before the "PM".
622+
*
623+
* <p>Thus, the following should theoretically be supported on any JDK (or at least
624+
* JDK 17 - 23, where we have tested it).
625+
*/
626+
@ParameterizedTest(name = "input date: {0}") // gh-33151
627+
@ValueSource(strings = { "12:00:00 PM", "12:00:00\u202FPM" })
628+
void styleLocalTime_PreAndPostJdk20(String propertyValue) {
629+
String propertyName = "styleLocalTimeWithFallbackPatternsForPreAndPostJdk20";
630+
MutablePropertyValues propertyValues = new MutablePropertyValues();
631+
propertyValues.add(propertyName, propertyValue);
632+
binder.bind(propertyValues);
633+
BindingResult bindingResult = binder.getBindingResult();
634+
assertThat(bindingResult.getErrorCount()).isEqualTo(0);
635+
String value = binder.getBindingResult().getFieldValue(propertyName).toString();
636+
// Since the "-M" style is always used for printing and the underlying format
637+
// changes depending on the JDK version, we cannot be certain that a normal
638+
// space is used before the "PM". Consequently we have to use a regular
639+
// expression to match against any Unicode space character (\p{Zs}).
640+
assertThat(value).matches("12:00:00\\p{Zs}PM");
641+
}
642+
608643
@ParameterizedTest(name = "input date: {0}")
609644
@ValueSource(strings = {"2021-03-02T12:00:00", "2021-03-02 12:00:00", "3/2/21 12:00"})
610645
void isoLocalDateTime(String propertyValue) {
@@ -682,6 +717,12 @@ public static class DateTimeBean {
682717
@DateTimeFormat(style = "-M", fallbackPatterns = {"HH:mm:ss", "HH:mm"})
683718
private LocalTime styleLocalTimeWithFallbackPatterns;
684719

720+
// "-M" style matches either a standard space or a narrow non-breaking space (NNBSP) before AM/PM,
721+
// depending on the version of the JDK.
722+
// Fallback patterns match a standard space OR a narrow non-breaking space (NNBSP) before AM/PM.
723+
@DateTimeFormat(style = "-M", fallbackPatterns = {"HH:mm:ss a", "HH:mm:ss\u202Fa"})
724+
private LocalTime styleLocalTimeWithFallbackPatternsForPreAndPostJdk20;
725+
685726
private LocalDateTime localDateTime;
686727

687728
@DateTimeFormat(style = "MM")
@@ -782,6 +823,15 @@ public void setStyleLocalTimeWithFallbackPatterns(LocalTime styleLocalTimeWithFa
782823
this.styleLocalTimeWithFallbackPatterns = styleLocalTimeWithFallbackPatterns;
783824
}
784825

826+
public LocalTime getStyleLocalTimeWithFallbackPatternsForPreAndPostJdk20() {
827+
return this.styleLocalTimeWithFallbackPatternsForPreAndPostJdk20;
828+
}
829+
830+
public void setStyleLocalTimeWithFallbackPatternsForPreAndPostJdk20(
831+
LocalTime styleLocalTimeWithFallbackPatternsForPreAndPostJdk20) {
832+
this.styleLocalTimeWithFallbackPatternsForPreAndPostJdk20 = styleLocalTimeWithFallbackPatternsForPreAndPostJdk20;
833+
}
834+
785835
public LocalDateTime getLocalDateTime() {
786836
return this.localDateTime;
787837
}

0 commit comments

Comments
 (0)