From fe591f6be70860632f59d443ed33eb4c47e9eec2 Mon Sep 17 00:00:00 2001 From: Kieran Black <48463323+kieranlblack@users.noreply.github.com> Date: Tue, 28 Mar 2023 01:35:06 -0400 Subject: [PATCH] fix stats calendar incorrect due to daylight savings time (#2456) * fix stats calendar daylight saving time offset bug Previously, when computing counts for the calendar in the stats menu, it was assumed that days had 86,400 seconds. However, this assumption does not hold true on the day when daylight savings occurs. * add self to CONTRIBUTORS and about.py * fix stats calendar anki day to calendar day mapping Since Anki days don't necessarily roll over at midnight, mapping an Anki day into a calendar day needs to have a linear shift applied. By providing the frontend with access to the scheduler's rollover hour, we can account for this offset. --- CONTRIBUTORS | 1 + proto/anki/stats.proto | 1 + qt/aqt/about.py | 1 + rslib/src/stats/graphs/mod.rs | 1 + ts/graphs/calendar.ts | 12 ++++++++---- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 19bbc0603..fa8a8f700 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -118,6 +118,7 @@ yellowjello Ingemar Berg Ben Kerman Euan Kemp +Kieran Black ******************** diff --git a/proto/anki/stats.proto b/proto/anki/stats.proto index a800b33ae..8e1ca8566 100644 --- a/proto/anki/stats.proto +++ b/proto/anki/stats.proto @@ -141,6 +141,7 @@ message GraphsResponse { FutureDue future_due = 7; Added added = 8; ReviewCountsAndTimes reviews = 9; + uint32 rollover_hour = 10; } message GraphPreferences { diff --git a/qt/aqt/about.py b/qt/aqt/about.py index 3c7241385..c795c74b6 100644 --- a/qt/aqt/about.py +++ b/qt/aqt/about.py @@ -230,6 +230,7 @@ def show(mw: aqt.AnkiQt) -> QDialog: "hafatsat anki", "Carlos Duarte", "Edgar Benavent CatalĂ ", + "Kieran Black", ) ) diff --git a/rslib/src/stats/graphs/mod.rs b/rslib/src/stats/graphs/mod.rs index 38d482437..a25ad9e8a 100644 --- a/rslib/src/stats/graphs/mod.rs +++ b/rslib/src/stats/graphs/mod.rs @@ -71,6 +71,7 @@ impl Collection { hours: Some(ctx.hours()), buttons: Some(ctx.buttons()), card_counts: Some(ctx.card_counts()), + rollover_hour: self.rollover_for_current_scheduler()? as u32, }; Ok(resp) } diff --git a/ts/graphs/calendar.ts b/ts/graphs/calendar.ts index ae9cab3e4..51506e454 100644 --- a/ts/graphs/calendar.ts +++ b/ts/graphs/calendar.ts @@ -5,6 +5,7 @@ import * as tr from "@tslib/ftl"; import { localizedDate, weekdayLabel } from "@tslib/i18n"; import { Stats } from "@tslib/proto"; import type { CountableTimeInterval } from "d3"; +import { timeHour } from "d3"; import { interpolateBlues, pointer, @@ -29,6 +30,7 @@ export interface GraphData { reviewCount: Map; timeFunction: CountableTimeInterval; weekdayLabels: number[]; + rolloverHour: number; } interface DayDatum { @@ -59,7 +61,7 @@ export function gatherData( weekdayLabels.push((firstDayOfWeek + i) % 7); } - return { reviewCount, timeFunction, weekdayLabels }; + return { reviewCount, timeFunction, weekdayLabels, rolloverHour: data.rolloverHour }; } export function renderCalendar( @@ -85,7 +87,9 @@ export function renderCalendar( const dayMap: Map = new Map(); let maxCount = 0; for (const [day, count] of sourceData.reviewCount.entries()) { - const date = new Date(now.getTime() + day * 86400 * 1000); + let date = timeDay.offset(now, day); + // anki day does not necessarily roll over at midnight, we account for this when mapping onto calendar days + date = timeHour.offset(date, -1 * sourceData.rolloverHour); if (count > maxCount) { maxCount = count; } @@ -105,12 +109,12 @@ export function renderCalendar( setDataAvailable(svg, true); } - // fill in any blanks + // fill in any blanks, including the current calendar day even if the anki day has not rolled over const startDate = timeYear(nowForYear); const oneYearAgoFromNow = new Date(now); oneYearAgoFromNow.setFullYear(now.getFullYear() - 1); for (let i = 0; i < 365; i++) { - const date = new Date(startDate.getTime() + i * 86400 * 1000); + const date = timeDay.offset(startDate, i); if (date > now) { // don't fill out future dates continue;