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:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user