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

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 3aaff4b01 fix(tencent): preserve message timestamp offsets (#2432)
3aaff4b01 is described below

commit 3aaff4b01456c6cdab9f1d6341cd0d632782c221
Author: btlqql <[email protected]>
AuthorDate: Fri Aug 21 18:02:02 2026 +0800

    fix(tencent): preserve message timestamp offsets (#2432)
---
 .../provider/tencent/TencentInstanceProvider.java  | 20 ++++++-
 .../tencent/TencentInstanceProviderTest.java       | 67 ++++++++++++++++++++++
 2 files changed, 85 insertions(+), 2 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProvider.java
 
b/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProvider.java
index b75694a72..79560ea4e 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProvider.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProvider.java
@@ -75,6 +75,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 
 import java.time.Instant;
 import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
@@ -110,8 +111,13 @@ public class TencentInstanceProvider implements 
InstanceProvider {
     private static final DateTimeFormatter[] TENCENT_TIME_FORMATTERS = {
         TENCENT_TIME_FORMATTER,
         DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
-        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"),
-        DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
+        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")
+    };
+    private static final DateTimeFormatter[] TENCENT_OFFSET_TIME_FORMATTERS = {
+        DateTimeFormatter.ISO_OFFSET_DATE_TIME,
+        DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"),
+        DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"),
+        DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss,SSSZ")
     };
     private static final long ONE_HOUR_MILLIS = 60L * 60L * 1000L;
     private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@@ -800,8 +806,18 @@ public class TencentInstanceProvider implements 
InstanceProvider {
             return 0L;
         }
         String trimmed = value.trim();
+        for (DateTimeFormatter formatter : TENCENT_OFFSET_TIME_FORMATTERS) {
+            try {
+                return OffsetDateTime.parse(trimmed, 
formatter).toInstant().toEpochMilli();
+            } catch (Exception ignored) {
+                // try the next offset-aware format before falling back to 
legacy local times
+            }
+        }
         for (DateTimeFormatter formatter : TENCENT_TIME_FORMATTERS) {
             try {
+                // Tencent's legacy formats carry no offset. Preserve the 
existing policy of
+                // interpreting those values in the Studio JVM timezone, but 
never apply that
+                // timezone to values that explicitly identify their own 
offset.
                 return LocalDateTime.parse(trimmed, formatter)
                         
.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
             } catch (Exception ignored) {
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProviderTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProviderTest.java
index 640ea6895..b93489163 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProviderTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/provider/tencent/TencentInstanceProviderTest.java
@@ -67,6 +67,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
 
 import java.util.List;
 import java.util.Optional;
+import java.util.TimeZone;
 import java.util.stream.IntStream;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -650,6 +651,72 @@ class TencentInstanceProviderTest {
         assertThat(captor.getValue().getMsgId()).isEqualTo("MSG-1");
     }
 
+    @Test
+    void getMessageTraceShouldPreserveOffsetsForAllTimestampedStagesTest() 
throws Exception {
+        MessageTraceItem produce = new MessageTraceItem();
+        produce.setStage("produce");
+        
produce.setData("{\"Status\":0,\"ProduceTime\":\"2026-08-20T10:00:00.000+0800\"}");
+        MessageTraceItem persist = new MessageTraceItem();
+        persist.setStage("persist");
+        
persist.setData("{\"Status\":0,\"PersistTime\":\"2026-08-20T10:00:01.000+0800\"}");
+        MessageTraceItem consume = new MessageTraceItem();
+        consume.setStage("consume");
+        consume.setData("{\"RocketMqConsumeLogs\":[{\"Status\":2,"
+                + "\"PushTime\":\"2026-08-20T10:00:02.000+0800\","
+                + "\"ConsumerGroup\":\"GID_test\"}]}");
+        DescribeMessageTraceResponse response = new 
DescribeMessageTraceResponse();
+        response.setData(new MessageTraceItem[]{produce, persist, consume});
+        when(client.DescribeMessageTrace(any())).thenReturn(response);
+
+        TimeZone original = TimeZone.getDefault();
+        try {
+            TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+            TraceRecordVO trace = provider.getMessageTrace(STUDIO_INSTANCE_ID, 
"MSG-OFFSET", "orders");
+
+            
assertThat(trace.getNodes()).extracting(TraceNodeVO::getTimestamp).containsExactly(
+                    
java.time.Instant.parse("2026-08-20T02:00:00Z").toEpochMilli(),
+                    
java.time.Instant.parse("2026-08-20T02:00:01Z").toEpochMilli(),
+                    
java.time.Instant.parse("2026-08-20T02:00:02Z").toEpochMilli());
+            assertThat(trace.getConsumerStatus()).hasSize(1);
+            assertThat(trace.getConsumerStatus().get(0).getConsumeTime())
+                    
.isEqualTo(java.time.Instant.parse("2026-08-20T02:00:02Z").toEpochMilli());
+        } finally {
+            TimeZone.setDefault(original);
+        }
+    }
+
+    @Test
+    void 
queryMessagesShouldPreserveExplicitTimestampOffsetsAcrossJvmTimezonesTest() 
throws Exception {
+        DescribeMessageResponse detail = new DescribeMessageResponse();
+        detail.setMessageId("MSG-OFFSET");
+        detail.setShowTopicName("orders");
+        when(client.DescribeMessage(any())).thenReturn(detail);
+
+        TimeZone original = TimeZone.getDefault();
+        long expected = 
java.time.Instant.parse("2026-08-20T02:00:00Z").toEpochMilli();
+        List<String> timestamps = List.of(
+                "2026-08-20T10:00:00.000+0800",
+                "2026-08-20T10:00:00,000+0800",
+                "2026-08-20T10:00:00.000+08:00",
+                "2026-08-20T02:00:00Z"
+        );
+        try {
+            for (String zone : List.of("UTC", "Asia/Shanghai")) {
+                TimeZone.setDefault(TimeZone.getTimeZone(zone));
+                for (String timestamp : timestamps) {
+                    detail.setProduceTime(timestamp);
+                    MessageRecordVO record = provider.queryMessages(
+                            STUDIO_INSTANCE_ID, "orders", "MSG-OFFSET", null, 
null, null, null).get(0);
+                    assertThat(record.getStoreTime())
+                            .as("timestamp %s in JVM timezone %s", timestamp, 
zone)
+                            .isEqualTo(expected);
+                }
+            }
+        } finally {
+            TimeZone.setDefault(original);
+        }
+    }
+
     @Test
     void queryMessagesShouldSkipNullAndStructuredPropertyValues() throws 
Exception {
         DescribeMessageResponse detail = new DescribeMessageResponse();

Reply via email to