1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// devela::phys::time::scale
//
//! Defines [`TimeScale`].
//
use crate::{NonZeroU32, Ratio, niche, unwrap};
#[doc = crate::_tags!(time)]
/// Describes the conceptual scale at which time is expressed or interpreted.
#[doc = crate::_doc_meta!{location("phys/time")}]
///
/// `TimeScale` is lightweight, descriptive metadata. It can be used to label
/// time sources, parameters, or policies without implying exact duration,
/// normalization, or convertibility between scales.
///
/// Calendar-based variants (such as years or days) are symbolic and may depend
/// on external conventions. The `Ratio` variant allows expressing custom scales
/// relative to seconds.
#[rustfmt::skip]
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum TimeScale {
Years,
Days,
Hours,
Minutes,
/// Default time scale.
#[default]
Seconds,
Milliseconds,
Microseconds,
Nanoseconds,
/// A custom, exact time scale expressed as a rational multiple of seconds.
Ratio(Ratio<NonZeroU32, NonZeroU32>),
}
/// # Aliases
#[allow(non_upper_case_globals)]
impl TimeScale {
/// Alias for `Minutes`.
pub const Mins: Self = Self::Minutes;
/// Alias for `Seconds`.
pub const Secs: Self = Self::Seconds;
/// Alias for `Milliseconds`.
pub const Millis: Self = Self::Milliseconds;
/// Alias for `Microseconds`.
pub const Micros: Self = Self::Microseconds;
/// Alias for `Nanoseconds`.
pub const Nanos: Self = Self::Nanoseconds;
}
impl TimeScale {
/// Returns `true` if this scale is calendar-based.
pub const fn is_calendar(self) -> bool {
matches!(self, Self::Years | Self::Days)
}
/// Returns `true` if this scale is not calendar-based.
pub const fn is_fixed(self) -> bool {
!self.is_calendar()
}
/// Returns `true` if this scale is sub-second.
pub const fn is_subsecond(self) -> bool {
matches!(self, Self::Milliseconds | Self::Microseconds | Self::Nanoseconds)
}
/// Returns `true` if this scale is second-based or finer.
pub const fn is_second_based(self) -> bool {
matches!(
self,
Self::Seconds
| Self::Milliseconds
| Self::Microseconds
| Self::Nanoseconds
| Self::Ratio(_)
)
}
/// Returns a short, lowercase name for this time scale.
pub const fn name(self) -> &'static str {
match self {
Self::Years => "years",
Self::Days => "days",
Self::Hours => "hours",
Self::Minutes => "minutes",
Self::Seconds => "seconds",
Self::Milliseconds => "milliseconds",
Self::Microseconds => "microseconds",
Self::Nanoseconds => "nanoseconds",
Self::Ratio(_) => "ratio",
}
}
/// Converts a numeric value from this scale into `target`, if both
/// scales have a fixed, exact ratio to seconds.
///
/// Returns `None` if either scale is calendar-based.
pub const fn convert(self, value: u64, target: TimeScale) -> Option<u64> {
let (from, to) = (unwrap![some? self.to_ratio()], unwrap![some? target.to_ratio()]);
// value * from / to
let num = (value as u128) * (from.n.get() as u128) * (to.d.get() as u128);
let den = (from.d.get() as u128) * (to.n.get() as u128);
Some((num / den) as u64)
}
/// Converts a numeric value from this scale into `target`
/// using fixed, simulation-friendly assumptions.
///
/// Calendar-based scales are treated as fixed-duration:
/// - Days = 24 hours
/// - Years = 365 days
///
/// This method never fails and is intended for testing,
/// simulation, and synthetic time sources.
pub fn convert_simulated(self, value: u64, target: TimeScale) -> u64 {
let from = self.to_ratio_simulated();
let to = target.to_ratio_simulated();
let num = (value as u128) * (from.n.get() as u128) * (to.d.get() as u128);
let den = (from.d.get() as u128) * (to.n.get() as u128);
(num / den) as u64
}
}
impl TimeScale {
/// Creates a ratio-based time scale relative to seconds.
///
/// Returns `None` if either component is zero.
pub const fn new_ratio(num: u32, den: u32) -> Option<Self> {
match (NonZeroU32::new(num), NonZeroU32::new(den)) {
(Some(n), Some(d)) => Some(Self::Ratio(Ratio::new(n, d))),
_ => None,
}
}
/// Returns the underlying ratio if this scale is already expressed as one.
pub const fn some_ratio(self) -> Option<Ratio<NonZeroU32, NonZeroU32>> {
match self {
Self::Ratio(r) => Some(r),
_ => None,
}
}
/// Returns the exact ratio of this scale relative to seconds, if fixed-duration.
///
/// The returned ratio expresses:
/// `1 unit of this scale = num / den seconds`
///
/// Calendar-based scales (`Days`, `Years`) return `None`.
pub const fn to_ratio(self) -> Option<Ratio<NonZeroU32, NonZeroU32>> {
match self {
Self::Seconds => Some(Ratio::new(niche!(1u32;!=0), niche!(1u32;!=0))),
Self::Milliseconds => Some(Ratio::new(niche!(1u32; !=0), niche!(1_000u32;!=0))),
Self::Microseconds => Some(Ratio::new(niche!(1u32; !=0), niche!(1_000_000u32;!=0))),
Self::Nanoseconds => Some(Ratio::new(niche!(1u32;!=0), niche!(1_000_000_000u32;!=0))),
Self::Minutes => Some(Ratio::new(niche!(60u32;!=0), niche!(1u32;!=0))),
Self::Hours => Some(Ratio::new(niche!(3_600u32;!=0), niche!(1u32;!=0))),
Self::Ratio(r) => Some(r),
Self::Days | Self::Years => None,
}
}
/// Returns a simulation-friendly ratio of this scale relative to seconds.
///
/// The returned ratio expresses:
/// `1 unit of this scale = num / den seconds`
///
/// Unlike [`to_ratio`](Self::to_ratio), this method never fails.
/// Calendar-based scales are mapped to fixed-duration approximations:
/// - `Days` are treated as 24-hour days.
/// - `Years` are treated as 365-day years.
///
/// This method is intended for testing, simulation, and synthetic
/// time sources where deterministic behavior is preferred over
/// civil-time accuracy.
pub const fn to_ratio_simulated(self) -> Ratio<NonZeroU32, NonZeroU32> {
match self {
Self::Seconds => Ratio::new(niche!(1u32;!=0), niche!(1u32;!=0)),
Self::Milliseconds => Ratio::new(niche!(1u32;!=0), niche!(1_000u32;!=0)),
Self::Microseconds => Ratio::new(niche!(1u32;!=0), niche!(1_000_000u32;!=0)),
Self::Nanoseconds => Ratio::new(niche!(1u32;!=0), niche!(1_000_000_000u32;!=0)),
Self::Minutes => Ratio::new(niche!(60u32;!=0), niche!(1u32;!=0)),
Self::Hours => Ratio::new(niche!(3_600u32;!=0), niche!(1u32;!=0)),
Self::Ratio(r) => r,
Self::Days => Ratio::new(niche!(86_400u32;!=0), niche!(1u32;!=0)), // 24 hours
Self::Years => Ratio::new(niche!(31_536_000u32;!=0), niche!(1u32;!=0)), // 365 days
}
}
}