Skip to main content

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