diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index dcc87c24e..a29502d63 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -235,16 +235,25 @@ impl SqlWriter<'_> { fn write_prop(&mut self, op: &str, kind: &PropertyKind) -> Result<()> { let timing = self.col.timing_today()?; + match kind { PropertyKind::Due(days) => { let day = days + (timing.days_elapsed as i32); write!( self.sql, - "(c.queue in ({rev},{daylrn}) and due {op} {day})", + // SQL does integer division if both parameters are integers + "(\ + (c.queue in ({rev},{daylrn}) and c.due {op} {day}) or \ + (c.queue in ({lrn},{previewrepeat}) and ((c.due - {cutoff}) / 86400) {op} {days})\ + )", rev = CardQueue::Review as u8, daylrn = CardQueue::DayLearn as u8, op = op, - day = day + day = day, + lrn = CardQueue::Learn as i8, + previewrepeat = CardQueue::PreviewRepeat as i8, + cutoff = timing.next_day_at, + days = days ) } PropertyKind::Position(pos) => { @@ -292,14 +301,15 @@ impl SqlWriter<'_> { StateKind::Suspended => write!(self.sql, "c.queue = {}", CardQueue::Suspended as i8), StateKind::Due => write!( self.sql, - "( - (c.queue in ({rev},{daylrn}) and c.due <= {today}) or - (c.queue = {lrn} and c.due <= {learncutoff}) - )", + "(\ + (c.queue in ({rev},{daylrn}) and c.due <= {today}) or \ + (c.queue in ({lrn},{previewrepeat}) and c.due <= {learncutoff})\ + )", rev = CardQueue::Review as i8, daylrn = CardQueue::DayLearn as i8, today = timing.days_elapsed, lrn = CardQueue::Learn as i8, + previewrepeat = CardQueue::PreviewRepeat as i8, learncutoff = TimestampSecs::now().0 + (self.col.learn_ahead_secs() as i64), ), StateKind::UserBuried => write!(self.sql, "c.queue = {}", CardQueue::UserBuried as i8), @@ -725,8 +735,9 @@ mod test { assert_eq!( s(ctx, "prop:due!=-1").0, format!( - "((c.queue in (2,3) and due != {}))", - timing.days_elapsed - 1 + "(((c.queue in (2,3) and c.due != {days}) or (c.queue in (1,4) and ((c.due - {cutoff}) / 86400) != -1)))", + days = timing.days_elapsed - 1, + cutoff = timing.next_day_at ) ); diff --git a/ts/graphs/future-due.ts b/ts/graphs/future-due.ts index 09e33133b..1ae73c0bf 100644 --- a/ts/graphs/future-due.ts +++ b/ts/graphs/future-due.ts @@ -23,31 +23,36 @@ export interface GraphData { } export function gatherData(data: pb.BackendProto.GraphsOut): GraphData { - const isLearning = (queue: number): boolean => - [CardQueue.Learn, CardQueue.PreviewRepeat].includes(queue); + const isLearning = (card: pb.BackendProto.Card): boolean => + [CardQueue.Learn, CardQueue.PreviewRepeat].includes(card.queue); + let haveBacklog = false; const due = (data.cards as pb.BackendProto.Card[]) - .filter( - (c) => - // reviews + .filter((c: pb.BackendProto.Card) => { + // reviews + return ( [CardQueue.Review, CardQueue.DayLearn].includes(c.queue) || - // or learning cards due today - (isLearning(c.queue) && c.due < data.nextDayAtSecs) - ) - .map((c) => { - if (isLearning(c.queue)) { - return 0; + // or learning cards + isLearning(c) + ); + }) + .map((c: pb.BackendProto.Card) => { + let dueDay: number; + + if (isLearning(c)) { + const offset = c.due - data.nextDayAtSecs; + dueDay = Math.floor(offset / 86_400) + 1; } else { // - testing just odue fails on day 1 // - testing just odid fails on lapsed cards that // have due calculated at regraduation time const due = c.originalDeckId && c.originalDue ? c.originalDue : c.due; - const dueDay = due - data.daysElapsed; - if (dueDay < 0) { - haveBacklog = true; - } - return dueDay; + dueDay = due - data.daysElapsed; } + + haveBacklog = haveBacklog || dueDay < 0; + + return dueDay; }); const dueCounts = rollup( diff --git a/ts/lib/time.ts b/ts/lib/time.ts index 7ad6c61d7..c3eebefb8 100644 --- a/ts/lib/time.ts +++ b/ts/lib/time.ts @@ -151,8 +151,8 @@ export function dayLabel(i18n: I18n, daysStart: number, daysEnd: number): string }); } else { return i18n.tr(i18n.TR.STATISTICS_DAYS_AGO_RANGE, { - daysStart: Math.abs(daysEnd), - daysEnd: -daysStart - 1, + daysStart: Math.abs(daysEnd - 1), + daysEnd: -daysStart, }); } }