Prepare for reminding

This commit is contained in:
Rahix 2026-03-02 08:46:52 +01:00
parent c1c0ced266
commit aebbcd7815
5 changed files with 124 additions and 5 deletions

96
src/reminder.rs Normal file
View file

@ -0,0 +1,96 @@
use crate::task;
use crate::util;
use anyhow::Context as _;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BatchingInterval {
Monthly,
Weekly,
OnTheDay,
}
pub async fn remind_due_tasks(ctx: &crate::Context, tasks: &[task::Task]) -> anyhow::Result<()> {
for task in tasks {
let Some(due_date) = task.state.due_date_if_open() else {
continue;
};
let batching_interval = find_batching_interval(ctx, due_date, task);
log::debug!("Reminding {task} with interval {batching_interval:?}.");
if !is_time_to_remind(ctx, due_date, batching_interval) {
log::debug!("Not yet time, skipping.");
continue;
}
if is_overdue(ctx, due_date) {
log::info!("Task {task} is already overdue!");
}
log::warn!("TODO: Remind {task}");
}
Ok(())
}
fn find_batching_interval(
ctx: &crate::Context,
due_date: &jiff::Zoned,
task: &task::Task,
) -> BatchingInterval {
let time_until_due = due_date - &ctx.timestamp;
if let Some(recurring) = &task.recurring {
match &recurring.interval {
task::RecurringInterval::Months(_) => BatchingInterval::Monthly,
task::RecurringInterval::Weeks(_) => BatchingInterval::Weekly,
task::RecurringInterval::Days(_) => BatchingInterval::OnTheDay,
}
} else {
// For tasks that are not recurring, the batching interval is determined based on how
// far in the future the task is due.
let weeks_until_due = time_until_due
.total((jiff::Unit::Week, jiff::SpanRelativeTo::days_are_24_hours()))
.unwrap();
if weeks_until_due >= 3. {
BatchingInterval::Monthly
} else if weeks_until_due >= 1. {
BatchingInterval::Weekly
} else {
BatchingInterval::OnTheDay
}
}
}
fn is_time_to_remind(
ctx: &crate::Context,
due_date: &jiff::Zoned,
batching_interval: BatchingInterval,
) -> bool {
let batch_time = match batching_interval {
BatchingInterval::Monthly => due_date.first_of_month().unwrap(),
BatchingInterval::Weekly => start_of_week(due_date).unwrap(),
BatchingInterval::OnTheDay => due_date.clone(),
};
let batch_time = batch_time
.round(
jiff::ZonedRound::new()
.smallest(jiff::Unit::Day)
.mode(jiff::RoundMode::Floor),
)
.unwrap();
ctx.timestamp >= batch_time
}
fn is_overdue(ctx: &crate::Context, due_date: &jiff::Zoned) -> bool {
&ctx.timestamp >= due_date
}
fn start_of_week(t: &jiff::Zoned) -> anyhow::Result<jiff::Zoned> {
Ok(t.tomorrow()?
.nth_weekday(-1, jiff::civil::Weekday::Monday)?)
}