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 df597c099 fix(alert): preserve silence end boundaries across DST gaps 
(#6422)
df597c099 is described below

commit df597c0998b5a084463ed4ff2537c144691149f2
Author: Loyal-Young <[email protected]>
AuthorDate: Thu Oct 15 18:10:09 2026 +0800

    fix(alert): preserve silence end boundaries across DST gaps (#6422)
---
 .../studio/ops/alert/AlertSilenceSchedule.java     | 18 ++++------
 .../studio/ops/alert/AlertSilenceScheduleTest.java | 42 ++++++++++++++++++++++
 2 files changed, 48 insertions(+), 12 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertSilenceSchedule.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertSilenceSchedule.java
index 46390d1c0..3aa931172 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertSilenceSchedule.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertSilenceSchedule.java
@@ -20,7 +20,6 @@ import java.time.Duration;
 import java.time.Instant;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
-import java.time.LocalTime;
 import java.time.ZoneId;
 import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
@@ -62,8 +61,12 @@ final class AlertSilenceSchedule {
             if (!runsOn(recurrence, silence.getRecurrenceDays(), 
candidateDate)) {
                 continue;
             }
-            ZonedDateTime candidateStart = resolve(zone, candidateDate, 
seedStart.toLocalTime(), preferredStartOffset);
-            ZonedDateTime candidateEnd = resolveEnd(zone, candidateStart, 
wallDuration, preferredEndOffset);
+            LocalDateTime scheduledStart = LocalDateTime.of(candidateDate, 
seedStart.toLocalTime());
+            ZonedDateTime candidateStart = 
ZonedDateTime.ofLocal(scheduledStart, zone, preferredStartOffset);
+            // Resolve each wall-clock boundary independently: adjusting a 
start inside a DST gap
+            // must not shift an otherwise valid end by the gap a second time.
+            ZonedDateTime candidateEnd = 
ZonedDateTime.ofLocal(scheduledStart.plus(wallDuration),
+                    zone, preferredEndOffset);
             Instant start = candidateStart.toInstant();
             Instant end = candidateEnd.toInstant();
             Instant scheduleStart = 
silence.getStartsAt().toInstant(ZoneOffset.UTC);
@@ -87,15 +90,6 @@ final class AlertSilenceSchedule {
                 || recurrenceDays != null && 
recurrenceDays.contains(candidateDate.getDayOfWeek().getValue());
     }
 
-    private static ZonedDateTime resolve(ZoneId zone, LocalDate date, 
LocalTime time, ZoneOffset preferredOffset) {
-        return ZonedDateTime.ofLocal(LocalDateTime.of(date, time), zone, 
preferredOffset);
-    }
-
-    private static ZonedDateTime resolveEnd(ZoneId zone, ZonedDateTime start, 
Duration wallDuration,
-            ZoneOffset preferredOffset) {
-        return 
ZonedDateTime.ofLocal(start.toLocalDateTime().plus(wallDuration), zone, 
preferredOffset);
-    }
-
     private static boolean isInside(LocalDateTime now, LocalDateTime start, 
LocalDateTime end) {
         return !now.isBefore(start) && now.isBefore(end);
     }
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertSilenceScheduleTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertSilenceScheduleTest.java
index d3c8817f6..78331e68a 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertSilenceScheduleTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertSilenceScheduleTest.java
@@ -147,6 +147,48 @@ class AlertSilenceScheduleTest {
         assertThat(AlertSilenceSchedule.activeUntil(silence, 
at("2027-11-07T05:45:00"))).isNull();
     }
 
+    @Test
+    void gapAdjustedStartDoesNotMoveTheConfiguredEndTest() {
+        AlertSilenceVO silence = recurring(AlertSilenceRecurrence.DAILY, 
"America/New_York", Set.of(),
+                "2026-03-07T07:30:00", "2026-03-07T09:00:00", 
"2026-03-12T00:00:00");
+
+        // 02:30 moves to 03:30 in the gap, but the configured 04:00 end still 
exists.
+        assertThat(AlertSilenceSchedule.activeUntil(silence, 
at("2026-03-08T07:45:00")))
+                .isEqualTo(at("2026-03-08T08:00:00"));
+        assertThat(AlertSilenceSchedule.activeUntil(silence, 
at("2026-03-08T08:15:00"))).isNull();
+        assertThat(AlertSilenceSchedule.activeUntil(silence, 
at("2026-03-09T06:45:00")))
+                .isEqualTo(at("2026-03-09T08:00:00"));
+    }
+
+    @Test
+    void weeklyWindowDoesNotExtendWhenItsStartFallsInAGapTest() {
+        AlertSilenceVO silence = recurring(AlertSilenceRecurrence.WEEKLY, 
"America/New_York", Set.of(7),
+                "2026-03-01T07:30:00", "2026-03-01T09:00:00", 
"2026-03-12T00:00:00");
+
+        assertThat(AlertSilenceSchedule.activeUntil(silence, 
at("2026-03-08T07:45:00")))
+                .isEqualTo(at("2026-03-08T08:00:00"));
+        assertThat(AlertSilenceSchedule.activeUntil(silence, 
at("2026-03-08T08:00:00"))).isNull();
+    }
+
+    @Test
+    void halfHourGapDoesNotMoveTheConfiguredEndTest() {
+        AlertSilenceVO silence = recurring(AlertSilenceRecurrence.DAILY, 
"Australia/Lord_Howe", Set.of(),
+                "2026-10-02T15:45:00", "2026-10-02T16:30:00", 
"2026-10-06T00:00:00");
+
+        // The October 4 gap moves 02:15 to 02:45; the end remains 03:00.
+        assertThat(AlertSilenceSchedule.activeUntil(silence, 
at("2026-10-03T15:50:00")))
+                .isEqualTo(at("2026-10-03T16:00:00"));
+        assertThat(AlertSilenceSchedule.activeUntil(silence, 
at("2026-10-03T16:15:00"))).isNull();
+    }
+
+    @Test
+    void gapThatMovesStartPastEndDoesNotCreateAnExtraWindowTest() {
+        AlertSilenceVO silence = recurring(AlertSilenceRecurrence.DAILY, 
"America/New_York", Set.of(),
+                "2026-03-07T07:30:00", "2026-03-07T08:15:00", 
"2026-03-12T00:00:00");
+
+        assertThat(AlertSilenceSchedule.activeUntil(silence, 
at("2026-03-08T07:45:00"))).isNull();
+    }
+
     private static AlertSilenceVO once(String start, String end) {
         return AlertSilenceVO.builder().startsAt(at(start)).endsAt(at(end))
                 
.recurrence(AlertSilenceRecurrence.ONCE).recurrenceDays(Set.of()).build();

Reply via email to