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
extern crate chrono;
extern crate clap;
extern crate two_timer;

use crate::configure::Configuration;
use crate::log::{Event, Filter, LogController};
use crate::util::fatal;
use crate::vacation::VacationController;
use chrono::{Duration, Local, NaiveDate, 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(matches: &ArgMatches) {
    let configuration = Configuration::read(None);
    let phrase = matches
        .values_of("period")
        .unwrap()
        .collect::<Vec<&str>>()
        .join(" ");
    println!("when: {}", phrase);
    match parse(&phrase, configuration.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
                    ),
                    &configuration,
                )
            } else if start >= end {
                fatal(
                    format!(
                        "the current moment, {}, must be before the last moment sought: {}.",
                        now, end
                    ),
                    &configuration,
                )
            } else {
                let mut reader = LogController::new(None).expect("could not read log");
                let events = reader.events_in_range(&start, &now);
                let events = Event::gather_by_day(events, &end);
                let filter = Filter::dummy();
                let events = VacationController::read(None).add_vacation_times(
                    &start,
                    &end,
                    events,
                    &configuration,
                    None,
                    &filter,
                );
                let mut hours_required = 0.0;
                let mut seconds_worked = 0.0;
                let mut last_workday: Option<NaiveDate> = None;
                for e in events {
                    let date = e.start.date();
                    if configuration.is_workday(&date) {
                        if last_workday.is_none() || last_workday.unwrap() != date {
                            hours_required += configuration.day_length;
                            last_workday = Some(date);
                        }
                    }
                    seconds_worked += e.duration(&now);
                }
                let seconds_required = hours_required * (60.0 * 60.0);
                let delta = seconds_required - seconds_worked;
                let delta_hours = delta / (60.0 * 60.0);
                let completion_time = now + Duration::seconds(delta as i64);
                if completion_time > now {
                    println!(
                        "you will be finished at {}, {:.2} hours from now",
                        tell_time(&now, &completion_time),
                        delta_hours
                    );
                } else {
                    println!("you were done at {}", tell_time(&now, &completion_time));
                }
            }
        }
        Err(e) => fatal(e.msg(), &configuration),
    }
}

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"))
    }
}