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();