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.
This commit is contained in:
Kieran Black 2023-03-28 01:35:06 -04:00 committed by GitHub
parent 0e0436f850
commit fe591f6be7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 12 additions and 4 deletions

View file

@ -118,6 +118,7 @@ yellowjello <github.com/yellowjello>
Ingemar Berg <github.com/ingemarberg> Ingemar Berg <github.com/ingemarberg>
Ben Kerman <ben@kermanic.org> Ben Kerman <ben@kermanic.org>
Euan Kemp <euank@euank.com> Euan Kemp <euank@euank.com>
Kieran Black <kieranlblack@gmail.com>
******************** ********************

View file

@ -141,6 +141,7 @@ message GraphsResponse {
FutureDue future_due = 7; FutureDue future_due = 7;
Added added = 8; Added added = 8;
ReviewCountsAndTimes reviews = 9; ReviewCountsAndTimes reviews = 9;
uint32 rollover_hour = 10;
} }
message GraphPreferences { message GraphPreferences {

View file

@ -230,6 +230,7 @@ def show(mw: aqt.AnkiQt) -> QDialog:
"hafatsat anki", "hafatsat anki",
"Carlos Duarte", "Carlos Duarte",
"Edgar Benavent Català", "Edgar Benavent Català",
"Kieran Black",
) )
) )

View file

@ -71,6 +71,7 @@ impl Collection {
hours: Some(ctx.hours()), hours: Some(ctx.hours()),
buttons: Some(ctx.buttons()), buttons: Some(ctx.buttons()),
card_counts: Some(ctx.card_counts()), card_counts: Some(ctx.card_counts()),
rollover_hour: self.rollover_for_current_scheduler()? as u32,
}; };
Ok(resp) Ok(resp)
} }

View file

@ -5,6 +5,7 @@ import * as tr from "@tslib/ftl";
import { localizedDate, weekdayLabel } from "@tslib/i18n"; import { localizedDate, weekdayLabel } from "@tslib/i18n";
import { Stats } from "@tslib/proto"; import { Stats } from "@tslib/proto";
import type { CountableTimeInterval } from "d3"; import type { CountableTimeInterval } from "d3";
import { timeHour } from "d3";
import { import {
interpolateBlues, interpolateBlues,
pointer, pointer,
@ -29,6 +30,7 @@ export interface GraphData {
reviewCount: Map<number, number>; reviewCount: Map<number, number>;
timeFunction: CountableTimeInterval; timeFunction: CountableTimeInterval;
weekdayLabels: number[]; weekdayLabels: number[];
rolloverHour: number;
} }
interface DayDatum { interface DayDatum {
@ -59,7 +61,7 @@ export function gatherData(
weekdayLabels.push((firstDayOfWeek + i) % 7); weekdayLabels.push((firstDayOfWeek + i) % 7);
} }
return { reviewCount, timeFunction, weekdayLabels }; return { reviewCount, timeFunction, weekdayLabels, rolloverHour: data.rolloverHour };
} }
export function renderCalendar( export function renderCalendar(
@ -85,7 +87,9 @@ export function renderCalendar(
const dayMap: Map<number, DayDatum> = new Map(); const dayMap: Map<number, DayDatum> = new Map();
let maxCount = 0; let maxCount = 0;
for (const [day, count] of sourceData.reviewCount.entries()) { 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) { if (count > maxCount) {
maxCount = count; maxCount = count;
} }
@ -105,12 +109,12 @@ export function renderCalendar(
setDataAvailable(svg, true); 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 startDate = timeYear(nowForYear);
const oneYearAgoFromNow = new Date(now); const oneYearAgoFromNow = new Date(now);
oneYearAgoFromNow.setFullYear(now.getFullYear() - 1); oneYearAgoFromNow.setFullYear(now.getFullYear() - 1);
for (let i = 0; i < 365; i++) { 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) { if (date > now) {
// don't fill out future dates // don't fill out future dates
continue; continue;