1use bevy::prelude::Reflect;
2use egui::emath;
3use serde::{Deserialize, Serialize};
4use std::ops;
5
6#[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#[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 #[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 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 #[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 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 #[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 #[inline]
213 pub fn normalized(self) -> Self {
214 Self(self.0.rem_euclid(86400))
215 }
216 #[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#[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 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}