Defensively clone the TimeZone in FastDatePrinter and FastDateParser - #1795
Merged
Merged
Conversation
TimeZone is mutable and FastDateFormat shares cached instances process-wide, so mutating the zone passed to the factory, or the one returned by getTimeZone(), changed the formatter for every other holder. Clone it in both constructors and both getters.
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The implementation and regression tests consistently enforce the documented defensive-copy behavior.
Review effort: Lite
Findings: None
What changed in this PR
This PR prevents mutable TimeZone instances from altering cached date formatters and parsers.
Changes:
- Clone zones during construction and when returned by getters.
- Document defensive-copy behavior.
- Add regression tests for argument and getter mutations.
| File | Description |
|---|---|
FastDateParser.java |
Defensively copies the parser time zone. |
FastDatePrinter.java |
Defensively copies the printer time zone. |
FastDateFormat.java |
Documents copied time-zone access. |
FastDateParserTest.java |
Tests parser isolation. |
FastDateFormatTest.java |
Tests formatter isolation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
garydgregory
added a commit
that referenced
this pull request
Sep 19, 2026
Member
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
FastDateFormatis documented as immutable and thread-safe and hands out instances from a process-wide cache, butFastDatePrinterandFastDateParserkeep the caller'sTimeZoneby reference, andgetTimeZone()returns that same object.TimeZoneis mutable (setRawOffset(),setID()), so one caller can change the zone of a formatter that unrelated code already holds. Same hazard asTimeZones.GMTin #1666, on the cached formatters (theDateFormatUtilsconstants included).Repro:
Expected:
1970-01-01 00:00 +0000.Actual:
1970-01-01 05:00 +0500.parse()shifts by the same five hours, and mutating theTimeZonepassed togetInstance()after the call has the same effect.Cause: both constructors store the
TimeZoneargument as is, and bothgetTimeZone()getters return the field.Fix: clone the zone in both constructors and return a clone from both getters, as
TimeZone.getDefault()does.FastDateFormat.getTimeZone()delegates to the printer.GmtTimeZoneandImmutableTimeZoneare already immutable, andTimeZoneStrategyonly copies offsets out of its zones, so these are the only exits.getTimeZone()stillequals()the zone passed in; only its identity changes.The added
FastDateFormatTestandFastDateParserTestcases fail on the current source and pass after the change. These classes have no Commons Text counterpart.mvnbuild was run locally and is green.mvn; that'smvnon the command line by itself.