This is an automated email from the ASF dual-hosted git repository.

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git


The following commit(s) were added to refs/heads/master by this push:
     new 117a9ea79 Clone the TimeZone in FastDatePrinter and FastDateParser 
(#1795)
117a9ea79 is described below

commit 117a9ea791b87af1b90413fa8290891ca3106cfd
Author: alhuda <[email protected]>
AuthorDate: Sat Sep 19 20:01:47 2026 +0530

    Clone the TimeZone in FastDatePrinter and FastDateParser (#1795)
    
    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.
---
 .../apache/commons/lang3/time/FastDateFormat.java  |  2 +-
 .../apache/commons/lang3/time/FastDateParser.java  |  5 ++--
 .../apache/commons/lang3/time/FastDatePrinter.java |  5 ++--
 .../commons/lang3/time/FastDateFormatTest.java     | 28 ++++++++++++++++++++++
 .../commons/lang3/time/FastDateParserTest.java     | 14 +++++++++++
 5 files changed, 49 insertions(+), 5 deletions(-)

diff --git a/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java 
b/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java
index e7c012938..b1bf48748 100644
--- a/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java
+++ b/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java
@@ -591,7 +591,7 @@ public String getPattern() {
      * This zone is always used for {@link Date} formatting.
      * </p>
      *
-     * @return The time zone.
+     * @return A copy of the time zone, changing it has no effect on this 
formatter.
      */
     @Override
     public TimeZone getTimeZone() {
diff --git a/src/main/java/org/apache/commons/lang3/time/FastDateParser.java 
b/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
index 3f47d4cf9..7b632083e 100644
--- a/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
+++ b/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
@@ -940,7 +940,8 @@ protected FastDateParser(final String pattern, final 
TimeZone timeZone, final Lo
      */
     protected FastDateParser(final String pattern, final TimeZone timeZone, 
final Locale locale, final Date centuryStart) {
         this.pattern = Objects.requireNonNull(pattern, "pattern");
-        this.timeZone = Objects.requireNonNull(timeZone, "timeZone");
+        // TimeZone is mutable and instances are shared through the 
FastDateFormat cache.
+        this.timeZone = (TimeZone) Objects.requireNonNull(timeZone, 
"timeZone").clone();
         this.locale = LocaleUtils.toLocale(locale);
         final Calendar definingCalendar = Calendar.getInstance(timeZone, 
this.locale);
         final int centuryStartYear;
@@ -1108,7 +1109,7 @@ private Strategy getStrategy(final char f, final int 
width, final Calendar defin
      */
     @Override
     public TimeZone getTimeZone() {
-        return timeZone;
+        return (TimeZone) timeZone.clone();
     }
 
     /**
diff --git a/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java 
b/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
index 0beed585e..e67971fa2 100644
--- a/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
+++ b/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java
@@ -1067,7 +1067,8 @@ static String getTimeZoneDisplay(final TimeZone tz, final 
boolean daylight, fina
      */
     protected FastDatePrinter(final String pattern, final TimeZone timeZone, 
final Locale locale) {
         this.pattern = pattern;
-        this.timeZone = timeZone;
+        // TimeZone is mutable and instances are shared through the 
FastDateFormat cache.
+        this.timeZone = (TimeZone) timeZone.clone();
         this.locale = LocaleUtils.toLocale(locale);
         init();
     }
@@ -1304,7 +1305,7 @@ public String getPattern() {
      */
     @Override
     public TimeZone getTimeZone() {
-        return timeZone;
+        return (TimeZone) timeZone.clone();
     }
 
     /**
diff --git 
a/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java 
b/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java
index 30586ac0a..d86d7ec4b 100644
--- a/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java
+++ b/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java
@@ -478,4 +478,32 @@ void testTimeDefaults() {
         assertEquals(FastDateFormat.getTimeInstance(FastDateFormat.LONG),
                 FastDateFormat.getTimeInstance(FastDateFormat.LONG, 
TimeZone.getDefault(), Locale.getDefault()));
     }
+
+    /**
+     * Mutating the TimeZone passed to the factory must not change the cached, 
shared instance.
+     */
+    @Test
+    void testTimeZoneArgumentIsCopied() throws ParseException {
+        final TimeZone timeZone = TimeZones.getTimeZone("UTC");
+        final FastDateFormat printer = FastDateFormat.getInstance("yyyy-MM-dd 
HH:mm Z", timeZone, Locale.US);
+        final FastDateFormat parser = FastDateFormat.getInstance("yyyy-MM-dd 
HH:mm", timeZone, Locale.US);
+        timeZone.setRawOffset(5 * 3_600_000);
+        assertEquals(TimeZones.getTimeZone("UTC"), printer.getTimeZone());
+        assertEquals("1970-01-01 00:00 +0000", printer.format(new Date(0)));
+        assertEquals(new Date(0), parser.parse("1970-01-01 00:00"));
+    }
+
+    /**
+     * Mutating the TimeZone returned by the getter must not change the 
cached, shared instance.
+     */
+    @Test
+    void testTimeZoneGetterReturnsCopy() throws ParseException {
+        final FastDateFormat printer = FastDateFormat.getInstance("yyyy-MM-dd 
HH:mm:ss Z", TimeZones.getTimeZone("UTC"), Locale.US);
+        final FastDateFormat parser = FastDateFormat.getInstance("yyyy-MM-dd 
HH:mm:ss", TimeZones.getTimeZone("UTC"), Locale.US);
+        printer.getTimeZone().setRawOffset(5 * 3_600_000);
+        parser.getTimeZone().setRawOffset(5 * 3_600_000);
+        assertEquals(TimeZones.getTimeZone("UTC"), printer.getTimeZone());
+        assertEquals("1970-01-01 00:00:00 +0000", printer.format(new Date(0)));
+        assertEquals(new Date(0), parser.parse("1970-01-01 00:00:00"));
+    }
 }
diff --git 
a/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java 
b/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
index 22b6357ee..0fddcbafc 100644
--- a/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
+++ b/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
@@ -863,6 +863,20 @@ void testSpecialCharacters(final TriFunction<String, 
TimeZone, Locale, DateParse
         testSdfAndFdp(dpProvider, "yyyy-MM-dd 'QED'", "2003-02-10 qed", true);
     }
 
+    /**
+     * Mutating the TimeZone passed to the constructor or returned by the 
getter must not change the parser.
+     */
+    @Test
+    void testTimeZoneIsCopied() throws ParseException {
+        final TimeZone timeZone = TimeZones.getTimeZone("UTC");
+        final FastDateParser parser = new FastDateParser("yyyy-MM-dd HH:mm", 
timeZone, Locale.US);
+        timeZone.setRawOffset(5 * 3_600_000);
+        assertEquals(new Date(0), parser.parse("1970-01-01 00:00"));
+        parser.getTimeZone().setRawOffset(5 * 3_600_000);
+        assertEquals(TimeZones.getTimeZone("UTC"), parser.getTimeZone());
+        assertEquals(new Date(0), parser.parse("1970-01-01 00:00"));
+    }
+
     @Test
     @ReadsDefaultLocale
     void testTimeZoneMatches() {

Reply via email to