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 e7c01293804..b1bf487489f 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. *

* - * @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 3f47d4cf9f4..7b632083e8c 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 0beed585eee..e67971fa2a1 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 30586ac0a84..d86d7ec4be5 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 22b6357ee48..0fddcbafce4 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