Implement DATE statement
This commit is contained in:
parent
85d2c4bf89
commit
4c83df76d4
6 changed files with 159 additions and 11 deletions
|
|
@ -66,6 +66,22 @@ impl<'a> CommandState<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn limit_from_until(&self, range: DateRange) -> Option<DateRange> {
|
||||||
|
let range_from = range.from();
|
||||||
|
let from = self
|
||||||
|
.from
|
||||||
|
.filter(|&from| from > range_from)
|
||||||
|
.unwrap_or(range_from);
|
||||||
|
|
||||||
|
let range_until = range.until();
|
||||||
|
let until = self
|
||||||
|
.until
|
||||||
|
.filter(|&until| until < range_until)
|
||||||
|
.unwrap_or(range_until);
|
||||||
|
|
||||||
|
DateRange::new(from, until)
|
||||||
|
}
|
||||||
|
|
||||||
/// Add an entry, respecting [`Self::from`] and [`Self::until`]. Does not
|
/// Add an entry, respecting [`Self::from`] and [`Self::until`]. Does not
|
||||||
/// overwrite existing entries if a root date is specified.
|
/// overwrite existing entries if a root date is specified.
|
||||||
fn add(&mut self, kind: EntryKind, dates: Option<Dates>) {
|
fn add(&mut self, kind: EntryKind, dates: Option<Dates>) {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
|
|
||||||
use crate::files::commands::{self, Spanned, Time};
|
use crate::eval::date::Dates;
|
||||||
|
use crate::eval::DateRange;
|
||||||
|
use crate::files::commands::{self, Command, Spanned, Time};
|
||||||
|
|
||||||
use super::super::command::CommandState;
|
use super::super::command::CommandState;
|
||||||
use super::super::delta::{Delta, DeltaStep};
|
use super::super::delta::{Delta, DeltaStep};
|
||||||
use super::super::Result;
|
use super::super::{Error, Result};
|
||||||
|
|
||||||
pub struct DateSpec {
|
pub struct DateSpec {
|
||||||
pub start: NaiveDate,
|
pub start: NaiveDate,
|
||||||
|
|
@ -52,8 +54,77 @@ impl From<&commands::DateSpec> for DateSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DateSpec {
|
||||||
|
fn start_and_range(&self, s: &CommandState<'_>) -> Option<(NaiveDate, DateRange)> {
|
||||||
|
let (start, range) = match s.command.command {
|
||||||
|
Command::Task(_) => {
|
||||||
|
let last_done = s.last_done();
|
||||||
|
let start = last_done
|
||||||
|
.filter(|_| self.start_at_done)
|
||||||
|
.unwrap_or(self.start);
|
||||||
|
let range_from = last_done.map(|date| date.succ()).unwrap_or(self.start);
|
||||||
|
let range = s
|
||||||
|
.range
|
||||||
|
.expand_by(&self.end_delta)
|
||||||
|
.move_by(&self.start_delta)
|
||||||
|
.with_from(range_from)?;
|
||||||
|
(start, range)
|
||||||
|
}
|
||||||
|
Command::Note(_) => {
|
||||||
|
let start = self.start;
|
||||||
|
let range = s
|
||||||
|
.range
|
||||||
|
.expand_by(&self.end_delta)
|
||||||
|
.move_by(&self.start_delta);
|
||||||
|
(start, range)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let range = s.limit_from_until(range)?;
|
||||||
|
Some((start, range))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn step(from: NaiveDate, repeat: &Delta) -> Result<NaiveDate> {
|
||||||
|
let to = repeat.apply_date(from)?;
|
||||||
|
if to > from {
|
||||||
|
Ok(to)
|
||||||
|
} else {
|
||||||
|
Err(Error::RepeatDidNotMoveForwards {
|
||||||
|
span: todo!(),
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dates(&self, start: NaiveDate) -> Result<Dates> {
|
||||||
|
let root = self.start_delta.apply_date(start)?;
|
||||||
|
Ok(if let Some(root_time) = self.start_time {
|
||||||
|
let (other, other_time) = self.end_delta.apply_date_time(root, root_time)?;
|
||||||
|
Dates::new_with_time(root, root_time, other, other_time)
|
||||||
|
} else {
|
||||||
|
let other = self.end_delta.apply_date(root)?;
|
||||||
|
Dates::new(root, other)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> CommandState<'a> {
|
impl<'a> CommandState<'a> {
|
||||||
pub fn eval_date_spec(&mut self, spec: DateSpec) -> Result<()> {
|
pub fn eval_date_spec(&mut self, spec: DateSpec) -> Result<()> {
|
||||||
todo!()
|
if let Some(repeat) = &spec.repeat {
|
||||||
|
if let Some((mut start, range)) = spec.start_and_range(self) {
|
||||||
|
while start < range.from() {
|
||||||
|
start = DateSpec::step(start, repeat)?;
|
||||||
|
}
|
||||||
|
while start <= range.until() {
|
||||||
|
let dates = spec.dates(start)?;
|
||||||
|
self.add(self.kind(), Some(dates));
|
||||||
|
start = DateSpec::step(start, repeat)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let dates = spec.dates(spec.start)?;
|
||||||
|
self.add(self.kind(), Some(dates));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -266,11 +266,20 @@ impl Delta {
|
||||||
self.steps.iter().map(|step| step.value.upper_bound()).sum()
|
self.steps.iter().map(|step| step.value.upper_bound()).sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply(&self, start: (NaiveDate, Option<Time>)) -> Result<(NaiveDate, Option<Time>)> {
|
fn apply(&self, start: (NaiveDate, Option<Time>)) -> Result<(NaiveDate, Option<Time>)> {
|
||||||
let mut eval = DeltaEval::new(start.0, start.1);
|
let mut eval = DeltaEval::new(start.0, start.1);
|
||||||
for step in &self.steps {
|
for step in &self.steps {
|
||||||
eval.apply(step)?;
|
eval.apply(step)?;
|
||||||
}
|
}
|
||||||
Ok((eval.curr, eval.curr_time))
|
Ok((eval.curr, eval.curr_time))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apply_date(&self, date: NaiveDate) -> Result<NaiveDate> {
|
||||||
|
Ok(self.apply((date, None))?.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_date_time(&self, date: NaiveDate, time: Time) -> Result<(NaiveDate, Time)> {
|
||||||
|
let (date, time) = self.apply((date, Some(time)))?;
|
||||||
|
Ok((date, time.expect("time was not preserved")))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,14 @@ pub enum Error {
|
||||||
start: NaiveDate,
|
start: NaiveDate,
|
||||||
prev: NaiveDate,
|
prev: NaiveDate,
|
||||||
},
|
},
|
||||||
|
/// A `DATE`'s repeat delta did not move the date forwards in time. Instead,
|
||||||
|
/// it either remained at the current date (`to == from`) or moved backwards
|
||||||
|
/// in time (`to < from`).
|
||||||
|
RepeatDidNotMoveForwards {
|
||||||
|
span: Span,
|
||||||
|
from: NaiveDate,
|
||||||
|
to: NaiveDate,
|
||||||
|
},
|
||||||
/// A `MOVE a TO b` statement was executed, but there was no entry at the
|
/// A `MOVE a TO b` statement was executed, but there was no entry at the
|
||||||
/// date `a`.
|
/// date `a`.
|
||||||
MoveWithoutSource { span: Span },
|
MoveWithoutSource { span: Span },
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
|
use std::cmp;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
use chrono::{Datelike, NaiveDate};
|
use chrono::{Datelike, Duration, NaiveDate};
|
||||||
|
|
||||||
|
use super::delta::Delta;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct DateRange {
|
pub struct DateRange {
|
||||||
|
|
@ -9,9 +12,20 @@ pub struct DateRange {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DateRange {
|
impl DateRange {
|
||||||
pub fn new(from: NaiveDate, until: NaiveDate) -> Self {
|
pub fn new(from: NaiveDate, until: NaiveDate) -> Option<Self> {
|
||||||
assert!(from <= until);
|
if from <= until {
|
||||||
Self { from, until }
|
Some(Self { from, until })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_from(&self, from: NaiveDate) -> Option<Self> {
|
||||||
|
Self::new(from, self.until)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_until(&self, until: NaiveDate) -> Option<Self> {
|
||||||
|
Self::new(self.from, until)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains(&self, date: NaiveDate) -> bool {
|
pub fn contains(&self, date: NaiveDate) -> bool {
|
||||||
|
|
@ -29,4 +43,33 @@ impl DateRange {
|
||||||
pub fn years(&self) -> RangeInclusive<i32> {
|
pub fn years(&self) -> RangeInclusive<i32> {
|
||||||
self.from.year()..=self.until.year()
|
self.from.year()..=self.until.year()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Expand the range so that it contains at least all dates from which the
|
||||||
|
/// original range could be reached using `delta`. This new range will
|
||||||
|
/// always contain the old range.
|
||||||
|
pub fn expand_by(&self, delta: &Delta) -> Self {
|
||||||
|
let expand_lower = cmp::min(-delta.upper_bound(), 0);
|
||||||
|
let expand_upper = cmp::max(-delta.lower_bound(), 0);
|
||||||
|
Self::new(
|
||||||
|
self.from + Duration::days(expand_lower.into()),
|
||||||
|
self.until + Duration::days(expand_upper.into()),
|
||||||
|
)
|
||||||
|
// The range is never shrunk, so the new range should always be valid.
|
||||||
|
.expect("expanded range shrunk")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a new range that contains at least all dates from which the
|
||||||
|
/// original range could be reached using `delta`. This new range might not
|
||||||
|
/// contain the old range.
|
||||||
|
pub fn move_by(&self, delta: &Delta) -> Self {
|
||||||
|
let move_lower = -delta.upper_bound();
|
||||||
|
let move_upper = -delta.lower_bound();
|
||||||
|
Self::new(
|
||||||
|
self.from + Duration::days(move_lower.into()),
|
||||||
|
self.until + Duration::days(move_upper.into()),
|
||||||
|
)
|
||||||
|
// The delta's upper bound is greater or equal than its lower bound, so
|
||||||
|
// the range should never become smaller. It can only move and expand.
|
||||||
|
.expect("moved range shrunk")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,8 @@ fn main() -> anyhow::Result<()> {
|
||||||
let range = DateRange::new(
|
let range = DateRange::new(
|
||||||
NaiveDate::from_ymd(2021, 1, 1),
|
NaiveDate::from_ymd(2021, 1, 1),
|
||||||
NaiveDate::from_ymd(2021, 12, 31),
|
NaiveDate::from_ymd(2021, 12, 31),
|
||||||
);
|
)
|
||||||
|
.unwrap();
|
||||||
println!("{:#?}", files.eval(EntryMode::Relevant, range));
|
println!("{:#?}", files.eval(EntryMode::Relevant, range));
|
||||||
|
|
||||||
files.mark_all_dirty();
|
files.mark_all_dirty();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue