use chrono::{Datelike, NaiveTime, Weekday};
use serde::de::{IntoDeserializer, Visitor};
use serde::{Deserialize, Deserializer};
use std::fmt::Display;
use std::marker::PhantomData;
use std::str::FromStr;
use std::sync::Mutex;
fn space_separated<'de, V, D>(deserializer: D) -> Result<V, D::Error>
where
V: FromIterator<Weekday>,
D: Deserializer<'de>,
{
struct SpaceSeparated<V, T>(PhantomData<V>, PhantomData<T>);
impl<'de, V, T> Visitor<'de> for SpaceSeparated<V, T>
where
V: FromIterator<Weekday>,
T: FromStr,
T::Err: Display,
{
type Value = V;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("space separated days")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let iter = v
.split_whitespace()
.map(|v| v.to_string())
.map(|v| Weekday::deserialize(v.into_deserializer()));
Result::from_iter(iter)
}
}
let visitor = SpaceSeparated(PhantomData, PhantomData::<String>);
deserializer.deserialize_str(visitor)
}
#[derive(Debug, Deserialize)]
pub struct CSVTime {
#[serde(deserialize_with = "space_separated")]
days: Vec<Weekday>,
name: String,
begin: String,
end: String,
}
#[derive(Debug)]
pub struct ClassTime {
class: String,
days: Vec<Weekday>,
begin: NaiveTime,
end: NaiveTime,
}
impl From<CSVTime> for ClassTime {
fn from(value: CSVTime) -> Self {
ClassTime {
class: value.name,
days: value.days,
begin: NaiveTime::parse_from_str(value.begin.as_str(), "%H:%M").unwrap(),
end: NaiveTime::parse_from_str(value.end.as_str(), "%H:%M").unwrap(),
}
}
}
struct PrintableTime {
hours: i64,
minutes: i64,
seconds: i64,
}
impl PrintableTime {
fn from_duration(d: Duration) -> Self {
let total_ms = d.num_milliseconds();
let total_h = d.num_hours();
let total_m = d.num_minutes();
let total_s = d.num_seconds();
let h = total_h;
let m = total_m - (total_h * 60);
let s = total_s - (total_m * 60);
let ms = total_ms - (total_s * 60);
return Self {
hours: h,
minutes: m,
seconds: s + if ms > 0 { 1 } else { 0 },
};
}
}
use std::{cmp::Ordering, fmt};
impl fmt::Display for PrintableTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.hours > 0 {
write!(f, "{:0>2}:", self.hours)?
}
if self.minutes > 0 {
write!(f, "{:0>2}:", self.minutes)?
}
write!(f, "{:0>2}", self.seconds)
}
}
use chrono::{prelude::Local, Duration};
#[derive(PartialEq)]
enum RelPos {
Before,
In,
After,
}
static CSVTIMES: Mutex<Vec<ClassTime>> = Mutex::new(Vec::new());
static DRAWN_GLOBAL: Mutex<LastDrawn> = Mutex::new(LastDrawn::None);
#[derive(PartialEq, Debug, Clone, Copy)]
enum LastDrawn {
Time, Before, Out, None, }
pub enum DrawType {
In, Before, Out, }
pub type DrawFn = fn(
draw_type: DrawType,
redraw_all: bool,
class: &String,
time_left: &String,
current_time: &String,
);
static EMPTY: String = String::new();
pub fn get_time_left(draw: &DrawFn) {
let local_time = Local::now();
let current_time: NaiveTime = local_time.time();
let mut time_class_pos: Vec<RelPos> = Vec::new(); let mut found_class = false;
let csvtimes_local = CSVTIMES.lock().unwrap();
let mut drawn = DRAWN_GLOBAL.lock().unwrap();
let current_day = local_time.weekday();
for i in csvtimes_local
.iter()
.filter(|x| x.days.contains(¤t_day))
{
if (i.begin..=i.end).contains(¤t_time) {
time_class_pos.push(RelPos::In);
let d = i.end.signed_duration_since(current_time);
let p = PrintableTime::from_duration(d);
let time_left = format!("{}", p);
let current_time = current_time.format("%I:%M:%S %P").to_string();
draw(
DrawType::In,
*drawn != LastDrawn::Time,
&i.class,
&time_left,
¤t_time,
);
*drawn = LastDrawn::Time;
found_class = true;
} else {
time_class_pos.push(match i.begin.cmp(¤t_time) {
Ordering::Greater => RelPos::Before,
Ordering::Equal => unreachable!(), Ordering::Less => match i.end.cmp(¤t_time) {
Ordering::Greater | Ordering::Equal => unreachable!(), Ordering::Less => RelPos::After,
},
});
}
} if found_class == false {
let mut first_before_i = None;
for (i, pos) in time_class_pos.iter().enumerate() {
if pos == &RelPos::Before {
first_before_i = Some(i);
break;
}
}
if let Some(first_before_i) = first_before_i {
let first_before = &csvtimes_local[first_before_i];
let d = first_before.begin.signed_duration_since(current_time);
let p = PrintableTime::from_duration(d);
let time_left = format!("{}", p);
let current_time = current_time.format("%I:%M:%S %P").to_string();
draw(
DrawType::Before,
*drawn != LastDrawn::Before,
&first_before.class,
&time_left,
¤t_time,
);
*drawn = LastDrawn::Before;
} else if *drawn != LastDrawn::Out {
if *drawn != LastDrawn::Out {
let current_time = current_time.format("%I:%M:%S %P").to_string();
draw(DrawType::Out, true, &EMPTY, &EMPTY, ¤t_time);
}
*drawn = LastDrawn::Out;
}
}
}
pub fn set_csv(csv: Vec<ClassTime>) {
*CSVTIMES.lock().unwrap() = csv;
}