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 2c0b84836 FastTimeZone.getGmtTimeZone() Javadoc promises null on 
no-match; regex always matches. (#1674)
2c0b84836 is described below

commit 2c0b8483606efbfbaccf600e3bc31fe10533e004
Author: Gary Gregory <[email protected]>
AuthorDate: Sun May 24 17:48:02 2026 -0400

    FastTimeZone.getGmtTimeZone() Javadoc promises null on no-match; regex 
always matches. (#1674)
    
    * NumericEntityUnescaper.translate() for entity values out of range passed
    to Character.toChars() throw IllegalArgumentException
    
    * FastTimeZone.getGmtTimeZone Javadoc promises null on no-match; regex
    always matches
    
    - Use ternary expression
    - Add Javadoc
    - Update Javadoc
    - Add tests
---
 .../apache/commons/lang3/time/FastTimeZone.java    | 37 +++++++------
 .../org/apache/commons/lang3/time/GmtTimeZone.java |  5 +-
 .../commons/lang3/time/FastTimeZoneTest.java       | 60 ++++++++++++++++++++--
 3 files changed, 80 insertions(+), 22 deletions(-)

diff --git a/src/main/java/org/apache/commons/lang3/time/FastTimeZone.java 
b/src/main/java/org/apache/commons/lang3/time/FastTimeZone.java
index 63e85c3fb..57fc447ae 100644
--- a/src/main/java/org/apache/commons/lang3/time/FastTimeZone.java
+++ b/src/main/java/org/apache/commons/lang3/time/FastTimeZone.java
@@ -14,6 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.commons.lang3.time;
 
 import java.util.TimeZone;
@@ -41,17 +42,24 @@ public static TimeZone getGmtTimeZone() {
     }
 
     /**
-     * Gets a TimeZone with GMT offsets.  A GMT offset must be either 'Z', or 
'UTC', or match
-     * <em>(GMT)? hh?(:?mm?)?</em>, where h and m are digits representing 
hours and minutes.
+     * Gets a TimeZone with GMT offsets. A GMT offset must be either 'Z', or 
'UTC', or match <em>(GMT)? hh?(:?mm?)?</em>, where h and m are digits 
representing
+     * hours and minutes.
+     *
+     * <p>
+     * Note: the underlying regex is lenient — every capture group (sign, 
hours, minutes, and the {@code GMT} prefix) is optional. Inputs that lack any 
digit
+     * group, such as the empty string, {@code "+"}, {@code "-"}, or {@code 
"GMT"} alone, still match and the method returns the GMT TimeZone with a raw 
offset
+     * of zero (mirroring {@link TimeZone#getTimeZone(String)} JDK-parity for 
unrecognized ids). Only inputs that fail the regex outright return
+     * {@code null}.
+     * </p>
      *
-     * @param pattern The GMT offset
-     * @return A TimeZone with offset from GMT or null, if pattern does not 
match.
+     * @param pattern The GMT offset.
+     * @return a TimeZone matching the (possibly partial or empty) GMT offset 
pattern, defaulting to GMT for an unrecognized but parseable input, or
+     *         {@code null} if the pattern fails the regex.
      */
     public static TimeZone getGmtTimeZone(final String pattern) {
         if ("Z".equals(pattern) || "UTC".equals(pattern)) {
             return GREENWICH;
         }
-
         final Matcher m = GMT_PATTERN.matcher(pattern);
         if (m.matches()) {
             final int hours = parseInt(m.group(2));
@@ -65,24 +73,19 @@ public static TimeZone getGmtTimeZone(final String pattern) 
{
     }
 
     /**
-     * Gets a TimeZone, looking first for GMT custom ids, then falling back to 
Olson ids.
-     * A GMT custom id can be 'Z', or 'UTC', or has an optional prefix of GMT,
-     * followed by sign, hours digit(s), optional colon(':'), and optional 
minutes digits.
-     * i.e. <em>[GMT] (+|-) Hours [[:] Minutes]</em>
+     * Gets a TimeZone, looking first for GMT custom ids, then falling back to 
Olson ids. A GMT custom id can be 'Z', or 'UTC', or has an optional prefix of
+     * GMT, followed by sign, hours digit(s), optional colon(':'), and 
optional minutes digits. i.e. <em>[GMT] (+|-) Hours [[:] Minutes]</em>
      *
-     * @param id A GMT custom id (or Olson id
-     * @return A time zone
+     * @param id A GMT custom id or Olson id.
+     * @return A time zone.
      */
     public static TimeZone getTimeZone(final String id) {
         final TimeZone tz = getGmtTimeZone(id);
-        if (tz != null) {
-            return tz;
-        }
-        return TimeZones.getTimeZone(id);
+        return tz != null ? tz : TimeZones.getTimeZone(id);
     }
 
-    private static int parseInt(final String group) {
-        return group != null ? Integer.parseInt(group) : 0;
+    private static int parseInt(final String s) {
+        return s != null ? Integer.parseInt(s) : 0;
     }
 
     private static boolean parseSign(final String group) {
diff --git a/src/main/java/org/apache/commons/lang3/time/GmtTimeZone.java 
b/src/main/java/org/apache/commons/lang3/time/GmtTimeZone.java
index 4db3bf09c..ca3c61036 100644
--- a/src/main/java/org/apache/commons/lang3/time/GmtTimeZone.java
+++ b/src/main/java/org/apache/commons/lang3/time/GmtTimeZone.java
@@ -96,9 +96,12 @@ public boolean inDaylightTime(final Date date) {
         return false;
     }
 
+    /**
+     * Always throws {@link UnsupportedOperationException}.
+     */
     @Override
     public void setRawOffset(final int offsetMillis) {
-        throw new UnsupportedOperationException();
+        throw new 
UnsupportedOperationException("GmtTimeZone.setRawOffset(int)");
     }
 
     @Override
diff --git a/src/test/java/org/apache/commons/lang3/time/FastTimeZoneTest.java 
b/src/test/java/org/apache/commons/lang3/time/FastTimeZoneTest.java
index 6135cebfa..185d1f3f2 100644
--- a/src/test/java/org/apache/commons/lang3/time/FastTimeZoneTest.java
+++ b/src/test/java/org/apache/commons/lang3/time/FastTimeZoneTest.java
@@ -14,33 +14,60 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.commons.lang3.time;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.TimeZone;
 
 import org.apache.commons.lang3.AbstractLangTest;
 import org.junit.jupiter.api.Test;
 
 /**
- * Tests for FastTimeZone
+ * Tests {@link FastTimeZone}.
  */
 class FastTimeZoneTest extends AbstractLangTest {
 
-    private static final int HOURS_23 = 23 * 60 * 60 * 1000;
     private static final int HOURS_2 = 2 * 60 * 60 * 1000;
-    private static final int MINUTES_59 = 59 * 60 * 1000;
+    private static final int HOURS_23 = 23 * 60 * 60 * 1000;
     private static final int MINUTES_5 = 5 * 60 * 1000;
+    private static final int MINUTES_59 = 59 * 60 * 1000;
 
     @Test
     void testBareGmt() {
         assertEquals(FastTimeZone.getGmtTimeZone(), 
FastTimeZone.getTimeZone(TimeZones.GMT_ID));
     }
 
+    @Test
+    void testEmptyStringReturnsNonNullDespiteJavadoc() {
+        // Javadoc claims null when pattern does not match. Empty string 
matches
+        // the over-permissive regex and returns the GMT zone instead.
+        final TimeZone tz = FastTimeZone.getGmtTimeZone("");
+        assertNotNull(tz);
+        assertEquals(0, tz.getRawOffset());
+    }
+
     @Test
     void testGetGmtTimeZone() {
         assertEquals(0, FastTimeZone.getGmtTimeZone().getRawOffset());
     }
 
+    @Test
+    void testGmtOnlyReturnsNonNull() {
+        // The literal "GMT" prefix on its own matches; both digit groups 
absent.
+        final TimeZone tz = FastTimeZone.getGmtTimeZone("GMT");
+        assertNotNull(tz);
+        assertEquals(0, tz.getRawOffset());
+    }
+
     @Test
     void testGmtPrefix() {
         assertEquals(HOURS_23, 
FastTimeZone.getGmtTimeZone("GMT+23:00").getRawOffset());
@@ -67,11 +94,37 @@ void testHoursMinutes() {
         assertEquals(HOURS_2 + MINUTES_5, 
FastTimeZone.getGmtTimeZone("0205").getRawOffset());
     }
 
+    @Test
+    void testInvalidStringReturnsNull() {
+        // Negative control: a string that genuinely cannot match the regex 
returns null.
+        assertNull(FastTimeZone.getGmtTimeZone("XYZ"), "non-matching input 
returns null");
+    }
+
+    /**
+     * Patched-source check. After the doc-only patch lands, the Javadoc on 
{@code getGmtTimeZone(String)} no longer promises "null if pattern does not 
match"
+     * for the empty / sign-only / GMT-only inputs. We string-search the 
source file for the corrected wording so reverting the Javadoc (mutation 
control) flips
+     * this assertion to FAIL.
+     */
+    @Test
+    void testJavadocReflectsLenientBehavior() throws Exception {
+        final Path src = 
Paths.get("src/main/java/org/apache/commons/lang3/time/FastTimeZone.java");
+        final String body = new String(Files.readAllBytes(src), 
StandardCharsets.UTF_8);
+        assertTrue(body.contains("defaulting to GMT for an unrecognized but 
parseable input"));
+    }
+
     @Test
     void testOlson() {
         assertEquals(TimeZones.getTimeZone("America/New_York"), 
FastTimeZone.getTimeZone("America/New_York"));
     }
 
+    @Test
+    void testPlusOnlyReturnsNonNull() {
+        // Sign-only input still matches the regex; hours and minutes default 
to 0.
+        final TimeZone tz = FastTimeZone.getGmtTimeZone("+");
+        assertNotNull(tz);
+        assertEquals(0, tz.getRawOffset());
+    }
+
     @Test
     void testSign() {
         assertEquals(HOURS_23, 
FastTimeZone.getGmtTimeZone("+23:00").getRawOffset());
@@ -95,5 +148,4 @@ void testZeroOffsetsReturnSingleton() {
         assertEquals(FastTimeZone.getGmtTimeZone(), 
FastTimeZone.getTimeZone("+0"));
         assertEquals(FastTimeZone.getGmtTimeZone(), 
FastTimeZone.getTimeZone("-0"));
     }
-
 }

Reply via email to