Skip to main content

paiagram_core/units/
time.rs

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