@@ -339,6 +339,70 @@ void isoDate(String propertyValue) {
339
339
assertThat (bindingResult .getFieldValue (propertyName )).isEqualTo ("2021-03-02" );
340
340
}
341
341
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 ≤ 19 requires a standard space before the "PM".
349
+ * <li>JDK ≥ 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\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\u202F PM" })
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\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\u202F PM" })
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
+
342
406
@ Test
343
407
void patternDateWithUnsupportedPattern () {
344
408
String propertyValue = "210302" ;
@@ -389,12 +453,23 @@ private static class SimpleDateBean {
389
453
@ DateTimeFormat (style = "S-" , fallbackPatterns = { "yyyy-MM-dd" , "yyyyMMdd" , "yyyy.MM.dd" })
390
454
private Date styleDateWithFallbackPatterns ;
391
455
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\u202F a" })
460
+ private Date styleDateTimeWithFallbackPatternsForPreAndPostJdk20 ;
461
+
392
462
@ DateTimeFormat (pattern = "M/d/yy h:mm" )
393
463
private Date patternDate ;
394
464
395
465
@ DateTimeFormat (pattern = "yyyy-MM-dd" , fallbackPatterns = { "M/d/yy" , "yyyyMMdd" , "yyyy.MM.dd" })
396
466
private Date patternDateWithFallbackPatterns ;
397
467
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\u202F a" )
471
+ private Date patternDateTimeWithFallbackPatternForPreAndPostJdk20 ;
472
+
398
473
@ DateTimeFormat (iso = ISO .DATE )
399
474
private Date isoDate ;
400
475
@@ -459,6 +534,14 @@ public void setStyleDateWithFallbackPatterns(Date styleDateWithFallbackPatterns)
459
534
this .styleDateWithFallbackPatterns = styleDateWithFallbackPatterns ;
460
535
}
461
536
537
+ public Date getStyleDateTimeWithFallbackPatternsForPreAndPostJdk20 () {
538
+ return this .styleDateTimeWithFallbackPatternsForPreAndPostJdk20 ;
539
+ }
540
+
541
+ public void setStyleDateTimeWithFallbackPatternsForPreAndPostJdk20 (Date styleDateTimeWithFallbackPatternsForPreAndPostJdk20 ) {
542
+ this .styleDateTimeWithFallbackPatternsForPreAndPostJdk20 = styleDateTimeWithFallbackPatternsForPreAndPostJdk20 ;
543
+ }
544
+
462
545
public Date getPatternDate () {
463
546
return this .patternDate ;
464
547
}
@@ -475,6 +558,14 @@ public void setPatternDateWithFallbackPatterns(Date patternDateWithFallbackPatte
475
558
this .patternDateWithFallbackPatterns = patternDateWithFallbackPatterns ;
476
559
}
477
560
561
+ public Date getPatternDateTimeWithFallbackPatternForPreAndPostJdk20 () {
562
+ return this .patternDateTimeWithFallbackPatternForPreAndPostJdk20 ;
563
+ }
564
+
565
+ public void setPatternDateTimeWithFallbackPatternForPreAndPostJdk20 (Date patternDateTimeWithFallbackPatternForPreAndPostJdk20 ) {
566
+ this .patternDateTimeWithFallbackPatternForPreAndPostJdk20 = patternDateTimeWithFallbackPatternForPreAndPostJdk20 ;
567
+ }
568
+
478
569
public Date getIsoDate () {
479
570
return this .isoDate ;
480
571
}
0 commit comments