Fix/set due date on intraday learning card (#4101)

- Introduced `next_day_start` parameter to `set_due_date` for improved due date handling.
- Updated logic to account for Unix epoch timestamps when calculating due dates.
This commit is contained in:
Jarrett Ye 2025-06-21 20:15:30 +08:00 committed by GitHub
parent cc395f7c44
commit 88538d8bad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -17,6 +17,7 @@ use crate::collection::Collection;
use crate::config::StringKey; use crate::config::StringKey;
use crate::error::Result; use crate::error::Result;
use crate::prelude::*; use crate::prelude::*;
use crate::scheduler::timing::is_unix_epoch_timestamp;
impl Card { impl Card {
/// Make card due in `days_from_today`. /// Make card due in `days_from_today`.
@ -27,6 +28,7 @@ impl Card {
fn set_due_date( fn set_due_date(
&mut self, &mut self,
today: u32, today: u32,
next_day_start: i64,
days_from_today: u32, days_from_today: u32,
ease_factor: f32, ease_factor: f32,
force_reset: bool, force_reset: bool,
@ -34,8 +36,15 @@ impl Card {
let new_due = (today + days_from_today) as i32; let new_due = (today + days_from_today) as i32;
let fsrs_enabled = self.memory_state.is_some(); let fsrs_enabled = self.memory_state.is_some();
let new_interval = if fsrs_enabled { let new_interval = if fsrs_enabled {
self.interval let due = self.original_or_current_due();
.saturating_add_signed(new_due - self.original_or_current_due()) let due_diff = if is_unix_epoch_timestamp(due) {
let offset = (due as i64 - next_day_start) / 86_400;
let due = (today as i64 + offset) as i32;
new_due - due
} else {
new_due - due
};
self.interval.saturating_add_signed(due_diff)
} else if force_reset || !matches!(self.ctype, CardType::Review | CardType::Relearn) { } else if force_reset || !matches!(self.ctype, CardType::Review | CardType::Relearn) {
days_from_today.max(1) days_from_today.max(1)
} else { } else {
@ -114,6 +123,7 @@ impl Collection {
let spec = parse_due_date_str(days)?; let spec = parse_due_date_str(days)?;
let usn = self.usn()?; let usn = self.usn()?;
let today = self.timing_today()?.days_elapsed; let today = self.timing_today()?.days_elapsed;
let next_day_start = self.timing_today()?.next_day_at.0;
let mut rng = rand::rng(); let mut rng = rand::rng();
let distribution = Uniform::new_inclusive(spec.min, spec.max).unwrap(); let distribution = Uniform::new_inclusive(spec.min, spec.max).unwrap();
let mut decks_initial_ease: HashMap<DeckId, f32> = HashMap::new(); let mut decks_initial_ease: HashMap<DeckId, f32> = HashMap::new();
@ -137,7 +147,13 @@ impl Collection {
}; };
let original = card.clone(); let original = card.clone();
let days_from_today = distribution.sample(&mut rng); let days_from_today = distribution.sample(&mut rng);
card.set_due_date(today, days_from_today, ease_factor, spec.force_reset); card.set_due_date(
today,
next_day_start,
days_from_today,
ease_factor,
spec.force_reset,
);
col.log_manually_scheduled_review(&card, original.interval, usn)?; col.log_manually_scheduled_review(&card, original.interval, usn)?;
col.update_card_inner(&mut card, original, usn)?; col.update_card_inner(&mut card, original, usn)?;
} }
@ -228,26 +244,26 @@ mod test {
let mut c = Card::new(NoteId(0), 0, DeckId(0), 0); let mut c = Card::new(NoteId(0), 0, DeckId(0), 0);
// setting the due date of a new card will convert it // setting the due date of a new card will convert it
c.set_due_date(5, 2, 1.8, false); c.set_due_date(5, 0, 2, 1.8, false);
assert_eq!(c.ctype, CardType::Review); assert_eq!(c.ctype, CardType::Review);
assert_eq!(c.due, 7); assert_eq!(c.due, 7);
assert_eq!(c.interval, 2); assert_eq!(c.interval, 2);
assert_eq!(c.ease_factor, 1800); assert_eq!(c.ease_factor, 1800);
// reschedule it again the next day, shifting it from day 7 to day 9 // reschedule it again the next day, shifting it from day 7 to day 9
c.set_due_date(6, 3, 2.5, false); c.set_due_date(6, 0, 3, 2.5, false);
assert_eq!(c.due, 9); assert_eq!(c.due, 9);
assert_eq!(c.interval, 2); assert_eq!(c.interval, 2);
assert_eq!(c.ease_factor, 1800); // interval doesn't change assert_eq!(c.ease_factor, 1800); // interval doesn't change
// we can bring cards forward too - return it to its original due date // we can bring cards forward too - return it to its original due date
c.set_due_date(6, 1, 2.4, false); c.set_due_date(6, 0, 1, 2.4, false);
assert_eq!(c.due, 7); assert_eq!(c.due, 7);
assert_eq!(c.interval, 2); assert_eq!(c.interval, 2);
assert_eq!(c.ease_factor, 1800); // interval doesn't change assert_eq!(c.ease_factor, 1800); // interval doesn't change
// we can force the interval to be reset instead of shifted // we can force the interval to be reset instead of shifted
c.set_due_date(6, 3, 2.3, true); c.set_due_date(6, 0, 3, 2.3, true);
assert_eq!(c.due, 9); assert_eq!(c.due, 9);
assert_eq!(c.interval, 3); assert_eq!(c.interval, 3);
assert_eq!(c.ease_factor, 1800); // interval doesn't change assert_eq!(c.ease_factor, 1800); // interval doesn't change
@ -259,7 +275,7 @@ mod test {
c.original_deck_id = DeckId(1); c.original_deck_id = DeckId(1);
c.due = -10000; c.due = -10000;
c.queue = CardQueue::New; c.queue = CardQueue::New;
c.set_due_date(6, 1, 2.2, false); c.set_due_date(6, 0, 1, 2.2, false);
assert_eq!(c.due, 7); assert_eq!(c.due, 7);
assert_eq!(c.interval, 2); assert_eq!(c.interval, 2);
assert_eq!(c.ease_factor, 2200); assert_eq!(c.ease_factor, 2200);
@ -271,7 +287,7 @@ mod test {
c.ctype = CardType::Relearn; c.ctype = CardType::Relearn;
c.original_due = c.due; c.original_due = c.due;
c.due = 12345678; c.due = 12345678;
c.set_due_date(6, 10, 2.1, false); c.set_due_date(6, 0, 10, 2.1, false);
assert_eq!(c.due, 16); assert_eq!(c.due, 16);
assert_eq!(c.interval, 2); assert_eq!(c.interval, 2);
assert_eq!(c.ease_factor, 2200); // interval doesn't change assert_eq!(c.ease_factor, 2200); // interval doesn't change