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

wusheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking.git


The following commit(s) were added to refs/heads/master by this push:
     new 82c070d837 PromQL service: fix time parse issue when using RFC3339 
time format for querying. (#13677)
82c070d837 is described below

commit 82c070d83709c580a7588654155e43f43a03e101
Author: Wan Kai <[email protected]>
AuthorDate: Tue Jan 20 20:32:23 2026 +0800

    PromQL service: fix time parse issue when using RFC3339 time format for 
querying. (#13677)
---
 docs/en/api/promql-service.md                      | 16 +++----
 docs/en/changes/changes.md                         |  1 +
 .../oap/query/promql/handler/PromQLApiHandler.java | 23 +++++++--
 .../promql/handler/PromQLApiHandlerTest.java       | 55 ++++++++++++++++++++++
 test/e2e-v2/cases/promql/promql-cases.yaml         | 10 ++--
 5 files changed, 89 insertions(+), 16 deletions(-)

diff --git a/docs/en/api/promql-service.md b/docs/en/api/promql-service.md
index 69cc3966ae..6acc59fae1 100644
--- a/docs/en/api/promql-service.md
+++ b/docs/en/api/promql-service.md
@@ -148,7 +148,7 @@ GET|POST /api/v1/query
 | Parameter | Definition                                                       
                                                                                
                                     | Support | Optional   |
 
|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|------------|
 | query     | prometheus expression                                            
                                                                                
                                     | yes     | no         |
-| time      | **The latest metrics value from current time to this time is 
returned. If time is empty, the default look-back time is 2 minutes.** time 
format: RFC3399 or unix_timestamp in seconds | yes     | yes        |
+| time      | **The latest metrics value from current time to this time is 
returned. If time is empty, the default look-back time is 2 minutes.** time 
format: RFC3339 or unix_timestamp in seconds | yes     | yes        |
 | timeout   | evaluation timeout                                               
                                                                                
                                     | **no**  | **ignore** |
 
 For example:
@@ -198,8 +198,8 @@ GET|POST /api/v1/query_range
 | Parameter | Definition                                                       
                    | Support | Optional   |
 
|-----------|--------------------------------------------------------------------------------------|---------|------------|
 | query     | prometheus expression                                            
                    | yes     | no         |
-| start     | start timestamp, format: RFC3399 or unix_timestamp in seconds    
                    | yes     | no         |
-| end       | end timestamp, format: RFC3399 or unix_timestamp in seconds      
                    | yes     | no         |
+| start     | start timestamp, format: RFC3339 or unix_timestamp in seconds    
                    | yes     | no         |
+| end       | end timestamp, format: RFC3339 or unix_timestamp in seconds      
                    | yes     | no         |
 | step      | **SkyWalking will automatically fit Step(DAY, HOUR, MINUTE) 
through start and end.** | **no**  | **ignore** |
 | timeout   | evaluation timeout                                               
                    | **no**  | **ignore** |
 
@@ -266,8 +266,8 @@ GET|POST /api/v1/series
 | Parameter | Definition                                          | Support | 
Optional |
 
|-----------|-----------------------------------------------------|---------|----------|
 | match[]   | series selector                                     | yes     | 
no       |
-| start     | start, format: RFC3399 or unix_timestamp in seconds | yes     | 
no       |
-| end       | end, format: RFC3399 or unix_timestamp in seconds   | yes     | 
no       |
+| start     | start, format: RFC3339 or unix_timestamp in seconds | yes     | 
no       |
+| end       | end, format: RFC3339 or unix_timestamp in seconds   | yes     | 
no       |
 | limit     | integer, maximum number of returned series          | yes     | 
yes      |
 
 **For metadata metrics**:
@@ -362,7 +362,7 @@ GET|POST /api/v1/labels
 | Parameter | Definition                                                       
               | Support | Optional |
 
|-----------|---------------------------------------------------------------------------------|---------|----------|
 | match[]   | series selector                                                  
               | yes     | yes      |
-| start     | start, format: RFC3399 or unix_timestamp in seconds              
               | **no**  | yes      |
+| start     | start, format: RFC3339 or unix_timestamp in seconds              
               | **no**  | yes      |
 | end       | end timestamp, if end time is not present, use current time as 
default end time | yes     | yes      |
 | limit     | integer, maximum number of returned labels, default 100          
               | yes     | yes      |
 
@@ -396,8 +396,8 @@ GET /api/v1/label/<label_name>/values
 | Parameter | Definition                                                       
                                                   | Support | Optional |
 
|-----------|---------------------------------------------------------------------------------------------------------------------|---------|----------|
 | match[]   | series selector                                                  
                                                   | yes     | yes      |
-| start     | start, format: RFC3399 or unix_timestamp in seconds              
                                                   | **no**  | yes      |
-| end       | end, format: RFC3399 or unix_timestamp in seconds, if end time 
is not present, use current time as default end time | yes     | yes      |
+| start     | start, format: RFC3339 or unix_timestamp in seconds              
                                                   | **no**  | yes      |
+| end       | end, format: RFC3339 or unix_timestamp in seconds, if end time 
is not present, use current time as default end time | yes     | yes      |
 | limit     | integer, maximum number of returned label values, default 100    
                                                   | yes     | yes      |
 
 For example:
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index 07d88d36ad..2ca4981b90 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -26,6 +26,7 @@
 * Fix `BrowserErrorLog` BanyanDB storage query order.
 * `BanyanDB Client`: Property query support `Order By`.
 * MQE: trim the label values condition for the labeled metrics query to 
enhance the readability.
+* PromQL service: fix time parse issue when using RFC3339 time format for 
querying.
 
 #### UI
 * Fix the missing icon in new native trace view.
diff --git 
a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/handler/PromQLApiHandler.java
 
b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/handler/PromQLApiHandler.java
index e97594be03..4fa5a286a5 100644
--- 
a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/handler/PromQLApiHandler.java
+++ 
b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/handler/PromQLApiHandler.java
@@ -29,6 +29,10 @@ import com.linecorp.armeria.server.annotation.Get;
 import com.linecorp.armeria.server.annotation.Param;
 import com.linecorp.armeria.server.annotation.Path;
 import com.linecorp.armeria.server.annotation.Post;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.temporal.ChronoField;
 import java.util.LinkedHashSet;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -102,7 +106,6 @@ import 
org.apache.skywalking.oap.server.library.util.StringUtil;
 import org.apache.skywalking.promql.rt.grammar.PromQLLexer;
 import org.apache.skywalking.promql.rt.grammar.PromQLParser;
 import org.joda.time.DateTime;
-import org.joda.time.format.ISODateTimeFormat;
 
 import static 
org.apache.skywalking.oap.query.promql.rt.PromOpUtils.formatDoubleValue;
 
@@ -115,6 +118,19 @@ public class PromQLApiHandler {
     private AggregationQueryService aggregationQueryService;
     private RecordQueryService recordQueryService;
     private static final ObjectMapper MAPPER = new ObjectMapper();
+    public static final DateTimeFormatter RFC3339_FORMATTER = new 
DateTimeFormatterBuilder()
+        .parseCaseInsensitive()
+        .append(DateTimeFormatter.ISO_LOCAL_DATE)
+        .appendLiteral('T')
+        .append(DateTimeFormatter.ISO_LOCAL_TIME)
+        .optionalStart()
+        .appendOffset("+HH:MM", "Z")  // Time zone offset, support Z
+        .optionalEnd()
+        .optionalStart()
+        .appendOffset("+HHMM", "Z")   // support +0800 format
+        .optionalEnd()
+        .parseDefaulting(ChronoField.OFFSET_SECONDS, 0)  // default UTC
+        .toFormatter();
 
     public PromQLApiHandler(ModuleManager moduleManager, PromQLConfig config) {
         this.metadataQuery = new MetadataQueryV2(moduleManager);
@@ -596,8 +612,9 @@ public class PromQLApiHandler {
             // if Unix timestamp in seconds, such as 1627756800
             time = Double.valueOf(timestamp).longValue() * 1000;
         } catch (NumberFormatException e) {
-            // if RFC3399 format, such as 2024-09-19T20:11:00.781Z
-            time = ISODateTimeFormat.dateTime().parseMillis(timestamp);
+            // if RFC3339 format, such as 2024-09-19T20:11:00.781Z
+            OffsetDateTime odt = OffsetDateTime.parse(timestamp, 
RFC3339_FORMATTER);
+            time = odt.toEpochSecond() * 1000;
         }
         return time;
     }
diff --git 
a/oap-server/server-query-plugin/promql-plugin/src/test/java/org/apache/skywalking/promql/handler/PromQLApiHandlerTest.java
 
b/oap-server/server-query-plugin/promql-plugin/src/test/java/org/apache/skywalking/promql/handler/PromQLApiHandlerTest.java
new file mode 100644
index 0000000000..ce0cfb6f3b
--- /dev/null
+++ 
b/oap-server/server-query-plugin/promql-plugin/src/test/java/org/apache/skywalking/promql/handler/PromQLApiHandlerTest.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.promql.handler;
+
+import java.time.OffsetDateTime;
+import org.apache.skywalking.oap.query.promql.handler.PromQLApiHandler;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class PromQLApiHandlerTest {
+
+    @Test
+    public void testRFC3339Parse() {
+        String[] testCases = {
+            "2026-01-20T03:27:18Z",
+            "2026-01-20T03:27:18.0Z",
+            "2026-01-20T03:27:18.000Z",
+            "2026-01-20T03:27:18.123Z",
+            "2026-01-20T11:27:18+08:00",
+            "2026-01-20T11:27:18.0+08:00",
+            "2026-01-20T11:27:18.123+08:00",
+            "2026-01-20T11:27:18+0800",
+            "2026-01-20T11:27:18.0+0800",
+            "2026-01-20T11:27:18.123+0800",
+            "2026-01-19T19:27:18-08:00",
+            "2026-01-19T19:27:18.0-08:00",
+            "2026-01-19T19:27:18.123-08:00",
+            "2026-01-19T19:27:18-0800",
+            "2026-01-19T19:27:18.0-0800",
+            "2026-01-19T19:27:18.123-0800",
+            "2026-01-20T03:27:18"
+        };
+
+        for (String str : testCases) {
+            OffsetDateTime odt = OffsetDateTime.parse(str, 
PromQLApiHandler.RFC3339_FORMATTER);
+            Assertions.assertEquals(1768879638, odt.toEpochSecond());
+        }
+    }
+}
diff --git a/test/e2e-v2/cases/promql/promql-cases.yaml 
b/test/e2e-v2/cases/promql/promql-cases.yaml
index 480feaac78..6d57543dee 100644
--- a/test/e2e-v2/cases/promql/promql-cases.yaml
+++ b/test/e2e-v2/cases/promql/promql-cases.yaml
@@ -23,7 +23,7 @@ cases:
     expected: expected/service-traffic-limit.yml
   - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/series -d 
'match[]=service_traffic{layer="GENERAL",service=~".*-provider"}&start='$(($(date
 +%s)-1800))'&end='$(date +%s)
     expected: expected/service-traffic-limit.yml
-    #RFC3399
+    #RFC3339
   - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/series -d 
'match[]=service_traffic{layer="GENERAL"}&start='$(($(date 
+%s)-1800))'&end='$(date -u +%Y-%m-%dT%H:%M:%S.111Z)
     expected: expected/service-traffic.yml
   - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/series -d 
'match[]=instance_traffic{layer="GENERAL", 
service="e2e-service-provider"}&limit=1&start='$(($(date 
+%s)-1800))'&end='$(date +%s)
@@ -88,8 +88,8 @@ cases:
     expected: expected/service-metric-vector.yml
   - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/query -d 
