Repository: apache/skywalking (language: python)

Issue:
[Feature] PROMQL query support for timestamp in RFC3339 format
### Search before asking

- [X] I had searched in the [issues](https://github.com/apache/skywalking/issues?q=is%3Aissue) and found no similar feature requirement.


### Description

PROMQL `/api/v1/query` have a parameter for `time` as per [docs](https://skywalking.apache.org/docs/main/next/en/api/promql-service/#instant-queries) but expects specific unix timestamp format.  Looking to support format outlined as provided in [RFC3339](https://www.rfc-editor.org/rfc/rfc3339) to support integrations that append a different format such as [KEDA ScaledObjects](https://github.com/kedacore/keda/blob/4444aca424ce12a0e711da9ac106c2c8e829d0c9/pkg/scalers/prometheus_scaler.go#L199).

### Use case

Currently `time` is required to be in specific format being unix timestamp
`query=service_apdex{service="*|details|bookinfo|aks-swdemo01-centralus-1|-", layer="MESH"}[3m]&time=1726668431`

Ask is to support timestamp in RFC3339 format:
`query=service_apdex{service="*|details|bookinfo|aks-swdemo01-centralus-1|-", layer="MESH"}&time=2024-09-18T13:48:18Z`

### Related issues

_No response_

### Are you willing to submit a pull request to implement this on your own?

- [ ] Yes I am willing to submit a pull request on my own!

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](https://www.apache.org/foundation/policies/conduct)



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml
index 4ab92bb148d9..a547a09ac67d 100644
--- a/.github/workflows/codeql.yaml
+++ b/.github/workflows/codeql.yaml
@@ -57,7 +57,7 @@ jobs:
         with:
           languages: ${{ matrix.language }}
 
-      - run: ./mvnw -q -Dmaven.test.skip=true clean install || ./mvnw -q -Dmaven.test.skip=true clean install
+      - run: ./mvnw -q -Dmaven.test.skip=true clean install -Dgpg.skip || ./mvnw -q -Dmaven.test.skip=true clean install -Dgpg.skip
 
       - name: Perform CodeQL Analysis
         uses: github/codeql-action/analyze@v3
diff --git a/docs/en/api/promql-service.md b/docs/en/api/promql-service.md
index 88861ae03e7a..9b7b3f4874f0 100644
--- a/docs/en/api/promql-service.md
+++ b/docs/en/api/promql-service.md
@@ -145,11 +145,11 @@ sum(http_requests_total{service='$service', layer='$layer'})
 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.** | yes     | yes        |
-| timeout   | evaluation timeout                                                                                                                  | **no**  | **ignore** |
+| 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        |
+| timeout   | evaluation timeout                                                                                                                                                                    | **no**  | **ignore** |
 
 For example:
 ```text
@@ -191,8 +191,8 @@ GET|POST /api/v1/query_range
 | Parameter | Definition                                                                           | Support | Optional   |
 |-----------|--------------------------------------------------------------------------------------|---------|------------|
 | query     | prometheus expression                                                                | yes     | no         |
-| start     | start timestamp, **seconds**                                                         | yes     | no         |
-| end       | end timestamp, **seconds**                                                           | 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         |
 | step      | **SkyWalking will automatically fit Step(DAY, HOUR, MINUTE) through start and end.** | **no**  | **ignore** |
 | timeout   | evaluation timeout                                                                   | **no**  | **ignore** |
 
@@ -256,11 +256,11 @@ Result:
 GET|POST /api/v1/series
 ```
 
-| Parameter | Definition                   | Support | Optional |
-|-----------|------------------------------|---------|----------|
-| match[]   | series selector              | yes     | no       |
-| start     | start timestamp, **seconds** | yes     | no       |
-| end       | end timestamp, **seconds**   | yes     | no       |
+| 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       |
 
 For example:
 ```text
@@ -321,7 +321,7 @@ GET|POST /api/v1/labels
 | Parameter | Definition                                                                      | Support | Optional |
 |-----------|---------------------------------------------------------------------------------|---------|----------|
 | match[]   | series selector                                                                 | yes     | yes      |
-| start     | start timestamp                                                                 | **no**  | yes      |
+| start     | start, format: RFC3399 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      |
 
 For example:
@@ -351,11 +351,11 @@ Result:
 GET /api/v1/label/<label_name>/values
 ```
 
-| Parameter | Definition                                                                      | Support | Optional |
-|-----------|---------------------------------------------------------------------------------|---------|----------|
-| match[]   | series selector                                                                 | yes     | yes      |
-| start     | start timestamp                                                                 | **no**  | yes      |
-| end       | end timestamp, if end time is not present, use current time as default end time | yes     | yes      |
+| 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      |
 
 For example:
 ```text
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index 1c89cbd4700e..5ecf53a0097e 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -66,12 +66,13 @@
 * Fix the previous analysis result missing in the ALS `k8s-mesh` analyzer.
 * Fix `findEndpoint` query require `keyword` when using BanyanDB.
 * Support to analysis the ztunnel mapped IP address in eBPF Access Log Receiver.
-* Adapt BanyanDB Java Client 0.7.0-rc3.
+* Adapt BanyanDB Java Client 0.7.0.
 * Add SkyWalking Java Agent self observability dashboard.
 * Add Component ID(5022) for the GoFrame framework.
 * Bump up protobuf java dependencies to 3.25.5.
 * BanyanDB: support using native term searching for `keyword` in query `findEndpoint` and `getAlarm`.
 * BanyanDB: support TLS connection and configuration.
+* PromQL service: query API support RFC3399 time format.
 
 #### UI
 
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 8371b0df7d99..85652125e484 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
@@ -100,6 +100,7 @@
 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;
 
@@ -582,7 +583,15 @@ private void buildScalarMatrixRsp(Duration duration, ParseResult parseResult, Ex
     }
 
     private static long formatTimestamp2Millis(String timestamp) {
-        return Double.valueOf(timestamp).longValue() * 1000;
+        long time;
+        try {
+            // 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);
+        }
+        return time;
     }
 
     private List<String> buildLabelNames(Scope scope, ValueColumnMetadata.ValueColumn metaData) {
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromQLExprQueryVisitor.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromQLExprQueryVisitor.java
index f46bf2ddd0ee..e96dd72b0b78 100644
--- a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromQLExprQueryVisitor.java
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromQLExprQueryVisitor.java
@@ -312,7 +312,7 @@ public ParseResult visitMetricRange(PromQLParser.MetricRangeContext ctx) {
         }
 
         String timeRange = ctx.DURATION().getText().toUpperCase();
-        long endTS = System.currentTimeMillis();
+        long endTS = this.duration.getEndTimestamp();
         long startTS = endTS - formatDuration(timeRange).getMillis();
         duration = DurationUtils.timestamp2Duration(startTS, endTS);
         ParseResult result = visit(ctx.metricInstant());


Test patch:
diff --git a/test/e2e-v2/cases/promql/promql-cases.yaml b/test/e2e-v2/cases/promql/promql-cases.yaml
index c32dd53ff341..3548d54e5089 100644
--- a/test/e2e-v2/cases/promql/promql-cases.yaml
+++ b/test/e2e-v2/cases/promql/promql-cases.yaml
@@ -17,6 +17,9 @@ cases:
   # traffics query
   - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/series -d 'match[]=service_traffic{layer="GENERAL"}&start='$(($(date +%s)-1800))'&end='$(date +%s)
     expected: expected/service-traffic.yml
+    #RFC3399
+  - 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"}&start='$(($(date +%s)-1800))'&end='$(date +%s)
     expected: expected/instance-traffic.yml
   - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/series -d 'match[]=endpoint_traffic{layer="GENERAL", service="e2e-service-provider", keyword="POST:/users"}&start='$(($(date +%s)-1800))'&end='$(date +%s)
@@ -29,11 +32,15 @@ cases:
   - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/series -d 'match[]=endpoint_cpm&start='$(($(date +%s)-1800))'&end='$(date +%s)
     expected: expected/endpoint-metric-series.yml
   # metrics names query
-  - query: curl -X  GET http://${oap_host}:${oap_9090}/api/v1/label/__name__/values
+  - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/label/__name__/values
+    expected: expected/metrics-names.yml
+  - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/label/__name__/values -d 'start='$(($(date +%s)-1800))'&end='$(date -u +%Y-%m-%dT%H:%M:%S.111Z)
     expected: expected/metrics-names.yml
   # labels query
   - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/labels -d 'match[]=service_cpm'
     expected: expected/service-metric-label.yml
+  - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/labels -d 'match[]=service_cpm&start='$(($(date +%s)-1800))'&end='$(date -u +%Y-%m-%dT%H:%M:%S.111Z)
+    expected: expected/service-metric-label.yml
   - query: |
       sleep 30;
       curl -X GET http://${oap_host}:${oap_9090}/api/v1/labels -d 'match[]=service_percentile'
@@ -54,6 +61,9 @@ 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)
+    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
   - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/query -d 'query=service_percentile_old{service="e2e-service-consumer", layer="GENERAL", labels="0,1,2", relabels="P50,P75,P90"}'
@@ -72,6 +82,9 @@ 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)
+    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
   - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/query_range -d 'query=sum by (p) (service_percentile{service="e2e-service-consumer", layer="GENERAL", p="50,75,90"})&start='$(($(date +%s)-1800))'&end='$(date +%s)