rtimelogger 0.5.1

A simple cross-platform CLI tool to track working hours, lunch breaks, and calculate surplus time
Documentation
use crate::config::Config;
use chrono::{Duration, NaiveTime};

pub fn month_name(month: &str) -> &'static str {
    match month {
        "01" => "January",
        "02" => "February",
        "03" => "March",
        "04" => "April",
        "05" => "May",
        "06" => "June",
        "07" => "July",
        "08" => "August",
        "09" => "September",
        "10" => "October",
        "11" => "November",
        "12" => "December",
        _ => "Unknown",
    }
}

/// Calculate the expected exit time.
///
/// Rules:
/// - Standard working time: 8 hours
/// - Lunch break: minimum 30 minutes (included in the calculation)
/// - If the break is longer than 30 minutes, the extra time must be recovered
///   by leaving later the same day
/// - Maximum lunch break allowed: 90 minutes
pub fn calculate_expected_exit(
    start: &str,
    work_minutes: i64,
    lunch: i32,
    config: &Config,
) -> NaiveTime {
    let start_time = NaiveTime::parse_from_str(start, "%H:%M").expect("Invalid start time format");
    let min_l = config.min_duration_lunch_break as i64;
    let max_l = config.max_duration_lunch_break as i64;
    // Se lunch è 0 (ancora non registrato), consideriamo almeno il minimo
    let lunch_eff = (lunch as i64).clamp(min_l, max_l);
    start_time + Duration::minutes(work_minutes + lunch_eff)
}

pub fn calculate_surplus(
    start: &str,
    lunch: i32,
    end: &str,
    work_minutes: i64,
    config: &Config,
) -> Duration {
    let expected = calculate_expected_exit(start, work_minutes, lunch, config);
    let actual = NaiveTime::parse_from_str(end, "%H:%M").expect("Invalid end time format");
    actual - expected
}
/// Return true if the interval [start, end] overlaps the lunch window 12:30–14:30.
pub fn crosses_lunch_window(start: &str, end: &str) -> bool {
    let start_time = match NaiveTime::parse_from_str(start, "%H:%M") {
        Ok(t) => t,
        Err(_) => return false,
    };
    let end_time = match NaiveTime::parse_from_str(end, "%H:%M") {
        Ok(t) => t,
        Err(_) => return false,
    };

    let lunch_start = NaiveTime::parse_from_str("12:30", "%H:%M").unwrap();
    let lunch_end = NaiveTime::parse_from_str("14:30", "%H:%M").unwrap();

    start_time < lunch_end && end_time > lunch_start
}

/// Compute the effective lunch minutes based on position and work interval.
///
/// Rules:
/// - `A` (office): if the interval overlaps 12:30–14:30, lunch is mandatory [30..90].
///   If missing (0) it defaults to 30. Outside the window, lunch = 0.
/// - `R` (remote): lunch is optional, 0..90 accepted, even if overlapping the window.
pub fn effective_lunch_minutes(
    lunch: i32,
    start: &str,
    end: &str,
    position: char,
    config: &Config,
) -> i32 {
    let crosses = crosses_lunch_window(start, end);
    match position {
        'O' => {
            if crosses {
                let l = lunch.clamp(
                    config.min_duration_lunch_break,
                    config.max_duration_lunch_break,
                );
                if l < config.min_duration_lunch_break {
                    config.min_duration_lunch_break
                } else {
                    l
                }
            } else {
                0
            }
        }
        'H' => 0,
        _ => lunch.clamp(0, config.max_duration_lunch_break),
    }
}