From 3672ea38b85b077986351fd35574442aec81f579 Mon Sep 17 00:00:00 2001 From: Matthew Sotoudeh Date: Sun, 23 Jan 2022 16:11:52 -0800 Subject: Break main.rs into sub-files --- src/html_calendar.rs | 192 +++++++++++++++++++++++++++ src/main.rs | 365 +-------------------------------------------------- src/parse.rs | 144 ++++++++++++++++++++ src/task.rs | 30 +++++ 4 files changed, 372 insertions(+), 359 deletions(-) create mode 100644 src/html_calendar.rs create mode 100644 src/parse.rs create mode 100644 src/task.rs diff --git a/src/html_calendar.rs b/src/html_calendar.rs new file mode 100644 index 0000000..b16b987 --- /dev/null +++ b/src/html_calendar.rs @@ -0,0 +1,192 @@ +use std::collections::HashMap; +use chrono::{NaiveTime, Duration, Local}; +use crate::task::*; + +pub enum CalendarPrivacy { + Public, + Private, +} + +pub fn tasks_to_html(tasks: &Vec, privacy: CalendarPrivacy) -> String { + let public_tags = HashMap::from([ + ("busy", "I will be genuinely busy, e.g., a meeting with others."), + ("rough", "The nature of the event (e.g., a hike) makes it difficult to preduct the exact start/end times."), + ("tentative", "This event timing is only tentative."), + ("join-me", "This is an open event; if you're interested in joining please reach out!"), + ("self", "This is scheduled time for me to complete a specific work or personal task; I can usually reschedule such blocks when requested."), + ]); + + let mut html = "Calendar".to_string(); + + let n_days = 14; + let start_period = Local::now().date().naive_local(); + let end_period = start_period + Duration::days(n_days); + let mut week_task_ids: Vec = Vec::new(); + for (i, task) in tasks.iter().enumerate() { + if task.date >= start_period && task.date < end_period { + week_task_ids.push(i); + } + } + + let min_incr: i64 = 15; + let timespans_per_day = (24 * 60 ) / min_incr; + let mut table: Vec>> = Vec::new(); + for i in 0..timespans_per_day { + table.push(Vec::new()); + for _ in 0..n_days { + table[i as usize].push(None); + } + } + + html.push_str(""); + html.push_str(""); + for offset in 0..n_days { + html.push_str(""); + } + html.push_str(""); + + week_task_ids.sort_by(|a, b| cmp_tasks(&tasks[*a], &tasks[*b])); + + for i in 0..timespans_per_day { + let timespan_start = NaiveTime::from_hms(0, 0, 0) + Duration::minutes(i * min_incr); + let timespan_end = NaiveTime::from_hms(0, 0, 0) + Duration::minutes((i + 1) * min_incr); + for offset in 0..n_days { + // (1) Find all task ids that intersect this timespan on this day. + let this_date = start_period + Duration::days(offset); + let on_this_date: Vec + = week_task_ids.iter().map(|&idx| idx) + .filter(|&idx| tasks[idx].date == this_date).collect(); + let intersecting: Vec + = on_this_date.iter().map(|&idx| idx) + .filter(|&idx| does_overlap(×pan_start, ×pan_end, &tasks[idx])).collect(); + // (2) Find the event ending first and place it in the table. + table[i as usize][offset as usize] = intersecting.iter() + .map(|&idx| idx) + .min_by_key(|&idx| tasks[idx].end_time.expect("Should have an end time at this point...")); + } + } + for row_idx in 0..timespans_per_day { + let timespan_start = NaiveTime::from_hms(0, 0, 0) + Duration::minutes(row_idx * min_incr); + html.push_str(""); + for col_idx in 0..n_days { + let task_idx = table[row_idx as usize][col_idx as usize]; + match task_idx { + Some(idx) => { + if row_idx == 0 || table[(row_idx - 1) as usize][col_idx as usize] != task_idx { + let mut rowspan = 0; + for i in row_idx..timespans_per_day { + if table[i as usize][col_idx as usize] == task_idx { + rowspan += 1; + } else { + break; + } + } + html.push_str(""); + } + }, + _ => { + html.push_str(""); + }, + } + } + html.push_str(""); + } + html.push_str("
Time"); + html.push_str(&(start_period + Duration::days(offset)).format("%a %-m/%-d/%y").to_string()); + html.push_str("
"); + html.push_str(×pan_start.format("%l:%M %p").to_string()); + html.push_str(""); + html.push_str(""); + match privacy { + CalendarPrivacy::Public => { + let mut any_yet = false; + for tag in &tasks[idx].tags { + if public_tags.contains_key(tag.as_str()) { + if any_yet { html.push_str(", "); } + html.push_str(tag.as_str()); + any_yet = true; + } + } + if tasks[idx].tags.contains(&&"public".to_string()) { + if any_yet { html.push_str(": \""); } + html.push_str(&tasks[idx].details.as_str()); + html.push_str("\""); + any_yet = true; + } + if !any_yet { + html.push_str("has-task"); + } + }, + CalendarPrivacy::Private => { + html.push_str(&tasks[idx].details.as_str()); + }, + } + html.push_str("
    "); + for i in week_task_ids.iter() { + let task = &tasks[*i]; + let is_public = task.tags.contains(&"public".to_string()); + match (&privacy, &task.start_time, &is_public) { + (CalendarPrivacy::Public, None, false) => continue, + _ => (), + } + html.push_str("
  • "); + html.push_str(task.date.format("%a %-m/%-d/%y ").to_string().as_str()); + match [task.start_time, task.end_time] { + [Some(start), Some(end)] => { + html.push_str(start.format("%l:%M%p").to_string().as_str()); + html.push_str(" -- "); + html.push_str(end.format("%l:%M%p").to_string().as_str()); + } + _ => (), + } + html.push_str("
      "); + match privacy { + CalendarPrivacy::Public => { + if task.tags.contains(&"public".to_string()) { + html.push_str("
    • Description: "); + html.push_str(task.details.as_str()); + html.push_str("
    • "); + } + for tag in &task.tags { + if public_tags.contains_key(&tag.as_str()) { + html.push_str("
    • Tagged "); + html.push_str(tag.as_str()); + html.push_str(": "); + html.push_str(public_tags.get(&tag.as_str()).expect("")); + html.push_str("
    • "); + } + } + }, + CalendarPrivacy::Private => { + html.push_str("
    • Description: "); + html.push_str(task.details.as_str()); + html.push_str("
    • Tagged: "); + for (i, tag) in task.tags.iter().enumerate() { + if i > 0 { html.push_str(", "); } + html.push_str(""); + html.push_str(tag.as_str()); + html.push_str(""); + } + html.push_str("
    • "); + } + } + html.push_str("
    "); + html.push_str("
  • "); + } + html.push_str("
src"); + return html; +} diff --git a/src/main.rs b/src/main.rs index 593d413..6dfb441 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,325 +1,12 @@ use std::fs::File; use std::io::prelude::*; use std::path::Path; -use std::str::FromStr; -use std::cmp::Ordering; -use std::collections::HashMap; -use chrono::{Datelike, NaiveDate, NaiveTime, Weekday, Duration, Timelike, Local}; -// use chrono::format::ParseError; -struct Task { - date: NaiveDate, - start_time: Option, - end_time: Option, - details: String, - tags: Vec, -} - -fn parse_date_line(l: &str) -> Option { - for maybe_date_str in l.split(' ') { - if let Ok(date) = NaiveDate::parse_from_str(maybe_date_str, "%m/%d/%y") { - return Some(date); - } - }; - return None; -} - -fn parse_day_line(l: &str) -> Weekday { - let daystr = l.get(3..).expect("Day-of-week line not long enough..."); - return Weekday::from_str(daystr).expect("Misparse day-of-week str..."); -} - -fn parse_time(s_: &str) -> NaiveTime { - let formats = vec!["%l:%M%p", "%H:%M"]; - let mut s = s_.to_string(); - if !s.contains(":") { - if s.ends_with("M") { - s.insert_str(s.len() - 2, ":00"); - } else { - s.push_str(":00"); - } - } - for format in formats { - if let Ok(parsed) = NaiveTime::parse_from_str(&s, format) { - if !format.contains("%p") && parsed.hour() < 6 { - return parsed + Duration::hours(12); - } - return parsed; - } - } - panic!("Couldn't parse time {}", s); -} - -fn parse_duration(s: &str) -> chrono::Duration { - // We try to find Mm, HhMm, Hh - if s.contains("h") && s.contains("m") { - // TODO: Decompose this case into the two below. - let hstr = s.split("h").collect::>().get(0).expect("").to_string(); - let mstr = s.split("h").collect::>().get(1).expect("").split("m").collect::>().get(0).expect("Expected XhYm").to_string(); - let secs = ((hstr.parse::().unwrap() * 60) - + (mstr.parse::().unwrap())) * 60; - return chrono::Duration::from_std(std::time::Duration::new(secs, 0)).unwrap(); - } else if s.contains("h") { - let hstr = s.split("h").collect::>().get(0).expect("").to_string(); - let secs = hstr.parse::().unwrap() * 60 * 60; - return chrono::Duration::from_std(std::time::Duration::new(secs, 0)).unwrap(); - } else if s.contains("m") { - let hstr = s.split("m").collect::>().get(0).expect("").to_string(); - let secs = hstr.parse::().unwrap() * 60; - return chrono::Duration::from_std(std::time::Duration::new(secs, 0)).unwrap(); - } - panic!("Couldn't parse duration {}", s); -} - -fn handle_task_details(l: &str, t: &mut Task) { - for tok in l.split(' ') { - if tok.starts_with("+") { - let tag = tok.get(1..).expect("Unexpected"); - t.tags.push(tag.to_string()); - } else if tok.starts_with("@") { - let timestr = tok.get(1..).expect("Unexpected"); - if timestr.contains("+") { // @Start+Duration - let parts: Vec<&str> = timestr.split("+").collect(); - match parts[..] { - [startstr, durstr] => { - t.start_time = Some(parse_time(startstr)); - t.end_time = Some(t.start_time.unwrap() + parse_duration(durstr)); - }, - _ => panic!("Not 2 parts to {}\n", timestr) - } - } else if timestr.contains("--") { // @Start--End - let parts: Vec<&str> = timestr.split("--").collect(); - match parts[..] { - [startstr, endstr] => { - t.start_time = Some(parse_time(startstr)); - t.end_time = Some(parse_time(endstr)); - if t.start_time > t.end_time { - panic!("Start time {} interpreted as after end time {}", - startstr, endstr); - } - }, - _ => panic!("Not 2 parts to {}\n", timestr) - } - } else { - panic!("'{}' is not of the form Start+Duration or Start--End\n", timestr); - } - } else { - if t.details.len() > 0 { - t.details.push(' '); - } - t.details.push_str(tok.trim()); - } - } -} - -fn cmp_tasks(a: &Task, b: &Task) -> Ordering { - if a.date != b.date { - return if a.date < b.date { Ordering::Less } else { Ordering::Greater }; - } - match [a.start_time, b.start_time] { - [None, None] => return Ordering::Equal, - [None, Some(_)] => return Ordering::Greater, - [Some(_), None] => return Ordering::Less, - [Some(atime), Some(btime)] => return if atime < btime { Ordering::Less } else { Ordering::Greater }, - } -} - -fn does_overlap(timespan_start: &NaiveTime, timespan_end: &NaiveTime, task: &Task) -> bool { - return match [task.start_time, task.end_time] { - [Some(start), Some(end)] => (&start <= timespan_start && timespan_start < &end) - || (&start < timespan_end && timespan_end < &end), - _ => false, - } -} - -enum CalendarPrivacy { - Public, - Private, -} - -fn tasks_to_html(tasks: &Vec, privacy: CalendarPrivacy) -> String { - let public_tags = HashMap::from([ - ("busy", "I will be genuinely busy, e.g., a meeting with others."), - ("rough", "The nature of the event (e.g., a hike) makes it difficult to preduct the exact start/end times."), - ("tentative", "This event timing is only tentative."), - ("join-me", "This is an open event; if you're interested in joining please reach out!"), - ("self", "This is scheduled time for me to complete a specific work or personal task; I can usually reschedule such blocks when requested."), - ]); - - let mut html = "Calendar".to_string(); - - let n_days = 14; - let start_period = Local::now().date().naive_local(); - let end_period = start_period + Duration::days(n_days); - let mut week_task_ids: Vec = Vec::new(); - for (i, task) in tasks.iter().enumerate() { - if task.date >= start_period && task.date < end_period { - week_task_ids.push(i); - } - } - - let min_incr: i64 = 15; - let timespans_per_day = (24 * 60 ) / min_incr; - let mut table: Vec>> = Vec::new(); - for i in 0..timespans_per_day { - table.push(Vec::new()); - for _ in 0..n_days { - table[i as usize].push(None); - } - } - - html.push_str(""); - html.push_str(""); - for offset in 0..n_days { - html.push_str(""); - } - html.push_str(""); - - week_task_ids.sort_by(|a, b| cmp_tasks(&tasks[*a], &tasks[*b])); - - for i in 0..timespans_per_day { - let timespan_start = NaiveTime::from_hms(0, 0, 0) + Duration::minutes(i * min_incr); - let timespan_end = NaiveTime::from_hms(0, 0, 0) + Duration::minutes((i + 1) * min_incr); - for offset in 0..n_days { - // (1) Find all task ids that intersect this timespan on this day. - let this_date = start_period + Duration::days(offset); - let on_this_date: Vec - = week_task_ids.iter().map(|&idx| idx) - .filter(|&idx| tasks[idx].date == this_date).collect(); - let intersecting: Vec - = on_this_date.iter().map(|&idx| idx) - .filter(|&idx| does_overlap(×pan_start, ×pan_end, &tasks[idx])).collect(); - // (2) Find the event ending first and place it in the table. - table[i as usize][offset as usize] = intersecting.iter() - .map(|&idx| idx) - .min_by_key(|&idx| tasks[idx].end_time.expect("Should have an end time at this point...")); - } - } - for row_idx in 0..timespans_per_day { - let timespan_start = NaiveTime::from_hms(0, 0, 0) + Duration::minutes(row_idx * min_incr); - html.push_str(""); - for col_idx in 0..n_days { - let task_idx = table[row_idx as usize][col_idx as usize]; - match task_idx { - Some(idx) => { - if row_idx == 0 || table[(row_idx - 1) as usize][col_idx as usize] != task_idx { - let mut rowspan = 0; - for i in row_idx..timespans_per_day { - if table[i as usize][col_idx as usize] == task_idx { - rowspan += 1; - } else { - break; - } - } - html.push_str(""); - } - }, - _ => { - html.push_str(""); - }, - } - } - html.push_str(""); - } - html.push_str("
Time"); - html.push_str(&(start_period + Duration::days(offset)).format("%a %-m/%-d/%y").to_string()); - html.push_str("
"); - html.push_str(×pan_start.format("%l:%M %p").to_string()); - html.push_str(""); - html.push_str(""); - match privacy { - CalendarPrivacy::Public => { - let mut any_yet = false; - for tag in &tasks[idx].tags { - if public_tags.contains_key(tag.as_str()) { - if any_yet { html.push_str(", "); } - html.push_str(tag.as_str()); - any_yet = true; - } - } - if tasks[idx].tags.contains(&&"public".to_string()) { - if any_yet { html.push_str(": \""); } - html.push_str(&tasks[idx].details.as_str()); - html.push_str("\""); - any_yet = true; - } - if !any_yet { - html.push_str("has-task"); - } - }, - CalendarPrivacy::Private => { - html.push_str(&tasks[idx].details.as_str()); - }, - } - html.push_str("
    "); - for i in week_task_ids.iter() { - let task = &tasks[*i]; - let is_public = task.tags.contains(&"public".to_string()); - match (&privacy, &task.start_time, &is_public) { - (CalendarPrivacy::Public, None, false) => continue, - _ => (), - } - html.push_str("
  • "); - html.push_str(task.date.format("%a %-m/%-d/%y ").to_string().as_str()); - match [task.start_time, task.end_time] { - [Some(start), Some(end)] => { - html.push_str(start.format("%l:%M%p").to_string().as_str()); - html.push_str(" -- "); - html.push_str(end.format("%l:%M%p").to_string().as_str()); - } - _ => (), - } - html.push_str("
      "); - match privacy { - CalendarPrivacy::Public => { - if task.tags.contains(&"public".to_string()) { - html.push_str("
    • Description: "); - html.push_str(task.details.as_str()); - html.push_str("
    • "); - } - for tag in &task.tags { - if public_tags.contains_key(&tag.as_str()) { - html.push_str("
    • Tagged "); - html.push_str(tag.as_str()); - html.push_str(": "); - html.push_str(public_tags.get(&tag.as_str()).expect("")); - html.push_str("
    • "); - } - } - }, - CalendarPrivacy::Private => { - html.push_str("
    • Description: "); - html.push_str(task.details.as_str()); - html.push_str("
    • Tagged: "); - for (i, tag) in task.tags.iter().enumerate() { - if i > 0 { html.push_str(", "); } - html.push_str(""); - html.push_str(tag.as_str()); - html.push_str(""); - } - html.push_str("
    • "); - } - } - html.push_str("
    "); - html.push_str("
  • "); - } - html.push_str("
src"); - return html; -} +mod task; +mod parse; +use parse::*; +mod html_calendar; +use html_calendar::*; // https://doc.rust-lang.org/std/fs/struct.File.html fn main() { @@ -337,47 +24,7 @@ fn main() { match file.read_to_string(&mut s) { Err(why) => panic!("Couldn't read {}: {}", display, why), Ok(_) => { - - let mut tasks = Vec::new(); - let mut start_date = None; - let mut the_date = None; - for l in s.split('\n') { - if l.starts_with("# ") { - // '# 12/27/21', starts a new week block - start_date = parse_date_line(l); - } else if l.starts_with("## ") { - // '## Monday/Tuesday/...', starts a new day block - // Need to compute the actual date, basically looking for the first one after - // start_date. - let dayofweek = parse_day_line(l); - let mut current = start_date.expect("Invalid or missing '# ' date"); - the_date = loop { - if current.weekday() == dayofweek { - break Some(current); - } - current = current.succ(); - }; - } else if l.starts_with("- [ ]") || l.starts_with("- [X]") { - // '- [ ] ...', starts a new task block - let date = the_date.expect("No current date parsed yet..."); - tasks.push(Task { - date: date, - start_time: None, - end_time: None, - details: "".to_string(), - tags: Vec::new(), - }); - let details = l.get(5..).expect("").trim(); - handle_task_details(details, tasks.last_mut().expect("Unexpected error...")); - } else if l.starts_with(" ") { - // Extends the last task. - handle_task_details(l, tasks.last_mut().expect("Unexpected error...")); - } else { - if l.trim().len() > 0 { - eprint!("Ignoring line: {}\n", l); - } - } - } + let tasks = parse_file_str(&s); let public_html = tasks_to_html(&tasks, CalendarPrivacy::Public); let private_html = tasks_to_html(&tasks, CalendarPrivacy::Private); // https://riptutorial.com/rust/example/4276/write-in-a-file diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000..079e83a --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,144 @@ +use std::str::FromStr; +use chrono::{Datelike, NaiveDate, NaiveTime, Weekday, Duration, Timelike}; + +// mod task; +use crate::task::*; + +pub fn parse_file_str(file_str: &str) -> Vec { + let mut tasks = Vec::new(); + let mut start_date = None; + let mut the_date = None; + for l in file_str.split('\n') { + if l.starts_with("# ") { + // '# 12/27/21', starts a new week block + start_date = parse_date_line(l); + } else if l.starts_with("## ") { + // '## Monday/Tuesday/...', starts a new day block + // Need to compute the actual date, basically looking for the first one after + // start_date. + let dayofweek = parse_day_line(l); + let mut current = start_date.expect("Invalid or missing '# ' date"); + the_date = loop { + if current.weekday() == dayofweek { + break Some(current); + } + current = current.succ(); + }; + } else if l.starts_with("- [ ]") || l.starts_with("- [X]") { + // '- [ ] ...', starts a new task block + let date = the_date.expect("No current date parsed yet..."); + tasks.push(Task { + date: date, + start_time: None, + end_time: None, + details: "".to_string(), + tags: Vec::new(), + }); + let details = l.get(5..).expect("").trim(); + handle_task_details(details, tasks.last_mut().expect("Unexpected error...")); + } else if l.starts_with(" ") { + // Extends the last task. + handle_task_details(l, tasks.last_mut().expect("Unexpected error...")); + } else if l.trim().len() > 0 { + eprint!("Ignoring line: {}\n", l); + } + } + return tasks; +} + +fn parse_date_line(l: &str) -> Option { + for maybe_date_str in l.split(' ') { + if let Ok(date) = NaiveDate::parse_from_str(maybe_date_str, "%m/%d/%y") { + return Some(date); + } + }; + return None; +} + +fn parse_day_line(l: &str) -> Weekday { + let daystr = l.get(3..).expect("Day-of-week line not long enough..."); + return Weekday::from_str(daystr).expect("Misparse day-of-week str..."); +} + +fn parse_time(s_: &str) -> NaiveTime { + let formats = vec!["%l:%M%p", "%H:%M"]; + let mut s = s_.to_string(); + if !s.contains(":") { + if s.ends_with("M") { + s.insert_str(s.len() - 2, ":00"); + } else { + s.push_str(":00"); + } + } + for format in formats { + if let Ok(parsed) = NaiveTime::parse_from_str(&s, format) { + if !format.contains("%p") && parsed.hour() < 6 { + return parsed + Duration::hours(12); + } + return parsed; + } + } + panic!("Couldn't parse time {}", s); +} + +fn parse_duration(s: &str) -> chrono::Duration { + // We try to find Mm, HhMm, Hh + if s.contains("h") && s.contains("m") { + // TODO: Decompose this case into the two below. + let hstr = s.split("h").collect::>().get(0).expect("").to_string(); + let mstr = s.split("h").collect::>().get(1).expect("").split("m").collect::>().get(0).expect("Expected XhYm").to_string(); + let secs = ((hstr.parse::().unwrap() * 60) + + (mstr.parse::().unwrap())) * 60; + return chrono::Duration::from_std(std::time::Duration::new(secs, 0)).unwrap(); + } else if s.contains("h") { + let hstr = s.split("h").collect::>().get(0).expect("").to_string(); + let secs = hstr.parse::().unwrap() * 60 * 60; + return chrono::Duration::from_std(std::time::Duration::new(secs, 0)).unwrap(); + } else if s.contains("m") { + let hstr = s.split("m").collect::>().get(0).expect("").to_string(); + let secs = hstr.parse::().unwrap() * 60; + return chrono::Duration::from_std(std::time::Duration::new(secs, 0)).unwrap(); + } + panic!("Couldn't parse duration {}", s); +} + +fn handle_task_details(l: &str, t: &mut Task) { + for tok in l.split(' ') { + if tok.starts_with("+") { + let tag = tok.get(1..).expect("Unexpected"); + t.tags.push(tag.to_string()); + } else if tok.starts_with("@") { + let timestr = tok.get(1..).expect("Unexpected"); + if timestr.contains("+") { // @Start+Duration + let parts: Vec<&str> = timestr.split("+").collect(); + match parts[..] { + [startstr, durstr] => { + t.start_time = Some(parse_time(startstr)); + t.end_time = Some(t.start_time.unwrap() + parse_duration(durstr)); + }, + _ => panic!("Not 2 parts to {}\n", timestr) + } + } else if timestr.contains("--") { // @Start--End + let parts: Vec<&str> = timestr.split("--").collect(); + match parts[..] { + [startstr, endstr] => { + t.start_time = Some(parse_time(startstr)); + t.end_time = Some(parse_time(endstr)); + if t.start_time > t.end_time { + panic!("Start time {} interpreted as after end time {}", + startstr, endstr); + } + }, + _ => panic!("Not 2 parts to {}\n", timestr) + } + } else { + panic!("'{}' is not of the form Start+Duration or Start--End\n", timestr); + } + } else { + if t.details.len() > 0 { + t.details.push(' '); + } + t.details.push_str(tok.trim()); + } + } +} diff --git a/src/task.rs b/src/task.rs new file mode 100644 index 0000000..871d8ff --- /dev/null +++ b/src/task.rs @@ -0,0 +1,30 @@ +use std::cmp::Ordering; +use chrono::{NaiveDate, NaiveTime}; + +pub struct Task { + pub date: NaiveDate, + pub start_time: Option, + pub end_time: Option, + pub details: String, + pub tags: Vec, +} + +pub fn cmp_tasks(a: &Task, b: &Task) -> Ordering { + if a.date != b.date { + return if a.date < b.date { Ordering::Less } else { Ordering::Greater }; + } + match [a.start_time, b.start_time] { + [None, None] => return Ordering::Equal, + [None, Some(_)] => return Ordering::Greater, + [Some(_), None] => return Ordering::Less, + [Some(atime), Some(btime)] => return if atime < btime { Ordering::Less } else { Ordering::Greater }, + } +} + +pub fn does_overlap(timespan_start: &NaiveTime, timespan_end: &NaiveTime, task: &Task) -> bool { + return match [task.start_time, task.end_time] { + [Some(start), Some(end)] => (&start <= timespan_start && timespan_start < &end) + || (&start < timespan_end && timespan_end < &end), + _ => false, + } +} -- cgit v1.2.3