1use std::ops::{Add, AddAssign, Div, Mul, Sub, SubAssign};
4
5mod frame_rate;
6mod offset;
7
8pub use frame_rate::FrameRate;
9pub use offset::TimeOffset;
10
11pub const TICKS_PER_SECOND: i64 = 705_600_000;
13
14const TICKS_PER_SECOND_UNSIGNED: u64 = TICKS_PER_SECOND as u64;
15
16#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
24pub struct Time(i64);
25
26#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
33pub struct Duration(u64);
34
35impl Time {
36 pub const ZERO: Self = Self(0);
38
39 pub const fn from_ticks(ticks: i64) -> Self {
41 Self(ticks)
42 }
43
44 pub fn from_seconds(seconds: f64) -> Self {
50 Self::try_from_seconds(seconds).unwrap_or_else(|error| panic!("{}", error.message))
51 }
52
53 pub fn try_from_seconds(seconds: f64) -> Result<Self, crate::ValidationError> {
55 try_rounded_ticks(seconds * TICKS_PER_SECOND as f64).map(Self)
56 }
57
58 pub fn from_frame(frame: i64, rate: FrameRate) -> Self {
69 Self::checked_from_frame(frame, rate).expect("frame time exceeds the signed tick range")
70 }
71
72 pub fn checked_from_frame(frame: i64, rate: FrameRate) -> Option<Self> {
75 let numerator =
76 i128::from(frame) * i128::from(TICKS_PER_SECOND) * i128::from(rate.denominator());
77 let denominator = i128::from(rate.numerator());
78 let ticks =
79 numerator.div_euclid(denominator) + i128::from(numerator.rem_euclid(denominator) != 0);
80 i64::try_from(ticks).ok().map(Self)
81 }
82
83 pub fn to_frame(self, rate: FrameRate) -> i64 {
88 self.checked_to_frame(rate)
89 .expect("frame index exceeds the signed 64-bit range")
90 }
91
92 pub fn checked_to_frame(self, rate: FrameRate) -> Option<i64> {
95 let numerator = i128::from(self.0) * i128::from(rate.numerator());
96 let denominator = i128::from(TICKS_PER_SECOND) * i128::from(rate.denominator());
97 i64::try_from(numerator.div_euclid(denominator)).ok()
98 }
99
100 pub fn as_seconds(self) -> f64 {
102 self.0 as f64 / TICKS_PER_SECOND as f64
103 }
104
105 pub const fn ticks(self) -> i64 {
107 self.0
108 }
109
110 pub const fn since_origin(self) -> TimeOffset {
112 TimeOffset::from_ticks(self.0)
113 }
114
115 pub fn duration_since(self, earlier: Self) -> Option<Duration> {
117 (self.0 >= earlier.0).then(|| Duration(self.0.abs_diff(earlier.0)))
118 }
119
120 pub fn checked_add(self, span: Duration) -> Option<Self> {
122 i64::try_from(span.0)
123 .ok()
124 .and_then(|ticks| self.0.checked_add(ticks))
125 .map(Self)
126 }
127
128 pub fn checked_sub(self, span: Duration) -> Option<Self> {
130 i64::try_from(span.0)
131 .ok()
132 .and_then(|ticks| self.0.checked_sub(ticks))
133 .map(Self)
134 }
135}
136
137impl Add<Duration> for Time {
138 type Output = Self;
139
140 fn add(self, span: Duration) -> Self {
141 self.checked_add(span).expect("time addition overflow")
142 }
143}
144
145impl Sub<Duration> for Time {
146 type Output = Self;
147
148 fn sub(self, span: Duration) -> Self {
149 self.checked_sub(span).expect("time subtraction overflow")
150 }
151}
152
153impl Add<TimeOffset> for Time {
154 type Output = Self;
155
156 fn add(self, offset: TimeOffset) -> Self {
157 Self(
158 self.0
159 .checked_add(offset.ticks())
160 .expect("time addition overflow"),
161 )
162 }
163}
164
165impl Sub<TimeOffset> for Time {
166 type Output = Self;
167
168 fn sub(self, offset: TimeOffset) -> Self {
169 Self(
170 self.0
171 .checked_sub(offset.ticks())
172 .expect("time subtraction overflow"),
173 )
174 }
175}
176
177impl Sub for Time {
178 type Output = TimeOffset;
179
180 fn sub(self, other: Self) -> TimeOffset {
181 TimeOffset::from_ticks(
182 self.0
183 .checked_sub(other.0)
184 .expect("time subtraction overflow"),
185 )
186 }
187}
188
189impl AddAssign<Duration> for Time {
190 fn add_assign(&mut self, span: Duration) {
191 *self = *self + span;
192 }
193}
194
195impl SubAssign<Duration> for Time {
196 fn sub_assign(&mut self, span: Duration) {
197 *self = *self - span;
198 }
199}
200
201impl AddAssign<TimeOffset> for Time {
202 fn add_assign(&mut self, offset: TimeOffset) {
203 *self = *self + offset;
204 }
205}
206
207impl SubAssign<TimeOffset> for Time {
208 fn sub_assign(&mut self, offset: TimeOffset) {
209 *self = *self - offset;
210 }
211}
212
213impl Duration {
214 pub const ZERO: Self = Self(0);
216
217 pub const fn from_ticks(ticks: u64) -> Self {
219 Self(ticks)
220 }
221
222 pub fn from_seconds(seconds: f64) -> Self {
228 Self::try_from_seconds(seconds).unwrap_or_else(|error| panic!("{}", error.message))
229 }
230
231 pub fn try_from_seconds(seconds: f64) -> Result<Self, crate::ValidationError> {
233 try_rounded_unsigned_ticks(seconds * TICKS_PER_SECOND as f64).map(Self)
234 }
235
236 pub fn from_frame(frame: u64, rate: FrameRate) -> Self {
242 Self::checked_from_frame(frame, rate).expect("frame time exceeds the tick range")
243 }
244
245 pub fn checked_from_frame(frame: u64, rate: FrameRate) -> Option<Self> {
247 let numerator = u128::from(frame)
248 * u128::from(TICKS_PER_SECOND_UNSIGNED)
249 * u128::from(rate.denominator());
250 let denominator = u128::from(rate.numerator());
251 let ticks = numerator / denominator + u128::from(numerator % denominator != 0);
252 u64::try_from(ticks).ok().map(Self)
253 }
254
255 pub fn to_frame(self, rate: FrameRate) -> u64 {
260 self.checked_to_frame(rate)
261 .expect("frame index exceeds the unsigned 64-bit range")
262 }
263
264 pub fn checked_to_frame(self, rate: FrameRate) -> Option<u64> {
266 let numerator = u128::from(self.0) * u128::from(rate.numerator());
267 let denominator = u128::from(TICKS_PER_SECOND_UNSIGNED) * u128::from(rate.denominator());
268 u64::try_from(numerator / denominator).ok()
269 }
270
271 pub fn as_seconds(self) -> f64 {
273 self.0 as f64 / TICKS_PER_SECOND as f64
274 }
275
276 pub const fn ticks(self) -> u64 {
278 self.0
279 }
280
281 pub fn after_origin(self) -> Time {
286 Time(i64::try_from(self.0).expect("duration exceeds the signed tick range"))
287 }
288
289 pub fn checked_offset(self) -> Option<TimeOffset> {
291 i64::try_from(self.0).ok().map(TimeOffset::from_ticks)
292 }
293
294 pub fn offset(self) -> TimeOffset {
299 self.checked_offset()
300 .expect("duration exceeds the signed tick range")
301 }
302
303 pub fn checked_sub(self, other: Self) -> Option<Self> {
305 self.0.checked_sub(other.0).map(Self)
306 }
307
308 pub fn saturating_sub(self, other: Self) -> Self {
310 Self(self.0.saturating_sub(other.0))
311 }
312
313 pub fn checked_mul(self, factor: u64) -> Option<Self> {
315 self.0.checked_mul(factor).map(Self)
316 }
317
318 pub fn try_mul(self, factor: f64) -> Result<Self, crate::ValidationError> {
320 if factor == 1.0 {
321 return Ok(self);
322 }
323 if !factor.is_finite() || factor < 0.0 {
324 return Err(time_error(
325 "factor",
326 "duration factor must be finite and nonnegative",
327 ));
328 }
329 try_rounded_unsigned_ticks(self.0 as f64 * factor).map(Self)
330 }
331
332 pub fn try_div(self, divisor: f64) -> Result<Self, crate::ValidationError> {
334 if !divisor.is_finite() || divisor <= 0.0 {
335 return Err(time_error(
336 "divisor",
337 "duration divisor must be finite and positive",
338 ));
339 }
340 if divisor == 1.0 {
341 return Ok(self);
342 }
343 try_rounded_unsigned_ticks(self.0 as f64 / divisor).map(Self)
344 }
345
346 pub fn try_ratio(self, divisor: Self) -> Result<f64, crate::ValidationError> {
348 if divisor.0 == 0 {
349 return Err(time_error(
350 "divisor",
351 "duration ratio requires a nonzero divisor",
352 ));
353 }
354 Ok(self.0 as f64 / divisor.0 as f64)
355 }
356}
357
358impl Add for Duration {
359 type Output = Self;
360
361 fn add(self, other: Self) -> Self {
362 Self(
363 self.0
364 .checked_add(other.0)
365 .expect("duration addition overflow"),
366 )
367 }
368}
369
370impl Sub for Duration {
371 type Output = Self;
372
373 fn sub(self, other: Self) -> Self {
378 self.checked_sub(other)
379 .expect("duration subtraction underflow")
380 }
381}
382
383impl AddAssign for Duration {
384 fn add_assign(&mut self, other: Self) {
385 *self = *self + other;
386 }
387}
388
389impl SubAssign for Duration {
390 fn sub_assign(&mut self, other: Self) {
391 *self = *self - other;
392 }
393}
394
395impl std::iter::Sum for Duration {
396 fn sum<Spans: Iterator<Item = Self>>(spans: Spans) -> Self {
397 spans.fold(Self::ZERO, |total, span| total + span)
398 }
399}
400
401impl Mul<f64> for Duration {
402 type Output = Self;
403
404 fn mul(self, factor: f64) -> Self {
409 self.try_mul(factor)
410 .unwrap_or_else(|error| panic!("{}", error.message))
411 }
412}
413
414macro_rules! integer_duration_mul {
415 ($($integer:ty),+ $(,)?) => {$ (
416 impl Mul<$integer> for Duration {
417 type Output = Duration;
418
419 fn mul(self, factor: $integer) -> Duration {
424 Duration(u64::try_from(u128::from(self.0) * u128::try_from(factor).expect("duration multiplication overflow")).expect("duration multiplication overflow"))
425 }
426 }
427 impl Mul<Duration> for $integer {
428 type Output = Duration;
429 fn mul(self, span: Duration) -> Duration { span * self }
430 }
431 )+};
432}
433
434integer_duration_mul!(i32, i64, u32, u64, usize);
435
436impl Mul<Duration> for f64 {
437 type Output = Duration;
438
439 fn mul(self, span: Duration) -> Duration {
440 span * self
441 }
442}
443
444impl Div<f64> for Duration {
445 type Output = Self;
446
447 fn div(self, divisor: f64) -> Self {
452 self.try_div(divisor)
453 .unwrap_or_else(|error| panic!("{}", error.message))
454 }
455}
456
457impl Div for Duration {
458 type Output = f64;
459
460 fn div(self, divisor: Self) -> f64 {
465 self.try_ratio(divisor)
466 .unwrap_or_else(|error| panic!("{}", error.message))
467 }
468}
469
470pub trait TimeLiteral {
478 fn seconds(self) -> Duration;
480
481 fn milliseconds(self) -> Duration;
483}
484
485impl TimeLiteral for i64 {
486 fn seconds(self) -> Duration {
487 Duration(
488 u64::try_from(self)
489 .ok()
490 .and_then(|seconds| seconds.checked_mul(TICKS_PER_SECOND_UNSIGNED))
491 .expect("seconds overflow"),
492 )
493 }
494
495 fn milliseconds(self) -> Duration {
496 Duration(
497 u64::try_from(self)
498 .ok()
499 .and_then(|milliseconds| {
500 milliseconds.checked_mul(TICKS_PER_SECOND_UNSIGNED / 1_000)
501 })
502 .expect("milliseconds overflow"),
503 )
504 }
505}
506
507impl TimeLiteral for i32 {
508 fn seconds(self) -> Duration {
509 i64::from(self).seconds()
510 }
511
512 fn milliseconds(self) -> Duration {
513 i64::from(self).milliseconds()
514 }
515}
516
517impl TimeLiteral for u64 {
518 fn seconds(self) -> Duration {
519 Duration(
520 self.checked_mul(TICKS_PER_SECOND_UNSIGNED)
521 .expect("seconds overflow"),
522 )
523 }
524
525 fn milliseconds(self) -> Duration {
526 Duration(
527 self.checked_mul(TICKS_PER_SECOND_UNSIGNED / 1_000)
528 .expect("milliseconds overflow"),
529 )
530 }
531}
532
533macro_rules! unsigned_time_literal {
534 ($($integer:ty),+ $(,)?) => {$ (
535 impl TimeLiteral for $integer {
536 fn seconds(self) -> Duration { u64::try_from(self).expect("seconds overflow").seconds() }
537 fn milliseconds(self) -> Duration { u64::try_from(self).expect("milliseconds overflow").milliseconds() }
538 }
539 )+};
540}
541
542unsigned_time_literal!(u32, usize);
543
544impl TimeLiteral for f64 {
545 fn seconds(self) -> Duration {
546 Duration::from_seconds(self)
547 }
548
549 fn milliseconds(self) -> Duration {
550 Duration(
551 try_rounded_unsigned_ticks(self * (TICKS_PER_SECOND / 1_000) as f64)
552 .unwrap_or_else(|error| panic!("{}", error.message)),
553 )
554 }
555}
556
557fn try_rounded_ticks(ticks: f64) -> Result<i64, crate::ValidationError> {
558 let rounded = ticks.round();
559 if rounded.is_finite() && rounded >= i64::MIN as f64 && rounded < -(i64::MIN as f64) {
560 Ok(rounded as i64)
561 } else {
562 Err(time_error(
563 "seconds",
564 "time must be finite and fit the signed tick range",
565 ))
566 }
567}
568
569fn try_rounded_unsigned_ticks(ticks: f64) -> Result<u64, crate::ValidationError> {
570 let rounded = ticks.round();
571 if rounded.is_finite() && rounded >= 0.0 && rounded < u64::MAX as f64 {
572 Ok(rounded as u64)
573 } else {
574 Err(time_error(
575 "seconds",
576 "duration must be finite, nonnegative and fit the tick range",
577 ))
578 }
579}
580
581fn time_error(field: &'static str, message: &'static str) -> crate::ValidationError {
582 crate::ValidationError::invalid("time", field, message)
583}
584
585#[cfg(test)]
586mod tests;