From f97844ef3dbf9ea4426840dfb41a6f16431632a4 Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Thu, 25 May 2023 17:19:07 -0600 Subject: [PATCH 01/11] Cleanup README (fix #221) and fix #204 --- README.md | 424 +++++++++++++++++++++++++----------------------- src/duration.rs | 2 +- src/epoch.rs | 15 +- tests/epoch.rs | 21 +++ 4 files changed, 252 insertions(+), 210 deletions(-) diff --git a/README.md b/README.md index b3e9ec86..34fc20f5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,209 @@ -# hifitime 3 +# Introduction to Hifitime -Scientifically accurate date and time handling with guaranteed nanosecond precision for 32,768 years _before_ 01 January 1900 and 32,767 years _after_ that reference epoch. -Formally verified to not crash on operations on epochs and durations using the [`Kani`](https://model-checking.github.io/kani/) model checking. +Hifitime is a powerful Rust and Python library designed for time management. It provides extensive functionalities with precise operations for time calculation in different time scales, making it suitable for engineering and scientific applications where general relativity and time dilation matter. Hifitime guarantees nanosecond precision for 65,536 years around 01 January 1900 TAI. Hifitime is also formally verified using the [`Kani` model checker](https://model-checking.github.io/kani/), read more about it [this verification here](https://model-checking.github.io/kani-verifier-blog/2023/03/31/how-kani-helped-find-bugs-in-hifitime.html). + +## Usage + +First, install `hifitime` either with `cargo add hifitime` in your Rust project or `pip install hifitime` in Python. + +If building from source, note that the Python package is only built if the `python` feature is enabled. + +### Epoch ("datetime" equivalent) + +**Create an epoch in different time scales.** + +```rust +use hifitime::prelude::*; +use std::str::FromStr; +// Create an epoch in UTC +let epoch = Epoch::from_gregorian_utc(2000, 2, 29, 14, 57, 29, 37); +// Or from a string +let epoch_from_str = Epoch::from_str("2000-02-29T14:57:29.000000037 UTC").unwrap(); +assert_eq!(epoch, epoch_from_str); +// Creating it from TAI will effectively show the number of leap seconds in between UTC an TAI at that epoch +let epoch_tai = Epoch::from_gregorian_tai(2000, 2, 29, 14, 57, 29, 37); +// The difference between two epochs is a Duration +let num_leap_s = epoch - epoch_tai; +assert_eq!(format!("{num_leap_s}"), "32 s"); + +// Trivially convert to another time scale +// Either by grabbing a subdivision of time in that time scale +assert_eq!(epoch.to_gpst_days(), 7359.623402777777); // Compare to the GPS time scale + +// Or by fetching the exact duration +let mjd_offset = Duration::from_str("51603 days 14 h 58 min 33 s 184 ms 37 ns").unwrap(); +assert_eq!(epoch.to_mjd_tt_duration(), mjd_offset); // Compare to the modified Julian days in the Terrestrial Time time scale. +``` + +In Python: +```python +>>> from hifitime import * +>>> epoch = Epoch("2000-02-29T14:57:29.000000037 UTC") +>>> epoch +2000-02-29T14:57:29.000000037 UTC +>>> epoch_tai = Epoch.init_from_gregorian_tai(2000, 2, 29, 14, 57, 29, 37) +>>> epoch_tai +2000-02-29T14:57:29.000000037 TAI +>>> epoch.timedelta(epoch_tai) +32 s +>>> epoch.to_gpst_days() +7359.623402777777 +>>> epoch.to_mjd_tt_duration() +51603 days 14 h 58 min 33 s 184 ms 37 ns +>>> +``` + +**Hifitime provides several date time formats like RFC2822, ISO8601, or RFC3339.** + +```rust +use hifitime::efmt::consts::{ISO8601, RFC2822, RFC3339}; +use hifitime::prelude::*; + +let epoch = Epoch::from_gregorian_utc(2000, 2, 29, 14, 57, 29, 37); +// The default Display shows the UTC time scale +assert_eq!(format!("{epoch}"), "2000-02-29T14:57:29.000000037 UTC"); +// Format it in RFC 2822 +let fmt = Formatter::new(epoch, RFC2822); +assert_eq!(format!("{fmt}"), format!("Tue, 29 Feb 2000 14:57:29")); + +// Or in ISO8601 +let fmt = Formatter::new(epoch, ISO8601); +assert_eq!( + format!("{fmt}"), + format!("2000-02-29T14:57:29.000000037 UTC") +); + +// Which is somewhat similar to RFC3339 +let fmt = Formatter::new(epoch, RFC3339); +assert_eq!( + format!("{fmt}"), + format!("2000-02-29T14:57:29.000000037+00:00") +); +``` + +**Need some custom format? Hifitime also supports the C89 token, cf. [the documentation](https://docs.rs/hifitime/latest/hifitime/efmt/format/struct.Format.html).** + +```rust +use hifitime::prelude::*; +use std::str::FromStr; +let epoch = Epoch::from_gregorian_utc_hms(2015, 2, 7, 11, 22, 33); + +// Parsing with a custom format +assert_eq!( + Epoch::from_format_str("Sat, 07 Feb 2015 11:22:33", "%a, %d %b %Y %H:%M:%S").unwrap(), + epoch +); + +// And printing with a custom format +let fmt = Format::from_str("%a, %d %b %Y %H:%M:%S").unwrap(); +assert_eq!( + format!("{}", Formatter::new(epoch, fmt)), + "Sat, 07 Feb 2015 11:22:33" +); +``` + +**You can also grab the current system time in UTC, if the `std` feature is enabled (default), and find the next or previous day of the week.** +```rust +use hifitime::prelude::*; + +let now = Epoch::now().unwrap(); +println!("{}", now.next(Weekday::Tuesday)); +println!("{}", now.previous(Weekday::Sunday)); +``` + +**Oftentimes, we'll want to query something at a fixed step between two epochs. Hifitime makes this trivial with `TimeSeries`.** + +```rust +use hifitime::prelude::*; + +let start = Epoch::from_gregorian_utc_at_midnight(2017, 1, 14); +let end = start + 12.hours(); +let step = 2.hours(); + +let time_series = TimeSeries::inclusive(start, end, step); +let mut cnt = 0; +for epoch in time_series { + println!("{}", epoch); + cnt += 1 +} +// Check that there are indeed six two-hour periods in a half a day, +// including start and end times. +assert_eq!(cnt, 7) +``` + +In Python: +```python +>>> from hifitime import * +>>> start = Epoch.init_from_gregorian_utc_at_midnight(2017, 1, 14) +>>> end = start + Unit.Hour*12 +>>> iterator = TimeSeries(start, end, step=Unit.Hour*2, inclusive=True) +>>> for epoch in iterator: +... print(epoch) +... +2017-01-14T00:00:00 UTC +2017-01-14T02:00:00 UTC +2017-01-14T04:00:00 UTC +2017-01-14T06:00:00 UTC +2017-01-14T08:00:00 UTC +2017-01-14T10:00:00 UTC +2017-01-14T12:00:00 UTC +>>> + +``` + +### Duration + +```rust +use hifitime::prelude::*; +use core::str::FromStr; + +// Create a duration using the `TimeUnits` helping trait. +let d = 5.minutes() + 7.minutes() + 35.nanoseconds(); +assert_eq!(format!("{d}"), "12 min 35 ns"); + +// Or using the built-in enums +let d_enum = 12 * Unit::Minute + 35.0 * Unit::Nanosecond; + +// But it can also be created from a string +let d_from_str = Duration::from_str("12 min 35 ns").unwrap(); +assert_eq!(d, d_from_str); +``` + +**Hifitime guarantees nanosecond precision, but most human applications don't care too much about that. Durations can be rounded to provide a useful approximation for humans.** + +```rust +use hifitime::prelude::*; + +// Create a duration using the `TimeUnits` helping trait. +let d = 5.minutes() + 7.minutes() + 35.nanoseconds(); +// Round to the nearest minute +let rounded = d.round(1.minutes()); +assert_eq!(format!("{rounded}"), "12 min"); + +// And this works on Epochs as well. +let previous_post = Epoch::from_gregorian_utc_hms(2015, 2, 7, 11, 22, 33); +let example_now = Epoch::from_gregorian_utc_hms(2015, 8, 17, 22, 55, 01); + +// We'll round to the nearest fifteen days +let this_much_ago = example_now - previous_post; +assert_eq!(format!("{this_much_ago}"), "191 days 11 h 32 min 29 s"); +let about_this_much_ago_floor = this_much_ago.floor(15.days()); +assert_eq!(format!("{about_this_much_ago_floor}"), "180 days"); +let about_this_much_ago_ceil = this_much_ago.ceil(15.days()); +assert_eq!(format!("{about_this_much_ago_ceil}"), "195 days"); +``` + +In Python: + +```python +>>> from hifitime import * +>>> d = Duration("12 min 32 ns") +>>> d.round(Unit.Minute*1) +12 min +>>> d +12 min 32 ns +>>> +``` [![hifitime on crates.io][cratesio-image]][cratesio] [![hifitime on docs.rs][docsrs-image]][docsrs] @@ -15,6 +217,15 @@ Formally verified to not crash on operations on epochs and durations using the [ [docsrs-image]: https://docs.rs/hifitime/badge.svg [docsrs]: https://docs.rs/hifitime/ +# Comparison with `time` and `chrono` + +First off, both `time` and `chrono` are fantastic libraries in their own right. There's a reason why they have millions and millions of downloads. Secondly, hifitime was started in October 2017, so quite a while before the revival of `time` (~ 2019). + +One of the key differences is that both `chrono` and `time` separate the concepts of "time" and "date." Hifitime asserts that this is physically invalid: both a time and a date are an offset from a reference in a given time scale. That's why, Hifitime does not separate the components that make up a date, but instead, only stores a fixed duration with respect to TAI. Moreover, Hifitime is formally verified with a model checker, which is much more thorough than property testing. + +More importantly, neither `time` nor `chrono` are suitable for astronomy, astrodynamics, or any physics that must account for time dilation due to relativistic speeds or lack of the Earth as a gravity source (which sets the "tick" of a second). + +Hifitime also natively supports the UT1 time scale (the only "true" time) if built with the `ut1` feature. # Features @@ -42,130 +253,10 @@ This library is validated against NASA/NAIF SPICE for the Ephemeris Time to Univ + BeiDou Time (BDT) + UNIX ## Non-features -* Time-agnostic / date-only epochs. Hifitime only supports the combination of date and time, but the `Epoch::{at_midnight, at_noon}` is provided as a helper function. - -# Usage - -Put this in your `Cargo.toml`: - -```toml -[dependencies] -hifitime = "3.8" -``` - -## Examples: -### Time creation -```rust -use hifitime::{Epoch, Unit, TimeUnits}; -use core::str::FromStr; - -#[cfg(feature = "std")] -{ -// Initialization from system time is only available when std feature is enabled -let now = Epoch::now().unwrap(); -println!("{}", now); -} - -let mut santa = Epoch::from_gregorian_utc_hms(2017, 12, 25, 01, 02, 14); -assert_eq!(santa.to_mjd_utc_days(), 58112.043217592590); -assert_eq!(santa.to_jde_utc_days(), 2458112.5432175924); - -assert_eq!( - santa + 3600 * Unit::Second, - Epoch::from_gregorian_utc_hms(2017, 12, 25, 02, 02, 14), - "Could not add one hour to Christmas" -); - -assert_eq!( - santa + 60.0.minutes(), - Epoch::from_gregorian_utc_hms(2017, 12, 25, 02, 02, 14), - "Could not add one hour to Christmas" -); - -assert_eq!( - santa + 1.hours(), - Epoch::from_gregorian_utc_hms(2017, 12, 25, 02, 02, 14), - "Could not add one hour to Christmas" -); - -let dt = Epoch::from_gregorian_utc_hms(2017, 1, 14, 0, 31, 55); -assert_eq!(dt, Epoch::from_str("2017-01-14T00:31:55 UTC").unwrap()); -// And you can print it too, although by default it will print in UTC -assert_eq!(format!("{}", dt), "2017-01-14T00:31:55 UTC".to_string()); - -``` -### Time differences, time unit, and duration handling - -Comparing times will lead to a Duration type. Printing that will automatically select the unit. - -```rust -use hifitime::{Epoch, Unit, Duration, TimeUnits}; - -let at_midnight = Epoch::from_gregorian_utc_at_midnight(2020, 11, 2); -let at_noon = Epoch::from_gregorian_utc_at_noon(2020, 11, 2); -assert_eq!(at_noon - at_midnight, 12 * Unit::Hour); -assert_eq!(at_noon - at_midnight, 1 * Unit::Day / 2); -assert_eq!(at_midnight - at_noon, -1.days() / 2); - -let delta_time = at_noon - at_midnight; -assert_eq!(format!("{}", delta_time), "12 h".to_string()); -// And we can multiply durations by a scalar... -let delta2 = 2 * delta_time; -assert_eq!(format!("{}", delta2), "1 days".to_string()); -// Or divide them by a scalar. -assert_eq!(format!("{}", delta2 / 2.0), "12 h".to_string()); - -// And of course, these comparisons account for differences in time scales -let at_midnight_utc = Epoch::from_gregorian_utc_at_midnight(2020, 11, 2); -let at_noon_tai = Epoch::from_gregorian_tai_at_noon(2020, 11, 2); -assert_eq!(format!("{}", at_noon_tai - at_midnight_utc), "11 h 59 min 23 s".to_string()); -``` - -Timeunits and frequency units are trivially supported. Hifitime only supports up to nanosecond precision (but guarantees it for 64 millennia), so any duration less than one nanosecond is truncated. - -```rust -use hifitime::{Epoch, Unit, Freq, Duration, TimeUnits}; - -// One can compare durations -assert!(10.seconds() > 5.seconds()); -assert!(10.days() + 1.nanoseconds() > 10.days()); - -// Those durations are more precise than floating point since this is integer math in nanoseconds -let d: Duration = 1.0.hours() / 3 - 20.minutes(); -assert!(d.abs() < Unit::Nanosecond); -assert_eq!(3 * 20.minutes(), Unit::Hour); - -// And also frequencies but note that frequencies are converted to Durations! -// So the duration of that frequency is compared, hence the following: -assert!(10 * Freq::Hertz < 5 * Freq::Hertz); -assert!(4 * Freq::MegaHertz > 5 * Freq::MegaHertz); - -// And asserts on the units themselves -assert!(Freq::GigaHertz < Freq::MegaHertz); -assert!(Unit::Second > Unit::Millisecond); -``` - -### Iterating over times ("linspace" of epochs) -Finally, something which may come in very handy, line spaces between times with a given step. - -```rust -use hifitime::{Epoch, Unit, TimeSeries}; -let start = Epoch::from_gregorian_utc_at_midnight(2017, 1, 14); -let end = Epoch::from_gregorian_utc_at_noon(2017, 1, 14); -let step = 2 * Unit::Hour; -let time_series = TimeSeries::inclusive(start, end, step); -let mut cnt = 0; -for epoch in time_series { - println!("{}", epoch); - cnt += 1 -} -// Check that there are indeed six two-hour periods in a half a day, -// including start and end times. -assert_eq!(cnt, 7) -``` +* Time-agnostic / date-only epochs. Hifitime only supports the combination of date and time, but the `Epoch::{at_midnight, at_noon}` is provided as helper functions. # Design -No software is perfect, so please report any issue or bugs on [Github](https://github.com/nyx-space/hifitime/issues/new). +No software is perfect, so please report any issue or bug on [Github](https://github.com/nyx-space/hifitime/issues/new). ## Duration Under the hood, a Duration is represented as a 16 bit signed integer of centuries (`i16`) and a 64 bit unsigned integer (`u64`) of the nanoseconds past that century. The overflowing and underflowing of nanoseconds is handled by changing the number of centuries such that the nanoseconds number never represents more than one century (just over four centuries can be stored in 64 bits). @@ -178,37 +269,6 @@ Advantages: Disadvantages: 1. Most astrodynamics applications require the computation of a duration in floating point values such as when querying an ephemeris. This design leads to an overhead of about 5.2 nanoseconds according to the benchmarks (`Duration to f64 seconds` benchmark). You may run the benchmarks with `cargo bench`. -### Printing and parsing - -When Durations are printed, only the units whose value is non-zero is printed. For example, `5.hours() + 256.0.milliseconds() + 1.0.nanoseconds()` will be printed as "5 h 256 ms 1 ns". - -```rust -use hifitime::{Duration, Unit, TimeUnits}; -use core::str::FromStr; - -assert_eq!( - format!( - "{}", - 5.hours() + 256.0.milliseconds() + 1.0.nanoseconds() - ), - "5 h 256 ms 1 ns" -); - -assert_eq!( - format!( - "{}", - 5.days() + 1.0.nanoseconds() - ), - "5 days 1 ns" -); - - -assert_eq!( - Duration::from_str("5 h 256 ms 1 ns").unwrap(), - 5 * Unit::Hour + 256 * Unit::Millisecond + Unit::Nanosecond -); -``` - ## Epoch The Epoch is simply a wrapper around a Duration. All epochs are stored in TAI duration with respect to 01 January 1900 at noon (the official TAI epoch). The choice of TAI meets the [Standard of Fundamental Astronomy (SOFA)](https://www.iausofa.org/) recommendation of opting for a glitch-free time scale (i.e. without discontinuities like leap seconds or non-uniform seconds like TDB). @@ -224,50 +284,6 @@ Epochs can be formatted and parsed in the following time scales: + UNIX: `{epoch:p}` + GPS: `{epoch:o}` -```rust -use hifitime::{Epoch, TimeScale}; -use core::str::FromStr; - -let epoch = Epoch::from_gregorian_utc_hms(2022, 9, 6, 23, 24, 29); - -assert_eq!(format!("{epoch}"), "2022-09-06T23:24:29 UTC"); -assert_eq!(format!("{epoch:x}"), "2022-09-06T23:25:06 TAI"); -assert_eq!(format!("{epoch:X}"), "2022-09-06T23:25:38.184000000 TT"); -assert_eq!(format!("{epoch:E}"), "2022-09-06T23:25:38.182538909 ET"); -assert_eq!(format!("{epoch:e}"), "2022-09-06T23:25:38.182541259 TDB"); -assert_eq!(format!("{epoch:p}"), "1662506669"); // UNIX seconds -assert_eq!(format!("{epoch:o}"), "1346541887000000000"); // GPS nanoseconds - -// RFC3339 parsing with time scales -assert_eq!( - Epoch::from_gregorian_utc_hms(1994, 11, 5, 13, 15, 30), - Epoch::from_str("1994-11-05T08:15:30-05:00").unwrap() -); -assert_eq!( - Epoch::from_gregorian_utc_hms(1994, 11, 5, 13, 15, 30), - Epoch::from_str("1994-11-05T13:15:30Z").unwrap() -); -// Same test with different time systems -// TAI -assert_eq!( - Epoch::from_gregorian_tai_hms(1994, 11, 5, 13, 15, 30), - Epoch::from_str("1994-11-05T08:15:30-05:00 TAI").unwrap() -); -assert_eq!( - Epoch::from_gregorian_tai_hms(1994, 11, 5, 13, 15, 30), - Epoch::from_str("1994-11-05T13:15:30Z TAI").unwrap() -); -// TDB -assert_eq!( - Epoch::from_gregorian_hms(1994, 11, 5, 13, 15, 30, TimeScale::TDB), - Epoch::from_str("1994-11-05T08:15:30-05:00 TDB").unwrap() -); -assert_eq!( - Epoch::from_gregorian_hms(1994, 11, 5, 13, 15, 30, TimeScale::TDB), - Epoch::from_str("1994-11-05T13:15:30Z TDB").unwrap() -); -``` - ## Leap second support Leap seconds allow TAI (the absolute time reference) and UTC (the civil time reference) to not drift too much. In short, UTC allows humans to see the sun at zenith at noon, whereas TAI does not worry about that. Leap seconds are introduced to allow for UTC to catch up with the absolute time reference of TAI. Specifically, UTC clocks are "stopped" for one second to make up for the accumulated difference between TAI and UTC. These leap seconds are announced several months in advance by IERS, cf. in the [IETF leap second reference](https://www.ietf.org/timezones/data/leap-seconds.list). @@ -286,6 +302,9 @@ In order to provide full interoperability with NAIF, hifitime uses the NAIF algo # Changelog +## 3.8.2 ++ Clarify README + ## 3.8.1 (unreleased) + Fix documentation for the formatter, cf. [#202](https://github.com/nyx-space/hifitime/pull/202) + Update MSRV to 1.59 for rayon v 1.10 @@ -367,8 +386,3 @@ Huge thanks to [@gwbres](https://github.com/gwbres) who put in all of the work f ## 3.0.0 + Backend rewritten from TwoFloat to a struct of the centuries in `i16` and nanoseconds in `u64`. Thanks to [@pwnorbitals](https://github.com/pwnorbitals) for proposing the idea in #[107](https://github.com/nyx-space/hifitime/issues/107) and writing the proof of concept. This leads to at least a 2x speed up in most calculations, cf. [this comment](https://github.com/nyx-space/hifitime/pull/107#issuecomment-1040702004). + Fix GPS epoch, and addition of a helper functions in `Epoch` by [@cjordan](https://github.com/cjordan) - -## 2.2.3 -+ More deterministic `as_jde_tdb_days()` in `Epoch`. In version 2.2.1, the ephemeris time and TDB _days_ were identical down to machine precision. After a number of validation cases in the rotation equations of the IAU Earth to Earth Mean Equator J2000 frame, the new formulation was shown to lead to less rounding errors when requesting the days. These rounding errors prevented otherwise trivial test cases. However, it adds an error of **40.2 nanoseconds** when initializing an Epoch with the days in ET and requesting the TDB days. - -_Note:_ this was originally published as 2.2.2 but I'd forgotten to update one of the tests with the 40.2 ns error. diff --git a/src/duration.rs b/src/duration.rs index 684d7c36..9f420812 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -360,7 +360,7 @@ impl Duration { } else { // Centuries negative by a decent amount Ok( - i64::from(self.centuries + 1) * NANOSECONDS_PER_CENTURY as i64 + i64::from(self.centuries) * NANOSECONDS_PER_CENTURY as i64 + self.nanoseconds as i64, ) } diff --git a/src/epoch.rs b/src/epoch.rs index 0aeb8836..9f827454 100644 --- a/src/epoch.rs +++ b/src/epoch.rs @@ -705,6 +705,7 @@ impl Epoch { // Add the seconds for the months prior to the current month duration_wrt_1900 += Unit::Day * i64::from(CUMULATIVE_DAYS_FOR_MONTH[(month - 1) as usize]); + if is_leap_year(year) && month > 2 { // NOTE: If on 29th of February, then the day is not finished yet, and therefore // the extra seconds are added below as per a normal day. @@ -1130,7 +1131,7 @@ impl Epoch { day = if sign >= 0 { days_in_year - days_so_far + 1.0 } else { - days_in_year - days_so_far - 1.0 + days_in_year - days_so_far }; break; } @@ -1150,9 +1151,6 @@ impl Epoch { } else { usual_days_per_month(11) as f64 }; - } else if sign < 0 { - // Must add one day because just below, we'll be ignoring the days when rebuilding the time. - day += 1.0; } if sign < 0 { @@ -1167,6 +1165,15 @@ impl Epoch { nanos, ); + // Last check on the validity of the Gregorian date + + if time == Duration::ZERO || month == 12 && day == 32.0 { + // We've underflowed since we're before 1900. + year += 1; + month = 1; + day = 1.0; + } + let (_, _, hours, minutes, seconds, milliseconds, microseconds, nanos) = (24 * Unit::Hour + time).decompose(); diff --git a/tests/epoch.rs b/tests/epoch.rs index 62850a9d..90fe364c 100644 --- a/tests/epoch.rs +++ b/tests/epoch.rs @@ -1844,3 +1844,24 @@ fn test_leap_seconds_file() { } } } + +#[test] +fn regression_test_gh_204() { + use core::str::FromStr; + use hifitime::Epoch; + + let e1700 = Epoch::from_str("1700-01-01T00:00:00 TAI").unwrap(); + assert_eq!(format!("{e1700:x}"), "1700-01-01T00:00:00 TAI"); + + let e1700 = Epoch::from_str("1700-04-17T02:10:09 TAI").unwrap(); + assert_eq!(format!("{e1700:x}"), "1700-04-17T02:10:09 TAI"); + + let e1799 = Epoch::from_str("1799-01-01T00:00:01 TAI").unwrap(); + assert_eq!(format!("{e1799:x}"), "1799-01-01T00:00:01 TAI"); + + let e1899 = Epoch::from_str("1899-01-01T00:00:00 TAI").unwrap(); + assert_eq!(format!("{e1899:x}"), "1899-01-01T00:00:00 TAI"); + + let e1900_m1 = Epoch::from_str("1899-12-31T23:59:59 TAI").unwrap(); + assert_eq!(format!("{e1900_m1:x}"), "1899-12-31T23:59:59 TAI"); +} From 4530f16477d8948272f7c7de83c8d10493f80e5d Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Thu, 25 May 2023 17:44:50 -0600 Subject: [PATCH 02/11] Fix no-std for readme tests Signed-off-by: Christopher Rabotin --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 34fc20f5..21ca83b9 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ If building from source, note that the Python package is only built if the `pyth ```rust use hifitime::prelude::*; -use std::str::FromStr; +use core::str::FromStr; // Create an epoch in UTC let epoch = Epoch::from_gregorian_utc(2000, 2, 29, 14, 57, 29, 37); // Or from a string @@ -84,8 +84,9 @@ assert_eq!( **Need some custom format? Hifitime also supports the C89 token, cf. [the documentation](https://docs.rs/hifitime/latest/hifitime/efmt/format/struct.Format.html).** ```rust +use core::str::FromStr; use hifitime::prelude::*; -use std::str::FromStr; + let epoch = Epoch::from_gregorian_utc_hms(2015, 2, 7, 11, 22, 33); // Parsing with a custom format @@ -106,9 +107,12 @@ assert_eq!( ```rust use hifitime::prelude::*; -let now = Epoch::now().unwrap(); -println!("{}", now.next(Weekday::Tuesday)); -println!("{}", now.previous(Weekday::Sunday)); +#[cfg(feature = "std")] +{ + let now = Epoch::now().unwrap(); + println!("{}", now.next(Weekday::Tuesday)); + println!("{}", now.previous(Weekday::Sunday)); +} ``` **Oftentimes, we'll want to query something at a fixed step between two epochs. Hifitime makes this trivial with `TimeSeries`.** @@ -123,6 +127,7 @@ let step = 2.hours(); let time_series = TimeSeries::inclusive(start, end, step); let mut cnt = 0; for epoch in time_series { + #[cfg(feature = "std")] println!("{}", epoch); cnt += 1 } From 1bf07caf4af840be0354eae0bac365a1acb2b134 Mon Sep 17 00:00:00 2001 From: massou Date: Fri, 26 May 2023 13:43:18 +0200 Subject: [PATCH 03/11] fix december for MonthName from u8 --- src/month.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/month.rs b/src/month.rs index 5fe29ea4..bf0f5adf 100644 --- a/src/month.rs +++ b/src/month.rs @@ -70,7 +70,7 @@ impl FromStr for MonthName { impl From for MonthName { fn from(u: u8) -> Self { - match u.rem_euclid(Self::MAX) { + match u { 1 => Self::January, 2 => Self::February, 3 => Self::March, From 1ac2aaa1b830849e5eeb4df1530d644dd9710915 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 11:58:54 +0000 Subject: [PATCH 04/11] Update criterion requirement from 0.4.0 to 0.5.1 Updates the requirements on [criterion](https://github.com/bheisler/criterion.rs) to permit the latest version. - [Changelog](https://github.com/bheisler/criterion.rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/bheisler/criterion.rs/compare/0.4.0...0.5.1) --- updated-dependencies: - dependency-name: criterion dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 449ddc42..d46f711c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ openssl = { version = "0.10", features = ["vendored"], optional = true } [dev-dependencies] serde_json = "1.0.91" -criterion = "0.4.0" +criterion = "0.5.1" iai = "0.1" [features] From 7be56a42551ffb6fbf82c25bc14256f5cda87f81 Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Fri, 26 May 2023 08:27:47 -0600 Subject: [PATCH 05/11] Fix formal verification test for very negative durations Signed-off-by: Christopher Rabotin --- src/duration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/duration.rs b/src/duration.rs index 9f420812..d733ac5f 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -1545,7 +1545,7 @@ fn formal_duration_truncated_ns_reciprocity() { // We fit on a i64 but we need to account for the number of nanoseconds wrapped to the negative centuries. let nanos = u_ns.rem_euclid(NANOSECONDS_PER_CENTURY); - let expect_rslt = i64::from(centuries + 1) * NANOSECONDS_PER_CENTURY as i64 + nanos as i64; + let expect_rslt = i64::from(centuries) * NANOSECONDS_PER_CENTURY as i64 + nanos as i64; let recip_ns = dur_from_part.try_truncated_nanoseconds().unwrap(); assert_eq!(recip_ns, expect_rslt); From 89dda54b520a3883e01f181f0bdadead95b81601 Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Fri, 26 May 2023 08:38:07 -0600 Subject: [PATCH 06/11] Cleanup README Signed-off-by: Christopher Rabotin --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 21ca83b9..3fdc39d8 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Hifitime is a powerful Rust and Python library designed for time management. It provides extensive functionalities with precise operations for time calculation in different time scales, making it suitable for engineering and scientific applications where general relativity and time dilation matter. Hifitime guarantees nanosecond precision for 65,536 years around 01 January 1900 TAI. Hifitime is also formally verified using the [`Kani` model checker](https://model-checking.github.io/kani/), read more about it [this verification here](https://model-checking.github.io/kani-verifier-blog/2023/03/31/how-kani-helped-find-bugs-in-hifitime.html). +Most users of Hifitime will only need to rely on the `Epoch` and `Duration` structures, and optionally the `Weekday` enum for week based computations. Scientific applications may make use of the `TimeScale` enum as well. + ## Usage First, install `hifitime` either with `cargo add hifitime` in your Rust project or `pip install hifitime` in Python. @@ -131,7 +133,7 @@ for epoch in time_series { println!("{}", epoch); cnt += 1 } -// Check that there are indeed six two-hour periods in a half a day, +// Check that there are indeed seven two-hour periods in a half a day, // including start and end times. assert_eq!(cnt, 7) ``` @@ -307,12 +309,11 @@ In order to provide full interoperability with NAIF, hifitime uses the NAIF algo # Changelog -## 3.8.2 -+ Clarify README - -## 3.8.1 (unreleased) +## 3.8.1 (work in progress) + Fix documentation for the formatter, cf. [#202](https://github.com/nyx-space/hifitime/pull/202) + Update MSRV to 1.59 for rayon v 1.10 ++ Clarify README and add a section comparing Hifitime to `time` and `chrono`, cf. [#221](https://github.com/nyx-space/hifitime/issues/221) ++ Fix incorrect printing of Gregorian dates prior to to 1900, cf. [#204](https://github.com/nyx-space/hifitime/issues/204) ## 3.8.0 Thanks again to [@gwbres](https://github.com/gwbres) for his work in this release! From 96a2b33c951f77e3ef50d07a04be9041647ecdbe Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 26 May 2023 08:51:11 -0600 Subject: [PATCH 07/11] Update month.rs to remove unused Self::MAX --- src/month.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/month.rs b/src/month.rs index bf0f5adf..b0f7d984 100644 --- a/src/month.rs +++ b/src/month.rs @@ -43,10 +43,6 @@ impl Default for MonthName { } } -impl MonthName { - const MAX: u8 = 12; -} - impl FromStr for MonthName { type Err = ParsingErrors; fn from_str(s: &str) -> Result { From ecb4fcf3a5722e4830bfd0c02b467e839f98f178 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 27 May 2023 07:56:59 -0600 Subject: [PATCH 08/11] Update README.md - 3.8.1 ready --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3fdc39d8..a3d58cdc 100644 --- a/README.md +++ b/README.md @@ -309,7 +309,7 @@ In order to provide full interoperability with NAIF, hifitime uses the NAIF algo # Changelog -## 3.8.1 (work in progress) +## 3.8.1 + Fix documentation for the formatter, cf. [#202](https://github.com/nyx-space/hifitime/pull/202) + Update MSRV to 1.59 for rayon v 1.10 + Clarify README and add a section comparing Hifitime to `time` and `chrono`, cf. [#221](https://github.com/nyx-space/hifitime/issues/221) From d36b6ee799589666ec63550039d8546d993368af Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 27 May 2023 07:58:12 -0600 Subject: [PATCH 09/11] Update README.md - call it 3.8.2 --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a3d58cdc..ea93d2cb 100644 --- a/README.md +++ b/README.md @@ -309,12 +309,14 @@ In order to provide full interoperability with NAIF, hifitime uses the NAIF algo # Changelog -## 3.8.1 -+ Fix documentation for the formatter, cf. [#202](https://github.com/nyx-space/hifitime/pull/202) -+ Update MSRV to 1.59 for rayon v 1.10 +## 3.8.2 + Clarify README and add a section comparing Hifitime to `time` and `chrono`, cf. [#221](https://github.com/nyx-space/hifitime/issues/221) + Fix incorrect printing of Gregorian dates prior to to 1900, cf. [#204](https://github.com/nyx-space/hifitime/issues/204) +## 3.8.1 (unreleased) ++ Fix documentation for the formatter, cf. [#202](https://github.com/nyx-space/hifitime/pull/202) ++ Update MSRV to 1.59 for rayon v 1.10 + ## 3.8.0 Thanks again to [@gwbres](https://github.com/gwbres) for his work in this release! From edc506bd65b79678417858bc3963b191785a6991 Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Sat, 27 May 2023 09:14:57 -0600 Subject: [PATCH 10/11] Fix version in Cargo Signed-off-by: Christopher Rabotin --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d46f711c..80b7c4b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hifitime" -version = "3.8.1" +version = "3.8.2" authors = ["Christopher Rabotin "] description = "Ultra-precise date and time handling in Rust for scientific applications with leap second support" homepage = "https://nyxspace.com/" From 3d7f5d07cb0d869dc3ae1c5a86e14198c31c0d9d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jun 2023 12:00:01 +0000 Subject: [PATCH 11/11] Update pyo3 requirement from 0.18.1 to 0.19.0 Updates the requirements on [pyo3](https://github.com/pyo3/pyo3) to permit the latest version. - [Release notes](https://github.com/pyo3/pyo3/releases) - [Changelog](https://github.com/PyO3/pyo3/blob/main/CHANGELOG.md) - [Commits](https://github.com/pyo3/pyo3/compare/v0.18.1...v0.19.0) --- updated-dependencies: - dependency-name: pyo3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 80b7c4b4..382c5f05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ name = "hifitime" serde = {version = "1.0.155", optional = true} serde_derive = {version = "1.0.155", optional = true} der = {version = "0.6.1", features = ["derive", "real"], optional = true} -pyo3 = { version = "0.18.1", features = ["extension-module"], optional = true } +pyo3 = { version = "0.19.0", features = ["extension-module"], optional = true } num-traits = {version = "0.2.15", default-features = false, features = ["libm"]} lexical-core = {version = "0.8.5", default-features = false, features = ["parse-integers", "parse-floats"]} reqwest = { version = "0.11", features = ["blocking", "json"], optional = true}