hgame 0.26.4

CG production management structs, e.g. of assets, personnels, progress, etc.
Documentation
use super::*;
use chrono::{offset::Utc, Local, NaiveDate};

// ----------------------------------------------------------------------------
#[derive(Debug, Clone)]
pub struct DateRange {
    start: NaiveDate,

    end: NaiveDate,

    /// If user ever pressed button to set the end date, this is switched to `true`.
    end_modified_by_user: bool,

    #[allow(dead_code)]
    /// Used to distinguish the `DatePickerButton`s from each other.
    id_source: Option<String>,
}

impl DateRange {
    pub fn new(start: &NaiveDate, end: &NaiveDate, id_source: Option<&str>) -> Self {
        Self {
            start: start.to_owned(),
            end: end.to_owned(),
            end_modified_by_user: false,
            id_source: id_source.map(|id| id.to_owned()),
        }
    }

    /// Sets range end to today (in `Utc`), and the range start to today (in `Utc`) minus offset.
    pub fn today(start_day_offset: i64, id_source: Option<&str>) -> Self {
        Self {
            start: Utc::now().naive_utc().date() - Duration::days(start_day_offset),
            end: Utc::now().naive_utc().date(),
            end_modified_by_user: false,
            id_source: id_source.map(|id| id.to_owned()),
        }
    }

    /// Sets range end to today (in `Local`), and the range start to today (in `Local`) minus offset.
    pub fn today_local(start_day_offset: i64, id_source: Option<&str>) -> Self {
        let today = today_local();
        Self {
            start: today - Duration::days(start_day_offset),
            end: today,
            end_modified_by_user: false,
            id_source: id_source.map(|id| id.to_owned()),
        }
    }

    pub fn dates_between(&self) -> AnyResult<Vec<NaiveDate>> {
        if self.start > self.end {
            return Err(anyhow!(
                "Invalid date range, start date must be prior to end date"
            ));
        };
        let mut dates = vec![];
        let mut d = self.start.clone();
        while d <= self.end {
            dates.push(d);
            d = d + Duration::days(1);
        }
        Ok(dates)
    }

    pub fn days_difference(&self) -> i64 {
        (self.end - self.start).num_days()
    }

    /// Makes the dates between a range in `String`s.
    pub fn dates_between_as_str(&self, fmt: &str) -> AnyResult<Vec<String>> {
        Ok(self
            .dates_between()?
            .into_iter()
            .map(|d| format!("{}", d.format(fmt)))
            .collect())
    }

    /// Opinionatedlly sets range end to today. Used to avoid stale data, e.g. app set open for days.
    /// However, to allow user to customize the end date, this only has effect
    /// when user never clicked on the `Self::end_button`.
    /// Using `Utc::today`.
    pub fn end_today_mut_if_unmodified(&mut self) {
        if !self.end_modified_by_user {
            debug!("Updating date range end to today (UTC)");
            self.end = Utc::now().naive_local().date();
        }
    }

    pub fn end_today_local_mut_if_unmodified(&mut self) {
        if !self.end_modified_by_user {
            debug!("Updating date range end to today (Local)");
            self.end = today_local();
        }
    }

    pub fn start_datetime(&self) -> DateTime<Utc> {
        naive_date_to_utc(&self.start, 0, 0, 0)
    }

    pub fn end_datetime(&self) -> DateTime<Utc> {
        naive_date_to_utc(&self.end, 23, 59, 59)
    }
}

#[cfg(feature = "gui")]
impl DateRange {
    #[cfg(any(feature = "query_message", feature = "review_item"))]
    /// [`DatePickerButton`] to choose the start of the date range.
    /// NOTE: `Self::id_source` must be `is_some`.
    fn start_button_unwrap(&mut self, ui: &mut egui::Ui) -> egui::Response {
        ui.add(
            DatePickerButton::new(&mut self.start)
                .id_source(&format!("{}_start", self.id_source.as_ref().unwrap())),
        )
    }

    #[cfg(any(feature = "query_message", feature = "review_item"))]
    /// [`DatePickerButton`] to choose the end of the date range with detection for click.
    /// NOTE: `Self::id_source` must be `is_some`.
    fn end_button_unwrap(&mut self, ui: &mut egui::Ui) -> egui::Response {
        let response = ui.add(
            DatePickerButton::new(&mut self.end)
                .id_source(&format!("{}_end", self.id_source.as_ref().unwrap())),
        );
        // since `egui::Response::changed` not working for `DatePickerButton`
        if response.clicked() {
            self.end_modified_by_user = true;
        };
        response
    }

    #[cfg(any(feature = "query_message", feature = "review_item"))]
    /// Two [`DatePickerButton`] to modify the start and end of date range.
    pub fn ui(&mut self, ui: &mut egui::Ui) {
        self.start_button_unwrap(ui);
        self.end_button_unwrap(ui);
    }

    #[cfg(any(feature = "query_message", feature = "review_item"))]
    /// Two [`DatePickerButton`] to modify only the start of date range.
    pub fn ui_end_range_disabled(&mut self, ui: &mut egui::Ui) {
        self.start_button_unwrap(ui);
        // ui.add_enabled(
        //     false,
        //     DatePickerButton::new(&mut self.end).id_source(&format!("{}_end", self.id_source)),
        // );
    }
}

impl fmt::Display for DateRange {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "From {} to {}", self.start, self.end)
    }
}

/// Manually sets the year-month-day of `Local` to `Utc`.
fn today_local() -> NaiveDate {
    Local::now().date_naive()
}

fn naive_date_to_utc(date: &NaiveDate, hour: u32, min: u32, sec: u32) -> DateTime<Utc> {
    DateTime::<Utc>::from_local(date.and_hms_opt(hour, min, sec).unwrap(), Utc)
}

#[cfg(test)]
mod tests {

    use super::*;
    // #[test]
    // fn dates_between() {
    //     // correct range
    //     let dates = DateRange::today(6).dates_between();
    //     eprintln!("Dates between: {:?}", dates);
    //     assert!(dates.is_ok());

    //     // wrong range
    //     let dates = DateRange::new(
    //         &NaiveDate::from_ymd(2021, 4, 1),
    //         &NaiveDate::from_ymd(2021, 3, 1),
    //     )
    //     .dates_between();
    //     assert!(dates.is_err());
    // }
    #[test]
    fn local_today() {
        use chrono::{Datelike, TimeZone};
        // let local = Local::now().naive_local();
        let local = Local::now();
        eprintln!(
            "LOCAL NAIVE TIME: {} - {} - {}",
            local.year(),
            local.month(),
            local.day()
        );

        // let utc = Utc::now().naive_local();
        // eprintln!("UTC NAIVE TIME: {}", utc);

        // let utc = Utc.ymd(local.year(), local.month(), local.day());
        let utc = Utc.with_ymd_and_hms(2023, 12, 2, 0, 0, 0).unwrap();
        eprintln!("UTC DATE FROM LOCAL: {}", utc);
    }
}