Skip to main content

paiagram_core/units/
time.rs

1use std::ops;
2
3use bevy::prelude::Reflect;
4use egui::emath;
5use serde::{Deserialize, Serialize};
6
7/// A tick. Each tick is 10ms
8#[derive(
9    Reflect, Debug, Deserialize, Serialize, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord,
10)]
11pub struct Tick(pub i64);
12
13impl Tick {
14    pub const ZERO: Self = Self(0);
15    pub const TICKS_PER_SECOND: i64 = 100;
16    pub const TICKS_PER_DAY: i64 = 24 * 3600 * Self::TICKS_PER_SECOND;
17
18    pub fn to_timetable_time(self) -> TimetableTime {
19        TimetableTime((self.0 / 100) as i32)
20    }
21    pub fn from_timetable_time(time: TimetableTime) -> Self {
22        Tick(time.0 as i64 * 100)
23    }
24    pub fn as_seconds_f64(self) -> f64 {
25        let ticks_per_second = Self::from_timetable_time(TimetableTime(1)).0 as f64;
26        self.0 as f64 / ticks_per_second
27    }
28
29    #[inline]
30    pub fn normalized_with(self, cycle: Tick) -> Self {
31        if cycle.0 <= 0 {
32            return self;
33        }
34        Self(self.0.rem_euclid(cycle.0))
35    }
36
37    #[inline]
38    pub fn normalized(self) -> Self {
39        self.normalized_with(Tick(Self::TICKS_PER_DAY))
40    }
41}
42
43impl From<TimetableTime> for Tick {
44    fn from(value: TimetableTime) -> Self {
45        Self::from_timetable_time(value)
46    }
47}
48
49impl Into<TimetableTime> for Tick {
50    fn into(self) -> TimetableTime {
51        self.to_timetable_time()
52    }
53}
54
55impl From<f64> for Tick {
56    fn from(value: f64) -> Self {
57        Tick(value as i64)
58    }
59}
60
61impl Into<f64> for Tick {
62    fn into(self) -> f64 {
63        self.0 as f64
64    }
65}
66
67/// The timetable timepoint in seconds from midnight
68#[derive(
69    Reflect, Debug, Deserialize, Serialize, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord,
70)]
71pub struct TimetableTime(pub i32);
72
73impl TimetableTime {
74    pub const ZERO: Self = Self(0);
75    #[inline]
76    pub fn as_duration(self) -> Duration {
77        Duration(self.0)
78    }
79    #[inline]
80    pub fn to_ticks(self) -> Tick {
81        Tick::from_timetable_time(self)
82    }
83    #[inline]
84    pub fn from_hms<T: Into<i32>>(h: T, m: T, s: T) -> Self {
85        TimetableTime(h.into() * 3600 + m.into() * 60 + s.into())
86    }
87    #[inline]
88    pub fn hour(&self) -> i32 {
89        self.to_hmsd().0
90    }
91    #[inline]
92    pub fn minute(&self) -> i32 {
93        self.to_hmsd().1
94    }
95    #[inline]
96    pub fn second(&self) -> i32 {
97        self.to_hmsd().2
98    }
99    #[inline]
100    pub fn day(&self) -> i32 {
101        self.to_hmsd().3
102    }
103    #[inline]
104    pub fn hours(&self) -> i32 {
105        self.0 / 3600
106    }
107    #[inline]
108    pub fn minutes(&self) -> i32 {
109        self.0 / 60
110    }
111    #[inline]
112    pub fn seconds(&self) -> i32 {
113        self.0
114    }
115    #[inline]
116    pub fn days(&self) -> i32 {
117        self.0 / 86400
118    }
119    #[inline]
120    pub fn to_hmsd(self) -> (i32, i32, i32, i32) {
121        let days = self.0.div_euclid(24 * 3600);
122        let seconds_of_day = self.0.rem_euclid(24 * 3600);
123
124        let hours = seconds_of_day / 3600;
125        let minutes = (seconds_of_day % 3600) / 60;
126        let seconds = seconds_of_day % 60;
127
128        (hours, minutes, seconds, days)
129    }
130    /// Parses a string in the following forms to [`TimetableTime`]:
131    /// - HH:MM:SS
132    /// - HH:MM
133    /// - HH:MM:SS+D
134    /// - HH:MM:SS-D
135    /// - HH:MM+D
136    /// - HH:MM-D
137    #[inline]
138    pub fn from_str(s: &str) -> Option<Self> {
139        let (time_part, day_offset_seconds) = if let Some(idx) = s.rfind(['+', '-']) {
140            let (time, offset_str) = s.split_at(idx);
141            // offset_str is "+1" or "-1", parse handles the sign for us
142            let days = offset_str.parse::<i32>().ok()?;
143            (time, days * 86400)
144        } else {
145            (s, 0)
146        };
147
148        let mut parts = time_part.split(':');
149        let h = parts.next()?.parse::<i32>().ok()?;
150        let m = parts.next()?.parse::<i32>().ok()?;
151        let s = parts
152            .next()
153            .map(|s| s.parse::<i32>().ok())
154            .flatten()
155            .unwrap_or(0);
156
157        if parts.next().is_some() {
158            return None;
159        }
160
161        Some(TimetableTime::from_hms(h, m, s + day_offset_seconds))
162    }
163    /// Parses strings in HMM, HHMM, HMMSS, HHMMSS
164    /// and with or without +D or -D
165    /// This format is commonly seen in Japanese timetables.
166    /// The +/-D is an extension.
167    #[inline]
168    pub fn from_oud2_str(s: &str) -> Option<Self> {
169        let (time_part, day_offset_seconds) = if let Some(idx) = s.rfind(['+', '-']) {
170            let (time, offset_str) = s.split_at(idx);
171            // offset_str is "+1" or "-1", parse handles the sign for us
172            let days = offset_str.parse::<i32>().ok()?;
173            (time, days * 86400)
174        } else {
175            (s, 0)
176        };
177        match time_part.len() {
178            3 => {
179                let h = time_part[0..1].parse::<i32>().ok()?;
180                let m = time_part[1..3].parse::<i32>().ok()?;
181                Some(TimetableTime::from_hms(h, m, day_offset_seconds))
182            }
183            4 => {
184                let h = time_part[0..2].parse::<i32>().ok()?;
185                let m = time_part[2..4].parse::<i32>().ok()?;
186                Some(TimetableTime::from_hms(h, m, day_offset_seconds))
187            }
188            5 => {
189                let h = time_part[0..1].parse::<i32>().ok()?;
190                let m = time_part[1..3].parse::<i32>().ok()?;
191                let s = time_part[3..5].parse::<i32>().ok()?;
192                Some(TimetableTime::from_hms(h, m, s + day_offset_seconds))
193            }
194            6 => {
195                let h = time_part[0..2].parse::<i32>().ok()?;
196                let m = time_part[2..4].parse::<i32>().ok()?;
197                let s = time_part[4..6].parse::<i32>().ok()?;
198                Some(TimetableTime::from_hms(h, m, s + day_offset_seconds))
199            }
200            _ => None,
201        }
202    }
203    /// Parses the current time to a oud2 formatted string and drop the date
204    /// offset.
205    #[inline]
206    pub fn to_oud2_str(&self, show_seconds: bool) -> String {
207        let (h, m, s, _) = self.to_hmsd();
208        if show_seconds {
209            format!("{:2}{:02}{:02}", h, m, s)
210        } else {
211            format!("{:2}{:02}", h, m)
212        }
213    }
214    /// Return the normalized time that is in 24 hour range
215    #[inline]
216    pub fn normalized(self) -> Self {
217        Self(self.0.rem_euclid(86400))
218    }
219    /// Return the normalized time that is always within 24 hours ahead of the
220    /// current time
221    #[inline]
222    pub fn normalized_ahead(&self, other: TimetableTime) -> Self {
223        let diff = other.0 - self.0;
224        Self(self.0 + diff.rem_euclid(86400))
225    }
226}
227
228impl emath::Numeric for TimetableTime {
229    const INTEGRAL: bool = true;
230    const MIN: Self = Self(i32::MIN);
231    const MAX: Self = Self(i32::MAX);
232
233    fn from_f64(num: f64) -> Self {
234        Self(num as i32)
235    }
236
237    fn to_f64(self) -> f64 {
238        self.0 as f64
239    }
240}
241
242impl std::fmt::Display for TimetableTime {
243    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244        let days = self.0.div_euclid(24 * 3600);
245        let seconds_of_day = self.0.rem_euclid(24 * 3600);
246
247        let hours = seconds_of_day / 3600;
248        let minutes = (seconds_of_day % 3600) / 60;
249        let seconds = seconds_of_day % 60;
250
251        write!(f, "{:02}:{:02}:{:02}", hours, minutes, seconds)?;
252
253        if days != 0 {
254            let sign = if days > 0 { '+' } else { '-' };
255            write!(f, "{}{}", sign, days.abs())?;
256        }
257
258        Ok(())
259    }
260}
261
262impl ops::Sub<TimetableTime> for TimetableTime {
263    type Output = Duration;
264    fn sub(self, rhs: TimetableTime) -> Self::Output {
265        Duration(self.0 - rhs.0)
266    }
267}
268
269impl ops::Add<Duration> for TimetableTime {
270    type Output = TimetableTime;
271    fn add(self, rhs: Duration) -> Self::Output {
272        TimetableTime(self.0 + rhs.0)
273    }
274}
275
276impl ops::AddAssign<Duration> for TimetableTime {
277    fn add_assign(&mut self, rhs: Duration) {
278        self.0 += rhs.0
279    }
280}
281
282impl ops::Sub<Duration> for TimetableTime {
283    type Output = TimetableTime;
284    fn sub(self, rhs: Duration) -> Self::Output {
285        TimetableTime(self.0 - rhs.0)
286    }
287}
288
289impl ops::SubAssign<Duration> for TimetableTime {
290    fn sub_assign(&mut self, rhs: Duration) {
291        self.0 -= rhs.0;
292    }
293}
294
295/// A duration in seconds.
296#[derive(
297    Reflect, Debug, Default, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord,
298)]
299pub struct Duration(pub i32);
300
301impl Duration {
302    pub const ZERO: Self = Self(0);
303    pub const MAX: Self = Self(i32::MAX);
304    #[inline]
305    pub fn to_hms(self) -> (i32, i32, i32) {
306        let hours = self.0 / 3600;
307        let minutes = (self.0 % 3600) / 60;
308        let seconds = self.0 % 60;
309        (hours, minutes, seconds)
310    }
311    pub fn to_timetable_time(self) -> TimetableTime {
312        TimetableTime(self.0)
313    }
314    pub fn to_ticks(self) -> Tick {
315        TimetableTime::to_ticks(self.to_timetable_time())
316    }
317}
318
319impl emath::Numeric for Duration {
320    const INTEGRAL: bool = true;
321    const MIN: Self = Self(i32::MIN);
322    const MAX: Self = Self(i32::MAX);
323
324    fn from_f64(num: f64) -> Self {
325        Self(num as i32)
326    }
327
328    fn to_f64(self) -> f64 {
329        self.0 as f64
330    }
331}
332
333impl std::iter::Sum for Duration {
334    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
335        let mut s = Duration::ZERO;
336        for i in iter {
337            s += i
338        }
339        s
340    }
341}
342
343impl Duration {
344    pub fn from_secs(s: i32) -> Self {
345        Self(s)
346    }
347    pub fn from_hms(h: i32, m: i32, s: i32) -> Self {
348        Self(h * 3600 + m * 60 + s)
349    }
350    /// Parses a [`Duration`] to HH:MM:SS, without the `->` arrow
351    pub fn to_string_no_arrow(&self) -> String {
352        TimetableTime(self.0).to_string()
353    }
354    #[inline]
355    pub fn from_str(s: &str) -> Option<Self> {
356        let time_parts = if let Some((_, rhs)) = s.rsplit_once('→') {
357            rhs
358        } else {
359            s
360        }
361        .trim();
362        Some(Self(TimetableTime::from_str(time_parts)?.0))
363    }
364}
365
366impl std::fmt::Display for Duration {
367    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368        write!(f, "→ {}", self.to_string_no_arrow())
369    }
370}
371
372impl ops::Add<Duration> for Duration {
373    type Output = Duration;
374    fn add(self, rhs: Duration) -> Self::Output {
375        Duration(self.0 + rhs.0)
376    }
377}
378
379impl ops::AddAssign<Duration> for Duration {
380    fn add_assign(&mut self, rhs: Duration) {
381        self.0 += rhs.0;
382    }
383}
384
385impl ops::Sub<Duration> for Duration {
386    type Output = Duration;
387    fn sub(self, rhs: Duration) -> Self::Output {
388        Duration(self.0 - rhs.0)
389    }
390}
391
392impl ops::SubAssign<Duration> for Duration {
393    fn sub_assign(&mut self, rhs: Duration) {
394        self.0 -= rhs.0;
395    }
396}
397
398impl ops::Add<TimetableTime> for Duration {
399    type Output = TimetableTime;
400    fn add(self, rhs: TimetableTime) -> Self::Output {
401        TimetableTime(self.0 + rhs.0)
402    }
403}
404
405impl ops::Div<i32> for Duration {
406    type Output = Duration;
407    fn div(self, rhs: i32) -> Self::Output {
408        Duration(self.0 / rhs)
409    }
410}
411
412impl ops::DivAssign<i32> for Duration {
413    fn div_assign(&mut self, rhs: i32) {
414        self.0 /= rhs;
415    }
416}
417
418impl ops::Mul<i32> for Duration {
419    type Output = Duration;
420    fn mul(self, rhs: i32) -> Self::Output {
421        Duration(self.0 * rhs)
422    }
423}
424
425impl ops::MulAssign<i32> for Duration {
426    fn mul_assign(&mut self, rhs: i32) {
427        self.0 *= rhs;
428    }
429}
430
431impl ops::Mul<Duration> for i32 {
432    type Output = Duration;
433    fn mul(self, rhs: Duration) -> Self::Output {
434        Duration(self * rhs.0)
435    }
436}