implement the month command

This commit is contained in:
Abraham Toriz 2021-07-10 13:20:42 -05:00
parent f5f6a44a4e
commit 5ce3de7b6b
No known key found for this signature in database
GPG Key ID: D5B4A746DB5DD42A
3 changed files with 104 additions and 10 deletions

View File

@ -1,8 +1,9 @@
use std::convert::TryFrom;
use std::io::Write;
use std::str::FromStr;
use clap::ArgMatches;
use chrono::{DateTime, Utc, Local, Datelike};
use chrono::{DateTime, Utc, Local, Datelike, TimeZone};
use regex::Regex;
use crate::error::{Result, Error};
@ -10,19 +11,64 @@ use crate::database::Database;
use crate::formatters::Formatter;
use crate::config::Config;
use crate::regex::parse_regex;
use crate::timeparse::parse_time;
use super::{Command, display::{Sheet, entries_for_display}};
/// Given a local datetime, returns the time of the previous monday's start
fn beginning_of_month(now: DateTime<Local>) -> DateTime<Utc> {
now.date().with_day(1).unwrap().and_hms(0, 0, 0).with_timezone(&Utc)
/// Given a local datetime, returns the time when the month it belongs started
fn beginning_of_month(time: DateTime<Local>) -> DateTime<Utc> {
time.date().with_day(1).unwrap().and_hms(0, 0, 0).with_timezone(&Utc)
}
/// Given a datetime compute the time where the previous_month started in UTC
fn beginning_of_previous_month(time: DateTime<Local>) -> DateTime<Utc> {
match time.month() {
1 => {
Local.ymd(time.year()-1, 12, 1).and_hms(0, 0, 0).with_timezone(&Utc)
}
n => Local.ymd(time.year(), n-1, 1).and_hms(0, 0, 0).with_timezone(&Utc)
}
}
enum MonthSpec {
Last,
This,
Month(u32),
}
impl Default for MonthSpec {
fn default() -> MonthSpec {
MonthSpec::This
}
}
impl FromStr for MonthSpec {
type Err = Error;
fn from_str(s: &str) -> Result<MonthSpec> {
match s.trim().to_lowercase().as_str() {
"this" => Ok(MonthSpec::This),
"last" => Ok(MonthSpec::Last),
"jan" | "january" => Ok(MonthSpec::Month(1)),
"feb" | "february" => Ok(MonthSpec::Month(2)),
"mar" | "march" => Ok(MonthSpec::Month(3)),
"apr" | "april" => Ok(MonthSpec::Month(4)),
"may" => Ok(MonthSpec::Month(5)),
"jun" | "june" => Ok(MonthSpec::Month(6)),
"jul" | "july" => Ok(MonthSpec::Month(7)),
"aug" | "august" => Ok(MonthSpec::Month(8)),
"sep" | "september" => Ok(MonthSpec::Month(9)),
"oct" | "october" => Ok(MonthSpec::Month(10)),
"nov" | "november" => Ok(MonthSpec::Month(11)),
"dic" | "december" => Ok(MonthSpec::Month(12)),
_ => Err(Error::InvalidMonthSpec(s.into())),
}
}
}
#[derive(Default)]
pub struct Args {
ids: bool,
end: Option<DateTime<Utc>>,
month: MonthSpec,
format: Formatter,
grep: Option<Regex>,
sheet: Option<Sheet>,
@ -34,7 +80,7 @@ impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
fn try_from(matches: &'a ArgMatches) -> Result<Args> {
Ok(Args {
ids: matches.is_present("ids"),
end: matches.value_of("end").map(|s| parse_time(s)).transpose()?,
month: matches.value_of("month").map(|s| s.parse()).transpose()?.unwrap_or(MonthSpec::This),
format: matches.value_of("format").unwrap().parse()?,
grep: matches.value_of("grep").map(parse_regex).transpose()?,
sheet: matches.value_of("sheet").map(|s| s.parse()).transpose()?,
@ -53,8 +99,38 @@ impl<'a> Command<'a> for MonthCommand {
O: Write,
E: Write,
{
let start = beginning_of_month(Local::now());
let now = Local::now();
entries_for_display(Some(start), args.end, args.sheet, db, out, err, args.format, args.ids, args.grep)
let (start, end) = match args.month {
MonthSpec::This => (beginning_of_month(now), Utc::now()),
MonthSpec::Last => {
(beginning_of_previous_month(now), beginning_of_month(now))
},
MonthSpec::Month(month) => {
if month < now.month() {
// the specified month is in the current year
(
Local.ymd(now.year(), month, 1).and_hms(0, 0, 0).with_timezone(&Utc),
if month < 12 {
Local.ymd(now.year(), month+1, 1).and_hms(0, 0, 0).with_timezone(&Utc)
} else {
Local.ymd(now.year()+1, 1, 1).and_hms(0, 0, 0).with_timezone(&Utc)
}
)
} else {
// use previous year
(
Local.ymd(now.year() - 1, month, 1).and_hms(0, 0, 0).with_timezone(&Utc),
if month < 12 {
Local.ymd(now.year() - 1, month + 1, 1).and_hms(0, 0, 0).with_timezone(&Utc)
} else {
Local.ymd(now.year(), 1, 1).and_hms(0, 0, 0).with_timezone(&Utc)
}
)
}
},
};
entries_for_display(Some(start), Some(end), args.sheet, db, out, err, args.format, args.ids, args.grep)
}
}

View File

@ -51,6 +51,9 @@ pub enum Error {
#[error("The provided regex '{0}' could not be parsed")]
InvalidRegex(String),
#[error("Could not understand '{0}' as a month specifier. Try 'this', 'last', or any month name like 'january' or 'nov'")]
InvalidMonthSpec(String),
}
pub type Result<T> = result::Result<T, Error>;

View File

@ -183,10 +183,25 @@ fn main() {
.visible_alias("m")
.about("Display entries starting this month")
.arg(ids_arg.clone())
.arg(end_arg.clone())
.arg(format_arg.clone())
.arg(grep_arg.clone())
.arg(sheet_arg.clone())
.arg(Arg::with_name("month")
.long("month").short("m")
.takes_value(true).value_name("TIME")
.aliases(&["s", "start"])
.possible_values(&[
"this", "last", "jan", "january", "feb", "february", "mar",
"march", "apr", "april", "may", "jun", "june", "jul",
"july", "aug", "august", "sep", "september", "oct",
"october", "nov", "november", "dic", "december",
])
.hide_possible_values(true)
.help(
"Include entries of the specified month instead of the \
current month"
)
)
)
.subcommand(SubCommand::with_name("in")