Skip to content

Defensively clone the TimeZone in FastDatePrinter and FastDateParser - #1795

Merged
garydgregory merged 1 commit into
apache:masterfrom
alhudz:fastdateformat-timezone-copy
Sep 19, 2026
Merged

garydgregory merged 1 commit into
apache:masterfrom
alhudz:fastdateformat-timezone-copy

Conversation

@alhudz

@alhudz alhudz commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

FastDateFormat is documented as immutable and thread-safe and hands out instances from a process-wide cache, but FastDatePrinter and FastDateParser keep the caller's TimeZone by reference, and getTimeZone() returns that same object. TimeZone is mutable (setRawOffset(), setID()), so one caller can change the zone of a formatter that unrelated code already holds. Same hazard as TimeZones.GMT in #1666, on the cached formatters (the DateFormatUtils constants included).

Repro:

FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm Z", TimeZone.getTimeZone("UTC"));
// anywhere else in the JVM: same cached instance
FastDateFormat.getInstance("yyyy-MM-dd HH:mm Z", TimeZone.getTimeZone("UTC")).getTimeZone().setRawOffset(5 * 3_600_000);
format.format(new Date(0));

Expected: 1970-01-01 00:00 +0000.
Actual: 1970-01-01 05:00 +0500. parse() shifts by the same five hours, and mutating the TimeZone passed to getInstance() after the call has the same effect.
Cause: both constructors store the TimeZone argument as is, and both getTimeZone() 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. GmtTimeZone and ImmutableTimeZone are already immutable, and TimeZoneStrategy only copies offsets out of its zones, so these are the only exits. getTimeZone() still equals() the zone passed in; only its identity changes.

The added FastDateFormatTest and FastDateParserTest cases fail on the current source and pass after the change. These classes have no Commons Text counterpart.


  • Read the contribution guidelines for this project.
  • Read the ASF Generative Tooling Guidance if you use Artificial Intelligence (AI).
  • I used AI to create any part of, or all of, this pull request. Which AI tool was used to create this pull request, and to what extent did it contribute? Claude Code (Anthropic) was used to find the defect and to write the patch, the tests and this description. The default mvn build was run locally and is green.
  • Run a successful build using the default Maven goal with mvn; that's mvn on the command line by itself.
  • Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied. This may not always be possible, but it is a best practice.
  • Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
  • Each commit in the pull request should have a meaningful subject line and body. Note that a maintainer may squash commits during the merge process.

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
garydgregory merged commit 117a9ea into apache:master Sep 19, 2026
23 of 24 checks passed
@garydgregory garydgregory changed the title Defensively copy the TimeZone in FastDatePrinter and FastDateParser Defensively clone the TimeZone in FastDatePrinter and FastDateParser Sep 19, 2026
@garydgregory

Copy link
Copy Markdown
Member

@alhudz merged 🚀 Thank you. Note that the PR was incomplete, see commit 55dcbc4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants