aboutsummaryrefslogtreecommitdiff
path: root/src/model/event.rs
blob: 5e92692bfcb9362af05ce509512312268255fe41 (plain)
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
use chrono::Timelike;
use chrono::{NaiveDate, NaiveTime};
use std::collections::HashMap;
use uuid::Uuid;

use crate::model::repetition::Repetition;

pub static DATE_FORMAT: &str = "%d/%m/%Y";

#[derive(Debug, Clone)]
pub struct Event {
    pub id: Uuid,
    pub date: NaiveDate,
    pub start: Option<NaiveTime>,
    pub end: Option<NaiveTime>,
    pub name: String,
    pub repetition: Option<Repetition>,
}

impl Event {
    pub fn pprint(&self) -> String {
        let start = self.start.map(pprint_time).unwrap_or_default();
        let end = self
            .end
            .map(|t| format!("-{}", pprint_time(t)))
            .unwrap_or_default();
        let space = if self.start.is_some() || self.end.is_some() {
            " "
        } else {
            ""
        };
        format!("{}{}{}{}", start, end, space, self.name)
    }
}

/// Recurring events in an date range (inclusive)
pub fn repetitions_between(
    events: &[Event],
    start: NaiveDate,
    end: NaiveDate,
) -> HashMap<NaiveDate, Vec<Event>> {
    let mut res: HashMap<NaiveDate, Vec<Event>> = HashMap::new();

    for event in events {
        if let Some(repetition) = &event.repetition {
            for date in repetition.between(event.date, start, end) {
                res.entry(date).or_insert_with(Vec::new).push(event.clone())
            }
        }
    }

    res
}

pub fn pprint_time(t: NaiveTime) -> String {
    if t.minute() == 0 {
        format!("{}h", t.hour())
    } else {
        format!("{}h{}", t.hour(), t.minute())
    }
}

fn parse_time(t: &str) -> Option<NaiveTime> {
    match t.split('h').collect::<Vec<&str>>()[..] {
        [hours, minutes] => {
            if minutes.trim().is_empty() {
                NaiveTime::from_hms_opt(hours.parse().ok()?, 0, 0)
            } else {
                NaiveTime::from_hms_opt(hours.parse().ok()?, minutes.parse().ok()?, 0)
            }
        }
        _ => None,
    }
}

// Validation

pub fn validate(
    id: Uuid,
    date: String,
    name: String,
    start: String,
    end: String,
    repetition: Option<Repetition>,
) -> Option<Event> {
    let start = validate_time(start)?;
    let end = validate_time(end)?;

    match (start, end) {
        (Some(s), Some(e)) if s > e => None?,
        _ => (),
    }

    Some(Event {
        id,
        date: NaiveDate::parse_from_str(&date, DATE_FORMAT).ok()?,
        name: validate_name(name)?,
        start,
        end,
        repetition,
    })
}

fn validate_time(time: String) -> Option<Option<NaiveTime>> {
    let time = time.trim();
    if time.is_empty() {
        Some(None)
    } else {
        parse_time(time).map(Some)
    }
}

fn validate_name(name: String) -> Option<String> {
    let name = name.trim();
    if name.is_empty() {
        None
    } else {
        Some(name.to_string())
    }
}