qtty_core/units/
time.rs

1//! Time units.
2//!
3//! The canonical scaling unit for this dimension is [`Second`] (`Second::RATIO == 1.0`). All other time unit ratios are
4//! expressed in *seconds*.
5//!
6//! ## Precision and conventions
7//!
8//! - The **SI second** is the canonical unit.
9//! - Civil units such as [`Day`] are expressed using the conventional mapping
10//!   `1 day = 86_400 s` (mean solar day; leap seconds ignored).
11//! - “Mean” astronomical units (e.g., [`SiderealDay`], [`SynodicMonth`], [`SiderealYear`]) are **approximations**
12//!   that vary slightly with epoch/definition. Each unit documents the convention used.
13//!
14//! ```rust
15//! use qtty_core::time::{Hours, Second, Hour};
16//!
17//! let half_hour = Hours::new(0.5);
18//! let seconds = half_hour.to::<Second>();
19//! assert!((seconds.value() - 1800.0).abs() < 1e-12);
20//!
21//! let two_hours = seconds.to::<Hour>();
22//! assert!((two_hours.value() - 0.5).abs() < 1e-12);
23//! ```
24
25use crate::{Dimension, Quantity, Unit};
26use qtty_derive::Unit;
27
28/// Dimension tag for time.
29pub enum Time {}
30impl Dimension for Time {}
31
32/// Marker trait for any [`Unit`] whose dimension is [`Time`].
33pub trait TimeUnit: Unit<Dim = Time> {}
34impl<T: Unit<Dim = Time>> TimeUnit for T {}
35
36/// Conventional civil mapping used by this module: seconds per mean solar day.
37pub const SECONDS_PER_DAY: f64 = 86_400.0;
38
39// --- SI submultiples of the second ---
40
41/// Attoseconds (`1 as = 10^-18 s`).
42#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
43#[unit(symbol = "as", dimension = Time, ratio = 1e-18)]
44pub struct Attosecond;
45/// A quantity measured in attoseconds.
46pub type Attoseconds = Quantity<Attosecond>;
47/// A constant representing one attosecond.
48pub const ATTOSEC: Attoseconds = Attoseconds::new(1.0);
49
50/// Femtoseconds (`1 fs = 10^-15 s`).
51#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
52#[unit(symbol = "fs", dimension = Time, ratio = 1e-15)]
53pub struct Femtosecond;
54/// A quantity measured in femtoseconds.
55pub type Femtoseconds = Quantity<Femtosecond>;
56/// A constant representing one femtosecond.
57pub const FEMTOSEC: Femtoseconds = Femtoseconds::new(1.0);
58
59/// Picoseconds (`1 ps = 10^-12 s`).
60#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
61#[unit(symbol = "ps", dimension = Time, ratio = 1e-12)]
62pub struct Picosecond;
63/// A quantity measured in picoseconds.
64pub type Picoseconds = Quantity<Picosecond>;
65/// A constant representing one picosecond.
66pub const PICOSEC: Picoseconds = Picoseconds::new(1.0);
67
68/// Nanoseconds (`1 ns = 10^-9 s`).
69#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
70#[unit(symbol = "ns", dimension = Time, ratio = 1e-9)]
71pub struct Nanosecond;
72/// A quantity measured in nanoseconds.
73pub type Nanoseconds = Quantity<Nanosecond>;
74/// A constant representing one nanosecond.
75pub const NANOSEC: Nanoseconds = Nanoseconds::new(1.0);
76
77/// Microseconds (`1 µs = 10^-6 s`).
78#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
79#[unit(symbol = "µs", dimension = Time, ratio = 1e-6)]
80pub struct Microsecond;
81/// A quantity measured in microseconds.
82pub type Microseconds = Quantity<Microsecond>;
83/// A constant representing one microsecond.
84pub const MICROSEC: Microseconds = Microseconds::new(1.0);
85
86/// Milliseconds (`1 ms = 10^-3 s`).
87#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
88#[unit(symbol = "ms", dimension = Time, ratio = 1e-3)]
89pub struct Millisecond;
90/// A quantity measured in milliseconds.
91pub type Milliseconds = Quantity<Millisecond>;
92/// A constant representing one millisecond.
93pub const MILLISEC: Milliseconds = Milliseconds::new(1.0);
94
95/// Centiseconds (`1 cs = 10^-2 s`).
96#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
97#[unit(symbol = "cs", dimension = Time, ratio = 1e-2)]
98pub struct Centisecond;
99/// A quantity measured in centiseconds.
100pub type Centiseconds = Quantity<Centisecond>;
101/// A constant representing one centisecond.
102pub const CENTISEC: Centiseconds = Centiseconds::new(1.0);
103
104/// Deciseconds (`1 ds = 10^-1 s`).
105#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
106#[unit(symbol = "ds", dimension = Time, ratio = 1e-1)]
107pub struct Decisecond;
108/// A quantity measured in deciseconds.
109pub type Deciseconds = Quantity<Decisecond>;
110/// A constant representing one decisecond.
111pub const DECISEC: Deciseconds = Deciseconds::new(1.0);
112
113/// Seconds (SI base unit).
114#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
115#[unit(symbol = "s", dimension = Time, ratio = 1.0)]
116pub struct Second;
117/// A quantity measured in seconds.
118pub type Seconds = Quantity<Second>;
119/// A constant representing one second.
120pub const SEC: Seconds = Seconds::new(1.0);
121
122// --- SI multiples of the second ---
123
124/// Decaseconds (`1 das = 10 s`).
125#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
126#[unit(symbol = "das", dimension = Time, ratio = 10.0)]
127pub struct Decasecond;
128/// A quantity measured in decaseconds.
129pub type Decaseconds = Quantity<Decasecond>;
130/// A constant representing one decasecond.
131pub const DECASEC: Decaseconds = Decaseconds::new(1.0);
132
133/// Hectoseconds (`1 hs = 100 s`).
134#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
135#[unit(symbol = "hs", dimension = Time, ratio = 100.0)]
136pub struct Hectosecond;
137/// A quantity measured in hectoseconds.
138pub type Hectoseconds = Quantity<Hectosecond>;
139/// A constant representing one hectosecond.
140pub const HECTOSEC: Hectoseconds = Hectoseconds::new(1.0);
141
142/// Kiloseconds (`1 ks = 1_000 s`).
143#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
144#[unit(symbol = "ks", dimension = Time, ratio = 1_000.0)]
145pub struct Kilosecond;
146/// A quantity measured in kiloseconds.
147pub type Kiloseconds = Quantity<Kilosecond>;
148/// A constant representing one kilosecond.
149pub const KILOSEC: Kiloseconds = Kiloseconds::new(1.0);
150
151/// Megaseconds (`1 Ms = 10^6 s`).
152#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
153#[unit(symbol = "Ms", dimension = Time, ratio = 1e6)]
154pub struct Megasecond;
155/// A quantity measured in megaseconds.
156pub type Megaseconds = Quantity<Megasecond>;
157/// A constant representing one megasecond.
158pub const MEGASEC: Megaseconds = Megaseconds::new(1.0);
159
160/// Gigaseconds (`1 Gs = 10^9 s`).
161#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
162#[unit(symbol = "Gs", dimension = Time, ratio = 1e9)]
163pub struct Gigasecond;
164/// A quantity measured in gigaseconds.
165pub type Gigaseconds = Quantity<Gigasecond>;
166/// A constant representing one gigasecond.
167pub const GIGASEC: Gigaseconds = Gigaseconds::new(1.0);
168
169/// Teraseconds (`1 Ts = 10^12 s`).
170#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
171#[unit(symbol = "Ts", dimension = Time, ratio = 1e12)]
172pub struct Terasecond;
173/// A quantity measured in teraseconds.
174pub type Teraseconds = Quantity<Terasecond>;
175/// A constant representing one terasecond.
176pub const TERASEC: Teraseconds = Teraseconds::new(1.0);
177
178// --- Common civil units ---
179
180/// Minutes (`60 s`).
181#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
182#[unit(symbol = "min", dimension = Time, ratio = 60.0)]
183pub struct Minute;
184/// A quantity measured in minutes.
185pub type Minutes = Quantity<Minute>;
186/// A constant representing one minute.
187pub const MIN: Minutes = Minutes::new(1.0);
188
189/// Hours (`3_600 s`).
190#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
191#[unit(symbol = "h", dimension = Time, ratio = 3_600.0)]
192pub struct Hour;
193/// A quantity measured in hours.
194pub type Hours = Quantity<Hour>;
195/// A constant representing one hour.
196pub const HOUR: Hours = Hours::new(1.0);
197
198/// Mean solar day (`86_400 s` by convention; leap seconds ignored).
199#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
200#[unit(symbol = "d", dimension = Time, ratio = SECONDS_PER_DAY)]
201pub struct Day;
202/// A quantity measured in days.
203pub type Days = Quantity<Day>;
204/// A constant representing one day.
205pub const DAY: Days = Days::new(1.0);
206
207/// Week (`7 d = 604_800 s`).
208#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
209#[unit(symbol = "wk", dimension = Time, ratio = 7.0 * SECONDS_PER_DAY)]
210pub struct Week;
211/// A quantity measured in weeks.
212pub type Weeks = Quantity<Week>;
213/// A constant representing one week.
214pub const WEEK: Weeks = Weeks::new(1.0);
215
216/// Fortnight (`14 d = 1_209_600 s`).
217#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
218#[unit(symbol = "fn", dimension = Time, ratio = 14.0 * SECONDS_PER_DAY)]
219pub struct Fortnight;
220/// A quantity measured in fortnights.
221pub type Fortnights = Quantity<Fortnight>;
222/// A constant representing one fortnight.
223pub const FORTNIGHT: Fortnights = Fortnights::new(1.0);
224
225/// Mean tropical year, as a conventional mean length.
226///
227/// Convention used: `365.2425 d`.
228#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
229#[unit(symbol = "yr", dimension = Time, ratio = 365.242_5 * SECONDS_PER_DAY)]
230pub struct Year;
231/// A quantity measured in years.
232pub type Years = Quantity<Year>;
233/// A constant representing one year.
234pub const YEAR: Years = Years::new(1.0);
235
236/// Decade (`10` mean tropical years).
237#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
238#[unit(symbol = "dec", dimension = Time, ratio = 10.0 * 365.242_5 * SECONDS_PER_DAY)]
239pub struct Decade;
240/// A quantity measured in decades.
241pub type Decades = Quantity<Decade>;
242/// A constant representing one decade.
243pub const DECADE: Decades = Decades::new(1.0);
244
245/// Century (`100` mean tropical years).
246#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
247#[unit(symbol = "cent", dimension = Time, ratio = 100.0 * 365.242_5 * SECONDS_PER_DAY)]
248pub struct Century;
249/// A quantity measured in centuries.
250pub type Centuries = Quantity<Century>;
251/// A constant representing one century.
252pub const CENTURY: Centuries = Centuries::new(1.0);
253
254/// Millennium (`1000` mean tropical years).
255#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
256#[unit(symbol = "mill", dimension = Time, ratio = 1000.0 * 365.242_5 * SECONDS_PER_DAY)]
257pub struct Millennium;
258/// A quantity measured in millennia.
259pub type Millennia = Quantity<Millennium>;
260/// A constant representing one millennium.
261pub const MILLENNIUM: Millennia = Millennia::new(1.0);
262
263// --- Julian conventions (useful in astronomy/ephemerides) ---
264
265/// Julian year (`365.25 d`), expressed in seconds.
266#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
267#[unit(symbol = "a", dimension = Time, ratio = 365.25 * SECONDS_PER_DAY)]
268pub struct JulianYear;
269/// A quantity measured in Julian years.
270pub type JulianYears = Quantity<JulianYear>;
271/// A constant representing one Julian year.
272pub const JULIAN_YEAR: JulianYears = JulianYears::new(1.0);
273
274/// Julian century (`36_525 d`), expressed in seconds.
275#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
276#[unit(symbol = "JC", dimension = Time, ratio = 36_525.0 * SECONDS_PER_DAY)]
277pub struct JulianCentury;
278/// A quantity measured in Julian centuries.
279pub type JulianCenturies = Quantity<JulianCentury>;
280/// A constant representing one Julian century.
281pub const JULIAN_CENTURY: JulianCenturies = JulianCenturies::new(1.0);
282
283// --- Astronomical mean units (explicitly approximate) ---
284
285/// Mean sidereal day (Earth), expressed in SI seconds.
286///
287/// Convention used: `1 sidereal day ≈ 86_164.0905 s`.
288#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
289#[unit(symbol = "sd", dimension = Time, ratio = 86_164.090_5)]
290pub struct SiderealDay;
291/// A quantity measured in sidereal days.
292pub type SiderealDays = Quantity<SiderealDay>;
293/// A constant representing one sidereal day.
294pub const SIDEREAL_DAY: SiderealDays = SiderealDays::new(1.0);
295
296/// Mean synodic month (lunar phase cycle), expressed in seconds.
297///
298/// Convention used: `1 synodic month ≈ 29.530588 d`.
299#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
300#[unit(symbol = "synmo", dimension = Time, ratio = 29.530_588 * SECONDS_PER_DAY)]
301pub struct SynodicMonth;
302/// A quantity measured in synodic months.
303pub type SynodicMonths = Quantity<SynodicMonth>;
304/// A constant representing one synodic month.
305pub const SYNODIC_MONTH: SynodicMonths = SynodicMonths::new(1.0);
306
307/// Mean sidereal year (Earth), expressed in seconds.
308///
309/// Common convention: `1 sidereal year ≈ 365.256363004 d`.
310#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
311#[unit(symbol = "syr", dimension = Time, ratio = 365.256_363_004 * SECONDS_PER_DAY)]
312pub struct SiderealYear;
313/// A quantity measured in sidereal years.
314pub type SiderealYears = Quantity<SiderealYear>;
315/// A constant representing one sidereal year.
316pub const SIDEREAL_YEAR: SiderealYears = SiderealYears::new(1.0);
317
318#[cfg(test)]
319mod tests {
320    use super::*;
321    use approx::assert_abs_diff_eq;
322    use proptest::prelude::*;
323
324    // ─────────────────────────────────────────────────────────────────────────────
325    // Basic conversions
326    // ─────────────────────────────────────────────────────────────────────────────
327
328    #[test]
329    fn seconds_to_minutes() {
330        let sec = Seconds::new(60.0);
331        let min = sec.to::<Minute>();
332        assert_abs_diff_eq!(min.value(), 1.0, epsilon = 1e-12);
333    }
334
335    #[test]
336    fn minutes_to_hours() {
337        let min = Minutes::new(60.0);
338        let hr = min.to::<Hour>();
339        assert_abs_diff_eq!(hr.value(), 1.0, epsilon = 1e-12);
340    }
341
342    #[test]
343    fn hours_to_days() {
344        let hr = Hours::new(24.0);
345        let day = hr.to::<Day>();
346        assert_abs_diff_eq!(day.value(), 1.0, epsilon = 1e-12);
347    }
348
349    #[test]
350    fn seconds_86400_equals_one_day() {
351        let sec = Seconds::new(86400.0);
352        let day = sec.to::<Day>();
353        assert_abs_diff_eq!(day.value(), 1.0, epsilon = 1e-12);
354    }
355
356    #[test]
357    fn day_to_seconds() {
358        let day = Days::new(1.0);
359        let sec = day.to::<Second>();
360        assert_abs_diff_eq!(sec.value(), 86400.0, epsilon = 1e-9);
361    }
362
363    #[test]
364    fn days_to_weeks() {
365        let day = Days::new(7.0);
366        let week = day.to::<Week>();
367        assert_abs_diff_eq!(week.value(), 1.0, epsilon = 1e-12);
368    }
369
370    #[test]
371    fn julian_year_to_days() {
372        let jy = JulianYears::new(1.0);
373        let day = jy.to::<Day>();
374        assert_abs_diff_eq!(day.value(), 365.25, epsilon = 1e-9);
375    }
376
377    #[test]
378    fn julian_century_to_days() {
379        let jc = JulianCenturies::new(1.0);
380        let day = jc.to::<Day>();
381        assert_abs_diff_eq!(day.value(), 36525.0, epsilon = 1e-9);
382    }
383
384    #[test]
385    fn julian_century_to_julian_years() {
386        let jc = JulianCenturies::new(1.0);
387        let jy = jc.to::<JulianYear>();
388        assert_abs_diff_eq!(jy.value(), 100.0, epsilon = 1e-9);
389    }
390
391    #[test]
392    fn tropical_year_to_days() {
393        let y = Years::new(1.0);
394        let day = y.to::<Day>();
395        assert_abs_diff_eq!(day.value(), 365.2425, epsilon = 1e-9);
396    }
397
398    #[test]
399    fn century_to_days() {
400        let c = Centuries::new(1.0);
401        let day = c.to::<Day>();
402        assert_abs_diff_eq!(day.value(), 36524.25, epsilon = 1e-9);
403    }
404
405    #[test]
406    fn milliseconds_to_seconds() {
407        let ms = Milliseconds::new(1000.0);
408        let sec = ms.to::<Second>();
409        assert_abs_diff_eq!(sec.value(), 1.0, epsilon = 1e-9);
410    }
411
412    // ─────────────────────────────────────────────────────────────────────────────
413    // Roundtrip conversions
414    // ─────────────────────────────────────────────────────────────────────────────
415
416    #[test]
417    fn roundtrip_day_second() {
418        let original = Days::new(1.5);
419        let converted = original.to::<Second>();
420        let back = converted.to::<Day>();
421        assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-12);
422    }
423
424    #[test]
425    fn roundtrip_julian_year_day() {
426        let original = JulianYears::new(2.5);
427        let converted = original.to::<Day>();
428        let back = converted.to::<JulianYear>();
429        assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-12);
430    }
431
432    // ─────────────────────────────────────────────────────────────────────────────
433    // Ratio sanity checks
434    // ─────────────────────────────────────────────────────────────────────────────
435
436    #[test]
437    fn second_ratio_sanity() {
438        // Second::RATIO = 1.0 (canonical unit)
439        assert_abs_diff_eq!(Second::RATIO, 1.0, epsilon = 1e-15);
440    }
441
442    #[test]
443    fn minute_ratio_sanity() {
444        // 1 minute = 60 seconds
445        assert_abs_diff_eq!(Minute::RATIO, 60.0, epsilon = 1e-15);
446    }
447
448    #[test]
449    fn hour_ratio_sanity() {
450        // 1 hour = 3600 seconds
451        assert_abs_diff_eq!(Hour::RATIO, 3_600.0, epsilon = 1e-15);
452    }
453
454    // ─────────────────────────────────────────────────────────────────────────────
455    // Property-based tests
456    // ─────────────────────────────────────────────────────────────────────────────
457
458    proptest! {
459        #[test]
460        fn prop_roundtrip_day_second(d in -1e6..1e6f64) {
461            let original = Days::new(d);
462            let converted = original.to::<Second>();
463            let back = converted.to::<Day>();
464            prop_assert!((back.value() - original.value()).abs() < 1e-9);
465        }
466
467        #[test]
468        fn prop_day_second_ratio(d in 1e-6..1e6f64) {
469            let day = Days::new(d);
470            let sec = day.to::<Second>();
471            // 1 day = 86400 seconds
472            prop_assert!((sec.value() / day.value() - 86400.0).abs() < 1e-9);
473        }
474
475        #[test]
476        fn prop_julian_year_day_ratio(y in 1e-6..1e6f64) {
477            let jy = JulianYears::new(y);
478            let day = jy.to::<Day>();
479            // 1 Julian year = 365.25 days
480            prop_assert!((day.value() / jy.value() - 365.25).abs() < 1e-9);
481        }
482    }
483}