Skip to content

Commit

Permalink
Update documentation for top-level lox-time modules (#110)
Browse files Browse the repository at this point in the history
  • Loading branch information
AngusGMorrison authored Jun 12, 2024
1 parent 0d5ace8 commit f89793f
Show file tree
Hide file tree
Showing 19 changed files with 490 additions and 50 deletions.
34 changes: 33 additions & 1 deletion crates/lox-time/src/calendar_dates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*/

/*!
`calendar_dates` exposes a concrete [Date] struct and the [CalendarDate] trait for working with
human-readable dates.
*/

use std::{
cmp::Ordering,
fmt::{Display, Formatter},
Expand All @@ -30,6 +35,7 @@ fn iso_regex() -> &'static Regex {
ISO.get_or_init(|| Regex::new(r"(?<year>-?\d{4,})-(?<month>\d{2})-(?<day>\d{2})").unwrap())
}

/// Error type returned when attempting to construct a [Date] from invalid inputs.
#[derive(Debug, Clone, Error, PartialEq, Eq, PartialOrd, Ord)]
pub enum DateError {
#[error("invalid date `{0}-{1}-{2}`")]
Expand All @@ -40,13 +46,15 @@ pub enum DateError {
NonLeapYear,
}

/// The calendars supported by Lox.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Calendar {
ProlepticJulian,
Julian,
Gregorian,
}

/// A calendar date.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Date {
calendar: Calendar,
Expand All @@ -70,6 +78,7 @@ impl FromStr for Date {
}

impl Default for Date {
/// [Date] defaults to 2000-01-01 of the Gregorian calendar.
fn default() -> Self {
Self {
calendar: Calendar::Gregorian,
Expand Down Expand Up @@ -124,6 +133,12 @@ impl Date {
self.day
}

/// Construct a new [Date] from a year, month and day. The [Calendar] is inferred from the input
/// fields.
///
/// # Errors
///
/// - [DateError::InvalidDate] if the input fields do not represent a valid date.
pub fn new(year: i64, month: u8, day: u8) -> Result<Self, DateError> {
if !(1..=12).contains(&month) {
Err(DateError::InvalidDate(year, month, day))
Expand All @@ -144,6 +159,12 @@ impl Date {
}
}

/// Constructs a new [Date] from an ISO 8601 string.
///
/// # Errors
///
/// - [DateError::InvalidIsoString] if the input string does not contain a valid ISO 8601 date.
/// - [DateError::InvalidDate] if the date parsed from the ISO 8601 string is invalid.
pub fn from_iso(iso: &str) -> Result<Self, DateError> {
let caps = iso_regex()
.captures(iso)
Expand All @@ -160,6 +181,8 @@ impl Date {
Date::new(year, month, day)
}

/// Constructs a new [Date] from a signed number of days since J2000. The [Calendar] is
/// inferred.
pub fn from_days_since_j2000(days: i64) -> Self {
let calendar = if days < LAST_JULIAN_DAY_J2K {
if days > LAST_PROLEPTIC_JULIAN_DAY_J2K {
Expand Down Expand Up @@ -187,6 +210,8 @@ impl Date {
}
}

/// Constructs a new [Date] from a signed number of seconds since J2000. The [Calendar] is
/// inferred.
pub fn from_seconds_since_j2000(seconds: i64) -> Self {
let seconds = seconds + SECONDS_PER_HALF_DAY;
let mut time = seconds % SECONDS_PER_DAY;
Expand All @@ -197,6 +222,12 @@ impl Date {
Self::from_days_since_j2000(days)
}

/// Constructs a new [Date] from a year and a day number within that year. The [Calendar] is
/// inferred.
///
/// # Errors
///
/// - [DateError::NonLeapYear] if the input day number is 366 and the year is not a leap year.
pub fn from_day_of_year(year: i64, day_of_year: u16) -> Result<Self, DateError> {
let calendar = calendar(year, 1, 1);
let leap = is_leap_year(calendar, year);
Expand All @@ -211,6 +242,7 @@ impl Date {
})
}

/// Returns the day number of `self` relative to J2000.
pub fn j2000_day_number(&self) -> i64 {
j2000_day_number(self.calendar, self.year, self.month, self.day)
}
Expand Down Expand Up @@ -329,7 +361,7 @@ fn j2000_day_number(calendar: Calendar, year: i64, month: u8, day: u8) -> i64 {
d1 + d2 as i64
}

/// CalendarDate allows continuous time formats to report their date in their respective calendar.
/// `CalendarDate` allows any date-time format to report its date in a human-readable way.
pub trait CalendarDate {
fn date(&self) -> Date;

Expand Down
2 changes: 2 additions & 0 deletions crates/lox-time/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*/

//! Module `constants` exposes time-related constants.
pub mod f64;
pub mod i64;
pub mod julian_dates;
2 changes: 2 additions & 0 deletions crates/lox-time/src/constants/f64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*/

//! Module `f64` exposes time-related `f64` constants.
pub const SECONDS_PER_FEMTOSECOND: f64 = 1e-15;
2 changes: 2 additions & 0 deletions crates/lox-time/src/constants/i64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*/

//! Module `i64` exposes time-related `i64` constants.
pub const SECONDS_PER_MINUTE: i64 = 60;

pub const SECONDS_PER_HOUR: i64 = 60 * SECONDS_PER_MINUTE;
Expand Down
5 changes: 5 additions & 0 deletions crates/lox-time/src/constants/julian_dates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*/

/*!
Module `julian_dates` exposes constants related to standard Julian epochs and dates in a variety
of formats.
*/

use crate::deltas::TimeDelta;
use crate::subsecond::Subsecond;

Expand Down
67 changes: 54 additions & 13 deletions crates/lox-time/src/deltas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*/

/*!
Module `deltas` contains [TimeDelta], the key primitive of the `lox-time` crate.
[TimeDelta] is a signed, unscaled delta relative to an arbitrary epoch. This forms the basis
of instants in all continuous time scales.
The [ToDelta] trait specifies the method by which such scaled time representations should
expose their underlying [TimeDelta].
*/

use std::ops::{Add, Neg, Sub};

use num::ToPrimitive;
Expand All @@ -25,11 +35,13 @@ use crate::{
subsecond::Subsecond,
};

/// A unifying trait for types that can be converted into a [TimeDelta].
pub trait ToDelta {
/// Transforms the value into a [TimeDelta].
fn to_delta(&self) -> TimeDelta;
}

/// Error type returned when attempting to construct a [TimeDelta] from an invalid `f64`.
#[derive(Clone, Debug, Default, Error)]
#[error("`{raw}` cannot be represented as a `TimeDelta`: {detail}")]
pub struct TimeDeltaError {
Expand All @@ -49,7 +61,7 @@ impl PartialEq for TimeDeltaError {
/// A signed, continuous time difference supporting femtosecond precision.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct TimeDelta {
// Like `BaseTime`, the sign of the delta is determined by the sign of the `seconds` field.
// The sign of the delta is determined by the sign of the `seconds` field.
pub seconds: i64,

// The positive subsecond since the last whole second. For example, a delta of -0.25 s would be
Expand All @@ -64,22 +76,28 @@ pub struct TimeDelta {
}

impl TimeDelta {
/// Construct a new [TimeDelta] from a number of seconds and a [Subsecond].
pub fn new(seconds: i64, subsecond: Subsecond) -> Self {
Self { seconds, subsecond }
}

/// Construct a [TimeDelta] from an integral number of seconds.
pub fn from_seconds(seconds: i64) -> Self {
Self {
seconds,
subsecond: Subsecond::default(),
}
}

/// Create a [TimeDelta] from a floating-point number of seconds.
/// Construct a [TimeDelta] from a floating-point number of seconds.
///
/// As the magnitude of the input's significand grows, the precision of the resulting
/// `TimeDelta` falls. Applications requiring precision guarantees should use `TimeDelta::new`
/// `TimeDelta` falls. Applications requiring precision guarantees should use [TimeDelta::new]
/// instead.
///
/// # Errors
///
/// - [TimeDeltaError] if the input is NaN or ±infinity.
pub fn from_decimal_seconds(value: f64) -> Result<Self, TimeDeltaError> {
if value.is_nan() {
return Err(TimeDeltaError {
Expand Down Expand Up @@ -122,51 +140,72 @@ impl TimeDelta {
Ok(result)
}

/// Create a [TimeDelta] from a floating-point number of minutes.
/// Construct a [TimeDelta] from a floating-point number of minutes.
///
/// As the magnitude of the input's significand grows, the resolution of the resulting
/// `TimeDelta` falls. Applications requiring precision guarantees should use `TimeDelta::new`
/// `TimeDelta` falls. Applications requiring precision guarantees should use [TimeDelta::new]
/// instead.
///
/// # Errors
///
/// - [TimeDeltaError] if the input is NaN or ±infinity.
pub fn from_minutes(value: f64) -> Result<Self, TimeDeltaError> {
Self::from_decimal_seconds(value * SECONDS_PER_MINUTE)
}

/// Create a [TimeDelta] from a floating-point number of hours.
/// Construct a [TimeDelta] from a floating-point number of hours.
///
/// As the magnitude of the input's significand grows, the resolution of the resulting
/// `TimeDelta` falls. Applications requiring precision guarantees should use `TimeDelta::new`
/// `TimeDelta` falls. Applications requiring precision guarantees should use [TimeDelta::new]
/// instead.
///
/// # Errors
///
/// - [TimeDeltaError] if the input is NaN or ±infinity.
pub fn from_hours(value: f64) -> Result<Self, TimeDeltaError> {
Self::from_decimal_seconds(value * SECONDS_PER_HOUR)
}

/// Create a [TimeDelta] from a floating-point number of days.
/// Construct a [TimeDelta] from a floating-point number of days.
///
/// As the magnitude of the input's significand grows, the resolution of the resulting
/// `TimeDelta` falls. Applications requiring precision guarantees should use `TimeDelta::new`
/// `TimeDelta` falls. Applications requiring precision guarantees should use [TimeDelta::new]
/// instead.
///
/// # Errors
///
/// - [TimeDeltaError] if the input is NaN or ±infinity.
pub fn from_days(value: f64) -> Result<Self, TimeDeltaError> {
Self::from_decimal_seconds(value * SECONDS_PER_DAY)
}

/// Create a [TimeDelta] from a floating-point number of Julian years.
/// Construct a [TimeDelta] from a floating-point number of Julian years.
///
/// As the magnitude of the input's significand grows, the resolution of the resulting
/// `TimeDelta` falls. Applications requiring precision guarantees should use `TimeDelta::new`
/// `TimeDelta` falls. Applications requiring precision guarantees should use [TimeDelta::new]
/// instead.
///
/// # Errors
///
/// - [TimeDeltaError] if the input is NaN or ±infinity.
pub fn from_julian_years(value: f64) -> Result<Self, TimeDeltaError> {
Self::from_decimal_seconds(value * SECONDS_PER_JULIAN_YEAR)
}

/// Create a [TimeDelta] from a floating-point number of Julian centuries.
/// Construct a [TimeDelta] from a floating-point number of Julian centuries.
///
/// As the magnitude of the input's significand grows, the resolution of the resulting
/// `TimeDelta` falls. Applications requiring precision guarantees should use `TimeDelta::new`
/// `TimeDelta` falls. Applications requiring precision guarantees should use [TimeDelta::new]
/// instead.
///
/// # Errors
///
/// - [TimeDeltaError] if the input is NaN or ±infinity.
pub fn from_julian_centuries(value: f64) -> Result<Self, TimeDeltaError> {
Self::from_decimal_seconds(value * SECONDS_PER_JULIAN_CENTURY)
}

/// Express `&self` as a floating-point number of seconds, with potential loss of precision.
pub fn to_decimal_seconds(&self) -> f64 {
self.subsecond.0 + self.seconds.to_f64().unwrap()
}
Expand All @@ -183,6 +222,7 @@ impl TimeDelta {
self.seconds > 0 || self.seconds == 0 && self.subsecond.0 > 0.0
}

/// Scale the [TimeDelta] by `factor`, with possible loss of precision.
pub fn scale(mut self, mut factor: f64) -> Self {
// Treating both `Self` and `factor` as positive and then correcting the sign at the end
// substantially simplifies the implementation.
Expand Down Expand Up @@ -225,6 +265,7 @@ impl TimeDelta {
}
}

/// Express the [TimeDelta] as an integral number of seconds since the given [Epoch].
pub fn seconds_from_epoch(&self, epoch: Epoch) -> i64 {
match epoch {
Epoch::JulianDate => self.seconds + SECONDS_BETWEEN_JD_AND_J2000,
Expand Down
Loading

0 comments on commit f89793f

Please sign in to comment.