96 lines
2.7 KiB
Rust
96 lines
2.7 KiB
Rust
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)?)
|
|
}
|