Skip to main content

qtty_core/units/time/
mod.rs

1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (C) 2026 Vallés Puig, Ramon
3
4//! Time units.
5//!
6//! The canonical scaling unit for this dimension is [`Second`] (`Second::RATIO == 1.0`). All other time unit ratios are
7//! expressed in *seconds*.
8//!
9//! ## Precision and conventions
10//!
11//! - The **SI second** is the canonical unit.
12//! - Civil units such as [`Day`] are expressed using the conventional mapping
13//!   `1 day = 86_400 s` (mean solar day; leap seconds ignored).
14//! - “Mean” astronomical units (e.g., [`SiderealDay`], [`SynodicMonth`], [`SiderealYear`]) are **approximations**
15//!   that vary slightly with epoch/definition. Each unit documents the convention used.
16//!
17//! ```rust
18//! use qtty_core::time::{Hours, Second, Hour};
19//!
20//! let half_hour = Hours::new(0.5);
21//! let seconds = half_hour.to::<Second>();
22//! assert!((seconds.value() - 1800.0).abs() < 1e-12);
23//!
24//! let two_hours = seconds.to::<Hour>();
25//! assert!((two_hours.value() - 0.5).abs() < 1e-12);
26//! ```
27//!
28//! ## All time units
29//!
30//! ```rust
31//! use qtty_core::time::*;
32//!
33//! macro_rules! touch {
34//!     ($T:ty, $v:expr) => {{
35//!         let q = <$T>::new($v);
36//!         let _cloned = q;
37//!         assert!(q == q);
38//!     }};
39//! }
40//!
41//! touch!(Attoseconds,  1.0); touch!(Femtoseconds, 1.0);
42//! touch!(Picoseconds,  1.0); touch!(Nanoseconds,  1.0);
43//! touch!(Microseconds, 1.0); touch!(Milliseconds, 1.0);
44//! touch!(Centiseconds, 1.0); touch!(Deciseconds,  1.0);
45//! touch!(Seconds,      1.0); touch!(Decaseconds,  1.0);
46//! touch!(Hectoseconds, 1.0); touch!(Kiloseconds,  1.0);
47//! touch!(Megaseconds,  1.0); touch!(Gigaseconds,  1.0);
48//! touch!(Teraseconds,  1.0); touch!(Minutes,      1.0);
49//! touch!(Hours,        1.0); touch!(Days,         1.0);
50//! touch!(Weeks,        1.0); touch!(Fortnights,   1.0);
51//! touch!(Years,        1.0); touch!(Decades,      1.0);
52//! touch!(Centuries,    1.0); touch!(Millennia,    1.0);
53//! ```
54
55use crate::{Quantity, Unit};
56use qtty_derive::Unit;
57
58/// Re-export from the dimension module.
59pub use crate::dimension::Time;
60
61/// Marker trait for any [`Unit`] whose dimension is [`Time`].
62pub trait TimeUnit: Unit<Dim = Time> {}
63impl<T: Unit<Dim = Time>> TimeUnit for T {}
64
65/// Conventional civil mapping used by this module: seconds per mean solar day.
66pub const SECONDS_PER_DAY: f64 = 86_400.0;
67
68/// Mean Gregorian year length in days (accounts for the leap-year rule:
69/// every 4 years except centuries not divisible by 400).
70pub const DAYS_PER_GREGORIAN_YEAR: f64 = 365.242_5;
71
72#[cfg(feature = "julian-time")]
73mod julian_time;
74#[cfg(feature = "julian-time")]
75pub use julian_time::*;
76#[cfg(feature = "astro")]
77mod astro;
78#[cfg(feature = "astro")]
79pub use astro::*;
80
81// --- SI submultiples of the second ---
82
83/// Attoseconds (`1 as = 10^-18 s`).
84#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
85#[unit(symbol = "as", dimension = Time, ratio = 1e-18)]
86pub struct Attosecond;
87/// A quantity measured in attoseconds.
88pub type Attoseconds = Quantity<Attosecond>;
89/// A constant representing one attosecond.
90pub const ATTOSEC: Attoseconds = Attoseconds::new(1.0);
91
92/// Femtoseconds (`1 fs = 10^-15 s`).
93#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
94#[unit(symbol = "fs", dimension = Time, ratio = 1e-15)]
95pub struct Femtosecond;
96/// A quantity measured in femtoseconds.
97pub type Femtoseconds = Quantity<Femtosecond>;
98/// A constant representing one femtosecond.
99pub const FEMTOSEC: Femtoseconds = Femtoseconds::new(1.0);
100
101/// Picoseconds (`1 ps = 10^-12 s`).
102#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
103#[unit(symbol = "ps", dimension = Time, ratio = 1e-12)]
104pub struct Picosecond;
105/// A quantity measured in picoseconds.
106pub type Picoseconds = Quantity<Picosecond>;
107/// A constant representing one picosecond.
108pub const PICOSEC: Picoseconds = Picoseconds::new(1.0);
109
110/// Nanoseconds (`1 ns = 10^-9 s`).
111#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
112#[unit(symbol = "ns", dimension = Time, ratio = 1e-9)]
113pub struct Nanosecond;
114/// A quantity measured in nanoseconds.
115pub type Nanoseconds = Quantity<Nanosecond>;
116/// A constant representing one nanosecond.
117pub const NANOSEC: Nanoseconds = Nanoseconds::new(1.0);
118
119/// Microseconds (`1 µs = 10^-6 s`).
120#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
121#[unit(symbol = "µs", dimension = Time, ratio = 1e-6)]
122pub struct Microsecond;
123/// A quantity measured in microseconds.
124pub type Microseconds = Quantity<Microsecond>;
125/// A constant representing one microsecond.
126pub const MICROSEC: Microseconds = Microseconds::new(1.0);
127
128/// Milliseconds (`1 ms = 10^-3 s`).
129#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
130#[unit(symbol = "ms", dimension = Time, ratio = 1e-3)]
131pub struct Millisecond;
132/// A quantity measured in milliseconds.
133pub type Milliseconds = Quantity<Millisecond>;
134/// A constant representing one millisecond.
135pub const MILLISEC: Milliseconds = Milliseconds::new(1.0);
136
137/// Centiseconds (`1 cs = 10^-2 s`).
138#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
139#[unit(symbol = "cs", dimension = Time, ratio = 1e-2)]
140pub struct Centisecond;
141/// A quantity measured in centiseconds.
142pub type Centiseconds = Quantity<Centisecond>;
143/// A constant representing one centisecond.
144pub const CENTISEC: Centiseconds = Centiseconds::new(1.0);
145
146/// Deciseconds (`1 ds = 10^-1 s`).
147#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
148#[unit(symbol = "ds", dimension = Time, ratio = 1e-1)]
149pub struct Decisecond;
150/// A quantity measured in deciseconds.
151pub type Deciseconds = Quantity<Decisecond>;
152/// A constant representing one decisecond.
153pub const DECISEC: Deciseconds = Deciseconds::new(1.0);
154
155/// Seconds (SI base unit).
156#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
157#[unit(symbol = "s", dimension = Time, ratio = 1.0)]
158pub struct Second;
159/// A quantity measured in seconds.
160pub type Seconds = Quantity<Second>;
161/// A constant representing one second.
162pub const SEC: Seconds = Seconds::new(1.0);
163
164// --- SI multiples of the second ---
165
166/// Decaseconds (`1 das = 10 s`).
167#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
168#[unit(symbol = "das", dimension = Time, ratio = 10.0)]
169pub struct Decasecond;
170/// A quantity measured in decaseconds.
171pub type Decaseconds = Quantity<Decasecond>;
172/// A constant representing one decasecond.
173pub const DECASEC: Decaseconds = Decaseconds::new(1.0);
174
175/// Hectoseconds (`1 hs = 100 s`).
176#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
177#[unit(symbol = "hs", dimension = Time, ratio = 100.0)]
178pub struct Hectosecond;
179/// A quantity measured in hectoseconds.
180pub type Hectoseconds = Quantity<Hectosecond>;
181/// A constant representing one hectosecond.
182pub const HECTOSEC: Hectoseconds = Hectoseconds::new(1.0);
183
184/// Kiloseconds (`1 ks = 1_000 s`).
185#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
186#[unit(symbol = "ks", dimension = Time, ratio = 1_000.0)]
187pub struct Kilosecond;
188/// A quantity measured in kiloseconds.
189pub type Kiloseconds = Quantity<Kilosecond>;
190/// A constant representing one kilosecond.
191pub const KILOSEC: Kiloseconds = Kiloseconds::new(1.0);
192
193/// Megaseconds (`1 Ms = 10^6 s`).
194#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
195#[unit(symbol = "Ms", dimension = Time, ratio = 1e6)]
196pub struct Megasecond;
197/// A quantity measured in megaseconds.
198pub type Megaseconds = Quantity<Megasecond>;
199/// A constant representing one megasecond.
200pub const MEGASEC: Megaseconds = Megaseconds::new(1.0);
201
202/// Gigaseconds (`1 Gs = 10^9 s`).
203#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
204#[unit(symbol = "Gs", dimension = Time, ratio = 1e9)]
205pub struct Gigasecond;
206/// A quantity measured in gigaseconds.
207pub type Gigaseconds = Quantity<Gigasecond>;
208/// A constant representing one gigasecond.
209pub const GIGASEC: Gigaseconds = Gigaseconds::new(1.0);
210
211/// Teraseconds (`1 Ts = 10^12 s`).
212#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
213#[unit(symbol = "Ts", dimension = Time, ratio = 1e12)]
214pub struct Terasecond;
215/// A quantity measured in teraseconds.
216pub type Teraseconds = Quantity<Terasecond>;
217/// A constant representing one terasecond.
218pub const TERASEC: Teraseconds = Teraseconds::new(1.0);
219
220// --- Common civil units ---
221
222/// Minutes (`60 s`).
223#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
224#[unit(symbol = "min", dimension = Time, ratio = 60.0)]
225pub struct Minute;
226/// A quantity measured in minutes.
227pub type Minutes = Quantity<Minute>;
228/// A constant representing one minute.
229pub const MIN: Minutes = Minutes::new(1.0);
230
231/// Hours (`3_600 s`).
232#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
233#[unit(symbol = "h", dimension = Time, ratio = 3_600.0)]
234pub struct Hour;
235/// A quantity measured in hours.
236pub type Hours = Quantity<Hour>;
237/// A constant representing one hour.
238pub const HOUR: Hours = Hours::new(1.0);
239
240/// Mean solar day (`86_400 s` by convention; leap seconds ignored).
241#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
242#[unit(symbol = "d", dimension = Time, ratio = SECONDS_PER_DAY)]
243pub struct Day;
244/// A quantity measured in days.
245pub type Days = Quantity<Day>;
246/// A constant representing one day.
247pub const DAY: Days = Days::new(1.0);
248
249/// Week (`7 d = 604_800 s`).
250#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
251#[unit(symbol = "wk", dimension = Time, ratio = 7.0 * SECONDS_PER_DAY)]
252pub struct Week;
253/// A quantity measured in weeks.
254pub type Weeks = Quantity<Week>;
255/// A constant representing one week.
256pub const WEEK: Weeks = Weeks::new(1.0);
257
258/// Fortnight (`14 d = 1_209_600 s`).
259#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
260#[unit(symbol = "fn", dimension = Time, ratio = 14.0 * SECONDS_PER_DAY)]
261pub struct Fortnight;
262/// A quantity measured in fortnights.
263pub type Fortnights = Quantity<Fortnight>;
264/// A constant representing one fortnight.
265pub const FORTNIGHT: Fortnights = Fortnights::new(1.0);
266
267/// Mean Gregorian year (`365.2425 d`).
268///
269/// This is the **Gregorian calendar mean year** — the average year length
270/// in the proleptic Gregorian calendar (`365 + 97/400 = 365.2425 d`), not the
271/// astronomical mean tropical year (≈ 365.242 19 d).  The two differ by about
272/// 27 seconds per year.
273#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
274#[unit(symbol = "yr", dimension = Time, ratio = DAYS_PER_GREGORIAN_YEAR * SECONDS_PER_DAY)]
275pub struct Year;
276/// A quantity measured in years.
277pub type Years = Quantity<Year>;
278/// A constant representing one year.
279pub const YEAR: Years = Years::new(1.0);
280
281/// Decade (`10` mean Gregorian years).
282#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
283#[unit(symbol = "dec", dimension = Time, ratio = 10.0 * DAYS_PER_GREGORIAN_YEAR * SECONDS_PER_DAY)]
284pub struct Decade;
285/// A quantity measured in decades.
286pub type Decades = Quantity<Decade>;
287/// A constant representing one decade.
288pub const DECADE: Decades = Decades::new(1.0);
289
290/// Century (`100` mean Gregorian years).
291#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
292#[unit(symbol = "c", dimension = Time, ratio = 100.0 * DAYS_PER_GREGORIAN_YEAR * SECONDS_PER_DAY)]
293pub struct Century;
294/// A quantity measured in centuries.
295pub type Centuries = Quantity<Century>;
296/// A constant representing one century.
297pub const CENTURY: Centuries = Centuries::new(1.0);
298
299/// Millennium (`1000` mean Gregorian years).
300#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
301#[unit(symbol = "mill", dimension = Time, ratio = 1000.0 * DAYS_PER_GREGORIAN_YEAR * SECONDS_PER_DAY)]
302pub struct Millennium;
303/// A quantity measured in millennia.
304pub type Millennia = Quantity<Millennium>;
305/// A constant representing one millennium.
306pub const MILLENNIUM: Millennia = Millennia::new(1.0);
307
308/// Canonical list of always-available (SI + calendar) time units.
309///
310/// Exported (`#[doc(hidden)]`) for use in `qtty`'s scalar alias generation and
311/// compile-time consistency checks.  Feature-gated units (julian-time, astro)
312/// are in their sub-modules.
313#[macro_export]
314#[doc(hidden)]
315macro_rules! time_units {
316    ($cb:path) => {
317        $cb!(
318            Attosecond,
319            Femtosecond,
320            Picosecond,
321            Nanosecond,
322            Microsecond,
323            Millisecond,
324            Centisecond,
325            Decisecond,
326            Second,
327            Decasecond,
328            Hectosecond,
329            Kilosecond,
330            Megasecond,
331            Gigasecond,
332            Terasecond,
333            Minute,
334            Hour,
335            Day,
336            Week,
337            Fortnight,
338            Year,
339            Decade,
340            Century,
341            Millennium
342        );
343    };
344}
345
346// Generate bidirectional From impls between base SI + calendar time units.
347time_units!(crate::impl_unit_from_conversions);
348
349// Optional cross-unit operator support.
350#[cfg(feature = "cross-unit-ops")]
351time_units!(crate::impl_unit_cross_unit_ops);
352
353// ── Cross-feature: astro × julian-time ────────────────────────────────────────
354#[cfg(all(feature = "astro", feature = "julian-time"))]
355crate::__impl_from_each_extra_to_bases!(
356    {SiderealDay, SynodicMonth, SiderealYear}
357    JulianYear, JulianCentury
358);
359
360#[cfg(all(feature = "astro", feature = "julian-time", feature = "cross-unit-ops"))]
361crate::__impl_cross_ops_each_extra_to_bases!(
362    {SiderealDay, SynodicMonth, SiderealYear}
363    JulianYear, JulianCentury
364);
365
366// Compile-time check: every base time unit is registered as BuiltinUnit.
367#[cfg(test)]
368time_units!(crate::assert_units_are_builtin);
369
370#[cfg(all(test, feature = "std"))]
371mod tests {
372    use super::*;
373    use approx::assert_abs_diff_eq;
374    use proptest::prelude::*;
375
376    // ─────────────────────────────────────────────────────────────────────────────
377    // Basic conversions
378    // ─────────────────────────────────────────────────────────────────────────────
379
380    #[test]
381    fn seconds_to_minutes() {
382        let sec = Seconds::new(60.0);
383        let min = sec.to::<Minute>();
384        assert_abs_diff_eq!(min.value(), 1.0, epsilon = 1e-12);
385    }
386
387    #[test]
388    fn minutes_to_hours() {
389        let min = Minutes::new(60.0);
390        let hr = min.to::<Hour>();
391        assert_abs_diff_eq!(hr.value(), 1.0, epsilon = 1e-12);
392    }
393
394    #[test]
395    fn hours_to_days() {
396        let hr = Hours::new(24.0);
397        let day = hr.to::<Day>();
398        assert_abs_diff_eq!(day.value(), 1.0, epsilon = 1e-12);
399    }
400
401    #[test]
402    fn seconds_86400_equals_one_day() {
403        let sec = Seconds::new(86400.0);
404        let day = sec.to::<Day>();
405        assert_abs_diff_eq!(day.value(), 1.0, epsilon = 1e-12);
406    }
407
408    #[test]
409    fn day_to_seconds() {
410        let day = Days::new(1.0);
411        let sec = day.to::<Second>();
412        assert_abs_diff_eq!(sec.value(), 86400.0, epsilon = 1e-9);
413    }
414
415    #[test]
416    fn days_to_weeks() {
417        let day = Days::new(7.0);
418        let week = day.to::<Week>();
419        assert_abs_diff_eq!(week.value(), 1.0, epsilon = 1e-12);
420    }
421
422    #[cfg(feature = "julian-time")]
423    #[test]
424    fn julian_year_to_days() {
425        let jy = JulianYears::new(1.0);
426        let day = jy.to::<Day>();
427        assert_abs_diff_eq!(day.value(), 365.25, epsilon = 1e-9);
428    }
429
430    #[cfg(feature = "julian-time")]
431    #[test]
432    fn julian_century_to_days() {
433        let jc = JulianCenturies::new(1.0);
434        let day = jc.to::<Day>();
435        assert_abs_diff_eq!(day.value(), 36525.0, epsilon = 1e-9);
436    }
437
438    #[cfg(feature = "julian-time")]
439    #[test]
440    fn julian_century_to_julian_years() {
441        let jc = JulianCenturies::new(1.0);
442        let jy = jc.to::<JulianYear>();
443        assert_abs_diff_eq!(jy.value(), 100.0, epsilon = 1e-9);
444    }
445
446    #[test]
447    fn tropical_year_to_days() {
448        let y = Years::new(1.0);
449        let day = y.to::<Day>();
450        assert_abs_diff_eq!(day.value(), 365.242_5, epsilon = 1e-9);
451    }
452
453    #[test]
454    fn century_to_days() {
455        let c = Centuries::new(1.0);
456        let day = c.to::<Day>();
457        assert_abs_diff_eq!(day.value(), 36524.25, epsilon = 1e-9);
458    }
459
460    #[test]
461    fn milliseconds_to_seconds() {
462        let ms = Milliseconds::new(1000.0);
463        let sec = ms.to::<Second>();
464        assert_abs_diff_eq!(sec.value(), 1.0, epsilon = 1e-9);
465    }
466
467    // ─────────────────────────────────────────────────────────────────────────────
468    // Roundtrip conversions
469    // ─────────────────────────────────────────────────────────────────────────────
470
471    #[test]
472    fn roundtrip_day_second() {
473        let original = Days::new(1.5);
474        let converted = original.to::<Second>();
475        let back = converted.to::<Day>();
476        assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-12);
477    }
478
479    #[cfg(feature = "julian-time")]
480    #[test]
481    fn roundtrip_julian_year_day() {
482        let original = JulianYears::new(2.5);
483        let converted = original.to::<Day>();
484        let back = converted.to::<JulianYear>();
485        assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-12);
486    }
487
488    // ─────────────────────────────────────────────────────────────────────────────
489    // Ratio sanity checks
490    // ─────────────────────────────────────────────────────────────────────────────
491
492    #[test]
493    fn second_ratio_sanity() {
494        // Second::RATIO = 1.0 (canonical unit)
495        assert_abs_diff_eq!(Second::RATIO, 1.0, epsilon = 1e-15);
496    }
497
498    #[test]
499    fn minute_ratio_sanity() {
500        // 1 minute = 60 seconds
501        assert_abs_diff_eq!(Minute::RATIO, 60.0, epsilon = 1e-15);
502    }
503
504    #[test]
505    fn hour_ratio_sanity() {
506        // 1 hour = 3600 seconds
507        assert_abs_diff_eq!(Hour::RATIO, 3_600.0, epsilon = 1e-15);
508    }
509
510    // ─────────────────────────────────────────────────────────────────────────────
511    // Property-based tests
512    // ─────────────────────────────────────────────────────────────────────────────
513
514    proptest! {
515        #[test]
516        fn prop_roundtrip_day_second(d in -1e6..1e6f64) {
517            let original = Days::new(d);
518            let converted = original.to::<Second>();
519            let back = converted.to::<Day>();
520            prop_assert!((back.value() - original.value()).abs() < 1e-9);
521        }
522
523        #[test]
524        fn prop_day_second_ratio(d in 1e-6..1e6f64) {
525            let day = Days::new(d);
526            let sec = day.to::<Second>();
527            // 1 day = 86400 seconds
528            prop_assert!((sec.value() / day.value() - 86400.0).abs() < 1e-9);
529        }
530
531    }
532
533    #[cfg(feature = "julian-time")]
534    proptest! {
535        #[test]
536        fn prop_julian_year_day_ratio(y in 1e-6..1e6f64) {
537            let jy = JulianYears::new(y);
538            let day = jy.to::<Day>();
539            // 1 Julian year = 365.25 days
540            prop_assert!((day.value() / jy.value() - 365.25).abs() < 1e-9);
541        }
542    }
543
544    // ─── SI sub-second units ────────────────────────────────────────────────
545
546    #[test]
547    fn attosecond_to_second() {
548        let q = Attoseconds::new(1e18);
549        let s = q.to::<Second>();
550        assert_abs_diff_eq!(s.value(), 1.0, epsilon = 1e-9);
551    }
552
553    #[test]
554    fn femtosecond_to_second() {
555        let q = Femtoseconds::new(1e15);
556        let s = q.to::<Second>();
557        assert_abs_diff_eq!(s.value(), 1.0, epsilon = 1e-9);
558    }
559
560    #[test]
561    fn picosecond_to_second() {
562        let q = Picoseconds::new(1e12);
563        let s = q.to::<Second>();
564        assert_abs_diff_eq!(s.value(), 1.0, epsilon = 1e-9);
565    }
566
567    #[test]
568    fn nanosecond_to_second() {
569        let q = Nanoseconds::new(1e9);
570        let s = q.to::<Second>();
571        assert_abs_diff_eq!(s.value(), 1.0, epsilon = 1e-9);
572    }
573
574    #[test]
575    fn microsecond_to_second() {
576        let q = Microseconds::new(1e6);
577        let s = q.to::<Second>();
578        assert_abs_diff_eq!(s.value(), 1.0, epsilon = 1e-9);
579    }
580
581    #[test]
582    fn centisecond_to_second() {
583        let q = Centiseconds::new(100.0);
584        let s = q.to::<Second>();
585        assert_abs_diff_eq!(s.value(), 1.0, epsilon = 1e-12);
586    }
587
588    #[test]
589    fn decisecond_to_second() {
590        let q = Deciseconds::new(10.0);
591        let s = q.to::<Second>();
592        assert_abs_diff_eq!(s.value(), 1.0, epsilon = 1e-12);
593    }
594
595    // ─── SI multi-second units ──────────────────────────────────────────────
596
597    #[test]
598    fn decasecond_to_second() {
599        let q = Decaseconds::new(1.0);
600        let s = q.to::<Second>();
601        assert_abs_diff_eq!(s.value(), 10.0, epsilon = 1e-12);
602    }
603
604    #[test]
605    fn hectosecond_to_second() {
606        let q = Hectoseconds::new(1.0);
607        let s = q.to::<Second>();
608        assert_abs_diff_eq!(s.value(), 100.0, epsilon = 1e-12);
609    }
610
611    #[test]
612    fn kilosecond_to_second() {
613        let q = Kiloseconds::new(1.0);
614        let s = q.to::<Second>();
615        assert_abs_diff_eq!(s.value(), 1_000.0, epsilon = 1e-12);
616    }
617
618    #[test]
619    fn megasecond_to_second() {
620        let q = Megaseconds::new(1.0);
621        let s = q.to::<Second>();
622        assert_abs_diff_eq!(s.value(), 1e6, epsilon = 1.0);
623    }
624
625    #[test]
626    fn gigasecond_to_second() {
627        let q = Gigaseconds::new(1.0);
628        let s = q.to::<Second>();
629        assert_abs_diff_eq!(s.value(), 1e9, epsilon = 1e3);
630    }
631
632    #[test]
633    fn terasecond_to_second() {
634        let q = Teraseconds::new(1.0);
635        let s = q.to::<Second>();
636        assert_abs_diff_eq!(s.value(), 1e12, epsilon = 1e6);
637    }
638
639    // ─── Civil units ────────────────────────────────────────────────────────
640
641    #[test]
642    fn fortnight_to_days() {
643        let q = Fortnights::new(1.0);
644        let d = q.to::<Day>();
645        assert_abs_diff_eq!(d.value(), 14.0, epsilon = 1e-12);
646    }
647
648    #[test]
649    fn decade_to_years() {
650        let q = Decades::new(1.0);
651        let y = q.to::<Year>();
652        assert_abs_diff_eq!(y.value(), 10.0, epsilon = 1e-9);
653    }
654
655    #[test]
656    fn millennium_to_years() {
657        let q = Millennia::new(1.0);
658        let y = q.to::<Year>();
659        assert_abs_diff_eq!(y.value(), 1000.0, epsilon = 1e-9);
660    }
661
662    // ─── Astronomical mean units ────────────────────────────────────────────
663
664    #[cfg(feature = "astro")]
665    #[test]
666    fn sidereal_day_to_seconds() {
667        let q = SiderealDays::new(1.0);
668        let s = q.to::<Second>();
669        // 1 sidereal day ≈ 86164.0905 s
670        assert_abs_diff_eq!(s.value(), 86_164.090_5, epsilon = 1e-3);
671    }
672
673    #[cfg(feature = "astro")]
674    #[test]
675    fn synodic_month_to_days() {
676        let q = SynodicMonths::new(1.0);
677        let d = q.to::<Day>();
678        // 1 synodic month ≈ 29.530590 d
679        assert_abs_diff_eq!(d.value(), 29.530_590, epsilon = 1e-6);
680    }
681
682    #[cfg(feature = "astro")]
683    #[test]
684    fn sidereal_year_to_days() {
685        let q = SiderealYears::new(1.0);
686        let d = q.to::<Day>();
687        // 1 sidereal year ≈ 365.256363004 d
688        assert_abs_diff_eq!(d.value(), 365.256_363_004, epsilon = 1e-6);
689    }
690
691    // ─── Symbol checks ──────────────────────────────────────────────────────
692
693    #[test]
694    fn symbols_are_correct() {
695        assert_eq!(format!("{}", Attoseconds::new(1.0)), "1 as");
696        assert_eq!(format!("{}", Nanoseconds::new(1.0)), "1 ns");
697        assert_eq!(format!("{}", Kiloseconds::new(1.0)), "1 ks");
698        assert_eq!(format!("{}", Fortnights::new(1.0)), "1 fn");
699    }
700
701    #[cfg(feature = "astro")]
702    #[test]
703    fn astro_symbols_are_correct() {
704        assert_eq!(format!("{}", SiderealDays::new(1.0)), "1 sd");
705    }
706}