Skip to content

Usability enhancements, especially for Python #165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/formal_verification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:

- name: Install Kani
run: |
sudo apt install python3-testresources
cargo install --locked kani-verifier
cargo-kani setup

Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,12 @@ In order to provide full interoperability with NAIF, hifitime uses the NAIF algo

# Changelog

## 3.5.1
+ Significant speed improvement in the initialization of Epochs from their Gregorian representation, thanks [@conradludgate](https://github.com/conradludgate) for [#160](https://github.com/nyx-space/hifitime/pull/160).
+ Epoch and Duration now have a `min` and `max` function which respectively returns a copy of the epoch/duration that is the smallest or the largest between `self` and `other`, cf. [#164](https://github.com/nyx-space/hifitime/issues/164).
+ [Python] Duration and Epochs now support the operators `>`, `>=`, `<`, `<=`, `==`, and `!=`. Epoch now supports `init_from_gregorian` with a time scape, like in Rust. Epochs can also be subtracted from one another using the `timedelta` function, cf. [#162](https://github.com/nyx-space/hifitime/issues/162).
+ TimeSeries can now be formatted in different time scales, cf. [#163](https://github.com/nyx-space/hifitime/issues/163)

## 3.5.0
+ Epoch now store the time scale that they were defined in: this allows durations to be added in their respective time scales. For example, adding 36 hours to 1971-12-31 at noon when the Epoch is initialized in UTC will lead to a different epoch than adding that same duration to an epoch initialized at the same time in TAI (because the first leap second announced by IERS was on 1972-01-01), cf. the `test_add_durations_over_leap_seconds` test.
+ RFC3339 and ISO8601 fully supported for initialization of an Epoch, including the offset, e.g. `Epoch::from_str("1994-11-05T08:15:30-05:00")`, cf. [#73](https://github.com/nyx-space/hifitime/issues/73).
Expand Down
163 changes: 114 additions & 49 deletions src/duration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ use core::str::FromStr;
#[cfg(feature = "python")]
use pyo3::prelude::*;

#[cfg(feature = "python")]
use pyo3::pyclass::CompareOp;

#[cfg(not(feature = "std"))]
use num_traits::Float;

Expand Down Expand Up @@ -286,26 +289,13 @@ impl Duration {
}
}

#[cfg(feature = "python")]
#[staticmethod]
/// Create a normalized duration from its parts
pub fn init_from_parts(centuries: i16, nanoseconds: u64) -> Self {
Self::from_parts(centuries, nanoseconds)
}

#[must_use]
/// Returns the centuries and nanoseconds of this duration
/// NOTE: These items are not public to prevent incorrect durations from being created by modifying the values of the structure directly.
pub const fn to_parts(&self) -> (i16, u64) {
(self.centuries, self.nanoseconds)
}

#[cfg(feature = "python")]
#[staticmethod]
pub fn init_from_total_nanoseconds(nanos: i128) -> Self {
Self::from_total_nanoseconds(nanos)
}

/// Returns the total nanoseconds in a signed 128 bit integer
#[must_use]
pub fn total_nanoseconds(&self) -> i128 {
Expand All @@ -321,13 +311,6 @@ impl Duration {
}
}

#[cfg(feature = "python")]
#[staticmethod]
/// Create a new duration from the truncated nanoseconds (+/- 2927.1 years of duration)
pub fn init_from_truncated_nanoseconds(nanos: i64) -> Self {
Self::from_truncated_nanoseconds(nanos)
}

/// Returns the truncated nanoseconds in a signed 64 bit integer, if the duration fits.
pub fn try_truncated_nanoseconds(&self) -> Result<i64, Errors> {
// If it fits, we know that the nanoseconds also fit. abs() will fail if the centuries are min'ed out.
Expand Down Expand Up @@ -403,33 +386,6 @@ impl Duration {
self.centuries.signum() as i8
}

/// Creates a new duration from its parts
#[allow(clippy::too_many_arguments)]
#[cfg(feature = "python")]
#[staticmethod]
#[must_use]
pub fn init_from_all_parts(
sign: i8,
days: u64,
hours: u64,
minutes: u64,
seconds: u64,
milliseconds: u64,
microseconds: u64,
nanoseconds: u64,
) -> Self {
Self::compose(
sign,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
)
}

/// Decomposes a Duration in its sign, days, hours, minutes, seconds, ms, us, ns
#[must_use]
pub fn decompose(&self) -> (i8, u64, u64, u64, u64, u64, u64, u64) {
Expand Down Expand Up @@ -569,6 +525,48 @@ impl Duration {
}
}

/// Returns the minimum of the two durations.
///
/// ```
/// use hifitime::TimeUnits;
///
/// let d0 = 20.seconds();
/// let d1 = 21.seconds();
///
/// assert_eq!(d0, d1.min(d0));
/// assert_eq!(d0, d0.min(d1));
/// ```
///
/// _Note:_ this uses a pointer to `self` which will be copied immediately because Python requires a pointer.
pub fn min(&self, other: Self) -> Self {
if *self < other {
*self
} else {
other
}
}

/// Returns the maximum of the two durations.
///
/// ```
/// use hifitime::TimeUnits;
///
/// let d0 = 20.seconds();
/// let d1 = 21.seconds();
///
/// assert_eq!(d1, d1.max(d0));
/// assert_eq!(d1, d0.max(d1));
/// ```
///
/// _Note:_ this uses a pointer to `self` which will be copied immediately because Python requires a pointer.
pub fn max(&self, other: Self) -> Self {
if *self > other {
*self
} else {
other
}
}

/// Returns whether this is a negative or positive duration.
pub const fn is_negative(&self) -> bool {
self.centuries.is_negative()
Expand Down Expand Up @@ -648,6 +646,25 @@ impl Duration {
*self / other
}

#[cfg(feature = "python")]
fn __eq__(&self, other: Self) -> bool {
*self == other
}

#[cfg(feature = "python")]
fn __richcmp__(&self, other: Self, op: CompareOp) -> bool {
match op {
CompareOp::Lt => *self < other,
CompareOp::Le => *self <= other,
CompareOp::Eq => *self == other,
CompareOp::Ne => *self != other,
CompareOp::Gt => *self > other,
CompareOp::Ge => *self >= other,
}
}

// Python constructors

#[cfg(feature = "python")]
#[staticmethod]
fn zero() -> Duration {
Expand All @@ -662,13 +679,13 @@ impl Duration {

#[cfg(feature = "python")]
#[staticmethod]
fn max() -> Duration {
fn init_from_max() -> Duration {
Duration::MAX
}

#[cfg(feature = "python")]
#[staticmethod]
fn min() -> Duration {
fn init_from_min() -> Duration {
Duration::MIN
}

Expand All @@ -683,9 +700,57 @@ impl Duration {
fn min_negative() -> Duration {
Duration::MIN_NEGATIVE
}

#[cfg(feature = "python")]
#[staticmethod]
/// Create a normalized duration from its parts
fn init_from_parts(centuries: i16, nanoseconds: u64) -> Self {
Self::from_parts(centuries, nanoseconds)
}

/// Creates a new duration from its parts
#[allow(clippy::too_many_arguments)]
#[cfg(feature = "python")]
#[staticmethod]
#[must_use]
fn init_from_all_parts(
sign: i8,
days: u64,
hours: u64,
minutes: u64,
seconds: u64,
milliseconds: u64,
microseconds: u64,
nanoseconds: u64,
) -> Self {
Self::compose(
sign,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
)
}

#[cfg(feature = "python")]
#[staticmethod]
fn init_from_total_nanoseconds(nanos: i128) -> Self {
Self::from_total_nanoseconds(nanos)
}

#[cfg(feature = "python")]
#[staticmethod]
/// Create a new duration from the truncated nanoseconds (+/- 2927.1 years of duration)
fn init_from_truncated_nanoseconds(nanos: i64) -> Self {
Self::from_truncated_nanoseconds(nanos)
}
}

#[cfg(feature = "std")]
#[cfg(not(kani))]
impl<'de> Deserialize<'de> for Duration {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
Expand Down
Loading