datetimeformatter: DateTimeFormatter cheat sheet module (pattern letters incl. the four hour letters, yyyy vs YYYY week-year bug and WeekFields, en_IN locales and the U+202F AM/PM trap with parseLenient fix, SMART/STRICT/LENIENT parsing, ISO constants, SimpleDateFormat thread safety), 18 contract tests

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_017ic4b3fvyFpxThdRxcozLx
This commit is contained in:
Claude
2026-11-14 01:24:52 +05:30
parent c8588ae520
commit 5faa57bb2d
22 changed files with 944 additions and 0 deletions
@@ -0,0 +1,61 @@
moment = 2026-11-14T01:17:05.123456789+05:30[Asia/Kolkata] (a Saturday, day 318 of the year)
yyyy -> 2026
yy -> 26
uuuu -> 2026
M -> 11
MM -> 11
MMM -> Nov
MMMM -> November
MMMMM -> N
d -> 14
dd -> 14
D -> 318
DDD -> 318
E -> Sat
EEEE -> Saturday
EEEEE -> S
e -> 7
ee -> 07
H -> 1
HH -> 01
h -> 1
hh -> 01
k -> 1
K -> 1
a -> AM
B -> in the morning
BBBB -> in the morning
m -> 17
mm -> 17
s -> 5
ss -> 05
S -> 1
SSS -> 123
SSSSSS -> 123456
n -> 123456789
A -> 4625123
G -> AD
Q -> 4
QQQ -> Q4
w -> 46
W -> 2
z -> IST
zzzz -> India Standard Time
VV -> Asia/Kolkata
O -> GMT+5:30
OOOO -> GMT+05:30
X -> +0530
XXX -> +05:30
x -> +0530
xxx -> +05:30
Z -> +0530
ZZZZ -> GMT+05:30
ZZZZZ -> +05:30
== the four hour letters, at midnight, 01:00, noon and 13:00 ==
time H h k K
00:17 0 12 24 0
01:17 1 1 1 1
12:17 12 12 12 0
13:17 13 1 13 1
@@ -0,0 +1,35 @@
== one date, two letters ==
date = 2026-12-29 (TUESDAY)
dd-MM-yyyy = 29-12-2026
dd-MM-YYYY = 29-12-2027
== the same YYYY pattern, four locales, three dates ==
date en_US en_GB fr_FR en_IN
2026-12-28 2027 2026 2026 2027
2026-12-29 2027 2026 2026 2027
2027-01-01 2027 2026 2026 2027
== why: each locale defines what a week is (WeekFields) ==
en_US WeekFields[SUNDAY,1] week-based-year of 2026-12-29 = 2027, week number = 1
en_GB WeekFields[MONDAY,4] week-based-year of 2026-12-29 = 2026, week number = 53
fr_FR WeekFields[MONDAY,4] week-based-year of 2026-12-29 = 2026, week number = 53
en_IN WeekFields[SUNDAY,1] week-based-year of 2026-12-29 = 2027, week number = 1
== how often is YYYY wrong? every day of 2020-2030, compared with yyyy ==
en_US 36 wrong days out of 4018
en_GB 19 wrong days out of 4018
fr_FR 19 wrong days out of 4018
en_IN 36 wrong days out of 4018
== the days that are wrong in the US locale ==
2024/2025: 2024-12-29->2025, 2024-12-30->2025, 2024-12-31->2025
2025/2026: 2025-12-28->2026, 2025-12-29->2026, 2025-12-30->2026, 2025-12-31->2026
2026/2027: 2026-12-27->2027, 2026-12-28->2027, 2026-12-29->2027, 2026-12-30->2027, 2026-12-31->2027
2027/2028: 2027-12-26->2028, 2027-12-27->2028, 2027-12-28->2028, 2027-12-29->2028, 2027-12-30->2028, 2027-12-31->2028
2028/2029: 2028-12-31->2029
== yyyy vs uuuu: same for modern dates, different for the era ==
date = -0043-03-15
yyyy = 0044
uuuu = -0043
yyyy G = 0044 BC
+34
View File
@@ -0,0 +1,34 @@
== the same pattern in four locales ==
en_US Tuesday, 1 September 2026
en_IN Tuesday, 1 September 2026
hi_IN मंगलवार, 1 सितंबर 2026
mr_IN मंगळवार, 1 सप्टेंबर 2026
== abbreviated month: not what every Indian or British reader expects ==
en_US 1 Sep 2026
en_GB 1 Sept 2026
en_IN 1 Sept 2026
== localized styles for en_IN (the pattern is chosen by the locale, not by you) ==
MEDIUM 1 Sept 2026
SHORT 01/09/26
FULL Tuesday, 1 September 2026
== AM/PM text per locale, with the hex of every character ==
en_US [03:30 PM] 30 33 3A 33 30 20 50 4D
en_IN [03:30 pm] 30 33 3A 33 30 20 70 6D
== ofLocalizedTime(SHORT): the space before PM is not a space ==
en_US 33 3A 33 30 202F 50 4D
en_IN 33 3A 33 30 202F 70 6D
== parsing it back with a plain space ==
DateTimeParseException: Text '3:30 PM' could not be parsed at index 4
15:30
== the other direction: text that CONTAINS U+202F, read by a pattern with a plain space ==
DateTimeParseException: Text '3:30_PM' could not be parsed at index 4
== parseLenient() accepts either kind of space, in both directions ==
localized SHORT, input with plain space : 15:30
pattern h:mm a, input with U+202F : 15:30
+30
View File
@@ -0,0 +1,30 @@
== 31 February under three resolver styles (pattern dd-MM-uuuu) ==
STRICT DateTimeParseException: Text '31-02-2026' could not be parsed: Invalid date 'FEBRUARY 31'
SMART 2026-02-28
LENIENT 2026-03-03
== 30 February and 32 January ==
SMART 30-02-2026 2026-02-28
SMART 32-01-2026 DateTimeParseException: Text '32-01-2026' could not be parsed: Invalid value for DayOfMonth (valid values 1 - 28/31): 32
== STRICT with yyyy: year-of-era needs an era, so it cannot resolve ==
yyyy DateTimeParseException: Text '14-11-2026' could not be parsed: Unable to obtain LocalDate from TemporalAccessor: {MonthOfYear=11, DayOfMonth=14, YearOfEra=2026},ISO of type java.time.format.Parsed
uuuu 2026-11-14
== how many digits? MM wants two, M accepts one or two ==
yyyy-MM-dd 2026-1-5 DateTimeParseException: Text '2026-1-5' could not be parsed at index 5
yyyy-M-d 2026-1-5 2026-01-05
yyyy-M-d 2026-01-05 2026-01-05
== two-digit years are read as 2000-2099 ==
01-01-30 -> 2030-01-01
01-01-99 -> 2099-01-01
== month names are case-sensitive unless you say otherwise ==
14 Nov 2026 2026-11-14
14 NOV 2026 DateTimeParseException: Text '14 NOV 2026' could not be parsed at index 3
14 NOV 2026 (parseCaseInsensitive) 2026-11-14
== the old SimpleDateFormat, same 31-02-2026 ==
lenient (default): Tue Mar 03 00:00:00 IST 2026
setLenient(false): ParseException: Unparseable date: "31-02-2026"
@@ -0,0 +1,19 @@
moment = 2026-12-29T01:17:05.123+05:30[Asia/Kolkata]
ISO_LOCAL_DATE 2026-12-29
ISO_LOCAL_TIME 01:17:05.123
ISO_LOCAL_DATE_TIME 2026-12-29T01:17:05.123
ISO_OFFSET_DATE_TIME 2026-12-29T01:17:05.123+05:30
ISO_ZONED_DATE_TIME 2026-12-29T01:17:05.123+05:30[Asia/Kolkata]
ISO_INSTANT 2026-12-28T19:47:05.123Z
ISO_DATE_TIME 2026-12-29T01:17:05.123+05:30[Asia/Kolkata]
ISO_ORDINAL_DATE 2026-363+05:30
ISO_WEEK_DATE 2026-W53-2+05:30
BASIC_ISO_DATE 20261229+0530
RFC_1123_DATE_TIME Tue, 29 Dec 2026 01:17:05 +0530
== ISO_INSTANT on a ZonedDateTime converts to UTC ==
2026-12-28T19:47:05.123Z
== toString() is already ISO for the java.time types ==
2026-12-29 | 01:17:05.123 | 2026-12-29T01:17:05.123+05:30
+11
View File
@@ -0,0 +1,11 @@
hour pattern on a LocalDate UnsupportedTemporalTypeException: Unsupported field: HourOfDay
zone pattern on a LocalDateTime DateTimeException: Unable to extract ZoneId from temporal 2026-11-14T01:17
unknown pattern letter 'b' IllegalArgumentException: Unknown pattern letter: b
unterminated quote IllegalArgumentException: Pattern ends with an incomplete string literal: uuuu-MM-dd'T
the T in an ISO pattern, unquoted IllegalArgumentException: Unknown pattern letter: T
the T in an ISO pattern, quoted OK: 2026-11-14T01:17
text too short DateTimeParseException: Text '2026-11' could not be parsed at index 7
text too long DateTimeParseException: Text '2026-11-14 extra' could not be parsed, unparsed text found at index 10
date-only text into a LocalDateTime DateTimeParseException: Text '2026-11-14' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {},ISO resolved to 2026-11-14 of type java.time.format.Parsed
month name in the wrong language DateTimeParseException: Text '14 novembre 2026' could not be parsed at index 3
same text, French locale OK: 2026-11-14
@@ -0,0 +1,3 @@
threads=8, parses per thread=20000
SimpleDateFormat (shared) : exceptions=461, wrong results=3824
DateTimeFormatter (shared): exceptions=0, wrong results=0
+3
View File
@@ -0,0 +1,3 @@
[INFO] Tests run: 18, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.918 s -- in com.ankurm.datetimeformatter.DateTimeFormatterTest
[INFO] Tests run: 18, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS