1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
extern crate chrono;
extern crate clap;
extern crate two_timer;

use crate::configure::Configuration;
use crate::log::{Event, Filter, LogController};
use crate::util::{fatal, Style};
use crate::vacation::VacationController;
use chrono::{Duration, Local, NaiveDateTime};
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
use two_timer::parse;

fn after_help() -> &'static str {
    "\
If you are expected to log a certain number of hours a day this command allows you \
to discover how many addional hours you will have to work to meet this expectation.

Without any additional arguments the assumed period is the current day. Perhaps more useful \
is the pay period, but to use 'pay period' (abbreviated 'pp') as your time expression, \
you must have configured a pay period for the job log. See the configure subcommand.

  > job when
  when: today
  you were done at  4:16:52 PM

All prefixes of 'when' are aliases of the subcommand.
"
}

pub fn cli(mast: App<'static, 'static>, display_order: usize) -> App<'static, 'static> {
    mast.subcommand(
        SubCommand::with_name("when")
            .aliases(&["w", "wh", "whe"])
            .about("Says when you will have worked all the hours expected within the given period")
            .after_help(after_help())
            .setting(AppSettings::TrailingVarArg)
            .arg(
                Arg::with_name("period")
                    .help("time expression")
                    .long_help(
                        "All the <period> arguments are concatenated to produce a time expression.",
                    )
                    .value_name("period")
                    .default_value("today")
                    .multiple(true),
            )
            .display_order(display_order),
    )
}

pub fn run(directory: Option<&str>, matches: &ArgMatches) {
    let conf = Configuration::read(None, directory);
    let phrase = matches
        .values_of("period")
        .unwrap()
        .collect::<Vec<&str>>()
        .join(" ");
    println!("when: {}", phrase);
    match parse(&phrase, conf.two_timer_config()) {
        Ok((start, end, _)) => {
            let now = Local::now().naive_local();
            if now <= start {
                fatal(
                    format!(
                        "the current moment, {}, must be after the first moment sought: {}.",
                        now, start
                    ),
                    &conf,
                )
            } else if start >= end {
                fatal(
                    format!(
                        "the current moment, {}, must be before the last moment sought: {}.",
                        now, end
                    ),
                    &conf,
                )
            } else {
                let mut reader = LogController::new(None, &conf).expect("could not read log");
                let events = reader.events_in_range(&start, &now);
                // first figure out how much you *should* work during the period
                let mut start_date = start.date();
                let end_time = if now < end { now } else { end };
                let mut hours_required = 0.0;
                while start_date.and_hms(0, 0, 0) < end_time {
                    if conf.is_workday(&start_date) {
                        hours_required += conf.day_length;
                    }
                    start_date += Duration::days(1);
                }
                // then figure out how much you have worked
                let events = Event::gather_by_day(events, &end);
                let filter = Filter::dummy();
                let events = VacationController::read(None, conf.directory())
                    .add_vacation_times(&start, &end, events, &conf, None, &filter);
                let mut seconds_worked = 0.0;
                let mut last_moment = None;
                for e in events {
                    seconds_worked += e.duration(&now);
                    last_moment = e.end.clone();
                }
                // now do the math
                let seconds_required = hours_required * (60.0 * 60.0);
                let delta = seconds_required - seconds_worked;
                let style = Style::new(&conf);
                if delta > 0.0 {
                    let completion_time = now + Duration::seconds(delta as i64);
                    let delta_hours = delta / (60.0 * 60.0);
                    println!(
                        "you will be finished at {}, {:.2} hours from now",
                        style.paint("important", tell_time(&now, &completion_time)),
                        delta_hours
                    );
                } else {
                    let completion_time =
                        last_moment.unwrap_or(now) + Duration::seconds(delta as i64);
                    println!(
                        "you were done at {}",
                        style.paint("important", tell_time(&now, &completion_time))
                    );
                }
            }
        }
        Err(e) => fatal(e.msg(), &conf),
    }
}

fn tell_time(now: &NaiveDateTime, then: &NaiveDateTime) -> String {
    if now.date() == then.date() {
        format!("{}", then.format("%l:%M:%S %p"))
    } else {
        format!("{}", then.format("%l:%M:%S %p on %A, %e %B %Y"))
    }
}