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

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


The following commit(s) were added to refs/heads/master by this push:
     new 8962253dd2 [manager] bugfix: fix incorrect status page date 
calculation (#3962)
8962253dd2 is described below

commit 8962253dd24a892a51d8482bc12dc2743ce7907d
Author: N.Bhanu Prasad <[email protected]>
AuthorDate: Sat Jan 17 17:38:12 2026 +0530

    [manager] bugfix: fix incorrect status page date calculation (#3962)
    
    Signed-off-by: Tomsun28 <[email protected]>
    Co-authored-by: Duansg <[email protected]>
    Co-authored-by: Tomsun28 <[email protected]>
    Co-authored-by: Copilot <[email protected]>
---
 .../service/impl/StatusPageServiceImpl.java        | 176 ++++++++++++++-------
 .../impl/StatusPageServiceImplTimeTest.java        | 142 +++++++++++++++++
 2 files changed, 260 insertions(+), 58 deletions(-)

diff --git 
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/StatusPageServiceImpl.java
 
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/StatusPageServiceImpl.java
index 0fa0ef9e69..22946902d8 100644
--- 
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/StatusPageServiceImpl.java
+++ 
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/StatusPageServiceImpl.java
@@ -18,9 +18,8 @@
 package org.apache.hertzbeat.manager.service.impl;
 
 import java.time.Instant;
-import java.time.LocalDateTime;
 import java.time.ZoneId;
-import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
@@ -131,45 +130,70 @@ public class StatusPageServiceImpl implements 
StatusPageService {
             componentStatus.setInfo(component);
             List<StatusPageHistory> histories = new LinkedList<>();
             // query today status
-            LocalDateTime nowTime = LocalDateTime.now();
-            LocalDateTime todayStartTime = 
nowTime.withHour(0).withMinute(0).withSecond(0).withNano(0);
-            ZoneOffset zoneOffset = 
ZoneId.systemDefault().getRules().getOffset(Instant.now());
-            long nowTimestamp = nowTime.toInstant(zoneOffset).toEpochMilli();
-            long todayStartTimestamp = 
todayStartTime.toInstant(zoneOffset).toEpochMilli();
+            ZoneId zoneId = ZoneId.systemDefault();
+
+            Instant now = Instant.now();
+            long nowTimestamp = now.toEpochMilli();
+
+            long todayStartTimestamp = now
+                .atZone(zoneId)
+                .toLocalDate()
+                .atStartOfDay(zoneId)
+                .toInstant()
+                .toEpochMilli();
+
             List<StatusPageHistory> todayStatusPageHistoryList = 
statusPageHistoryDao
                     
.findStatusPageHistoriesByComponentIdAndTimestampBetween(component.getId(), 
todayStartTimestamp, nowTimestamp);
             StatusPageHistory todayStatus = 
combineOneDayStatusPageHistory(todayStatusPageHistoryList, component, 
nowTimestamp);
             histories.add(todayStatus);
             // query 30d component status history
-            LocalDateTime preTime = 
todayStartTime.minusDays(HISTORY_SPAN_DAYS);
-            long preTimestamp = preTime.toInstant(zoneOffset).toEpochMilli();
+            long preTimestamp = now
+                .atZone(zoneId)
+                .toLocalDate()
+                .minusDays(HISTORY_SPAN_DAYS)
+                .atStartOfDay(zoneId)
+                .toInstant()
+                .toEpochMilli();
+
             List<StatusPageHistory> history = statusPageHistoryDao
                     
.findStatusPageHistoriesByComponentIdAndTimestampBetween(component.getId(), 
preTimestamp, todayStartTimestamp);
             LinkedList<StatusPageHistory> historyList = new 
LinkedList<>(history);
-            historyList.sort((o1, o2) -> (int) (o1.getTimestamp() - 
o2.getTimestamp()));
-            LocalDateTime endTime = todayStartTime.minusSeconds(1);
-            LocalDateTime startTime = 
endTime.withHour(0).withMinute(0).withSecond(0).withNano(0);
-            for (int index = 0; index < HISTORY_SPAN_DAYS; index++) {
-                long startTimestamp = 
startTime.toInstant(zoneOffset).toEpochMilli();
-                long endTimestamp = 
endTime.toInstant(zoneOffset).toEpochMilli();
-                List<StatusPageHistory> thisDayHistory = 
historyList.stream().filter(item ->
-                                item.getTimestamp() >= startTimestamp && 
item.getTimestamp() <= endTimestamp)
-                        .collect(Collectors.toList());
+            historyList.sort((o1, o2) -> Long.compare(o1.getTimestamp(), 
o2.getTimestamp()));
+            ZonedDateTime end = Instant.ofEpochMilli(todayStartTimestamp)
+                .atZone(zoneId)
+                .minusSeconds(1);   // yesterday 23:59:59 local time
+
+            for (int i = 0; i < HISTORY_SPAN_DAYS; i++) {
+                long endTimestamp = end.toInstant().toEpochMilli();
+
+                long startTimestamp = end.toLocalDate()
+                    .atStartOfDay(zoneId)
+                    .toInstant()
+                    .toEpochMilli();
+
+                List<StatusPageHistory> thisDayHistory = historyList.stream()
+                    .filter(h -> h.getTimestamp() >= startTimestamp && 
h.getTimestamp() <= endTimestamp)
+                    .collect(Collectors.toList());
+
                 if (thisDayHistory.isEmpty()) {
-                    StatusPageHistory statusPageHistory = 
StatusPageHistory.builder().timestamp(endTimestamp)
-                            
.componentId(component.getId()).state(CommonConstants.STATUS_PAGE_COMPONENT_STATE_UNKNOWN).build();
-                    histories.add(statusPageHistory);
+                    histories.add(StatusPageHistory.builder()
+                        .timestamp(endTimestamp)
+                        .componentId(component.getId())
+                        
.state(CommonConstants.STATUS_PAGE_COMPONENT_STATE_UNKNOWN)
+                        .build());
                 } else if (thisDayHistory.size() == 1) {
                     histories.add(thisDayHistory.get(0));
                 } else {
-                    StatusPageHistory statusPageHistory = 
combineOneDayStatusPageHistory(thisDayHistory, component, endTimestamp);
-                    histories.add(statusPageHistory);
+                    StatusPageHistory merged =
+                        combineOneDayStatusPageHistory(thisDayHistory, 
component, endTimestamp);
+                    histories.add(merged);
                     statusPageHistoryDao.deleteAll(thisDayHistory);
-                    statusPageHistoryDao.save(statusPageHistory);
+                    statusPageHistoryDao.save(merged);
                 }
-                startTime = startTime.minusDays(1);
-                endTime = endTime.minusDays(1);
+
+                end = end.minusDays(1);
             }
+
             componentStatus.setHistory(histories);
             componentStatusList.add(componentStatus);
         }
@@ -215,50 +239,86 @@ public class StatusPageServiceImpl implements 
StatusPageService {
 
     @Override
     public ComponentStatus queryComponentStatus(long id) {
-        StatusPageComponent component = 
statusPageComponentDao.findById(id).orElseThrow(() -> new 
IllegalArgumentException("component not found"));
+        StatusPageComponent component = statusPageComponentDao.findById(id)
+            .orElseThrow(() -> new IllegalArgumentException("component not 
found"));
+
         ComponentStatus componentStatus = new ComponentStatus();
         componentStatus.setInfo(component);
         List<StatusPageHistory> histories = new LinkedList<>();
-        // query today status
-        LocalDateTime nowTime = LocalDateTime.now();
-        LocalDateTime todayStartTime = 
nowTime.withHour(0).withMinute(0).withSecond(0).withNano(0);
-        ZoneOffset zoneOffset = 
ZoneId.systemDefault().getRules().getOffset(Instant.now());
-        long nowTimestamp = nowTime.toInstant(zoneOffset).toEpochMilli();
-        long todayStartTimestamp = 
todayStartTime.toInstant(zoneOffset).toEpochMilli();
-        List<StatusPageHistory> todayStatusPageHistoryList = 
statusPageHistoryDao
-                
.findStatusPageHistoriesByComponentIdAndTimestampBetween(component.getId(), 
todayStartTimestamp, nowTimestamp);
-        StatusPageHistory todayStatus = 
combineOneDayStatusPageHistory(todayStatusPageHistoryList, component, 
nowTimestamp);
+
+        ZoneId zoneId = ZoneId.systemDefault();
+
+        Instant now = Instant.now();
+        long nowTimestamp = now.toEpochMilli();
+
+        long todayStartTimestamp = now
+            .atZone(zoneId)
+            .toLocalDate()
+            .atStartOfDay(zoneId)
+            .toInstant()
+            .toEpochMilli();
+
+        // Today
+        List<StatusPageHistory> todayStatusPageHistoryList =
+            
statusPageHistoryDao.findStatusPageHistoriesByComponentIdAndTimestampBetween(
+                component.getId(), todayStartTimestamp, nowTimestamp);
+
+        StatusPageHistory todayStatus =
+            combineOneDayStatusPageHistory(todayStatusPageHistoryList, 
component, nowTimestamp);
+
         histories.add(todayStatus);
-        // query 30d component status history
-        LocalDateTime preTime = todayStartTime.minusDays(HISTORY_SPAN_DAYS);
-        long preTimestamp = preTime.toInstant(zoneOffset).toEpochMilli();
-        List<StatusPageHistory> history = statusPageHistoryDao
-                
.findStatusPageHistoriesByComponentIdAndTimestampBetween(component.getId(), 
preTimestamp, todayStartTimestamp);
+
+        // Previous HISTORY_SPAN_DAYS days (excluding today)
+        long preTimestamp = now
+            .atZone(zoneId)
+            .toLocalDate()
+            .minusDays(HISTORY_SPAN_DAYS)
+            .atStartOfDay(zoneId)
+            .toInstant()
+            .toEpochMilli();
+
+        List<StatusPageHistory> history =
+            
statusPageHistoryDao.findStatusPageHistoriesByComponentIdAndTimestampBetween(
+                component.getId(), preTimestamp, todayStartTimestamp);
+
         LinkedList<StatusPageHistory> historyList = new LinkedList<>(history);
-        historyList.sort((o1, o2) -> (int) (o1.getTimestamp() - 
o2.getTimestamp()));
-        LocalDateTime endTime = todayStartTime.minusSeconds(1);
-        LocalDateTime startTime = 
endTime.withHour(0).withMinute(0).withSecond(0).withNano(0);
-        for (int index = 0; index < HISTORY_SPAN_DAYS; index++) {
-            long startTimestamp = 
startTime.toInstant(zoneOffset).toEpochMilli();
-            long endTimestamp = endTime.toInstant(zoneOffset).toEpochMilli();
-            List<StatusPageHistory> thisDayHistory = 
historyList.stream().filter(item ->
-                            item.getTimestamp() >= startTimestamp && 
item.getTimestamp() <= endTimestamp)
-                    .collect(Collectors.toList());
+        historyList.sort((o1, o2) -> Long.compare(o1.getTimestamp(), 
o2.getTimestamp()));
+
+        ZonedDateTime end = Instant.ofEpochMilli(todayStartTimestamp)
+            .atZone(zoneId)
+            .minusSeconds(1);   // yesterday 23:59:59 local time
+
+        for (int i = 0; i < HISTORY_SPAN_DAYS; i++) {
+            long endTimestamp = end.toInstant().toEpochMilli();
+
+            long startTimestamp = end.toLocalDate()
+                .atStartOfDay(zoneId)
+                .toInstant()
+                .toEpochMilli();
+
+            List<StatusPageHistory> thisDayHistory = historyList.stream()
+                .filter(h -> h.getTimestamp() >= startTimestamp && 
h.getTimestamp() <= endTimestamp)
+                .collect(Collectors.toList());
+
             if (thisDayHistory.isEmpty()) {
-                StatusPageHistory statusPageHistory = 
StatusPageHistory.builder().timestamp(endTimestamp)
-                        
.componentId(component.getId()).state(CommonConstants.STATUS_PAGE_COMPONENT_STATE_UNKNOWN).build();
-                histories.add(statusPageHistory);
+                histories.add(StatusPageHistory.builder()
+                    .timestamp(endTimestamp)
+                    .componentId(component.getId())
+                    .state(CommonConstants.STATUS_PAGE_COMPONENT_STATE_UNKNOWN)
+                    .build());
             } else if (thisDayHistory.size() == 1) {
                 histories.add(thisDayHistory.get(0));
             } else {
-                StatusPageHistory statusPageHistory = 
combineOneDayStatusPageHistory(thisDayHistory, component, endTimestamp);
-                histories.add(statusPageHistory);
+                StatusPageHistory merged =
+                    combineOneDayStatusPageHistory(thisDayHistory, component, 
endTimestamp);
+                histories.add(merged);
                 statusPageHistoryDao.deleteAll(thisDayHistory);
-                statusPageHistoryDao.save(statusPageHistory);
+                statusPageHistoryDao.save(merged);
             }
-            startTime = startTime.minusDays(1);
-            endTime = endTime.minusDays(1);
+
+            end = end.minusDays(1);
         }
+
         componentStatus.setHistory(histories);
         return componentStatus;
     }
diff --git 
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/impl/StatusPageServiceImplTimeTest.java
 
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/impl/StatusPageServiceImplTimeTest.java
new file mode 100644
index 0000000000..8ad87c638e
--- /dev/null
+++ 
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/impl/StatusPageServiceImplTimeTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.hertzbeat.manager.service.impl;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.when;
+
+import java.lang.reflect.Field;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.List;
+
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.manager.StatusPageComponent;
+import org.apache.hertzbeat.common.entity.manager.StatusPageHistory;
+import org.apache.hertzbeat.manager.component.status.CalculateStatus;
+import org.apache.hertzbeat.manager.dao.StatusPageComponentDao;
+import org.apache.hertzbeat.manager.dao.StatusPageHistoryDao;
+import org.apache.hertzbeat.manager.dao.StatusPageIncidentComponentBindDao;
+import org.apache.hertzbeat.manager.dao.StatusPageIncidentDao;
+import org.apache.hertzbeat.manager.dao.StatusPageOrgDao;
+import org.apache.hertzbeat.manager.pojo.dto.ComponentStatus;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+class StatusPageServiceImplTimeTest {
+
+    @Mock private StatusPageHistoryDao historyDao;
+    @Mock private StatusPageComponentDao componentDao;
+    @Mock private StatusPageOrgDao orgDao;
+    @Mock private StatusPageIncidentDao incidentDao;
+    @Mock private StatusPageIncidentComponentBindDao bindDao;
+    @Mock private CalculateStatus calculateStatus;
+
+    private StatusPageServiceImpl service;
+    private StatusPageComponent component;
+
+    @BeforeEach
+    void setup() throws Exception {
+        MockitoAnnotations.openMocks(this);
+
+        // Create service using its real constructor
+        service = new StatusPageServiceImpl(bindDao);
+
+        // Inject all @Autowired fields manually
+        inject("statusPageHistoryDao", historyDao);
+        inject("statusPageComponentDao", componentDao);
+        inject("statusPageOrgDao", orgDao);
+        inject("statusPageIncidentDao", incidentDao);
+        inject("calculateStatus", calculateStatus);
+
+        component = new StatusPageComponent();
+        component.setId(1L);
+        component.setState((byte) 
CommonConstants.STATUS_PAGE_COMPONENT_STATE_NORMAL);
+
+        when(componentDao.findAll()).thenReturn(List.of(component));
+        when(calculateStatus.getCalculateStatusIntervals()).thenReturn(300);
+        when(bindDao.countByComponentId(anyLong())).thenReturn(0L);
+    }
+
+    private void inject(String field, Object value) throws Exception {
+        Field f = StatusPageServiceImpl.class.getDeclaredField(field);
+        f.setAccessible(true);
+        f.set(service, value);
+    }
+
+    @Test
+    void testMidnightBoundary() {
+        Instant midnight = LocalDate.of(2026, 1, 14)
+            .atStartOfDay(ZoneId.of("UTC"))
+            .toInstant();
+
+        StatusPageHistory before = history(midnight.minusSeconds(1),
+            CommonConstants.STATUS_PAGE_COMPONENT_STATE_ABNORMAL);
+        StatusPageHistory after = history(midnight.plusSeconds(1),
+            CommonConstants.STATUS_PAGE_COMPONENT_STATE_NORMAL);
+
+        
when(historyDao.findStatusPageHistoriesByComponentIdAndTimestampBetween(anyLong(),
 anyLong(), anyLong()))
+            .thenReturn(List.of(before, after));
+
+        List<ComponentStatus> result = service.queryComponentsStatus();
+        assertEquals(30, result.get(0).getHistory().size());
+    }
+
+    @Test
+    void testDstDay() {
+        ZoneId zone = ZoneId.of("America/New_York");
+        Instant dstDay = ZonedDateTime.of(2026, 3, 8, 12, 0, 0, 0, 
zone).toInstant();
+
+        StatusPageHistory history = history(dstDay,
+            CommonConstants.STATUS_PAGE_COMPONENT_STATE_ABNORMAL);
+
+        
when(historyDao.findStatusPageHistoriesByComponentIdAndTimestampBetween(anyLong(),
 anyLong(), anyLong()))
+            .thenReturn(List.of(history));
+
+        List<ComponentStatus> result = service.queryComponentsStatus();
+        assertEquals(30, result.get(0).getHistory().size());
+    }
+
+    @Test
+    void testHistoryWindowSize() {
+        Instant tenDaysAgo = Instant.now().minus(Duration.ofDays(10));
+
+        StatusPageHistory history = history(tenDaysAgo,
+            CommonConstants.STATUS_PAGE_COMPONENT_STATE_NORMAL);
+
+        
when(historyDao.findStatusPageHistoriesByComponentIdAndTimestampBetween(anyLong(),
 anyLong(), anyLong()))
+            .thenReturn(List.of(history));
+
+        List<ComponentStatus> result = service.queryComponentsStatus();
+        assertEquals(30, result.get(0).getHistory().size());
+    }
+
+    private StatusPageHistory history(Instant time, int state) {
+        return StatusPageHistory.builder()
+            .timestamp(time.toEpochMilli())
+            .state((byte) state)
+            .componentId(1L)
+            .build();
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to