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"));
}
-
}