From 23a3a7099ff86889183009791d95823291a4a165 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:28:31 +0000 Subject: [PATCH 1/3] Initial plan From 4a3493a5ceb28721d8dbe5e70c1b182298356d4e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:35:02 +0000 Subject: [PATCH 2/3] Fix count_last_minutes midnight wrapping and record_bloop timezone for bloops_per_day Co-authored-by: DASPRiD <233300+DASPRiD@users.noreply.github.com> --- src/statistics.rs | 55 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/src/statistics.rs b/src/statistics.rs index 095f18c..378a315 100644 --- a/src/statistics.rs +++ b/src/statistics.rs @@ -169,22 +169,24 @@ impl ClientStats { } fn record_bloop(&mut self, bloop: ProcessedBloop, tz: &Tz) { - let date = bloop.recorded_at.date_naive(); + let utc_date = bloop.recorded_at.date_naive(); let time = bloop.recorded_at.time(); let idx = (time.hour() * 60 + time.minute()) as usize; - if self.per_minute_dates[idx] != date { - self.per_minute_dates[idx] = date; + if self.per_minute_dates[idx] != utc_date { + self.per_minute_dates[idx] = utc_date; self.per_minute_bloops[idx] = 1; } else { self.per_minute_bloops[idx] = self.per_minute_bloops[idx].saturating_add(1); } - let local_hour = bloop.recorded_at.with_timezone(tz).hour() as usize; + let local_dt = bloop.recorded_at.with_timezone(tz); + let local_hour = local_dt.hour() as usize; + let local_date = local_dt.date_naive(); self.total_bloops += 1; self.bloops_per_hour[local_hour] += 1; - *self.bloops_per_day.entry(date).or_insert(0) += 1; + *self.bloops_per_day.entry(local_date).or_insert(0) += 1; } fn count_last_minutes(&self, now: DateTime, minutes: usize) -> u32 { @@ -192,7 +194,7 @@ impl ClientStats { let mut total = 0; for i in 0..minutes { - let idx = now_minute.wrapping_sub(i) % MINUTES_IN_DAY; + let idx = (now_minute + MINUTES_IN_DAY - i) % MINUTES_IN_DAY; let expected_date = (now - chrono::Duration::minutes(i as i64)).date_naive(); if self.per_minute_dates[idx] == expected_date { @@ -639,6 +641,47 @@ mod tests { assert_eq!(snapshot.global.total_bloops, 1); } + #[test] + fn count_last_minutes_across_midnight() { + // now_minute = 1 (00:01 UTC), so counting back 3 minutes wraps across midnight + let tz = UTC; + let mut stats = ClientStats::new(); + let now = Utc.with_ymd_and_hms(2025, 7, 6, 0, 1, 0).unwrap(); + + // 00:01 today + stats.record_bloop(make_bloop("c", now), &tz); + // 00:00 today + stats.record_bloop(make_bloop("c", now - Duration::minutes(1)), &tz); + // 23:59 yesterday + stats.record_bloop(make_bloop("c", now - Duration::minutes(2)), &tz); + + let count = stats.count_last_minutes(now, 3); + assert_eq!(count, 3, "should count all 3 minutes even when wrapping past midnight"); + } + + #[test] + fn record_bloop_uses_local_timezone_for_bloops_per_day() { + // Use a timezone that is ahead of UTC (e.g. UTC+2) so that a bloop + // recorded at 23:30 UTC on day D is actually local day D+1. + let tz = chrono_tz::Europe::Helsinki; // UTC+2 / UTC+3 + let mut stats = ClientStats::new(); + + // 2025-07-05 23:30 UTC == 2025-07-06 01:30 Helsinki (EEST, UTC+3) + let recorded_at = Utc.with_ymd_and_hms(2025, 7, 5, 23, 30, 0).unwrap(); + let bloop = make_bloop("client1", recorded_at); + stats.record_bloop(bloop, &tz); + + let local_date = recorded_at.with_timezone(&tz).date_naive(); + let utc_date = recorded_at.date_naive(); + + // The local date must differ from the UTC date for this test to be meaningful + assert_ne!(local_date, utc_date); + // bloops_per_day must be keyed by local date, not UTC date + assert_eq!(stats.bloops_per_day.get(&local_date), Some(&1)); + assert_eq!(stats.bloops_per_day.get(&utc_date), None); + } + + fn dummy_stats() -> HashMap { let mut map = HashMap::new(); map.insert("client1".to_string(), Default::default()); From 52364d79e3f0f62b91612400bab9333490d397e6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:41:09 +0000 Subject: [PATCH 3/3] Fix rustfmt formatting in statistics tests Co-authored-by: DASPRiD <233300+DASPRiD@users.noreply.github.com> --- src/statistics.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/statistics.rs b/src/statistics.rs index 378a315..555a8e3 100644 --- a/src/statistics.rs +++ b/src/statistics.rs @@ -656,7 +656,10 @@ mod tests { stats.record_bloop(make_bloop("c", now - Duration::minutes(2)), &tz); let count = stats.count_last_minutes(now, 3); - assert_eq!(count, 3, "should count all 3 minutes even when wrapping past midnight"); + assert_eq!( + count, 3, + "should count all 3 minutes even when wrapping past midnight" + ); } #[test] @@ -681,7 +684,6 @@ mod tests { assert_eq!(stats.bloops_per_day.get(&utc_date), None); } - fn dummy_stats() -> HashMap { let mut map = HashMap::new(); map.insert("client1".to_string(), Default::default());