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