From 0877941e3c90055abcf6d766f517abaf2254bd49 Mon Sep 17 00:00:00 2001 From: nathsou Date: Mon, 24 Aug 2026 13:06:56 +0200 Subject: [PATCH 1/2] SONARJAVA-6840: Implement S9363 Detect invalid constant time-zone identifiers before TimeZone silently falls back to GMT, including Java-version-specific custom offset validation and safe literal quick fixes. --- .../java/checks/TimeZoneIdCheckSample.java | 150 ++++++++++++++++++ .../java/checks/TimeZoneIdCheck_java18.java | 27 ++++ .../sonar/java/checks/TimeZoneIdCheck.java | 101 ++++++++++++ .../java/checks/TimeZoneIdCheckTest.java | 60 +++++++ .../org/sonar/l10n/java/rules/java/S9363.html | 102 ++++++++++++ .../org/sonar/l10n/java/rules/java/S9363.json | 25 +++ .../main/resources/profiles/Sonar_way/S9363 | 0 7 files changed, 465 insertions(+) create mode 100644 java-checks-test-sources/default/src/main/java/checks/TimeZoneIdCheckSample.java create mode 100644 java-checks-test-sources/default/src/main/java/checks/TimeZoneIdCheck_java18.java create mode 100644 java-checks/src/main/java/org/sonar/java/checks/TimeZoneIdCheck.java create mode 100644 java-checks/src/test/java/org/sonar/java/checks/TimeZoneIdCheckTest.java create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9363.html create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9363.json create mode 100644 sonar-java-plugin/src/main/resources/profiles/Sonar_way/S9363 diff --git a/java-checks-test-sources/default/src/main/java/checks/TimeZoneIdCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/TimeZoneIdCheckSample.java new file mode 100644 index 00000000000..efbc058084d --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/TimeZoneIdCheckSample.java @@ -0,0 +1,150 @@ +package checks; + +import java.util.TimeZone; + +class TimeZoneIdCheckSample { + + private static final String VALID_CONST = "America/Los_Angeles"; + private static final String INVALID_CONST = "America/Los_Angele"; + private static final String SPACED_CONST = "America/Los Angeles"; + + void namedIds() { + TimeZone.getTimeZone("America/Los_Angele"); // Noncompliant {{Change this invalid time zone ID; an unrecognized identifier makes TimeZone.getTimeZone(String) return GMT with no error.}} [[quickfixes=!]] +// ^^^^^^^^^^^^^^^^^^^^ + TimeZone.getTimeZone("America/New_Yorkk"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^^^^^^^^^^ + TimeZone.getTimeZone("Europe/Pariss"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^^^^^^ + TimeZone.getTimeZone("Unknown/Invalid_Zone"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^^^^^^^^^^^^^ + } + + void quickFixes() { + TimeZone.getTimeZone("America/Los Angeles"); // Noncompliant [[quickfixes=qf1]] +// ^^^^^^^^^^^^^^^^^^^^^ + // fix@qf1 {{Replace spaces with underscores}} + // edit@qf1 [[sc=26;ec=47]] {{"America/Los_Angeles"}} + + TimeZone.getTimeZone("America/New York"); // Noncompliant [[quickfixes=qf2]] +// ^^^^^^^^^^^^^^^^^^ + // fix@qf2 {{Replace spaces with underscores}} + // edit@qf2 [[sc=26;ec=44]] {{"America/New_York"}} + + TimeZone.getTimeZone("America/Port of Spain"); // Noncompliant [[quickfixes=qf3]] +// ^^^^^^^^^^^^^^^^^^^^^^^ + // fix@qf3 {{Replace spaces with underscores}} + // edit@qf3 [[sc=26;ec=49]] {{"America/Port_of_Spain"}} + + TimeZone.getTimeZone("Some Unknown City"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^^^^^^^^^^ + } + + void customOffsets() { + TimeZone.getTimeZone("GMT+24"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^ + TimeZone.getTimeZone("GMT-25"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^ + TimeZone.getTimeZone("GMT+01:60"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^^ + TimeZone.getTimeZone("GMT-8:99"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^ + TimeZone.getTimeZone("GMT+1260"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^ + TimeZone.getTimeZone("GMT+01:00:60"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^^^^^ + TimeZone.getTimeZone("GMT+010030"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^^^ + TimeZone.getTimeZone("GMT-080030"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^^^ + TimeZone.getTimeZone("GMT+0100:30"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^^^^ + TimeZone.getTimeZone("GMT+01:0030"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^^^^ + TimeZone.getTimeZone("GMT1"); // Noncompliant [[quickfixes=!]] +// ^^^^^^ + TimeZone.getTimeZone("GMT12"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^ + TimeZone.getTimeZone("GMT+"); // Noncompliant [[quickfixes=!]] +// ^^^^^^ + TimeZone.getTimeZone("GMT+ "); // Noncompliant [[quickfixes=!]] +// ^^^^^^^ + TimeZone.getTimeZone("GMT- "); // Noncompliant [[quickfixes=!]] +// ^^^^^^^ + TimeZone.getTimeZone("UTC+0"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^ + TimeZone.getTimeZone("UTC+1"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^ + TimeZone.getTimeZone("UT+1"); // Noncompliant [[quickfixes=!]] +// ^^^^^^ + } + + void constantsAndConcatenation() { + TimeZone.getTimeZone(VALID_CONST); // Compliant + TimeZone.getTimeZone(INVALID_CONST); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^^^^ + TimeZone.getTimeZone(SPACED_CONST); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^^^ + TimeZone.getTimeZone("America/" + "Los_Angeles"); // Compliant + TimeZone.getTimeZone("America/" + "Los_Angele"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^^^^^^^^^^^^^^^^ + TimeZone.getTimeZone("America/" + "Los Angeles"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^^^^^^^^^^^^^^^^^ + } + + void dynamicAndEdgeCases(String dynamicZone) { + TimeZone.getTimeZone(dynamicZone); // Compliant + TimeZone.getTimeZone(getDynamicZone()); // Compliant + TimeZone.getTimeZone((String) null); // Compliant + TimeZone.getTimeZone(java.time.ZoneId.of("UTC")); // Compliant + java.time.ZoneId.of("America/Los_Angeles"); // Compliant + new OtherClass().getTimeZone("America/Los_Angele"); // Compliant + } + + void compliantCases() { + TimeZone.getTimeZone("America/Los_Angeles"); + TimeZone.getTimeZone("America/New_York"); + TimeZone.getTimeZone("Europe/Paris"); + TimeZone.getTimeZone("UTC"); + TimeZone.getTimeZone("GMT"); + TimeZone.getTimeZone("PST"); + TimeZone.getTimeZone("EST"); + TimeZone.getTimeZone("HST"); + TimeZone.getTimeZone("CST"); + TimeZone.getTimeZone("JST"); + TimeZone.getTimeZone("US/Pacific"); + TimeZone.getTimeZone("Etc/GMT+8"); + TimeZone.getTimeZone("SystemV/PST8"); + + TimeZone.getTimeZone("GMT+10"); + TimeZone.getTimeZone("GMT-8"); + TimeZone.getTimeZone("GMT+0"); + TimeZone.getTimeZone("GMT-0"); + TimeZone.getTimeZone("GMT+00:00"); + TimeZone.getTimeZone("GMT-8:00"); + TimeZone.getTimeZone("GMT+12:30"); + TimeZone.getTimeZone("GMT+0800"); + TimeZone.getTimeZone("GMT-0800"); + TimeZone.getTimeZone("GMT+123"); + TimeZone.getTimeZone("GMT-800"); + TimeZone.getTimeZone("GMT+23"); + TimeZone.getTimeZone("GMT-23"); + TimeZone.getTimeZone("GMT+23:59"); + TimeZone.getTimeZone("GMT-2359"); + + TimeZone.getTimeZone("GMT+01:00:30"); + TimeZone.getTimeZone("GMT-8:00:00"); + TimeZone.getTimeZone("GMT+00:00:00"); + TimeZone.getTimeZone("GMT+23:59:59"); + TimeZone.getTimeZone("GMT-23:59:59"); + } + + private String getDynamicZone() { + return "America/New_York"; + } + + private static class OtherClass { + public TimeZone getTimeZone(String id) { + return null; + } + } +} diff --git a/java-checks-test-sources/default/src/main/java/checks/TimeZoneIdCheck_java18.java b/java-checks-test-sources/default/src/main/java/checks/TimeZoneIdCheck_java18.java new file mode 100644 index 00000000000..ea92fbc18f7 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/TimeZoneIdCheck_java18.java @@ -0,0 +1,27 @@ +package checks; + +import java.util.TimeZone; + +class TimeZoneIdCheck_java18 { + + void customOffsetsWithSeconds() { + TimeZone.getTimeZone("GMT+01:00:30"); // Noncompliant {{Change this invalid time zone ID; an unrecognized identifier makes TimeZone.getTimeZone(String) return GMT with no error.}} [[quickfixes=!]] +// ^^^^^^^^^^^^^^ + TimeZone.getTimeZone("GMT-8:00:00"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^^^^ + TimeZone.getTimeZone("GMT+00:00:00"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^^^^^ + TimeZone.getTimeZone("GMT+23:59:59"); // Noncompliant [[quickfixes=!]] +// ^^^^^^^^^^^^^^ + } + + void compliantCases() { + TimeZone.getTimeZone("America/Los_Angeles"); + TimeZone.getTimeZone("PST"); + TimeZone.getTimeZone("UTC"); + TimeZone.getTimeZone("GMT+10"); + TimeZone.getTimeZone("GMT-8:00"); + TimeZone.getTimeZone("GMT+00:00"); + TimeZone.getTimeZone("GMT-0800"); + } +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/TimeZoneIdCheck.java b/java-checks/src/main/java/org/sonar/java/checks/TimeZoneIdCheck.java new file mode 100644 index 00000000000..f3362473ea0 --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/TimeZoneIdCheck.java @@ -0,0 +1,101 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import java.util.Arrays; +import java.util.Optional; +import java.util.Set; +import java.util.TimeZone; +import java.util.regex.Pattern; +import org.sonar.check.Rule; +import org.sonar.java.checks.helpers.QuickFixHelper; +import org.sonar.java.checks.methods.AbstractMethodDetection; +import org.sonar.java.reporting.InternalJavaIssueBuilder; +import org.sonar.java.reporting.JavaQuickFix; +import org.sonar.java.reporting.JavaTextEdit; +import org.sonar.plugins.java.api.semantic.MethodMatchers; +import org.sonar.plugins.java.api.tree.ExpressionTree; +import org.sonar.plugins.java.api.tree.MethodInvocationTree; +import org.sonar.plugins.java.api.tree.Tree; + +@Rule(key = "S9363") +public class TimeZoneIdCheck extends AbstractMethodDetection { + + private static final String MESSAGE = "Change this invalid time zone ID; an unrecognized identifier makes TimeZone.getTimeZone(String) return GMT with no error."; + + private static final MethodMatchers TIME_ZONE_GET_TIME_ZONE = MethodMatchers.create() + .ofTypes("java.util.TimeZone") + .names("getTimeZone") + .addParametersMatcher("java.lang.String") + .build(); + + private static final Set AVAILABLE_ZONE_IDS = Set.copyOf(Arrays.asList(TimeZone.getAvailableIDs())); + + private static final Pattern CUSTOM_GMT_PRE_JAVA19 = Pattern.compile( + "^GMT[+-](?:[01]?[0-9]|2[0-3])(?::[0-5][0-9]|[0-5][0-9])?$" + ); + + private static final Pattern CUSTOM_GMT_JAVA19_PLUS = Pattern.compile( + "^GMT[+-](?:[01]?[0-9]|2[0-3])(?::[0-5][0-9](?::[0-5][0-9])?|[0-5][0-9])?$" + ); + + @Override + protected MethodMatchers getMethodInvocationMatchers() { + return TIME_ZONE_GET_TIME_ZONE; + } + + @Override + protected void onMethodInvocationFound(MethodInvocationTree mit) { + if (!mit.methodSymbol().isStatic() || mit.arguments().isEmpty()) { + return; + } + ExpressionTree argument = mit.arguments().get(0); + Optional constantValue = argument.asConstant(String.class); + if (constantValue.isEmpty()) { + return; + } + String zoneId = constantValue.get(); + if (isValidZoneId(zoneId)) { + return; + } + + InternalJavaIssueBuilder issueBuilder = QuickFixHelper.newIssue(context) + .forRule(this) + .onTree(argument) + .withMessage(MESSAGE); + + if (argument.is(Tree.Kind.STRING_LITERAL)) { + String proposed = zoneId.replace(' ', '_'); + if (!proposed.equals(zoneId) && AVAILABLE_ZONE_IDS.contains(proposed)) { + issueBuilder.withQuickFix(() -> JavaQuickFix.newQuickFix("Replace spaces with underscores") + .addTextEdit(JavaTextEdit.replaceTree(argument, "\"" + proposed + "\"")) + .build()); + } + } + + issueBuilder.report(); + } + + private boolean isValidZoneId(String zoneId) { + if (AVAILABLE_ZONE_IDS.contains(zoneId)) { + return true; + } + boolean allowsSeconds = context.getJavaVersion().isNotSet() || context.getJavaVersion().isJava19Compatible(); + Pattern customGmtPattern = allowsSeconds ? CUSTOM_GMT_JAVA19_PLUS : CUSTOM_GMT_PRE_JAVA19; + return customGmtPattern.matcher(zoneId).matches(); + } +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/TimeZoneIdCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/TimeZoneIdCheckTest.java new file mode 100644 index 00000000000..9143ebb31e1 --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/TimeZoneIdCheckTest.java @@ -0,0 +1,60 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath; + +class TimeZoneIdCheckTest { + + @Test + void test() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/TimeZoneIdCheckSample.java")) + .withCheck(new TimeZoneIdCheck()) + .verifyIssues(); + } + + @Test + void test_java_18() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/TimeZoneIdCheck_java18.java")) + .withCheck(new TimeZoneIdCheck()) + .withJavaVersion(18) + .verifyIssues(); + } + + @Test + void test_java_19() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/TimeZoneIdCheckSample.java")) + .withCheck(new TimeZoneIdCheck()) + .withJavaVersion(19) + .verifyIssues(); + } + + @Test + void test_without_semantic() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/TimeZoneIdCheckSample.java")) + .withCheck(new TimeZoneIdCheck()) + .withoutSemantic() + .verifyIssues(); + } +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9363.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9363.html new file mode 100644 index 00000000000..48b5bd4290d --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9363.html @@ -0,0 +1,102 @@ +

TimeZone.getTimeZone(String) returns the GMT zone when it cannot understand the identifier, and it does not throw an exception for an +unknown identifier. A misspelled value such as America/Los_Angele therefore uses GMT instead of the intended zone.

+

Why is this an issue?

+

The String overload of TimeZone.getTimeZone does not reject unknown identifiers. If the argument is not a named ID and +does not match custom GMT syntax, the method returns the GMT zone. The call succeeds, so the application can keep running with GMT instead of the +intended zone.

+

Use a named identifier that Java recognizes, such as America/Los_Angeles, or a custom GMT offset that matches the documented +syntax.

+

This rule accepts a named identifier when Java recognizes it, such as America/Los_Angeles. The recognized set can differ slightly +across JDK and time-zone database versions. Legacy three-letter IDs such as PST remain recognized for compatibility and are not +reported.

+

When no named ID matches, a custom GMT identifier can still be valid. The syntax is GMT followed by a sign and an offset: +GMT+10 and GMT-8:00 are accepted. Hours must be between 0 and 23, and minutes must be between 00 and 59. From Java 19, +seconds between 00 and 59 are also accepted, for example GMT+01:00:30. On earlier Java versions that form is not recognized and falls +back to GMT. When sonar.java.source is not set, this rule accepts identifiers that include seconds.

+

Exceptions

+

This rule does not raise an issue when the argument is not a compile-time constant String, including values computed at runtime.

+

Code examples

+

Noncompliant code example

+
+void namedIds(String userZone) {
+  TimeZone.getTimeZone("America/Los_Angele"); // Noncompliant
+}
+
+

Compliant solution

+
+void namedIds(String userZone) {
+  TimeZone.getTimeZone("America/Los_Angeles");
+  TimeZone.getTimeZone("PST"); // legacy three-letter ID
+  TimeZone.getTimeZone(userZone); // not a compile-time constant
+}
+
+

Noncompliant code example

+
+void spacedId() {
+  TimeZone.getTimeZone("America/Los Angeles"); // Noncompliant
+}
+
+

Compliant solution

+
+void spacedId() {
+  TimeZone.getTimeZone("America/Los_Angeles");
+}
+
+

Noncompliant code example

+
+void customOffsets() {
+  TimeZone.getTimeZone("GMT+24"); // Noncompliant
+  TimeZone.getTimeZone("GMT1"); // Noncompliant
+  TimeZone.getTimeZone("UTC+0"); // Noncompliant: custom offsets must start with "GMT"
+}
+
+

Compliant solution

+
+void customOffsets() {
+  TimeZone.getTimeZone("GMT+10");
+  TimeZone.getTimeZone("GMT-8");
+  TimeZone.getTimeZone("GMT+00:00");
+}
+
+

Noncompliant code example

+

For projects targeting Java 18 and earlier:

+
+void customOffsetWithSeconds() {
+  TimeZone.getTimeZone("GMT+01:00:30"); // Noncompliant: seconds are not recognized before Java 19
+}
+
+

Compliant solution

+
+void customOffsetWithSeconds() {
+  TimeZone.getTimeZone("GMT+01:00");
+}
+
+

Noncompliant code example

+

For projects targeting Java 19 and later:

+
+void customOffsetWithSeconds() {
+  TimeZone.getTimeZone("GMT+01:00:60"); // Noncompliant: seconds must be between 00 and 59
+}
+
+

Compliant solution

+
+void customOffsetWithSeconds() {
+  TimeZone.getTimeZone("GMT+01:00:30");
+}
+
+

Resources

+

Documentation

+ +

Related rules

+
    +
  • {rule:java:S2143} - "java.time" classes should be used for dates and times
  • +
  • {rule:java:S8688} - Time-based .now() methods should specify a ZoneId or a Clock
  • +
+ diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9363.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9363.json new file mode 100644 index 00000000000..f695758024c --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9363.json @@ -0,0 +1,25 @@ +{ + "title": "Invalid time-zone IDs should not silently fall back to GMT", + "type": "BUG", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "datetime", + "timezone", + "pitfall" + ], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-9363", + "sqKey": "S9363", + "scope": "All", + "quickfix": "partial", + "code": { + "impacts": { + "RELIABILITY": "MEDIUM" + }, + "attribute": "LOGICAL" + } +} diff --git a/sonar-java-plugin/src/main/resources/profiles/Sonar_way/S9363 b/sonar-java-plugin/src/main/resources/profiles/Sonar_way/S9363 new file mode 100644 index 00000000000..e69de29bb2d From 121a450c9083369da5d89104296a900903fceeed Mon Sep 17 00:00:00 2001 From: nathsou Date: Mon, 24 Aug 2026 13:29:15 +0200 Subject: [PATCH 2/2] fix-ci: address S9363 analysis and ruling failures Use the concise digit regex syntax required by S6353 and record the validated S9363 finding produced by the SonarQube server ruling project. --- its/ruling/src/test/resources/sonar-server/java-S9363.json | 5 +++++ .../src/main/java/org/sonar/java/checks/TimeZoneIdCheck.java | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 its/ruling/src/test/resources/sonar-server/java-S9363.json diff --git a/its/ruling/src/test/resources/sonar-server/java-S9363.json b/its/ruling/src/test/resources/sonar-server/java-S9363.json new file mode 100644 index 00000000000..7524676619b --- /dev/null +++ b/its/ruling/src/test/resources/sonar-server/java-S9363.json @@ -0,0 +1,5 @@ +{ +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/issue/index/IssueIndexDebtTest.java": [ +75 +] +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/TimeZoneIdCheck.java b/java-checks/src/main/java/org/sonar/java/checks/TimeZoneIdCheck.java index f3362473ea0..a0281834139 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/TimeZoneIdCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/TimeZoneIdCheck.java @@ -46,11 +46,11 @@ public class TimeZoneIdCheck extends AbstractMethodDetection { private static final Set AVAILABLE_ZONE_IDS = Set.copyOf(Arrays.asList(TimeZone.getAvailableIDs())); private static final Pattern CUSTOM_GMT_PRE_JAVA19 = Pattern.compile( - "^GMT[+-](?:[01]?[0-9]|2[0-3])(?::[0-5][0-9]|[0-5][0-9])?$" + "^GMT[+-](?:[01]?\\d|2[0-3])(?::[0-5]\\d|[0-5]\\d)?$" ); private static final Pattern CUSTOM_GMT_JAVA19_PLUS = Pattern.compile( - "^GMT[+-](?:[01]?[0-9]|2[0-3])(?::[0-5][0-9](?::[0-5][0-9])?|[0-5][0-9])?$" + "^GMT[+-](?:[01]?\\d|2[0-3])(?::[0-5]\\d(?::[0-5]\\d)?|[0-5]\\d)?$" ); @Override