'query=service_sla{service="e2e-service-consumer", layer="GENERAL"}[30m]'
     expected: expected/service-metric-matrix.yml
-    #RFC3399
-  - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/query -d 
'query=service_sla{service="e2e-service-consumer", 
layer="GENERAL"}[30m]&time='$(date -u +%Y-%m-%dT%H:%M:%S.111Z)
+    #RFC3339
+  - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/query -d 
'query=service_sla{service="e2e-service-consumer", 
layer="GENERAL"}[30m]&time='$(date -u +%Y-%m-%dT%H:%M:%SZ)
     expected: expected/service-metric-matrix.yml
   - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/query -d 
'query=service_percentile{service="e2e-service-consumer", layer="GENERAL", 
p="50,75,90"}'
     expected: expected/service-metric-labeled-vector.yml
@@ -113,8 +113,8 @@ cases:
   ## query_range
   - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/query_range -d 
'query=service_sla{service="e2e-service-consumer", 
layer="GENERAL"}&start='$(($(date +%s)-1800))'&end='$(date +%s)
     expected: expected/service-metric-matrix.yml
-    #RFC3399
-  - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/query_range -d 
'query=service_sla{service="e2e-service-consumer", 
layer="GENERAL"}&start='$(($(date +%s)-1800))'&end='$(date -u 
+%Y-%m-%dT%H:%M:%S.111Z)
+    #RFC3339
+  - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/query_range -d 
'query=service_sla{service="e2e-service-consumer", 
layer="GENERAL"}&start='$(($(date +%s)-1800))'&end='$(date -u 
+%Y-%m-%dT%H:%M:%S.0Z)
     expected: expected/service-metric-matrix.yml
   - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/query_range -d 
'query=service_percentile{service="e2e-service-consumer", layer="GENERAL", 
p="50,75,90"}&start='$(($(date +%s)-1800))'&end='$(date +%s)
     expected: expected/service-metric-labeled-matrix.yml

Reply via email to