Coverage Report

Created: 2026-01-13 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/hifitime-4.2.3/src/epoch/initializers.rs
Line
Count
Source
1
/*
2
* Hifitime
3
* Copyright (C) 2017-onward Christopher Rabotin <christopher.rabotin@gmail.com> et al. (cf. https://github.com/nyx-space/hifitime/graphs/contributors)
4
* This Source Code Form is subject to the terms of the Mozilla Public
5
* License, v. 2.0. If a copy of the MPL was not distributed with this
6
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7
*
8
* Documentation: https://nyxspace.com/
9
*/
10
11
use core::str::FromStr;
12
13
use snafu::ResultExt;
14
15
use crate::{
16
    efmt::Format, errors::ParseSnafu, Duration, Epoch, HifitimeError, TimeScale, Unit, Weekday,
17
    ET_OFFSET_US, MJD_J1900, MJD_OFFSET, NANOSECONDS_PER_DAY, UNIX_REF_EPOCH,
18
};
19
20
// Defines the methods that should be classmethods in Python, but must be redefined as per https://github.com/PyO3/pyo3/issues/1003#issuecomment-844433346
21
impl Epoch {
22
    #[must_use]
23
    /// Creates a new Epoch from a Duration as the time difference between this epoch and TAI reference epoch.
24
24
    pub const fn from_tai_duration(duration: Duration) -> Self {
25
24
        Self {
26
24
            duration,
27
24
            time_scale: TimeScale::TAI,
28
24
        }
29
24
    }
30
31
0
    pub fn to_duration_since_j1900(&self) -> Duration {
32
0
        self.to_time_scale(TimeScale::TAI).duration
33
0
    }
34
35
    #[must_use]
36
    /// Creates a new Epoch from its centuries and nanosecond since the TAI reference epoch.
37
0
    pub fn from_tai_parts(centuries: i16, nanoseconds: u64) -> Self {
38
0
        Self::from_tai_duration(Duration::from_parts(centuries, nanoseconds))
39
0
    }
40
41
    #[must_use]
42
    /// Initialize an Epoch from the provided TAI seconds since 1900 January 01 at midnight
43
24
    pub fn from_tai_seconds(seconds: f64) -> Self {
44
24
        assert!(
45
24
            seconds.is_finite(),
46
0
            "Attempted to initialize Epoch with non finite number"
47
        );
48
24
        Self::from_tai_duration(seconds * Unit::Second)
49
24
    }
50
51
    #[must_use]
52
    /// Initialize an Epoch from the provided TAI days since 1900 January 01 at midnight
53
0
    pub fn from_tai_days(days: f64) -> Self {
54
0
        assert!(
55
0
            days.is_finite(),
56
0
            "Attempted to initialize Epoch with non finite number"
57
        );
58
0
        Self::from_tai_duration(days * Unit::Day)
59
0
    }
60
61
    #[must_use]
62
    /// Initialize an Epoch from the provided UTC seconds since 1900 January 01 at midnight
63
13.7k
    pub fn from_utc_duration(duration: Duration) -> Self {
64
13.7k
        Self::from_duration(duration, TimeScale::UTC)
65
13.7k
    }
66
67
    #[must_use]
68
    /// Initialize an Epoch from the provided UTC seconds since 1900 January 01 at midnight
69
1.79k
    pub fn from_utc_seconds(seconds: f64) -> Self {
70
1.79k
        Self::from_utc_duration(seconds * Unit::Second)
71
1.79k
    }
72
73
    #[must_use]
74
    /// Initialize an Epoch from the provided UTC days since 1900 January 01 at midnight
75
0
    pub fn from_utc_days(days: f64) -> Self {
76
0
        Self::from_utc_duration(days * Unit::Day)
77
0
    }
78
79
    #[must_use]
80
    /// Initialize an Epoch from the provided duration since 1980 January 6 at midnight
81
0
    pub fn from_gpst_duration(duration: Duration) -> Self {
82
0
        Self::from_duration(duration, TimeScale::GPST)
83
0
    }
84
85
    #[must_use]
86
    /// Initialize an Epoch from the provided duration since 1980 January 6 at midnight
87
0
    pub fn from_qzsst_duration(duration: Duration) -> Self {
88
0
        Self::from_duration(duration, TimeScale::QZSST)
89
0
    }
90
91
    #[must_use]
92
    /// Initialize an Epoch from the provided duration since August 21st 1999 midnight
93
0
    pub fn from_gst_duration(duration: Duration) -> Self {
94
0
        Self::from_duration(duration, TimeScale::GST)
95
0
    }
96
97
    #[must_use]
98
    /// Initialize an Epoch from the provided duration since January 1st midnight
99
0
    pub fn from_bdt_duration(duration: Duration) -> Self {
100
0
        Self::from_duration(duration, TimeScale::BDT)
101
0
    }
102
103
    #[must_use]
104
3
    pub fn from_mjd_tai(days: f64) -> Self {
105
3
        Self::from_mjd_in_time_scale(days, TimeScale::TAI)
106
3
    }
107
108
65
    pub fn from_mjd_in_time_scale(days: f64, time_scale: TimeScale) -> Self {
109
65
        assert!(
110
65
            days.is_finite(),
111
0
            "Attempted to initialize Epoch with non finite number"
112
        );
113
65
        Self {
114
65
            duration: (days - MJD_J1900) * Unit::Day,
115
65
            time_scale,
116
65
        }
117
65
    }
118
119
    #[must_use]
120
0
    pub fn from_mjd_utc(days: f64) -> Self {
121
0
        Self::from_mjd_in_time_scale(days, TimeScale::UTC)
122
0
    }
123
    #[must_use]
124
0
    pub fn from_mjd_gpst(days: f64) -> Self {
125
0
        Self::from_mjd_in_time_scale(days, TimeScale::GPST)
126
0
    }
127
    #[must_use]
128
0
    pub fn from_mjd_qzsst(days: f64) -> Self {
129
0
        Self::from_mjd_in_time_scale(days, TimeScale::QZSST)
130
0
    }
131
    #[must_use]
132
0
    pub fn from_mjd_gst(days: f64) -> Self {
133
0
        Self::from_mjd_in_time_scale(days, TimeScale::GST)
134
0
    }
135
    #[must_use]
136
0
    pub fn from_mjd_bdt(days: f64) -> Self {
137
0
        Self::from_mjd_in_time_scale(days, TimeScale::BDT)
138
0
    }
139
140
    #[must_use]
141
0
    pub fn from_jde_tai(days: f64) -> Self {
142
0
        Self::from_jde_in_time_scale(days, TimeScale::TAI)
143
0
    }
144
145
0
    pub fn from_jde_in_time_scale(days: f64, time_scale: TimeScale) -> Self {
146
0
        assert!(
147
0
            days.is_finite(),
148
0
            "Attempted to initialize Epoch with non finite number"
149
        );
150
0
        Self {
151
0
            duration: (days - MJD_J1900 - MJD_OFFSET) * Unit::Day,
152
0
            time_scale,
153
0
        }
154
0
    }
155
156
    #[must_use]
157
0
    pub fn from_jde_utc(days: f64) -> Self {
158
0
        Self::from_jde_in_time_scale(days, TimeScale::UTC)
159
0
    }
160
    #[must_use]
161
0
    pub fn from_jde_gpst(days: f64) -> Self {
162
0
        Self::from_jde_in_time_scale(days, TimeScale::GPST)
163
0
    }
164
    #[must_use]
165
0
    pub fn from_jde_qzsst(days: f64) -> Self {
166
0
        Self::from_jde_in_time_scale(days, TimeScale::QZSST)
167
0
    }
168
    #[must_use]
169
0
    pub fn from_jde_gst(days: f64) -> Self {
170
0
        Self::from_jde_in_time_scale(days, TimeScale::GST)
171
0
    }
172
    #[must_use]
173
0
    pub fn from_jde_bdt(days: f64) -> Self {
174
0
        Self::from_jde_in_time_scale(days, TimeScale::BDT)
175
0
    }
176
177
    #[must_use]
178
    /// Initialize an Epoch from the provided TT seconds (approximated to 32.184s delta from TAI)
179
18
    pub fn from_tt_seconds(seconds: f64) -> Self {
180
18
        assert!(
181
18
            seconds.is_finite(),
182
0
            "Attempted to initialize Epoch with non finite number"
183
        );
184
18
        Self::from_tt_duration(seconds * Unit::Second)
185
18
    }
186
187
    #[must_use]
188
    /// Initialize an Epoch from the provided TT seconds (approximated to 32.184s delta from TAI)
189
18
    pub fn from_tt_duration(duration: Duration) -> Self {
190
18
        Self::from_duration(duration, TimeScale::TT)
191
18
    }
192
193
    #[must_use]
194
    /// Initialize an Epoch from the Ephemeris Time seconds past 2000 JAN 01 (J2000 reference)
195
4
    pub fn from_et_seconds(seconds_since_j2000: f64) -> Epoch {
196
4
        Self::from_et_duration(seconds_since_j2000 * Unit::Second)
197
4
    }
198
199
    /// Initializes an Epoch from the duration between J2000 and the current epoch as per NAIF SPICE.
200
    ///
201
    /// # Limitation
202
    /// This method uses a Newton Raphson iteration to find the appropriate TAI duration. This method is only accuracy to a few nanoseconds.
203
    /// Hence, when calling `as_et_duration()` and re-initializing it with `from_et_duration` you may have a few nanoseconds of difference (expect less than 10 ns).
204
    ///
205
    /// # Warning
206
    /// The et2utc function of NAIF SPICE will assume that there are 9 leap seconds before 01 JAN 1972,
207
    /// as this date introduces 10 leap seconds. At the time of writing, this does _not_ seem to be in
208
    /// line with IERS and the documentation in the leap seconds list.
209
    ///
210
    /// In order to match SPICE, the as_et_duration() function will manually get rid of that difference.
211
    #[must_use]
212
4
    pub fn from_et_duration(duration_since_j2000: Duration) -> Self {
213
4
        Self::from_duration(duration_since_j2000, TimeScale::ET)
214
4
    }
215
216
    #[must_use]
217
    /// Initialize an Epoch from Dynamic Barycentric Time (TDB) seconds past 2000 JAN 01 midnight (difference than SPICE)
218
    /// NOTE: This uses the ESA algorithm, which is a notch more complicated than the SPICE algorithm, but more precise.
219
    /// In fact, SPICE algorithm is precise +/- 30 microseconds for a century whereas ESA algorithm should be exactly correct.
220
68
    pub fn from_tdb_seconds(seconds_j2000: f64) -> Epoch {
221
68
        assert!(
222
68
            seconds_j2000.is_finite(),
223
0
            "Attempted to initialize Epoch with non finite number"
224
        );
225
68
        Self::from_tdb_duration(seconds_j2000 * Unit::Second)
226
68
    }
227
228
    #[must_use]
229
    /// Initialize from Dynamic Barycentric Time (TDB) (same as SPICE ephemeris time) whose epoch is 2000 JAN 01 noon TAI.
230
68
    pub fn from_tdb_duration(duration_since_j2000: Duration) -> Epoch {
231
68
        Self::from_duration(duration_since_j2000, TimeScale::TDB)
232
68
    }
233
234
    #[must_use]
235
    /// Initialize from the JDE days
236
0
    pub fn from_jde_et(days: f64) -> Self {
237
0
        assert!(
238
0
            days.is_finite(),
239
0
            "Attempted to initialize Epoch with non finite number"
240
        );
241
0
        Self::from_jde_tdb(days)
242
0
    }
243
244
    #[must_use]
245
    /// Initialize from Dynamic Barycentric Time (TDB) (same as SPICE ephemeris time) in JD days
246
0
    pub fn from_jde_tdb(days: f64) -> Self {
247
0
        assert!(
248
0
            days.is_finite(),
249
0
            "Attempted to initialize Epoch with non finite number"
250
        );
251
0
        Self::from_jde_tai(days) - Unit::Microsecond * ET_OFFSET_US
252
0
    }
253
254
    #[must_use]
255
    /// Initialize an Epoch from the number of seconds since the GPS Time Epoch,
256
    /// defined as UTC midnight of January 5th to 6th 1980 (cf. <https://gssc.esa.int/navipedia/index.php/Time_References_in_GNSS#GPS_Time_.28GPST.29>).
257
0
    pub fn from_gpst_seconds(seconds: f64) -> Self {
258
0
        Self::from_duration(seconds * Unit::Second, TimeScale::GPST)
259
0
    }
260
261
    #[must_use]
262
    /// Initialize an Epoch from the number of days since the GPS Time Epoch,
263
    /// defined as UTC midnight of January 5th to 6th 1980 (cf. <https://gssc.esa.int/navipedia/index.php/Time_References_in_GNSS#GPS_Time_.28GPST.29>).
264
0
    pub fn from_gpst_days(days: f64) -> Self {
265
0
        Self::from_duration(days * Unit::Day, TimeScale::GPST)
266
0
    }
267
268
    #[must_use]
269
    /// Initialize an Epoch from the number of nanoseconds since the GPS Time Epoch,
270
    /// defined as UTC midnight of January 5th to 6th 1980 (cf. <https://gssc.esa.int/navipedia/index.php/Time_References_in_GNSS#GPS_Time_.28GPST.29>).
271
    /// This may be useful for time keeping devices that use GPS as a time source.
272
0
    pub fn from_gpst_nanoseconds(nanoseconds: u64) -> Self {
273
0
        Self::from_duration(Duration::from_parts(0, nanoseconds), TimeScale::GPST)
274
0
    }
275
276
    #[must_use]
277
    /// Initialize an Epoch from the number of seconds since the QZSS Time Epoch,
278
    /// defined as UTC midnight of January 5th to 6th 1980 (cf. <https://gssc.esa.int/navipedia/index.php/Time_References_in_GNSS#GPS_Time_.28GPST.29>).
279
0
    pub fn from_qzsst_seconds(seconds: f64) -> Self {
280
0
        Self::from_duration(seconds * Unit::Second, TimeScale::QZSST)
281
0
    }
282
283
    #[must_use]
284
    /// Initialize an Epoch from the number of days since the QZSS Time Epoch,
285
    /// defined as UTC midnight of January 5th to 6th 1980 (cf. <https://gssc.esa.int/navipedia/index.php/Time_References_in_GNSS#GPS_Time_.28GPST.29>).
286
0
    pub fn from_qzsst_days(days: f64) -> Self {
287
0
        Self::from_duration(days * Unit::Day, TimeScale::QZSST)
288
0
    }
289
290
    #[must_use]
291
    /// Initialize an Epoch from the number of nanoseconds since the QZSS Time Epoch,
292
    /// defined as UTC midnight of January 5th to 6th 1980 (cf. <https://gssc.esa.int/navipedia/index.php/Time_References_in_GNSS#GPS_Time_.28GPST.29>).
293
    /// This may be useful for time keeping devices that use QZSS as a time source.
294
0
    pub fn from_qzsst_nanoseconds(nanoseconds: u64) -> Self {
295
0
        Self::from_duration(Duration::from_parts(0, nanoseconds), TimeScale::QZSST)
296
0
    }
297
298
    #[must_use]
299
    /// Initialize an Epoch from the number of seconds since the GST Time Epoch,
300
    /// starting August 21st 1999 midnight (UTC)
301
    /// (cf. <https://gssc.esa.int/navipedia/index.php/Time_References_in_GNSS>).
302
0
    pub fn from_gst_seconds(seconds: f64) -> Self {
303
0
        Self::from_duration(seconds * Unit::Second, TimeScale::GST)
304
0
    }
305
306
    #[must_use]
307
    /// Initialize an Epoch from the number of days since the GST Time Epoch,
308
    /// starting August 21st 1999 midnight (UTC)
309
    /// (cf. <https://gssc.esa.int/navipedia/index.php/Time_References_in_GNSS>)
310
0
    pub fn from_gst_days(days: f64) -> Self {
311
0
        Self::from_duration(days * Unit::Day, TimeScale::GST)
312
0
    }
313
314
    #[must_use]
315
    /// Initialize an Epoch from the number of nanoseconds since the GPS Time Epoch,
316
    /// starting August 21st 1999 midnight (UTC)
317
    /// (cf. <https://gssc.esa.int/navipedia/index.php/Time_References_in_GNSS>)
318
0
    pub fn from_gst_nanoseconds(nanoseconds: u64) -> Self {
319
0
        Self::from_duration(Duration::from_parts(0, nanoseconds), TimeScale::GST)
320
0
    }
321
322
    #[must_use]
323
    /// Initialize an Epoch from the number of seconds since the BDT Time Epoch,
324
    /// starting on January 1st 2006 (cf. <https://gssc.esa.int/navipedia/index.php/Time_References_in_GNSS>)
325
0
    pub fn from_bdt_seconds(seconds: f64) -> Self {
326
0
        Self::from_duration(seconds * Unit::Second, TimeScale::BDT)
327
0
    }
328
329
    #[must_use]
330
    /// Initialize an Epoch from the number of days since the BDT Time Epoch,
331
    /// starting on January 1st 2006 (cf. <https://gssc.esa.int/navipedia/index.php/Time_References_in_GNSS>)
332
0
    pub fn from_bdt_days(days: f64) -> Self {
333
0
        Self::from_duration(days * Unit::Day, TimeScale::BDT)
334
0
    }
335
336
    #[must_use]
337
    /// Initialize an Epoch from the number of nanoseconds since the BDT Time Epoch,
338
    /// starting on January 1st 2006 (cf. <https://gssc.esa.int/navipedia/index.php/Time_References_in_GNSS>).
339
    /// This may be useful for time keeping devices that use BDT as a time source.
340
0
    pub fn from_bdt_nanoseconds(nanoseconds: u64) -> Self {
341
0
        Self::from_duration(Duration::from_parts(0, nanoseconds), TimeScale::BDT)
342
0
    }
343
344
    #[must_use]
345
    /// Initialize an Epoch from the provided IEEE 1588-2008 (PTPv2) duration since TAI midnight 1970 January 01.
346
    /// PTP uses the TAI timescale but with the Unix Epoch for compatibility with unix systems.
347
0
    pub fn from_ptp_duration(duration: Duration) -> Self {
348
0
        Self::from_duration(UNIX_REF_EPOCH.to_utc_duration() + duration, TimeScale::TAI)
349
0
    }
350
351
    #[must_use]
352
    /// Initialize an Epoch from the provided IEEE 1588-2008 (PTPv2) second timestamp since TAI midnight 1970 January 01.
353
    /// PTP uses the TAI timescale but with the Unix Epoch for compatibility with unix systems.
354
0
    pub fn from_ptp_seconds(seconds: f64) -> Self {
355
0
        Self::from_ptp_duration(seconds * Unit::Second)
356
0
    }
357
358
    #[must_use]
359
    /// Initialize an Epoch from the provided IEEE 1588-2008 (PTPv2) nanoseconds timestamp since TAI midnight 1970 January 01.
360
    /// PTP uses the TAI timescale but with the Unix Epoch for compatibility with unix systems.
361
0
    pub fn from_ptp_nanoseconds(nanoseconds: u64) -> Self {
362
0
        Self::from_ptp_duration(Duration::from_parts(0, nanoseconds))
363
0
    }
364
365
    #[must_use]
366
    /// Initialize an Epoch from the provided duration since UTC midnight 1970 January 01.
367
11.9k
    pub fn from_unix_duration(duration: Duration) -> Self {
368
11.9k
        Self::from_utc_duration(UNIX_REF_EPOCH.to_utc_duration() + duration)
369
11.9k
    }
370
371
    #[must_use]
372
    /// Initialize an Epoch from the provided UNIX second timestamp since UTC midnight 1970 January 01.
373
0
    pub fn from_unix_seconds(seconds: f64) -> Self {
374
0
        Self::from_utc_duration(UNIX_REF_EPOCH.to_utc_duration() + seconds * Unit::Second)
375
0
    }
376
377
    #[must_use]
378
    /// Initialize an Epoch from the provided UNIX millisecond timestamp since UTC midnight 1970 January 01.
379
0
    pub fn from_unix_milliseconds(millisecond: f64) -> Self {
380
0
        Self::from_utc_duration(UNIX_REF_EPOCH.to_utc_duration() + millisecond * Unit::Millisecond)
381
0
    }
382
383
    /// Initializes an Epoch from the provided Format.
384
0
    pub fn from_str_with_format(s_in: &str, format: Format) -> Result<Self, HifitimeError> {
385
0
        format.parse(s_in)
386
0
    }
387
388
    /// Initializes an Epoch from the Format as a string.
389
0
    pub fn from_format_str(s_in: &str, format_str: &str) -> Result<Self, HifitimeError> {
390
0
        Format::from_str(format_str)
391
0
            .with_context(|_| ParseSnafu {
392
                details: "when using format string",
393
0
            })?
394
0
            .parse(s_in)
395
0
    }
396
397
    /// Builds an Epoch from given `week`: elapsed weeks counter into the desired Time scale, and the amount of nanoseconds within that week.
398
    /// For example, this is how GPS vehicles describe a GPST epoch.
399
    ///
400
    /// Note that this constructor relies on 128 bit integer math and may be slow on embedded devices.
401
    #[must_use]
402
0
    pub fn from_time_of_week(week: u32, nanoseconds: u64, time_scale: TimeScale) -> Self {
403
0
        let mut nanos = i128::from(nanoseconds);
404
0
        nanos += i128::from(week) * Weekday::DAYS_PER_WEEK_I128 * i128::from(NANOSECONDS_PER_DAY);
405
0
        let duration = Duration::from_total_nanoseconds(nanos);
406
0
        Self::from_duration(duration, time_scale)
407
0
    }
408
409
    #[must_use]
410
    /// Builds a UTC Epoch from given `week`: elapsed weeks counter and "ns" amount of nanoseconds since closest Sunday Midnight.
411
0
    pub fn from_time_of_week_utc(week: u32, nanoseconds: u64) -> Self {
412
0
        Self::from_time_of_week(week, nanoseconds, TimeScale::UTC)
413
0
    }
414
415
    #[must_use]
416
    /// Builds an Epoch from the provided year, days in the year, and a time scale.
417
    ///
418
    /// # Limitations
419
    /// In the TDB or ET time scales, there may be an error of up to 750 nanoseconds when initializing an Epoch this way.
420
    /// This is because we first initialize the epoch in Gregorian scale and then apply the TDB/ET offset, but that offset actually depends on the precise time.
421
    ///
422
    /// # Day couting behavior
423
    ///
424
    /// The day counter starts at 01, in other words, 01 January is day 1 of the counter, as per the GPS specificiations.
425
    ///
426
0
    pub fn from_day_of_year(year: i32, days: f64, time_scale: TimeScale) -> Self {
427
0
        let start_of_year = Self::from_gregorian(year, 1, 1, 0, 0, 0, 0, time_scale);
428
0
        start_of_year + (days - 1.0) * Unit::Day
429
0
    }
430
}