tallyweb_components/
time.rs

1use chrono::format::*;
2use leptos::*;
3use std::io::Write;
4
5const MINUTE_SECONDS: u64 = 60;
6const HOUR_SECONDS: u64 = MINUTE_SECONDS * 60;
7const DAY_SECONDS: u64 = HOUR_SECONDS * 24;
8
9#[component]
10pub fn Clock(
11    #[prop(optional, into)] value: Option<MaybeSignal<std::time::Duration>>,
12    #[prop(optional, default="%H:%M:%S".into(), into)] format: MaybeSignal<String>,
13    #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>,
14) -> impl IntoView {
15    let time = move || {
16        if let Some(v) = value {
17            let chrono_val = chrono::Duration::from_std(v()).unwrap();
18            let date = chrono::DateTime::UNIX_EPOCH
19                .checked_add_signed(chrono_val)
20                .unwrap();
21            date.format(&format()).to_string()
22        } else {
23            chrono::Local::now().format(&format()).to_string()
24        }
25    };
26
27    view! { <span {..attrs}>{time}</span> }
28}
29
30#[component]
31pub fn Timer(
32    #[prop(into)] value: MaybeSignal<std::time::Duration>,
33    #[prop(optional, default="%H:%M:%S".into(), into)] format: MaybeSignal<String>,
34    #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>,
35) -> impl IntoView {
36    let value = create_memo(move |_| value.get());
37    // TODO: factor this out into function
38    let stringified = Signal::derive(move || {
39        let format = format();
40        let strf_items = StrftimeItems::new(&format);
41        let mut writer = Vec::with_capacity(32);
42        for (idx, item) in strf_items.enumerate() {
43            match item {
44                Item::Literal(l) => write!(writer, "{}", l),
45                Item::OwnedLiteral(l) => write!(writer, "{}", l),
46                Item::Space(s) => write!(writer, "{}", s),
47                Item::OwnedSpace(s) => write!(writer, "{}", s),
48                Item::Numeric(typ, pad) => {
49                    let (mut val, modulo): (i128, i32) = match typ {
50                        Numeric::Year => todo!(),
51                        Numeric::YearDiv100 => todo!(),
52                        Numeric::YearMod100 => todo!(),
53                        Numeric::IsoYear => todo!(),
54                        Numeric::IsoYearDiv100 => todo!(),
55                        Numeric::IsoYearMod100 => todo!(),
56                        Numeric::Month => todo!(),
57                        Numeric::Day => ((value().as_secs() / DAY_SECONDS).into(), 28),
58                        Numeric::WeekFromSun => todo!(),
59                        Numeric::WeekFromMon => todo!(),
60                        Numeric::IsoWeek => todo!(),
61                        Numeric::NumDaysFromSun => todo!(),
62                        Numeric::WeekdayFromMon => todo!(),
63                        Numeric::Ordinal => todo!(),
64                        Numeric::Hour => ((value().as_secs() / 3600).into(), 24),
65                        Numeric::Hour12 => todo!(),
66                        Numeric::Minute => ((value().as_secs() / 60).into(), 60),
67                        Numeric::Second => ((value().as_secs()).into(), 60),
68                        Numeric::Nanosecond => ((value().as_nanos()) as i128, 1_000_000_000),
69                        Numeric::Timestamp => todo!(),
70                        Numeric::Internal(_) => todo!(),
71                        _ => unreachable!(),
72                    };
73
74                    if idx != 0 && modulo > 0 {
75                        val %= modulo as i128
76                    };
77
78                    match pad {
79                        Pad::None => write!(writer, "{}", val),
80                        Pad::Zero => write!(writer, "{:0width$}", val, width = 2),
81                        Pad::Space => write!(writer, "{:width$}", val, width = 2),
82                    }
83                }
84                Item::Fixed(f) => match f {
85                    Fixed::ShortMonthName => todo!(),
86                    Fixed::LongMonthName => todo!(),
87                    Fixed::ShortWeekdayName => todo!(),
88                    Fixed::LongWeekdayName => todo!(),
89                    Fixed::LowerAmPm => todo!(),
90                    Fixed::UpperAmPm => todo!(),
91                    Fixed::Nanosecond => write!(writer, ".{}", value().as_nanos() % 1_000_000_000),
92                    Fixed::Nanosecond3 => {
93                        write!(writer, ".{:03}", value().as_nanos() / 1_000_000 % 1_000)
94                    }
95                    Fixed::Nanosecond6 => {
96                        write!(writer, ".{:06}", value().as_nanos() / 1_000 % 1_000_000)
97                    }
98                    Fixed::Nanosecond9 => {
99                        write!(writer, ".{:09}", value().as_nanos() % 1_000_000_000)
100                    }
101                    Fixed::TimezoneName => todo!(),
102                    Fixed::TimezoneOffsetColon => todo!(),
103                    Fixed::TimezoneOffsetDoubleColon => todo!(),
104                    Fixed::TimezoneOffsetTripleColon => todo!(),
105                    Fixed::TimezoneOffsetColonZ => todo!(),
106                    Fixed::TimezoneOffset => todo!(),
107                    Fixed::TimezoneOffsetZ => todo!(),
108                    Fixed::RFC2822 => todo!(),
109                    Fixed::RFC3339 => todo!(),
110                    Fixed::Internal(_) => {
111                        if format.contains("%3f") {
112                            write!(writer, "{:03}", value().as_nanos() / 1_000_000 % 1_000)
113                        } else if format.contains("%6f") {
114                            write!(writer, "{:06}", value().as_nanos() / 1_000 % 1_000_000)
115                        } else {
116                            write!(writer, "{:09}", value().as_nanos() % 1_000_000_000)
117                        }
118                    }
119                    _ => unreachable!(),
120                },
121                Item::Error => todo!(),
122            }
123            // TODO: look into removing this unwrap
124            .unwrap();
125        }
126
127        String::from_utf8(writer).unwrap_or_default()
128    });
129
130    view! { <span {..attrs}>{stringified}</span> }
131}