Fix date-only parsing failure when local midnight is skipped by DST - #3109
zgj-ssslab wants to merge 4 commits into
Conversation
Parsing a date-only string like '1966-11-01' with default time zone
America/Sao_Paulo threw an exception ('HOUR_OF_DAY: 0 -> 1') because
local midnight did not exist due to a DST transition at midnight.
Strict parsing now only fails for dates which do not exist in the
calendar (e.g. 2021-02-30); a valid date whose midnight is skipped by
DST is resolved to the first existing instant of that day, following
the semantics of java.time's LocalDate.atStartOfDay(ZoneId).
Fixes google#2539
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
The exact resolved hour depends on the DST rules of the JDK's bundled timezone data, which differs between JDK versions. Assert only that the date fields are preserved and the resolved hour is within the day.
|
Could you please run |
The two modified files had been committed with CRLF line endings, which fails the spotless check. Re-applied via `mvn spotless:apply`; the only change is removal, no formatting or code changes.
| public void testParseDateOnlyDSTGap() throws ParseException { | ||
| TimeZone defaultTimeZone = TimeZone.getDefault(); | ||
| try { | ||
| // 1966-11-01 00:00 does not exist in America/Sao_Paulo because clocks were shifted |
There was a problem hiding this comment.
This example makes it look as if this is a problem that almost nobody will have. Googling suggests that there are present-day timezones that switch to and from DST at midnight. I believe you can use America/Santiago and 2026-09-06 for a more realistic test case.
There was a problem hiding this comment.
that almost nobody will have
It is the exact reproducer from #2539 though; and based on that issue it is parsed as date of birth, so not such an unreasonable / uncommon case?
(Not saying though that a reproducer with a more recent date wouldn't be useful.)
| try { | ||
| pos.setIndex(offset); | ||
| return calendar.getTime(); | ||
| } catch (IllegalArgumentException e) { |
There was a problem hiding this comment.
This makes me a bit uneasy. I'm sure there's more than one way we could get this exception.
Could we just construct a GregorianCalendar instance that specifies noon instead of midnight? Then we should be able to avoid the issue.
I also think ultimately we should rewrite this method so it uses java.time APIs, but that's a bigger project, and might have implications for Android (with old target SDKs and no desugaring).
Description
Parsing a date-only string (e.g.
"1966-11-01") fails withIllegalArgumentException: HOUR_OF_DAY: 0 -> 1when the default time zone skips midnight on that date due to a DST transition (e.g.America/Sao_Pauloin 1966).Root cause
For date-only input,
ISO8601Utils.parseconstructs aGregorianCalendarfor local midnight in the default time zone withsetLenient(false). When a DST transition shifts clocks from 0:00 to 1:00, that wall time does not exist and the non-lenient calendar rejects it — even though the calendar date itself is valid.Fix
When the strict calendar fails, resolve leniently only if the date fields (year/month/day) are unchanged by the lenient resolution — i.e. the date exists but its midnight was skipped; the result is the first existing instant of that day, matching the semantics of
java.time'sLocalDate.atStartOfDay(ZoneId). Genuinely invalid dates (e.g.2021-02-30) shift to a different day under lenient resolution and keep failing, preserving the strict behavior.How I checked
America/Sao_Paulo+1966-11-01; verified all other time zones/dates unchanged2021-02-30,2021-13-01,1966-00-01still failFixes #2539