poloto_chrono/
lib.rs

1//! Plot unix timestamps.
2//!
3//! Does not implement dashes/grid lines because due to leap days, the distance
4//! between the dashes can't be constant.
5//!
6//!
7//!
8//!  
9
10#[cfg(doctest)]
11mod test_readme {
12    macro_rules! external_doc_test {
13        ($x:expr) => {
14            #[doc = $x]
15            extern "C" {}
16        };
17    }
18
19    external_doc_test!(include_str!("../README.md"));
20}
21
22mod tick_finder;
23mod unixtime;
24
25use std::fmt::Display;
26
27use poloto::plotnum::*;
28use poloto::ticks::*;
29use poloto::*;
30
31use chrono::prelude::*;
32use chrono::DateTime;
33pub use unixtime::*;
34
35///
36/// Returns a 3 letter string for a month. input must be in the range `[1,12]` or it will panic.
37///
38pub fn month_str(month: u32) -> &'static str {
39    match month {
40        1 => "Jan",
41        2 => "Feb",
42        3 => "Mar",
43        4 => "Apr",
44        5 => "May",
45        6 => "Jun",
46        7 => "Jul",
47        8 => "Aug",
48        9 => "Sep",
49        10 => "Oct",
50        11 => "Nov",
51        12 => "Dec",
52        _ => unreachable!(),
53    }
54}
55
56pub struct UnixTimeTickFmt<T: TimeZone> {
57    timezone: T,
58}
59
60impl UnixTimeTickFmt<Utc> {
61    pub fn new() -> Self {
62        Self::with_timezone(Utc)
63    }
64}
65impl Default for UnixTimeTickFmt<Utc> {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70impl<T: TimeZone> UnixTimeTickFmt<T> {
71    pub fn with_timezone(timezone: T) -> Self {
72        UnixTimeTickFmt { timezone }
73    }
74}
75
76pub struct UnixTimeFmt<T: TimeZone + Display> {
77    step: StepUnit,
78    timezone: T,
79    start: UnixTime,
80    footnote: char,
81}
82impl<T: TimeZone + Display> UnixTimeFmt<T> {
83    pub fn footnote(&self) -> char {
84        self.footnote
85    }
86    pub fn step(&self) -> &StepUnit {
87        &self.step
88    }
89    pub fn timezone(&self) -> &T {
90        &self.timezone
91    }
92    pub fn start(&self) -> &UnixTime {
93        &self.start
94    }
95}
96impl<T> crate::ticks::tick_fmt::TickFmt<UnixTime> for UnixTimeFmt<T>
97where
98    T: chrono::TimeZone + Display,
99    T::Offset: Display,
100{
101    fn write_tick(&self, writer: &mut dyn std::fmt::Write, val: &UnixTime) -> std::fmt::Result {
102        if *val == self.start {
103            write!(
104                writer,
105                "{}{}",
106                val.dynamic_format(&self.timezone, &self.step),
107                self.footnote
108            )
109        } else {
110            write!(writer, "{}", val.dynamic_format(&self.timezone, &self.step))
111        }
112    }
113
114    fn write_where(&self, writer: &mut dyn std::fmt::Write) -> std::fmt::Result {
115        let f = self.start.dynamic_where_format(&self.timezone, &self.step);
116
117        write!(
118            writer,
119            "{}{} in {} in TZ:{}",
120            self.footnote, f, self.step, &self.timezone
121        )
122        //let val = self.start.datetime(&self.timezone);
123        //write!(writer, "{}{} in {}", self.footnote, val, self.step,)?;
124    }
125}
126
127impl<T: TimeZone + Display> TickDistGen<UnixTime> for UnixTimeTickFmt<T>
128where
129    T::Offset: Display,
130{
131    type Res = TickDistribution<Vec<UnixTime>, UnixTimeFmt<T>>;
132
133    fn generate(
134        self,
135        data: &ticks::DataBound<UnixTime>,
136        canvas: &RenderFrameBound,
137        req: IndexRequester,
138    ) -> Self::Res {
139        let range = [data.min, data.max];
140
141        assert!(range[0] <= range[1]);
142        let ideal_num_steps = canvas.ideal_num_steps;
143
144        let ideal_num_steps = ideal_num_steps.max(2);
145
146        let [start, end] = range;
147        let mut t = tick_finder::BestTickFinder::new(end, ideal_num_steps);
148
149        let steps_yr = &[1, 2, 5, 10, 20, 25, 50, 100, 200, 500, 1000, 2000, 5000];
150        let steps_mo = &[1, 2, 3, 6];
151        let steps_dy = &[1, 2, 4, 5, 7];
152        let steps_hr = &[1, 2, 4, 6];
153        let steps_mi = &[1, 2, 10, 15, 30];
154        let steps_se = &[1, 2, 5, 10, 15, 30];
155
156        let d = start.datetime(&self.timezone);
157        use StepUnit::*;
158        t.consider_meta(YR, UnixYearGenerator { date: d.clone() }, steps_yr);
159        t.consider_meta(MO, UnixMonthGenerator { date: d.clone() }, steps_mo);
160        t.consider_meta(DY, UnixDayGenerator { date: d.clone() }, steps_dy);
161        t.consider_meta(HR, UnixHourGenerator { date: d.clone() }, steps_hr);
162        t.consider_meta(MI, UnixMinuteGenerator { date: d.clone() }, steps_mi);
163        t.consider_meta(SE, UnixSecondGenerator { date: d }, steps_se);
164
165        let ret = t.into_best().unwrap();
166
167        let ticks: Vec<_> = ret.ticks.into_iter().collect();
168
169        assert!(ticks.len() >= 2);
170
171        let start = ticks[0];
172
173        let index = req.request();
174
175        let footnote = match index {
176            0 => '¹',
177            1 => '²',
178            _ => unreachable!("There is a maximum of only two axis!"),
179        };
180
181        TickDistribution {
182            res: TickRes { dash_size: None },
183            iter: ticks,
184            fmt: UnixTimeFmt {
185                timezone: self.timezone,
186                step: ret.unit_data,
187                footnote,
188                start,
189            },
190        }
191    }
192}
193
194///
195/// Conveys what unit is being used for step sizes.
196///
197#[derive(Copy, Clone, Debug, Eq, PartialEq)]
198pub enum StepUnit {
199    YR,
200    MO,
201    DY,
202    HR,
203    MI,
204    SE,
205}
206
207impl StepUnit {
208    pub fn is_years(&self) -> bool {
209        *self == StepUnit::YR
210    }
211    pub fn is_months(&self) -> bool {
212        *self == StepUnit::MO
213    }
214    pub fn is_days(&self) -> bool {
215        *self == StepUnit::DY
216    }
217    pub fn is_hours(&self) -> bool {
218        *self == StepUnit::HR
219    }
220
221    pub fn is_minutes(&self) -> bool {
222        *self == StepUnit::MI
223    }
224
225    pub fn is_seconds(&self) -> bool {
226        *self == StepUnit::SE
227    }
228}
229impl std::fmt::Display for StepUnit {
230    fn fmt(&self, a: &mut std::fmt::Formatter) -> std::fmt::Result {
231        use StepUnit::*;
232        let val = match &self {
233            YR => "Years",
234            MO => "Months",
235            DY => "Days",
236            HR => "Hours",
237            MI => "Minutes",
238            SE => "Seconds",
239        };
240        write!(a, "{}", val)
241    }
242}
243
244impl plotnum::AsPlotnum for &UnixTime {
245    type Target = UnixTime;
246    fn as_plotnum(&self) -> &Self::Target {
247        self
248    }
249}
250
251impl plotnum::AsPlotnum for &mut UnixTime {
252    type Target = UnixTime;
253    fn as_plotnum(&self) -> &Self::Target {
254        self
255    }
256}
257
258impl HasDefaultTicks for UnixTime {
259    type DefaultTicks = UnixTimeTickFmt<Utc>;
260    fn default_ticks() -> Self::DefaultTicks {
261        UnixTimeTickFmt::new()
262    }
263}
264impl PlotNum for UnixTime {
265    #[inline(always)]
266    fn is_hole(&self) -> bool {
267        false
268    }
269
270    #[inline(always)]
271    fn scale(&self, range: &[UnixTime; 2], max: f64) -> f64 {
272        let val = *self;
273        let [val1, val2] = range;
274        let [val1, val2] = [val1.0, val2.0];
275        assert!(val1 <= val2);
276        let diff = (val2 - val1) as f64;
277        let scale = max / diff;
278        val.0 as f64 * scale
279    }
280    #[inline(always)]
281    fn unit_range(offset: Option<UnixTime>) -> [UnixTime; 2] {
282        if let Some(o) = offset {
283            [o, UnixTime(o.0 + 1)]
284        } else {
285            [UnixTime(0), UnixTime(1)]
286        }
287    }
288}