From 9df66e522684f966d55252b5fba61a8507c3c6c7 Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Fri, 17 Jan 2025 22:39:40 -0600 Subject: [PATCH 1/8] Progress on structural changes to support the new layout --- Cargo.toml | 2 +- boa_test262.log | 19 + src/{components => builtins/core}/calendar.rs | 2 +- .../core}/calendar/era.rs | 0 .../core}/calendar/types.rs | 4 +- src/{components => builtins/core}/date.rs | 4 +- src/{components => builtins/core}/datetime.rs | 4 +- src/{components => builtins/core}/duration.rs | 23 +- .../core}/duration/date.rs | 0 .../core}/duration/normalized.rs | 2 +- src/builtins/core/duration/tests.rs | 18 + .../core}/duration/time.rs | 0 src/{components => builtins/core}/instant.rs | 53 +-- src/{components => builtins/core}/mod.rs | 5 +- .../core}/month_day.rs | 2 +- src/{components => builtins/core}/now.rs | 34 +- src/builtins/core/options.rs | 140 +++++++ src/{components => builtins/core}/time.rs | 4 +- src/{components => builtins/core}/timezone.rs | 5 +- .../core}/year_month.rs | 4 +- .../core}/zoneddatetime.rs | 353 +--------------- src/builtins/mod.rs | 8 + src/builtins/std/date.rs | 270 +++++++++++++ src/builtins/std/duration.rs | 206 ++++++++++ .../std}/duration/tests.rs | 28 +- src/builtins/std/instant.rs | 79 ++++ src/builtins/std/mod.rs | 55 +++ src/builtins/std/now.rs | 52 +++ src/builtins/std/options.rs | 53 +++ src/builtins/std/timezone.rs | 5 + src/builtins/std/zoneddatetime.rs | 377 ++++++++++++++++++ src/epoch_nanoseconds.rs | 48 +++ src/iso.rs | 2 +- src/lib.rs | 17 +- src/options/relative_to.rs | 135 +------ src/tzdb.rs | 5 +- 36 files changed, 1393 insertions(+), 625 deletions(-) create mode 100644 boa_test262.log rename src/{components => builtins/core}/calendar.rs (99%) rename src/{components => builtins/core}/calendar/era.rs (100%) rename src/{components => builtins/core}/calendar/types.rs (99%) rename src/{components => builtins/core}/date.rs (99%) rename src/{components => builtins/core}/datetime.rs (99%) rename src/{components => builtins/core}/duration.rs (97%) rename src/{components => builtins/core}/duration/date.rs (100%) rename src/{components => builtins/core}/duration/normalized.rs (99%) create mode 100644 src/builtins/core/duration/tests.rs rename src/{components => builtins/core}/duration/time.rs (100%) rename src/{components => builtins/core}/instant.rs (93%) rename src/{components => builtins/core}/mod.rs (88%) rename src/{components => builtins/core}/month_day.rs (96%) rename src/{components => builtins/core}/now.rs (80%) create mode 100644 src/builtins/core/options.rs rename src/{components => builtins/core}/time.rs (99%) rename src/{components => builtins/core}/timezone.rs (99%) rename src/{components => builtins/core}/year_month.rs (96%) rename src/{components => builtins/core}/zoneddatetime.rs (78%) create mode 100644 src/builtins/mod.rs create mode 100644 src/builtins/std/date.rs create mode 100644 src/builtins/std/duration.rs rename src/{components => builtins/std}/duration/tests.rs (97%) create mode 100644 src/builtins/std/instant.rs create mode 100644 src/builtins/std/mod.rs create mode 100644 src/builtins/std/now.rs create mode 100644 src/builtins/std/options.rs create mode 100644 src/builtins/std/timezone.rs create mode 100644 src/builtins/std/zoneddatetime.rs create mode 100644 src/epoch_nanoseconds.rs diff --git a/Cargo.toml b/Cargo.toml index 47484b300..ab39ab8ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ combine = { workspace = true, optional = true } web-time = { workspace = true, optional = true } [features] -default = ["now"] +default = ["now", "experimental"] log = ["dep:log"] experimental = ["tzdb"] now = ["std", "dep:web-time"] diff --git a/boa_test262.log b/boa_test262.log new file mode 100644 index 000000000..7b74997fd --- /dev/null +++ b/boa_test262.log @@ -0,0 +1,19 @@ +Boa #4098 + +Broken: + +test/intl402/Temporal/PlainMonthDay/from/fields-missing-properties.js (previously Passed) +test/built-ins/Temporal/PlainDate/prototype/until/largestunit-default.js (previously Passed) +test/built-ins/Temporal/PlainDate/prototype/since/largestunit-default.js (previously Passed) +test/built-ins/Temporal/Duration/prototype/round/year-zero.js (previously Passed) +test/built-ins/Temporal/Duration/prototype/round/relativeto-leap-second.js (previously Passed) +test/built-ins/Temporal/Duration/prototype/round/relativeto-string-invalid.js (previously Passed) +test/built-ins/Temporal/Duration/prototype/round/relativeTo-required-properties.js (previously Passed) +test/built-ins/Temporal/Duration/prototype/round/relativeto-wrong-type.js (previously Passed) +test/built-ins/Temporal/Duration/prototype/round/throws-on-wrong-offset-for-zoned-date-time-relative-to.js (previously Passed) +test/built-ins/Temporal/ZonedDateTime/from/overflow-invalid-string.js (previously Passed) +test/built-ins/Temporal/PlainDateTime/from/argument-string-minus-sign.js (previously Passed) +test/built-ins/Temporal/PlainDateTime/compare/argument-string-minus-sign.js (previously Passed) +test/built-ins/Temporal/PlainDateTime/prototype/equals/argument-string-minus-sign.js (previously Passed) +test/built-ins/Temporal/PlainDateTime/prototype/until/argument-string-minus-sign.js (previously Passed) +test/built-ins/Temporal/PlainDateTime/prototype/since/argument-string-minus-sign.js (previously Passed) diff --git a/src/components/calendar.rs b/src/builtins/core/calendar.rs similarity index 99% rename from src/components/calendar.rs rename to src/builtins/core/calendar.rs index b0284d868..b74596f1c 100644 --- a/src/components/calendar.rs +++ b/src/builtins/core/calendar.rs @@ -11,7 +11,7 @@ use core::str::FromStr; use icu_calendar::types::{Era as IcuEra, MonthCode as IcuMonthCode, MonthInfo, YearInfo}; use crate::{ - components::{ + builtins::core::{ duration::{DateDuration, TimeDuration}, Duration, PlainDate, PlainDateTime, PlainMonthDay, PlainYearMonth, }, diff --git a/src/components/calendar/era.rs b/src/builtins/core/calendar/era.rs similarity index 100% rename from src/components/calendar/era.rs rename to src/builtins/core/calendar/era.rs diff --git a/src/components/calendar/types.rs b/src/builtins/core/calendar/types.rs similarity index 99% rename from src/components/calendar/types.rs rename to src/builtins/core/calendar/types.rs index 7420bb634..1221a291e 100644 --- a/src/components/calendar/types.rs +++ b/src/builtins/core/calendar/types.rs @@ -9,7 +9,7 @@ use crate::iso::{constrain_iso_day, is_valid_iso_day}; use crate::options::ArithmeticOverflow; use crate::{TemporalError, TemporalResult}; -use crate::components::{calendar::Calendar, PartialDate}; +use crate::builtins::core::{calendar::Calendar, PartialDate}; /// `ResolvedCalendarFields` represents the resolved field values necessary for /// creating a Date from potentially partial values. @@ -322,7 +322,7 @@ mod tests { use tinystr::tinystr; use crate::{ - components::{calendar::Calendar, PartialDate}, + builtins::core::{calendar::Calendar, PartialDate}, options::ArithmeticOverflow, }; diff --git a/src/components/date.rs b/src/builtins/core/date.rs similarity index 99% rename from src/components/date.rs rename to src/builtins/core/date.rs index e8dd18de3..8dd9f856a 100644 --- a/src/components/date.rs +++ b/src/builtins/core/date.rs @@ -1,7 +1,7 @@ //! This module implements `Date` and any directly related algorithms. use crate::{ - components::{calendar::Calendar, duration::DateDuration, Duration, PlainDateTime}, + builtins::core::{calendar::Calendar, duration::DateDuration, Duration, PlainDateTime, PlainTime}, iso::{IsoDate, IsoDateTime, IsoTime}, options::{ ArithmeticOverflow, DifferenceOperation, DifferenceSettings, DisplayCalendar, @@ -18,7 +18,7 @@ use super::{ calendar::{ascii_four_to_integer, month_to_month_code}, duration::{normalized::NormalizedDurationRecord, TimeDuration}, timezone::NeverProvider, - PlainMonthDay, PlainTime, PlainYearMonth, + PlainMonthDay, PlainYearMonth, }; use tinystr::TinyAsciiStr; diff --git a/src/components/datetime.rs b/src/builtins/core/datetime.rs similarity index 99% rename from src/components/datetime.rs rename to src/builtins/core/datetime.rs index 48a3a23bc..d294f0f84 100644 --- a/src/components/datetime.rs +++ b/src/builtins/core/datetime.rs @@ -1,7 +1,7 @@ //! This module implements `DateTime` any directly related algorithms. use crate::{ - components::{calendar::Calendar, Instant}, + builtins::core::{calendar::Calendar, Instant}, iso::{IsoDate, IsoDateTime, IsoTime}, options::{ ArithmeticOverflow, DifferenceOperation, DifferenceSettings, DisplayCalendar, @@ -693,7 +693,7 @@ mod tests { use tinystr::{tinystr, TinyAsciiStr}; use crate::{ - components::{ + builtins::core::{ calendar::Calendar, duration::DateDuration, Duration, PartialDate, PartialDateTime, PartialTime, PlainDateTime, }, diff --git a/src/components/duration.rs b/src/builtins/core/duration.rs similarity index 97% rename from src/components/duration.rs rename to src/builtins/core/duration.rs index 6ea5bf219..fd8d590b4 100644 --- a/src/components/duration.rs +++ b/src/builtins/core/duration.rs @@ -1,10 +1,10 @@ //! This module implements `Duration` along with it's methods and components. use crate::{ - components::{timezone::TimeZoneProvider, PlainDateTime, PlainTime}, + builtins::core::{timezone::TimeZoneProvider, options::RelativeTo, PlainDateTime, PlainTime, ZonedDateTime}, iso::{IsoDateTime, IsoTime}, options::{ - ArithmeticOverflow, RelativeTo, ResolvedRoundingOptions, RoundingOptions, TemporalUnit, + ArithmeticOverflow, ResolvedRoundingOptions, RoundingOptions, TemporalUnit, }, primitive::FiniteF64, temporal_assert, Sign, TemporalError, TemporalResult, @@ -19,14 +19,10 @@ use num_traits::AsPrimitive; use self::normalized::NormalizedTimeDuration; -#[cfg(feature = "experimental")] -use crate::components::timezone::TZ_PROVIDER; - mod date; pub(crate) mod normalized; mod time; -#[cfg(feature = "experimental")] #[cfg(test)] mod tests; @@ -35,7 +31,6 @@ pub use date::DateDuration; #[doc(inline)] pub use time::TimeDuration; -use super::ZonedDateTime; /// A `PartialDuration` is a Duration that may have fields not set. #[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] @@ -620,20 +615,6 @@ impl Duration { } } -#[cfg(feature = "experimental")] -impl Duration { - pub fn round( - &self, - options: RoundingOptions, - relative_to: Option, - ) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.round_with_provider(options, relative_to, &*provider) - } -} - // TODO: Update, optimize, and fix the below. is_valid_duration should probably be generic over a T. // NOTE: Can FiniteF64 optimize the duration_validation diff --git a/src/components/duration/date.rs b/src/builtins/core/duration/date.rs similarity index 100% rename from src/components/duration/date.rs rename to src/builtins/core/duration/date.rs diff --git a/src/components/duration/normalized.rs b/src/builtins/core/duration/normalized.rs similarity index 99% rename from src/components/duration/normalized.rs rename to src/builtins/core/duration/normalized.rs index 03c078bb7..4bd09109f 100644 --- a/src/components/duration/normalized.rs +++ b/src/builtins/core/duration/normalized.rs @@ -5,7 +5,7 @@ use core::{num::NonZeroU128, ops::Add}; use num_traits::{AsPrimitive, Euclid, FromPrimitive}; use crate::{ - components::{ + builtins::core::{ timezone::{TimeZone, TimeZoneProvider}, PlainDate, PlainDateTime, }, diff --git a/src/builtins/core/duration/tests.rs b/src/builtins/core/duration/tests.rs new file mode 100644 index 000000000..2d2d4cdd0 --- /dev/null +++ b/src/builtins/core/duration/tests.rs @@ -0,0 +1,18 @@ +use crate::{partial::PartialDuration, primitive::FiniteF64}; + +use super::Duration; + +#[test] +fn partial_duration_empty() { + let err = Duration::from_partial_duration(PartialDuration::default()); + assert!(err.is_err()) +} + +#[test] +fn partial_duration_values() { + let mut partial = PartialDuration::default(); + let _ = partial.years.insert(FiniteF64(20.0)); + let result = Duration::from_partial_duration(partial).unwrap(); + assert_eq!(result.years(), 20.0); +} + diff --git a/src/components/duration/time.rs b/src/builtins/core/duration/time.rs similarity index 100% rename from src/components/duration/time.rs rename to src/builtins/core/duration/time.rs diff --git a/src/components/instant.rs b/src/builtins/core/instant.rs similarity index 93% rename from src/components/instant.rs rename to src/builtins/core/instant.rs index 6126ce391..54f316811 100644 --- a/src/components/instant.rs +++ b/src/builtins/core/instant.rs @@ -4,7 +4,7 @@ use alloc::string::String; use core::{num::NonZeroU128, str::FromStr}; use crate::{ - components::{ + builtins::core::{ duration::TimeDuration, zoneddatetime::nanoseconds_to_formattable_offset_minutes, Duration, }, iso::{IsoDate, IsoDateTime, IsoTime}, @@ -15,11 +15,11 @@ use crate::{ parsers::{parse_instant, IxdtfStringBuilder}, primitive::FiniteF64, rounding::{IncrementRounder, Round}, - Sign, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, NS_MAX_INSTANT, + time::EpochNanoseconds, + Sign, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, }; use ixdtf::parsers::records::UtcOffsetRecordOrZ; -use num_traits::FromPrimitive; use super::{ duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, @@ -30,42 +30,6 @@ const NANOSECONDS_PER_SECOND: f64 = 1e9; const NANOSECONDS_PER_MINUTE: f64 = 60f64 * NANOSECONDS_PER_SECOND; const NANOSECONDS_PER_HOUR: f64 = 60f64 * NANOSECONDS_PER_MINUTE; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct EpochNanoseconds(pub(crate) i128); - -impl TryFrom for EpochNanoseconds { - type Error = TemporalError; - fn try_from(value: i128) -> Result { - if !is_valid_epoch_nanos(&value) { - return Err(TemporalError::range() - .with_message("Instant nanoseconds are not within a valid epoch range.")); - } - Ok(Self(value)) - } -} - -impl TryFrom for EpochNanoseconds { - type Error = TemporalError; - fn try_from(value: u128) -> Result { - if (NS_MAX_INSTANT as u128) < value { - return Err(TemporalError::range() - .with_message("Instant nanoseconds are not within a valid epoch range.")); - } - Ok(Self(value as i128)) - } -} - -impl TryFrom for EpochNanoseconds { - type Error = TemporalError; - fn try_from(value: f64) -> Result { - let Some(value) = i128::from_f64(value) else { - return Err(TemporalError::range() - .with_message("Instant nanoseconds are not within a valid epoch range.")); - }; - Self::try_from(value) - } -} - /// The native Rust implementation of `Temporal.Instant` #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -307,13 +271,6 @@ impl Instant { // ==== Utility Functions ==== -/// Utility for determining if the nanos are within a valid range. -#[inline] -#[must_use] -pub(crate) fn is_valid_epoch_nanos(nanos: &i128) -> bool { - (crate::NS_MIN_INSTANT..=crate::NS_MAX_INSTANT).contains(nanos) -} - impl FromStr for Instant { type Err = TemporalError; fn from_str(s: &str) -> Result { @@ -359,7 +316,7 @@ impl FromStr for Instant { mod tests { use crate::{ - components::{duration::TimeDuration, Instant}, + builtins::core::{duration::TimeDuration, Instant}, options::{DifferenceSettings, TemporalRoundingMode, TemporalUnit}, primitive::FiniteF64, NS_MAX_INSTANT, NS_MIN_INSTANT, @@ -567,8 +524,8 @@ mod tests { fn instant_add_across_epoch() { use crate::{ options::ToStringRoundingOptions, partial::PartialDuration, tzdb::FsTzdbProvider, - Duration, }; + use crate::builtins::core::Duration; use core::str::FromStr; let instant = Instant::from_str("1969-12-25T12:23:45.678901234Z").unwrap(); diff --git a/src/components/mod.rs b/src/builtins/core/mod.rs similarity index 88% rename from src/components/mod.rs rename to src/builtins/core/mod.rs index 92d7f7dfd..bf60aa5b2 100644 --- a/src/components/mod.rs +++ b/src/builtins/core/mod.rs @@ -14,6 +14,7 @@ mod date; mod datetime; mod instant; mod month_day; +pub(crate) mod options; mod time; mod year_month; pub(crate) mod zoneddatetime; @@ -30,9 +31,9 @@ pub use date::{PartialDate, PlainDate}; #[doc(inline)] pub use datetime::{PartialDateTime, PlainDateTime}; #[doc(inline)] -pub use duration::{DateDuration, Duration, TimeDuration}; +pub use duration::{DateDuration, Duration, TimeDuration, PartialDuration}; #[doc(inline)] -pub use instant::{EpochNanoseconds, Instant}; +pub use instant::Instant; #[doc(inline)] pub use month_day::PlainMonthDay; #[doc(inline)] diff --git a/src/components/month_day.rs b/src/builtins/core/month_day.rs similarity index 96% rename from src/components/month_day.rs rename to src/builtins/core/month_day.rs index 956065639..14b083ffa 100644 --- a/src/components/month_day.rs +++ b/src/builtins/core/month_day.rs @@ -5,7 +5,7 @@ use core::str::FromStr; use tinystr::TinyAsciiStr; use crate::{ - components::calendar::Calendar, iso::IsoDate, options::ArithmeticOverflow, TemporalError, + builtins::core::calendar::Calendar, iso::IsoDate, options::ArithmeticOverflow, TemporalError, TemporalResult, TemporalUnwrap, }; diff --git a/src/components/now.rs b/src/builtins/core/now.rs similarity index 80% rename from src/components/now.rs rename to src/builtins/core/now.rs index 5f91f7b2c..21b6bcb84 100644 --- a/src/components/now.rs +++ b/src/builtins/core/now.rs @@ -5,17 +5,14 @@ use alloc::string::String; use num_traits::FromPrimitive; -use crate::{iso::IsoDateTime, TemporalUnwrap}; +use crate::{iso::IsoDateTime, TemporalUnwrap, time::EpochNanoseconds}; use super::{ calendar::Calendar, timezone::{TimeZone, TimeZoneProvider}, - EpochNanoseconds, Instant, PlainDate, PlainDateTime, PlainTime, ZonedDateTime, + Instant, PlainDate, PlainDateTime, PlainTime, ZonedDateTime, }; -#[cfg(feature = "experimental")] -use crate::{components::timezone::TZ_PROVIDER, TemporalError}; - /// The Temporal Now object. pub struct Now; @@ -87,30 +84,6 @@ impl Now { } } -#[cfg(feature = "experimental")] -impl Now { - pub fn plain_datetime_iso(timezone: Option) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - Now::plain_datetime_iso_with_provider(timezone, &*provider) - } - - pub fn plain_date_iso(timezone: Option) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - Now::plain_date_iso_with_provider(timezone, &*provider) - } - - pub fn plain_time_iso(timezone: Option) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - Now::plain_time_iso_with_provider(timezone, &*provider) - } -} - fn system_datetime( tz: Option, provider: &impl TimeZoneProvider, @@ -138,7 +111,8 @@ mod tests { use std::thread; use std::time::Duration as StdDuration; - use crate::{options::DifferenceSettings, tzdb::FsTzdbProvider, Now}; + use crate::{options::DifferenceSettings, tzdb::FsTzdbProvider}; + use crate::builtins::core::Now; #[test] fn now_datetime_test() { diff --git a/src/builtins/core/options.rs b/src/builtins/core/options.rs new file mode 100644 index 000000000..1817c7f50 --- /dev/null +++ b/src/builtins/core/options.rs @@ -0,0 +1,140 @@ +//! A module to handle RelativeTo + +use alloc::string::String; + +use crate::builtins::core::{ + timezone::TimeZoneProvider, zoneddatetime::interpret_isodatetime_offset, +}; +use crate::iso::{IsoDate, IsoTime}; +use crate::options::{ArithmeticOverflow, Disambiguation, OffsetDisambiguation}; +use crate::parsers::parse_date_time; +use crate::builtins::core::{ZonedDateTime, PlainDate, timezone::TimeZone, calendar::Calendar}; +use crate::{ + TemporalError, TemporalResult, TemporalUnwrap, +}; + +use ixdtf::parsers::records::{TimeZoneRecord, UtcOffsetRecordOrZ}; + +// ==== RelativeTo Object ==== + +#[derive(Debug, Clone)] +pub enum RelativeTo { + PlainDate(PlainDate), + ZonedDateTime(ZonedDateTime), +} + +impl From for RelativeTo { + fn from(value: PlainDate) -> Self { + Self::PlainDate(value) + } +} + +impl From for RelativeTo { + fn from(value: ZonedDateTime) -> Self { + Self::ZonedDateTime(value) + } +} + +impl RelativeTo { + /// Attempts to parse a `ZonedDateTime` string falling back to a `PlainDate` + /// if possible. + /// + /// If the fallback fails or either the `ZonedDateTime` or `PlainDate` + /// is invalid, then an error is returned. + pub fn try_from_str_with_provider( + source: &str, + provider: &impl TimeZoneProvider, + ) -> TemporalResult { + let result = parse_date_time(source)?; + + let Some(annotation) = result.tz else { + let date_record = result.date.temporal_unwrap()?; + + let calendar = result + .calendar + .map(Calendar::from_utf8) + .transpose()? + .unwrap_or_default(); + + return Ok(PlainDate::try_new( + date_record.year, + date_record.month, + date_record.day, + calendar, + )? + .into()); + }; + + let timezone = match annotation.tz { + TimeZoneRecord::Name(s) => { + TimeZone::IanaIdentifier(String::from_utf8_lossy(s).into_owned()) + } + TimeZoneRecord::Offset(offset_record) => { + // NOTE: ixdtf parser restricts minute/second to 0..=60 + let minutes = i16::from(offset_record.hour) * 60 + offset_record.minute as i16; + TimeZone::OffsetMinutes(minutes * i16::from(offset_record.sign as i8)) + } + // TimeZoneRecord is non_exhaustive, but all current branches are matching. + _ => return Err(TemporalError::assert()), + }; + + let (offset_nanos, is_exact) = result + .offset + .map(|record| { + let UtcOffsetRecordOrZ::Offset(offset) = record else { + return (None, true); + }; + let hours_in_ns = i64::from(offset.hour) * 3_600_000_000_000_i64; + let minutes_in_ns = i64::from(offset.minute) * 60_000_000_000_i64; + let seconds_in_ns = i64::from(offset.minute) * 1_000_000_000_i64; + ( + Some( + (hours_in_ns + + minutes_in_ns + + seconds_in_ns + + i64::from(offset.nanosecond)) + * i64::from(offset.sign as i8), + ), + false, + ) + }) + .unwrap_or((None, false)); + + let calendar = result + .calendar + .map(Calendar::from_utf8) + .transpose()? + .unwrap_or_default(); + + let time = result + .time + .map(|time| { + IsoTime::from_components(time.hour, time.minute, time.second, time.nanosecond) + }) + .transpose()?; + + let date = result.date.temporal_unwrap()?; + let iso = IsoDate::new_with_overflow( + date.year, + date.month, + date.day, + ArithmeticOverflow::Constrain, + )?; + + let epoch_ns = interpret_isodatetime_offset( + iso, + time, + is_exact, + offset_nanos, + &timezone, + Disambiguation::Compatible, + OffsetDisambiguation::Reject, + true, + provider, + )?; + + Ok(ZonedDateTime::try_new(epoch_ns.0, calendar, timezone)?.into()) + } +} + + diff --git a/src/components/time.rs b/src/builtins/core/time.rs similarity index 99% rename from src/components/time.rs rename to src/builtins/core/time.rs index cd2ebf11a..80d3b7e3c 100644 --- a/src/components/time.rs +++ b/src/builtins/core/time.rs @@ -1,7 +1,7 @@ //! This module implements `Time` and any directly related algorithms. use crate::{ - components::{duration::TimeDuration, Duration}, + builtins::core::{duration::TimeDuration, Duration}, iso::IsoTime, options::{ ArithmeticOverflow, DifferenceOperation, DifferenceSettings, ResolvedRoundingOptions, @@ -470,7 +470,7 @@ impl FromStr for PlainTime { #[cfg(test)] mod tests { use crate::{ - components::Duration, + builtins::core::Duration, iso::IsoTime, options::{ArithmeticOverflow, DifferenceSettings, RoundingIncrement, TemporalUnit}, }; diff --git a/src/components/timezone.rs b/src/builtins/core/timezone.rs similarity index 99% rename from src/components/timezone.rs rename to src/builtins/core/timezone.rs index 22c70d1d0..101803977 100644 --- a/src/components/timezone.rs +++ b/src/builtins/core/timezone.rs @@ -7,12 +7,13 @@ use core::{iter::Peekable, str::Chars}; use num_traits::ToPrimitive; -use crate::components::duration::DateDuration; +use crate::builtins::core::duration::DateDuration; use crate::parsers::{FormattableOffset, FormattableTime, Precision}; use crate::{ - components::{duration::normalized::NormalizedTimeDuration, EpochNanoseconds, Instant}, + builtins::core::{duration::normalized::NormalizedTimeDuration, Instant}, iso::{IsoDate, IsoDateTime, IsoTime}, options::Disambiguation, + time::EpochNanoseconds, TemporalError, TemporalResult, ZonedDateTime, }; use crate::{Calendar, Sign}; diff --git a/src/components/year_month.rs b/src/builtins/core/year_month.rs similarity index 96% rename from src/components/year_month.rs rename to src/builtins/core/year_month.rs index 617c3e204..f10279d42 100644 --- a/src/components/year_month.rs +++ b/src/builtins/core/year_month.rs @@ -6,8 +6,8 @@ use core::str::FromStr; use tinystr::TinyAsciiStr; use crate::{ - components::calendar::Calendar, iso::IsoDate, options::ArithmeticOverflow, utils::pad_iso_year, - TemporalError, TemporalResult, TemporalUnwrap, + builtins::core::calendar::Calendar, iso::IsoDate, options::ArithmeticOverflow, + utils::pad_iso_year, TemporalError, TemporalResult, TemporalUnwrap, }; use super::{Duration, PartialDate}; diff --git a/src/components/zoneddatetime.rs b/src/builtins/core/zoneddatetime.rs similarity index 78% rename from src/components/zoneddatetime.rs rename to src/builtins/core/zoneddatetime.rs index de75b7d74..f1bdc5aef 100644 --- a/src/components/zoneddatetime.rs +++ b/src/builtins/core/zoneddatetime.rs @@ -6,10 +6,9 @@ use ixdtf::parsers::records::{TimeZoneRecord, UtcOffsetRecordOrZ}; use tinystr::TinyAsciiStr; use crate::{ - components::{ + builtins::core::{ duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, - timezone::{parse_offset, TimeZoneProvider}, - EpochNanoseconds, + timezone::{parse_offset, TimeZone, TimeZoneProvider}, calendar::Calendar, Duration, Instant, PlainDate, PlainDateTime, PlainTime, }, iso::{IsoDate, IsoDateTime, IsoTime}, options::{ @@ -20,13 +19,10 @@ use crate::{ parsers::{self, IxdtfStringBuilder}, partial::{PartialDate, PartialTime}, rounding::{IncrementRounder, Round}, - temporal_assert, Calendar, Duration, Instant, PlainDate, PlainDateTime, PlainTime, Sign, - TemporalError, TemporalResult, TimeZone, + time::EpochNanoseconds, + temporal_assert,TemporalError, TemporalResult, Sign }; -#[cfg(feature = "experimental")] -use crate::components::timezone::TZ_PROVIDER; - /// A struct representing a partial `ZonedDateTime`. #[derive(Debug, Default, Clone, PartialEq)] pub struct PartialZonedDateTime { @@ -365,268 +361,9 @@ impl ZonedDateTime { } } -// ===== Experimental TZ_PROVIDER accessor implementations ===== - -#[cfg(feature = "experimental")] -impl ZonedDateTime { - pub fn year(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.year_with_provider(&*provider) - } - - pub fn month(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.month_with_provider(&*provider) - } - - pub fn month_code(&self) -> TemporalResult> { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.month_code_with_provider(&*provider) - } - - pub fn day(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.day_with_provider(&*provider) - } - - pub fn hour(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.hour_with_provider(&*provider) - } - - pub fn minute(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.minute_with_provider(&*provider) - } - - pub fn second(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.second_with_provider(&*provider) - } - - pub fn millisecond(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.millisecond_with_provider(&*provider) - } - - pub fn microsecond(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.millisecond_with_provider(&*provider) - } - - pub fn nanosecond(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - - self.millisecond_with_provider(&*provider) - } -} - -// ==== Experimental TZ_PROVIDER calendar method implementations ==== - -#[cfg(feature = "experimental")] -impl ZonedDateTime { - pub fn era(&self) -> TemporalResult>> { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.era_with_provider(&*provider) - } - - pub fn era_year(&self) -> TemporalResult> { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.era_year_with_provider(&*provider) - } - - /// Returns the calendar day of week value. - pub fn day_of_week(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.day_of_week_with_provider(&*provider) - } - - /// Returns the calendar day of year value. - pub fn day_of_year(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.day_of_year_with_provider(&*provider) - } - - /// Returns the calendar week of year value. - pub fn week_of_year(&self) -> TemporalResult> { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.week_of_year_with_provider(&*provider) - } - - /// Returns the calendar year of week value. - pub fn year_of_week(&self) -> TemporalResult> { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.year_of_week_with_provider(&*provider) - } - - /// Returns the calendar days in week value. - pub fn days_in_week(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.days_in_week_with_provider(&*provider) - } - - /// Returns the calendar days in month value. - pub fn days_in_month(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.days_in_month_with_provider(&*provider) - } - - /// Returns the calendar days in year value. - pub fn days_in_year(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.days_in_year_with_provider(&*provider) - } - - /// Returns the calendar months in year value. - pub fn months_in_year(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.months_in_year_with_provider(&*provider) - } - - /// Returns returns whether the date in a leap year for the given calendar. - pub fn in_leap_year(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.in_leap_year_with_provider(&*provider) - } -} - -// ==== Experimental TZ_PROVIDER method implementations ==== - -#[cfg(feature = "experimental")] -impl ZonedDateTime { - /// Creates a new `ZonedDateTime` from the current `ZonedDateTime` - /// combined with the provided `TimeZone`. - pub fn with_plain_time(&self, time: PlainTime) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.with_plain_time_and_provider(time, &*provider) - } - - pub fn add( - &self, - duration: &Duration, - overflow: Option, - ) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - - self.add_internal( - duration, - overflow.unwrap_or(ArithmeticOverflow::Constrain), - &*provider, - ) - } - - pub fn subtract( - &self, - duration: &Duration, - overflow: Option, - ) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.add_internal( - &duration.negated(), - overflow.unwrap_or(ArithmeticOverflow::Constrain), - &*provider, - ) - } - - pub fn start_of_day(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.start_of_day_with_provider(&*provider) - } - - pub fn to_plain_date(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.to_plain_date_with_provider(&*provider) - } - - pub fn to_plain_time(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.to_plain_time_with_provider(&*provider) - } - - pub fn to_plain_datetime(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.to_plain_datetime_with_provider(&*provider) - } - - pub fn from_str( - source: &str, - disambiguation: Disambiguation, - offset_option: OffsetDisambiguation, - ) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - Self::from_str_with_provider(source, disambiguation, offset_option, &*provider) - } -} - // ==== HoursInDay accessor method implementation ==== impl ZonedDateTime { - #[cfg(feature = "experimental")] - pub fn hours_in_day(&self) -> TemporalResult { - let provider = TZ_PROVIDER - .lock() - .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.hours_in_day_with_provider(&*provider) - } - pub fn hours_in_day_with_provider( &self, provider: &impl TimeZoneProvider, @@ -1212,14 +949,12 @@ mod tests { partial::{PartialDate, PartialTime, PartialZonedDateTime}, primitive::FiniteF64, tzdb::FsTzdbProvider, - Calendar, TimeZone, ZonedDateTime, + Calendar, TimeZone }; + use super::ZonedDateTime; use core::str::FromStr; use tinystr::tinystr; - #[cfg(not(target_os = "windows"))] - use crate::Duration; - #[test] fn basic_zdt_test() { let provider = &FsTzdbProvider::default(); @@ -1268,82 +1003,6 @@ mod tests { assert_eq!(zdt_plus_eleven.second_with_provider(provider).unwrap(), 12); } - #[cfg(all(feature = "experimental", not(target_os = "windows")))] - #[test] - fn static_tzdb_zdt_test() { - let nov_30_2023_utc = 1_701_308_952_000_000_000i128; - - let zdt = ZonedDateTime::try_new( - nov_30_2023_utc, - Calendar::from_str("iso8601").unwrap(), - TimeZone::try_from_str("Z").unwrap(), - ) - .unwrap(); - - assert_eq!(zdt.year().unwrap(), 2023); - assert_eq!(zdt.month().unwrap(), 11); - assert_eq!(zdt.day().unwrap(), 30); - assert_eq!(zdt.hour().unwrap(), 1); - assert_eq!(zdt.minute().unwrap(), 49); - assert_eq!(zdt.second().unwrap(), 12); - - let zdt_minus_five = ZonedDateTime::try_new( - nov_30_2023_utc, - Calendar::from_str("iso8601").unwrap(), - TimeZone::try_from_str("America/New_York").unwrap(), - ) - .unwrap(); - - assert_eq!(zdt_minus_five.year().unwrap(), 2023); - assert_eq!(zdt_minus_five.month().unwrap(), 11); - assert_eq!(zdt_minus_five.day().unwrap(), 29); - assert_eq!(zdt_minus_five.hour().unwrap(), 20); - assert_eq!(zdt_minus_five.minute().unwrap(), 49); - assert_eq!(zdt_minus_five.second().unwrap(), 12); - - let zdt_plus_eleven = ZonedDateTime::try_new( - nov_30_2023_utc, - Calendar::from_str("iso8601").unwrap(), - TimeZone::try_from_str("Australia/Sydney").unwrap(), - ) - .unwrap(); - - assert_eq!(zdt_plus_eleven.year().unwrap(), 2023); - assert_eq!(zdt_plus_eleven.month().unwrap(), 11); - assert_eq!(zdt_plus_eleven.day().unwrap(), 30); - assert_eq!(zdt_plus_eleven.hour().unwrap(), 12); - assert_eq!(zdt_plus_eleven.minute().unwrap(), 49); - assert_eq!(zdt_plus_eleven.second().unwrap(), 12); - } - - #[cfg(all(feature = "experimental", not(target_os = "windows")))] - #[test] - fn basic_zdt_add() { - let zdt = - ZonedDateTime::try_new(-560174321098766, Calendar::default(), TimeZone::default()) - .unwrap(); - let d = Duration::new( - 0.into(), - 0.into(), - 0.into(), - 0.into(), - 240.into(), - 0.into(), - 0.into(), - 0.into(), - 0.into(), - 800.into(), - ) - .unwrap(); - // "1970-01-04T12:23:45.678902034+00:00[UTC]" - let expected = - ZonedDateTime::try_new(303825678902034, Calendar::default(), TimeZone::default()) - .unwrap(); - - let result = zdt.add(&d, None).unwrap(); - assert_eq!(result, expected); - } - #[test] fn zdt_from_partial() { let provider = &FsTzdbProvider::default(); diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs new file mode 100644 index 000000000..fea9bf348 --- /dev/null +++ b/src/builtins/mod.rs @@ -0,0 +1,8 @@ +pub(crate) mod core; +pub(crate) mod std; + +#[cfg(not(feature = "experimental"))] +pub use core::*; + +#[cfg(feature = "experimental")] +pub use std::*; diff --git a/src/builtins/std/date.rs b/src/builtins/std/date.rs new file mode 100644 index 000000000..b7c49b6d7 --- /dev/null +++ b/src/builtins/std/date.rs @@ -0,0 +1,270 @@ + +use alloc::string::String; +use crate::{builtins::core, options::{ArithmeticOverflow, DifferenceSettings, DisplayCalendar}, Calendar, TemporalResult}; +use crate::builtins::std::PlainTime; + +use super::{duration::Duration, PartialDate, PlainDateTime, PlainMonthDay, PlainYearMonth}; +use tinystr::TinyAsciiStr; + +#[derive(Debug, Clone)] +pub struct PlainDate(pub(crate) core::PlainDate); + +impl From for PlainDate { + fn from(value: core::PlainDate) -> Self { + Self(value) + } +} + +impl PlainDate { + /// Creates a new `PlainDate` automatically constraining any values that may be invalid. + pub fn new(year: i32, month: u8, day: u8, calendar: Calendar) -> TemporalResult { + core::PlainDate::new(year, month, day, calendar).map(Into::into) + } + + /// Creates a new `PlainDate` rejecting any date that may be invalid. + pub fn try_new(year: i32, month: u8, day: u8, calendar: Calendar) -> TemporalResult { + core::PlainDate::try_new(year, month, day, calendar).map(Into::into) + } + + /// Creates a new `PlainDate` with the specified overflow. + /// + /// This operation is the public facing API to Temporal's `RegulateIsoDate` + #[inline] + pub fn new_with_overflow( + year: i32, + month: u8, + day: u8, + calendar: Calendar, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + core::PlainDate::new_with_overflow(year, month, day, calendar, overflow).map(Into::into) + } + + /// Create a `PlainDate` from a `PartialDate` + /// + /// ```rust + /// use temporal_rs::{PlainDate, partial::PartialDate}; + /// + /// let partial = PartialDate { + /// year: Some(2000), + /// month: Some(13), + /// day: Some(2), + /// ..Default::default() + /// }; + /// + /// let date = PlainDate::from_partial(partial, None).unwrap(); + /// + /// assert_eq!(date.year().unwrap(), 2000); + /// assert_eq!(date.month().unwrap(), 12); + /// assert_eq!(date.day().unwrap(), 2); + /// assert_eq!(date.calendar().identifier(), "iso8601"); + /// + /// ``` + #[inline] + pub fn from_partial( + partial: PartialDate, + overflow: Option, + ) -> TemporalResult { + core::PlainDate::from_partial(partial, overflow).map(Into::into) + } + + /// Creates a date time with values from a `PartialDate`. + pub fn with( + &self, + partial: PartialDate, + overflow: Option, + ) -> TemporalResult { + self.0.with(partial, overflow).map(Into::into) + } + + /// Creates a new `Date` from the current `Date` and the provided calendar. + pub fn with_calendar(&self, calendar: Calendar) -> TemporalResult { + self.0.with_calendar(calendar).map(Into::into) + } + + #[inline] + #[must_use] + /// Returns this `Date`'s ISO year value. + pub const fn iso_year(&self) -> i32 { + self.0.iso_year() + } + + #[inline] + #[must_use] + /// Returns this `Date`'s ISO month value. + pub const fn iso_month(&self) -> u8 { + self.0.iso_month() + } + + #[inline] + #[must_use] + /// Returns this `Date`'s ISO day value. + pub const fn iso_day(&self) -> u8 { + self.0.iso_day() + } + + #[inline] + #[must_use] + /// Returns a reference to this `Date`'s calendar slot. + pub fn calendar(&self) -> &Calendar { + self.0.calendar() + } + + /// 3.5.7 `IsValidISODate` + /// + /// Checks if the current date is a valid `ISODate`. + #[must_use] + pub fn is_valid(&self) -> bool { + self.0.is_valid() + } + + /// `DaysUntil` + /// + /// Calculates the epoch days between two `Date`s + #[inline] + #[must_use] + pub fn days_until(&self, other: &Self) -> i32 { + self.0.days_until(&other.0) + } + + #[inline] + /// Adds a `Duration` to the current `Date` + pub fn add( + &self, + duration: &Duration, + overflow: Option, + ) -> TemporalResult { + self.0.add(&duration.0, overflow).map(Into::into) + } + + #[inline] + /// Subtracts a `Duration` to the current `Date` + pub fn subtract( + &self, + duration: &Duration, + overflow: Option, + ) -> TemporalResult { + self.0.subtract(&duration.0, overflow).map(Into::into) + } + + #[inline] + /// Returns a `Duration` representing the time from this `Date` until the other `Date`. + pub fn until(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult { + self.0.until(&other.0, settings).map(Into::into) + } + + #[inline] + /// Returns a `Duration` representing the time passed from this `Date` since the other `Date`. + pub fn since(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult { + self.0.since(&other.0, settings).map(Into::into) + } +} + +// ==== Calendar-derived Public API ==== + +impl PlainDate { + /// Returns the calendar year value. + pub fn year(&self) -> TemporalResult { + self.0.year() + } + + /// Returns the calendar month value. + pub fn month(&self) -> TemporalResult { + self.0.month() + } + + /// Returns the calendar month code value. + pub fn month_code(&self) -> TemporalResult> { + self.0.month_code() + } + + /// Returns the calendar day value. + pub fn day(&self) -> TemporalResult { + self.0.day() + } + + /// Returns the calendar day of week value. + pub fn day_of_week(&self) -> TemporalResult { + self.0.day_of_week() + } + + /// Returns the calendar day of year value. + pub fn day_of_year(&self) -> TemporalResult { + self.0.day_of_year() + } + + /// Returns the calendar week of year value. + pub fn week_of_year(&self) -> TemporalResult> { + self.0.week_of_year() + } + + /// Returns the calendar year of week value. + pub fn year_of_week(&self) -> TemporalResult> { + self.0.year_of_week() + } + + /// Returns the calendar days in week value. + pub fn days_in_week(&self) -> TemporalResult { + self.0.days_in_week() + } + + /// Returns the calendar days in month value. + pub fn days_in_month(&self) -> TemporalResult { + self.0.days_in_month() + } + + /// Returns the calendar days in year value. + pub fn days_in_year(&self) -> TemporalResult { + self.0.days_in_year() + } + + /// Returns the calendar months in year value. + pub fn months_in_year(&self) -> TemporalResult { + self.0.months_in_year() + } + + /// Returns returns whether the date in a leap year for the given calendar. + pub fn in_leap_year(&self) -> TemporalResult { + self.0.in_leap_year() + } + + pub fn era(&self) -> TemporalResult>> { + self.0.era() + } + + pub fn era_year(&self) -> TemporalResult> { + self.0.era_year() + } +} + +// ==== ToX Methods ==== + +impl PlainDate { + /// Converts the current `Date` into a `DateTime` + /// + /// # Notes + /// + /// If no time is provided, then the time will default to midnight. + #[inline] + pub fn to_date_time(&self, time: Option) -> TemporalResult { + self.0.to_date_time(time.map(Into::into)).map(Into::into) + } + + /// Converts the current `Date` into a `PlainYearMonth` + #[inline] + pub fn to_year_month(&self) -> TemporalResult { + self.0.to_year_month() + } + + /// Converts the current `Date` into a `PlainMonthDay` + #[inline] + pub fn to_month_day(&self) -> TemporalResult { + self.0.to_month_day() + } + + #[inline] + pub fn to_ixdtf_string(&self, display_calendar: DisplayCalendar) -> String { + self.0.to_ixdtf_string(display_calendar) + } +} + diff --git a/src/builtins/std/duration.rs b/src/builtins/std/duration.rs new file mode 100644 index 000000000..e2320e369 --- /dev/null +++ b/src/builtins/std/duration.rs @@ -0,0 +1,206 @@ + +use crate::{builtins::core, options::{RelativeTo, RoundingOptions}, primitive::FiniteF64, Sign, TemporalError, TemporalResult}; +use crate::builtins::core::PartialDuration; +use crate::builtins::core::options::RelativeTo as CoreRelativeTo; + +use super::{timezone::TZ_PROVIDER, DateDuration, TimeDuration}; + +#[cfg(test)] +mod tests; + +pub struct Duration(pub(crate) core::Duration); + +impl From for Duration { + fn from(value: core::Duration) -> Self { + Self(value) + } +} + +impl From for Duration { + fn from(value: DateDuration) -> Self { + Self(value.into()) + } +} + +impl From for Duration { + fn from(value: TimeDuration) -> Self { + Self(value.into()) + } +} + +impl Duration { + /// Creates a new validated `Duration`. + #[allow(clippy::too_many_arguments)] + pub fn new( + years: FiniteF64, + months: FiniteF64, + weeks: FiniteF64, + days: FiniteF64, + hours: FiniteF64, + minutes: FiniteF64, + seconds: FiniteF64, + milliseconds: FiniteF64, + microseconds: FiniteF64, + nanoseconds: FiniteF64, + ) -> TemporalResult { + core::Duration::new(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds).map(Into::into) + } + + /// Creates a `Duration` from a provided `PartialDuration`. + pub fn from_partial_duration(partial: PartialDuration) -> TemporalResult { + core::Duration::from_partial_duration(partial).map(Into::into) + } +} + +// ==== Public `Duration` Getters/Setters ==== + +impl Duration { + /// Returns a reference to the inner `TimeDuration` + #[inline] + #[must_use] + pub fn time(&self) -> &TimeDuration { + self.0.time() + } + + /// Returns a reference to the inner `DateDuration` + #[inline] + #[must_use] + pub fn date(&self) -> &DateDuration { + self.0.date() + } + + /// Set this `DurationRecord`'s `TimeDuration`. + #[inline] + pub fn set_time_duration(&mut self, time: TimeDuration) { + self.0.set_time_duration(time); + } + + /// Returns the `years` field of duration. + #[inline] + #[must_use] + pub const fn years(&self) -> FiniteF64 { + self.0.years() + } + + /// Returns the `months` field of duration. + #[inline] + #[must_use] + pub const fn months(&self) -> FiniteF64 { + self.0.months() + } + + /// Returns the `weeks` field of duration. + #[inline] + #[must_use] + pub const fn weeks(&self) -> FiniteF64 { + self.0.weeks() + } + + /// Returns the `weeks` field of duration. + #[inline] + #[must_use] + pub const fn days(&self) -> FiniteF64 { + self.0.days() + } + + /// Returns the `hours` field of duration. + #[inline] + #[must_use] + pub const fn hours(&self) -> FiniteF64 { + self.0.hours() + } + + /// Returns the `hours` field of duration. + #[inline] + #[must_use] + pub const fn minutes(&self) -> FiniteF64 { + self.0.minutes() + } + + /// Returns the `seconds` field of duration. + #[inline] + #[must_use] + pub const fn seconds(&self) -> FiniteF64 { + self.0.seconds() + } + + /// Returns the `hours` field of duration. + #[inline] + #[must_use] + pub const fn milliseconds(&self) -> FiniteF64 { + self.0.milliseconds() + } + + /// Returns the `microseconds` field of duration. + #[inline] + #[must_use] + pub const fn microseconds(&self) -> FiniteF64 { + self.0.microseconds() + } + + /// Returns the `nanoseconds` field of duration. + #[inline] + #[must_use] + pub const fn nanoseconds(&self) -> FiniteF64 { + self.0.nanoseconds() + } +} + +// ==== Public Duration methods ==== + +impl Duration { + /// Determines the sign for the current self. + #[inline] + #[must_use] + pub fn sign(&self) -> Sign { + self.0.sign() + } + + /// Returns whether the current `Duration` is zero. + /// + /// Equivalant to `Temporal.Duration.blank()`. + #[inline] + #[must_use] + pub fn is_zero(&self) -> bool { + self.0.is_zero() + } + + /// Returns a negated `Duration` + #[inline] + #[must_use] + pub fn negated(&self) -> Self { + self.0.negated().into() + } + + /// Returns the absolute value of `Duration`. + #[inline] + #[must_use] + pub fn abs(&self) -> Self { + self.0.abs().into() + } + + /// Returns the result of adding a `Duration` to the current `Duration` + #[inline] + pub fn add(&self, other: &Self) -> TemporalResult { + self.0.add(&other.0).map(Into::into) + } + + /// Returns the result of subtracting a `Duration` from the current `Duration` + #[inline] + pub fn subtract(&self, other: &Self) -> TemporalResult { + self.0.subtract(&other.0).map(Into::into) + } + + pub fn round( + &self, + options: RoundingOptions, + relative_to: Option, + ) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.round_with_provider(options, relative_to.map(Into::into), &*provider).map(Into::into) + } +} + + diff --git a/src/components/duration/tests.rs b/src/builtins/std/duration/tests.rs similarity index 97% rename from src/components/duration/tests.rs rename to src/builtins/std/duration/tests.rs index 105b38a14..f9a0b10d2 100644 --- a/src/components/duration/tests.rs +++ b/src/builtins/std/duration/tests.rs @@ -1,10 +1,11 @@ +use alloc::vec::Vec; +use core::str::FromStr; +use crate::builtins::std::PlainDate; use crate::{ - components::{calendar::Calendar, PlainDate}, - options::{RoundingIncrement, TemporalRoundingMode}, - TimeZone, + builtins::{core::calendar::Calendar, std::zoneddatetime::ZonedDateTime}, options::{RelativeTo, RoundingIncrement, RoundingOptions, TemporalRoundingMode, TemporalUnit}, partial::PartialDuration, primitive::FiniteF64, DateDuration, TimeDuration, TimeZone }; -use super::*; +use super::Duration; fn get_round_result( test_duration: &Duration, @@ -14,6 +15,7 @@ fn get_round_result( test_duration .round(options, Some(relative_to)) .unwrap() + .0 .fields() .iter() .map(|f| f.as_date_value().unwrap()) @@ -458,7 +460,7 @@ fn rounding_increment_non_integer() { .unwrap(); assert_eq!( - result.fields(), + result.0.fields(), &[ FiniteF64::default(), FiniteF64::default(), @@ -478,7 +480,7 @@ fn rounding_increment_non_integer() { .insert(RoundingIncrement::try_from(1e9 + 0.5).unwrap()); let result = test_duration.round(options, Some(relative_to)).unwrap(); assert_eq!( - result.fields(), + result.0.fields(), &[ FiniteF64::default(), FiniteF64::default(), @@ -594,20 +596,6 @@ fn basic_subtract_duration() { assert_eq!(result.minutes(), 30.0); } -#[test] -fn partial_duration_empty() { - let err = Duration::from_partial_duration(PartialDuration::default()); - assert!(err.is_err()) -} - -#[test] -fn partial_duration_values() { - let mut partial = PartialDuration::default(); - let _ = partial.years.insert(FiniteF64(20.0)); - let result = Duration::from_partial_duration(partial).unwrap(); - assert_eq!(result.years(), 20.0); -} - // days-24-hours-relative-to-zoned-date-time.js #[test] fn round_relative_to_zoned_datetime() { diff --git a/src/builtins/std/instant.rs b/src/builtins/std/instant.rs new file mode 100644 index 000000000..0cd268d63 --- /dev/null +++ b/src/builtins/std/instant.rs @@ -0,0 +1,79 @@ + +use crate::{builtins::core, options::{DifferenceSettings, RoundingOptions}, time::EpochNanoseconds, TemporalError, TemporalResult}; + +use super::{duration::Duration, TimeDuration}; + +pub struct Instant(core::Instant); + +impl From for Instant { + fn from(value: core::Instant) -> Self { + Self(value) + } +} + +impl Instant { + /// Create a new validated `Instant`. + #[inline] + pub fn try_new(nanoseconds: i128) -> TemporalResult { + Ok(core::Instant::from(EpochNanoseconds::try_from(nanoseconds)?).into()) + } + + pub fn from_epoch_milliseconds(epoch_milliseconds: i128) -> TemporalResult { + core::Instant::from_epoch_milliseconds(epoch_milliseconds).map(Into::into) + } + + /// Adds a `Duration` to the current `Instant`, returning an error if the `Duration` + /// contains a `DateDuration`. + #[inline] + pub fn add(&self, duration: Duration) -> TemporalResult { + self.0.add(duration.0).map(Into::into) + } + + /// Adds a `TimeDuration` to `Instant`. + #[inline] + pub fn add_time_duration(&self, duration: &TimeDuration) -> TemporalResult { + self.0.add_time_duration(duration).map(Into::into) + } + + /// Subtract a `Duration` to the current `Instant`, returning an error if the `Duration` + /// contains a `DateDuration`. + #[inline] + pub fn subtract(&self, duration: Duration) -> TemporalResult { + self.0.subtract(duration.0).map(Into::into) + } + + /// Subtracts a `TimeDuration` to `Instant`. + #[inline] + pub fn subtract_time_duration(&self, duration: &TimeDuration) -> TemporalResult { + self.0.subtract_time_duration(duration).map(Into::into) + } + + /// Returns a `TimeDuration` representing the duration since provided `Instant` + #[inline] + pub fn since(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult { + self.0.since(&other.0, settings).map(Into::into) + } + + /// Returns a `TimeDuration` representing the duration until provided `Instant` + #[inline] + pub fn until(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult { + self.0.until(&other.0, settings).map(Into::into) + } + + /// Returns an `Instant` by rounding the current `Instant` according to the provided settings. + pub fn round(&self, options: RoundingOptions) -> TemporalResult { + self.0.round(options).map(Into::into) + } + + /// Returns the `epochMilliseconds` value for this `Instant`. + #[must_use] + pub fn epoch_milliseconds(&self) -> i64 { + self.0.epoch_milliseconds() + } + + /// Returns the `epochNanoseconds` value for this `Instant`. + #[must_use] + pub fn epoch_nanoseconds(&self) -> i128 { + self.0.epoch_nanoseconds() + } +} diff --git a/src/builtins/std/mod.rs b/src/builtins/std/mod.rs new file mode 100644 index 000000000..accfe5ff1 --- /dev/null +++ b/src/builtins/std/mod.rs @@ -0,0 +1,55 @@ +pub(crate) mod timezone; + +mod date; +mod duration; +mod instant; +mod now; +pub(crate) mod options; +mod zoneddatetime; + + +// TODO: Remove the aliasing + +pub use date::PlainDate; +pub use datetime::PlainDateTime; +pub use zoneddatetime::ZonedDateTime; +pub use duration::Duration; +pub use instant::Instant; +pub use now::Now; +pub use time::PlainTime; + + +pub use crate::builtins::core::{ + DateDuration, PartialDate, PartialDateTime, PartialTime, PartialZonedDateTime, + PlainMonthDay, PlainYearMonth, TimeDuration, + calendar, +}; + +mod time { + use crate::builtins::core; + pub struct PlainTime(pub(crate) core::PlainTime); + + impl From for PlainTime { + fn from(value: core::PlainTime) -> Self { + Self(value) + } + } + + impl From for core::PlainTime { + fn from(value: PlainTime) -> Self { + value.0 + } + } +} + +mod datetime { + use crate::builtins::core; + pub struct PlainDateTime(pub(crate) core::PlainDateTime); + + impl From for PlainDateTime { + fn from(value: core::PlainDateTime) -> Self { + Self(value) + } + } +} + diff --git a/src/builtins/std/now.rs b/src/builtins/std/now.rs new file mode 100644 index 000000000..824259a14 --- /dev/null +++ b/src/builtins/std/now.rs @@ -0,0 +1,52 @@ + +use alloc::string::String; +use crate::{builtins::core, TemporalError, TemporalResult, TimeZone, Instant}; +use crate::builtins::std::{ZonedDateTime, PlainDateTime, PlainDate, PlainTime}; + +use super::timezone::TZ_PROVIDER; + +pub struct Now; + +impl Now { + /// Returns the current instant + pub fn instant() -> TemporalResult { + Now::instant() + } + + /// Returns the current time zone. + pub fn time_zone_id() -> TemporalResult { + Now::time_zone_id() + } + + /// Returns the current system time as a `ZonedDateTime` with an ISO8601 calendar. + /// + /// The time zone will be set to either the `TimeZone` if a value is provided, or + /// according to the system timezone if no value is provided. + pub fn zoneddatetime_iso(timezone: Option) -> TemporalResult { + Now::zoneddatetime_iso(timezone).map(Into::into) + } + + pub fn plain_datetime_iso(timezone: Option) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + core::Now::plain_datetime_iso_with_provider(timezone, &*provider).map(Into::into) + } + + pub fn plain_date_iso(timezone: Option) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + core::Now::plain_date_iso_with_provider(timezone, &*provider).map(Into::into) + } + + pub fn plain_time_iso(timezone: Option) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + core::Now::plain_time_iso_with_provider(timezone, &*provider).map(Into::into) + } +} + + + diff --git a/src/builtins/std/options.rs b/src/builtins/std/options.rs new file mode 100644 index 000000000..5eb76b417 --- /dev/null +++ b/src/builtins/std/options.rs @@ -0,0 +1,53 @@ +use crate::TemporalError; +use crate::{builtins::core, TemporalResult}; +use crate::builtins::std::timezone::TZ_PROVIDER; + +use super::{date::PlainDate, ZonedDateTime}; + +use core::options::RelativeTo as CoreRelativeTo; + +#[derive(Debug, Clone)] +pub enum RelativeTo { + PlainDate(PlainDate), + ZonedDateTime(ZonedDateTime), +} + +impl From for RelativeTo { + fn from(value: PlainDate) -> Self { + Self::PlainDate(value) + } +} + +impl From for RelativeTo { + fn from(value: ZonedDateTime) -> Self { + Self::ZonedDateTime(value) + } +} + +impl From for RelativeTo { + fn from(value: CoreRelativeTo) -> Self { + match value { + CoreRelativeTo::PlainDate(d) => Self::PlainDate(d.into()), + CoreRelativeTo::ZonedDateTime(d) => Self::ZonedDateTime(d.into()), + } + } +} + +impl From for CoreRelativeTo { + fn from(value: RelativeTo) -> Self { + match value { + RelativeTo::PlainDate(d) => CoreRelativeTo::PlainDate(d.0), + RelativeTo::ZonedDateTime(zdt) => CoreRelativeTo::ZonedDateTime(zdt.0), + } + } +} + +impl RelativeTo { + pub fn try_from_str(source: &str) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + core::options::RelativeTo::try_from_str_with_provider(source, &*provider).map(Into::into) + } +} + diff --git a/src/builtins/std/timezone.rs b/src/builtins/std/timezone.rs new file mode 100644 index 000000000..f725d9e3b --- /dev/null +++ b/src/builtins/std/timezone.rs @@ -0,0 +1,5 @@ +use crate::tzdb::FsTzdbProvider; +use std::sync::{LazyLock, Mutex}; + +pub static TZ_PROVIDER: LazyLock> = + LazyLock::new(|| Mutex::new(FsTzdbProvider::default())); diff --git a/src/builtins/std/zoneddatetime.rs b/src/builtins/std/zoneddatetime.rs new file mode 100644 index 000000000..f55c58653 --- /dev/null +++ b/src/builtins/std/zoneddatetime.rs @@ -0,0 +1,377 @@ +use tinystr::TinyAsciiStr; + +use super::timezone::TZ_PROVIDER; +use crate::{ + builtins::core, + options::{ArithmeticOverflow, Disambiguation, OffsetDisambiguation}, + Calendar, Duration, PlainDate, PlainDateTime, PlainTime, TemporalError, TemporalResult, + TimeZone, +}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct ZonedDateTime(pub(crate) core::ZonedDateTime); + +impl From for ZonedDateTime { + fn from(value: core::ZonedDateTime) -> Self { + Self(value) + } +} + +impl ZonedDateTime { + #[inline] + pub fn try_new(nanos: i128, calendar: Calendar, tz: TimeZone) -> TemporalResult { + core::ZonedDateTime::try_new(nanos, calendar, tz).map(Into::into) + } + + pub fn calendar(&self) -> &Calendar { + self.0.calendar() + } + + pub fn timezone(&self) -> &TimeZone { + self.0.timezone() + } +} + +// ===== Experimental TZ_PROVIDER accessor implementations ===== + +impl ZonedDateTime { + pub fn year(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.year_with_provider(&*provider) + } + + pub fn month(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.month_with_provider(&*provider) + } + + pub fn month_code(&self) -> TemporalResult> { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.month_code_with_provider(&*provider) + } + + pub fn day(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.day_with_provider(&*provider) + } + + pub fn hour(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.hour_with_provider(&*provider) + } + + pub fn minute(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.minute_with_provider(&*provider) + } + + pub fn second(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.second_with_provider(&*provider) + } + + pub fn millisecond(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.millisecond_with_provider(&*provider) + } + + pub fn microsecond(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.millisecond_with_provider(&*provider) + } + + pub fn nanosecond(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + + self.0.millisecond_with_provider(&*provider) + } +} + +// ==== Experimental TZ_PROVIDER calendar method implementations ==== + +#[cfg(feature = "experimental")] +impl ZonedDateTime { + pub fn era(&self) -> TemporalResult>> { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.era_with_provider(&*provider) + } + + pub fn era_year(&self) -> TemporalResult> { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.era_year_with_provider(&*provider) + } + + /// Returns the calendar day of week value. + pub fn day_of_week(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.day_of_week_with_provider(&*provider) + } + + /// Returns the calendar day of year value. + pub fn day_of_year(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.day_of_year_with_provider(&*provider) + } + + /// Returns the calendar week of year value. + pub fn week_of_year(&self) -> TemporalResult> { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.week_of_year_with_provider(&*provider) + } + + /// Returns the calendar year of week value. + pub fn year_of_week(&self) -> TemporalResult> { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.year_of_week_with_provider(&*provider) + } + + /// Returns the calendar days in week value. + pub fn days_in_week(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.days_in_week_with_provider(&*provider) + } + + /// Returns the calendar days in month value. + pub fn days_in_month(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.days_in_month_with_provider(&*provider) + } + + /// Returns the calendar days in year value. + pub fn days_in_year(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.days_in_year_with_provider(&*provider) + } + + /// Returns the calendar months in year value. + pub fn months_in_year(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.months_in_year_with_provider(&*provider) + } + + /// Returns returns whether the date in a leap year for the given calendar. + pub fn in_leap_year(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.in_leap_year_with_provider(&*provider) + } + + pub fn hours_in_day(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.hours_in_day_with_provider(&*provider) + } +} + +// ==== Experimental TZ_PROVIDER method implementations ==== + +impl ZonedDateTime { + /// Creates a new `ZonedDateTime` from the current `ZonedDateTime` + /// combined with the provided `TimeZone`. + pub fn with_plain_time(&self, time: PlainTime) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0 + .with_plain_time_and_provider(time.into(), &*provider) + .map(Into::into) + } + + pub fn add( + &self, + duration: &Duration, + overflow: Option, + ) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.add_with_provider(&duration.0, overflow, &*provider).map(Into::into) + } + + pub fn subtract( + &self, + duration: &Duration, + overflow: Option, + ) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.subtract_with_provider(&duration.0, overflow, &*provider).map(Into::into) + } + + pub fn start_of_day(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0 + .start_of_day_with_provider(&*provider) + .map(Into::into) + } + + pub fn to_plain_date(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.to_plain_date_with_provider(&*provider).map(Into::into) + } + + pub fn to_plain_time(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.to_plain_time_with_provider(&*provider).map(Into::into) + } + + pub fn to_plain_datetime(&self) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.to_plain_datetime_with_provider(&*provider).map(Into::into) + } + + pub fn from_str( + source: &str, + disambiguation: Disambiguation, + offset_option: OffsetDisambiguation, + ) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + core::ZonedDateTime::from_str_with_provider( + source, + disambiguation, + offset_option, + &*provider, + ) + .map(Into::into) + } +} + +mod tests { + #[cfg(not(target_os = "windows"))] + #[test] + fn static_tzdb_zdt_test() { + use crate::{Calendar, TimeZone}; + use super::ZonedDateTime; + use core::str::FromStr; + + let nov_30_2023_utc = 1_701_308_952_000_000_000i128; + + let zdt = ZonedDateTime::try_new( + nov_30_2023_utc, + Calendar::from_str("iso8601").unwrap(), + TimeZone::try_from_str("Z").unwrap(), + ) + .unwrap(); + + assert_eq!(zdt.year().unwrap(), 2023); + assert_eq!(zdt.month().unwrap(), 11); + assert_eq!(zdt.day().unwrap(), 30); + assert_eq!(zdt.hour().unwrap(), 1); + assert_eq!(zdt.minute().unwrap(), 49); + assert_eq!(zdt.second().unwrap(), 12); + + let zdt_minus_five = ZonedDateTime::try_new( + nov_30_2023_utc, + Calendar::from_str("iso8601").unwrap(), + TimeZone::try_from_str("America/New_York").unwrap(), + ) + .unwrap(); + + assert_eq!(zdt_minus_five.year().unwrap(), 2023); + assert_eq!(zdt_minus_five.month().unwrap(), 11); + assert_eq!(zdt_minus_five.day().unwrap(), 29); + assert_eq!(zdt_minus_five.hour().unwrap(), 20); + assert_eq!(zdt_minus_five.minute().unwrap(), 49); + assert_eq!(zdt_minus_five.second().unwrap(), 12); + + let zdt_plus_eleven = ZonedDateTime::try_new( + nov_30_2023_utc, + Calendar::from_str("iso8601").unwrap(), + TimeZone::try_from_str("Australia/Sydney").unwrap(), + ) + .unwrap(); + + assert_eq!(zdt_plus_eleven.year().unwrap(), 2023); + assert_eq!(zdt_plus_eleven.month().unwrap(), 11); + assert_eq!(zdt_plus_eleven.day().unwrap(), 30); + assert_eq!(zdt_plus_eleven.hour().unwrap(), 12); + assert_eq!(zdt_plus_eleven.minute().unwrap(), 49); + assert_eq!(zdt_plus_eleven.second().unwrap(), 12); + } + + #[cfg(not(target_os = "windows"))] + #[test] + fn basic_zdt_add() { + use crate::{Calendar, Duration, TimeZone}; + use super::ZonedDateTime; + + let zdt = + ZonedDateTime::try_new(-560174321098766, Calendar::default(), TimeZone::default()) + .unwrap(); + let d = Duration::new( + 0.into(), + 0.into(), + 0.into(), + 0.into(), + 240.into(), + 0.into(), + 0.into(), + 0.into(), + 0.into(), + 800.into(), + ) + .unwrap(); + // "1970-01-04T12:23:45.678902034+00:00[UTC]" + let expected = + ZonedDateTime::try_new(303825678902034, Calendar::default(), TimeZone::default()) + .unwrap(); + + let result = zdt.add(&d, None).unwrap(); + assert_eq!(result, expected); + } + + +} diff --git a/src/epoch_nanoseconds.rs b/src/epoch_nanoseconds.rs new file mode 100644 index 000000000..62bd89ea2 --- /dev/null +++ b/src/epoch_nanoseconds.rs @@ -0,0 +1,48 @@ + +use num_traits::FromPrimitive; + +use crate::{TemporalError, NS_MAX_INSTANT}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct EpochNanoseconds(pub(crate) i128); + +impl TryFrom for EpochNanoseconds { + type Error = TemporalError; + fn try_from(value: i128) -> Result { + if !is_valid_epoch_nanos(&value) { + return Err(TemporalError::range() + .with_message("Instant nanoseconds are not within a valid epoch range.")); + } + Ok(Self(value)) + } +} + +impl TryFrom for EpochNanoseconds { + type Error = TemporalError; + fn try_from(value: u128) -> Result { + if (NS_MAX_INSTANT as u128) < value { + return Err(TemporalError::range() + .with_message("Instant nanoseconds are not within a valid epoch range.")); + } + Ok(Self(value as i128)) + } +} + +impl TryFrom for EpochNanoseconds { + type Error = TemporalError; + fn try_from(value: f64) -> Result { + let Some(value) = i128::from_f64(value) else { + return Err(TemporalError::range() + .with_message("Instant nanoseconds are not within a valid epoch range.")); + }; + Self::try_from(value) + } +} + +/// Utility for determining if the nanos are within a valid range. +#[inline] +#[must_use] +pub(crate) fn is_valid_epoch_nanos(nanos: &i128) -> bool { + (crate::NS_MIN_INSTANT..=crate::NS_MAX_INSTANT).contains(nanos) +} + diff --git a/src/iso.rs b/src/iso.rs index adaef2290..9a2e13466 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -26,7 +26,7 @@ use alloc::string::ToString; use core::num::NonZeroU128; use crate::{ - components::{ + builtins::core::{ calendar::Calendar, duration::{ normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, diff --git a/src/lib.rs b/src/lib.rs index 2094c49f4..2bfb0a451 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,7 +52,10 @@ pub mod options; pub mod parsers; pub mod primitive; -pub(crate) mod components; +mod epoch_nanoseconds; + + +pub(crate) mod builtins; #[cfg(feature = "now")] mod sys; @@ -83,25 +86,25 @@ pub mod partial { //! //! The partial records are `temporal_rs`'s method of addressing //! `TemporalFields` in the specification. - pub use crate::components::{ - duration::PartialDuration, PartialDate, PartialDateTime, PartialTime, PartialZonedDateTime, + pub use crate::builtins::{ + core::PartialDuration, PartialDate, PartialDateTime, PartialTime, PartialZonedDateTime, }; } // TODO: Potentially bikeshed how `EpochNanoseconds` should be exported. pub mod time { - pub use crate::components::EpochNanoseconds; + pub use crate::epoch_nanoseconds::EpochNanoseconds; } -pub use crate::components::{ +pub use crate::builtins::{ calendar::Calendar, - timezone::{TimeZone, TimeZoneProvider}, + core::timezone::{TimeZone, TimeZoneProvider}, DateDuration, Duration, Instant, PlainDate, PlainDateTime, PlainMonthDay, PlainTime, PlainYearMonth, TimeDuration, ZonedDateTime, }; #[cfg(feature = "std")] -pub use crate::components::Now; +pub use crate::builtins::Now; /// A library specific trait for unwrapping assertions. pub(crate) trait TemporalUnwrap { diff --git a/src/options/relative_to.rs b/src/options/relative_to.rs index 2f451540b..8a8522985 100644 --- a/src/options/relative_to.rs +++ b/src/options/relative_to.rs @@ -1,135 +1,8 @@ //! RelativeTo rounding option -use alloc::string::String; +#[cfg(not(feature = "experimental"))] +pub use crate::builtins::core::options::RelativeTo; -use crate::components::{timezone::TimeZoneProvider, zoneddatetime::interpret_isodatetime_offset}; -use crate::iso::{IsoDate, IsoTime}; -use crate::options::{ArithmeticOverflow, Disambiguation, OffsetDisambiguation}; -use crate::parsers::parse_date_time; -use crate::{ - Calendar, PlainDate, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, ZonedDateTime, -}; -use ixdtf::parsers::records::{TimeZoneRecord, UtcOffsetRecordOrZ}; - -// ==== RelativeTo Object ==== - -#[derive(Debug, Clone)] -pub enum RelativeTo { - PlainDate(PlainDate), - ZonedDateTime(ZonedDateTime), -} - -impl From for RelativeTo { - fn from(value: PlainDate) -> Self { - Self::PlainDate(value) - } -} - -impl From for RelativeTo { - fn from(value: ZonedDateTime) -> Self { - Self::ZonedDateTime(value) - } -} - -impl RelativeTo { - /// Attempts to parse a `ZonedDateTime` string falling back to a `PlainDate` - /// if possible. - /// - /// If the fallback fails or either the `ZonedDateTime` or `PlainDate` - /// is invalid, then an error is returned. - pub fn try_from_str_with_provider( - source: &str, - provider: &impl TimeZoneProvider, - ) -> TemporalResult { - let result = parse_date_time(source)?; - - let Some(annotation) = result.tz else { - let date_record = result.date.temporal_unwrap()?; - - let calendar = result - .calendar - .map(Calendar::from_utf8) - .transpose()? - .unwrap_or_default(); - - return Ok(PlainDate::try_new( - date_record.year, - date_record.month, - date_record.day, - calendar, - )? - .into()); - }; - - let timezone = match annotation.tz { - TimeZoneRecord::Name(s) => { - TimeZone::IanaIdentifier(String::from_utf8_lossy(s).into_owned()) - } - TimeZoneRecord::Offset(offset_record) => { - // NOTE: ixdtf parser restricts minute/second to 0..=60 - let minutes = i16::from(offset_record.hour) * 60 + offset_record.minute as i16; - TimeZone::OffsetMinutes(minutes * i16::from(offset_record.sign as i8)) - } - // TimeZoneRecord is non_exhaustive, but all current branches are matching. - _ => return Err(TemporalError::assert()), - }; - - let (offset_nanos, is_exact) = result - .offset - .map(|record| { - let UtcOffsetRecordOrZ::Offset(offset) = record else { - return (None, true); - }; - let hours_in_ns = i64::from(offset.hour) * 3_600_000_000_000_i64; - let minutes_in_ns = i64::from(offset.minute) * 60_000_000_000_i64; - let seconds_in_ns = i64::from(offset.minute) * 1_000_000_000_i64; - ( - Some( - (hours_in_ns - + minutes_in_ns - + seconds_in_ns - + i64::from(offset.nanosecond)) - * i64::from(offset.sign as i8), - ), - false, - ) - }) - .unwrap_or((None, false)); - - let calendar = result - .calendar - .map(Calendar::from_utf8) - .transpose()? - .unwrap_or_default(); - - let time = result - .time - .map(|time| { - IsoTime::from_components(time.hour, time.minute, time.second, time.nanosecond) - }) - .transpose()?; - - let date = result.date.temporal_unwrap()?; - let iso = IsoDate::new_with_overflow( - date.year, - date.month, - date.day, - ArithmeticOverflow::Constrain, - )?; - - let epoch_ns = interpret_isodatetime_offset( - iso, - time, - is_exact, - offset_nanos, - &timezone, - Disambiguation::Compatible, - OffsetDisambiguation::Reject, - true, - provider, - )?; - - Ok(ZonedDateTime::try_new(epoch_ns.0, calendar, timezone)?.into()) - } -} +#[cfg(feature = "experimental")] +pub use crate::builtins::std::options::RelativeTo; diff --git a/src/tzdb.rs b/src/tzdb.rs index 92e0e481d..d3d3ecf4a 100644 --- a/src/tzdb.rs +++ b/src/tzdb.rs @@ -47,10 +47,11 @@ use tzif::{ }, }; -use crate::components::timezone::TimeZoneOffset; +use crate::builtins::core::timezone::TimeZoneOffset; use crate::{ - components::{timezone::TimeZoneProvider, EpochNanoseconds}, + builtins::core::timezone::TimeZoneProvider, iso::IsoDateTime, + time::EpochNanoseconds, utils, TemporalError, TemporalResult, }; From 4e904fa6cf65c45baacdf4f27ed419d5a40e2761 Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sat, 18 Jan 2025 00:04:29 -0600 Subject: [PATCH 2/8] Complete initial work on project restructure --- Cargo.toml | 4 +- src/builtins/core/date.rs | 6 +- src/builtins/core/datetime.rs | 11 +- src/builtins/core/duration.rs | 8 +- src/builtins/core/duration/normalized.rs | 6 +- src/builtins/core/duration/tests.rs | 1 - src/builtins/core/instant.rs | 16 +- src/builtins/core/mod.rs | 2 +- src/builtins/core/now.rs | 11 +- src/builtins/core/options.rs | 13 +- src/builtins/core/time.rs | 2 +- src/builtins/core/timezone.rs | 157 +++----- src/builtins/core/zoneddatetime.rs | 14 +- src/builtins/mod.rs | 8 +- src/builtins/{std => native}/date.rs | 12 +- src/builtins/native/datetime.rs | 368 ++++++++++++++++++ src/builtins/{std => native}/duration.rs | 31 +- .../{std => native}/duration/tests.rs | 11 +- src/builtins/{std => native}/instant.rs | 47 ++- src/builtins/native/mod.rs | 23 ++ src/builtins/{std => native}/now.rs | 14 +- src/builtins/{std => native}/options.rs | 3 +- src/builtins/native/time.rs | 217 +++++++++++ src/builtins/{std => native}/timezone.rs | 0 src/builtins/{std => native}/zoneddatetime.rs | 88 ++++- src/builtins/std/mod.rs | 55 --- src/epoch_nanoseconds.rs | 2 - src/lib.rs | 8 +- src/options/relative_to.rs | 7 +- src/provider.rs | 53 +++ src/tzdb.rs | 3 +- 31 files changed, 919 insertions(+), 282 deletions(-) rename src/builtins/{std => native}/date.rs (96%) create mode 100644 src/builtins/native/datetime.rs rename src/builtins/{std => native}/duration.rs (89%) rename src/builtins/{std => native}/duration/tests.rs (98%) rename src/builtins/{std => native}/instant.rs (61%) create mode 100644 src/builtins/native/mod.rs rename src/builtins/{std => native}/now.rs (83%) rename src/builtins/{std => native}/options.rs (96%) create mode 100644 src/builtins/native/time.rs rename src/builtins/{std => native}/timezone.rs (100%) rename src/builtins/{std => native}/zoneddatetime.rs (82%) delete mode 100644 src/builtins/std/mod.rs create mode 100644 src/provider.rs diff --git a/Cargo.toml b/Cargo.toml index ab39ab8ef..7828b4094 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,9 +66,9 @@ combine = { workspace = true, optional = true } web-time = { workspace = true, optional = true } [features] -default = ["now", "experimental"] +default = ["full"] log = ["dep:log"] -experimental = ["tzdb"] +full = ["tzdb", "now"] now = ["std", "dep:web-time"] tzdb = ["dep:tzif", "std", "dep:jiff-tzdb", "dep:combine"] std = [] diff --git a/src/builtins/core/date.rs b/src/builtins/core/date.rs index 8dd9f856a..a91c92dd8 100644 --- a/src/builtins/core/date.rs +++ b/src/builtins/core/date.rs @@ -1,7 +1,9 @@ //! This module implements `Date` and any directly related algorithms. use crate::{ - builtins::core::{calendar::Calendar, duration::DateDuration, Duration, PlainDateTime, PlainTime}, + builtins::core::{ + calendar::Calendar, duration::DateDuration, Duration, PlainDateTime, PlainTime, + }, iso::{IsoDate, IsoDateTime, IsoTime}, options::{ ArithmeticOverflow, DifferenceOperation, DifferenceSettings, DisplayCalendar, @@ -9,6 +11,7 @@ use crate::{ }, parsers::{parse_date_time, IxdtfStringBuilder}, primitive::FiniteF64, + provider::NeverProvider, Sign, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, }; use alloc::{format, string::String}; @@ -17,7 +20,6 @@ use core::str::FromStr; use super::{ calendar::{ascii_four_to_integer, month_to_month_code}, duration::{normalized::NormalizedDurationRecord, TimeDuration}, - timezone::NeverProvider, PlainMonthDay, PlainYearMonth, }; use tinystr::TinyAsciiStr; diff --git a/src/builtins/core/datetime.rs b/src/builtins/core/datetime.rs index d294f0f84..32e537780 100644 --- a/src/builtins/core/datetime.rs +++ b/src/builtins/core/datetime.rs @@ -1,5 +1,9 @@ //! This module implements `DateTime` any directly related algorithms. +use super::{ + duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, + Duration, PartialDate, PartialTime, PlainDate, PlainTime, +}; use crate::{ builtins::core::{calendar::Calendar, Instant}, iso::{IsoDate, IsoDateTime, IsoTime}, @@ -8,16 +12,11 @@ use crate::{ ResolvedRoundingOptions, RoundingOptions, TemporalUnit, ToStringRoundingOptions, }, parsers::{parse_date_time, IxdtfStringBuilder}, + provider::NeverProvider, temporal_assert, Sign, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, }; use alloc::string::String; use core::{cmp::Ordering, str::FromStr}; - -use super::{ - duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, - timezone::NeverProvider, - Duration, PartialDate, PartialTime, PlainDate, PlainTime, -}; use tinystr::TinyAsciiStr; /// A partial PlainDateTime record diff --git a/src/builtins/core/duration.rs b/src/builtins/core/duration.rs index fd8d590b4..c8416fd52 100644 --- a/src/builtins/core/duration.rs +++ b/src/builtins/core/duration.rs @@ -1,12 +1,11 @@ //! This module implements `Duration` along with it's methods and components. use crate::{ - builtins::core::{timezone::TimeZoneProvider, options::RelativeTo, PlainDateTime, PlainTime, ZonedDateTime}, + builtins::core::{options::RelativeTo, PlainDateTime, PlainTime, ZonedDateTime}, iso::{IsoDateTime, IsoTime}, - options::{ - ArithmeticOverflow, ResolvedRoundingOptions, RoundingOptions, TemporalUnit, - }, + options::{ArithmeticOverflow, ResolvedRoundingOptions, RoundingOptions, TemporalUnit}, primitive::FiniteF64, + provider::TimeZoneProvider, temporal_assert, Sign, TemporalError, TemporalResult, }; use alloc::format; @@ -31,7 +30,6 @@ pub use date::DateDuration; #[doc(inline)] pub use time::TimeDuration; - /// A `PartialDuration` is a Duration that may have fields not set. #[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] pub struct PartialDuration { diff --git a/src/builtins/core/duration/normalized.rs b/src/builtins/core/duration/normalized.rs index 4bd09109f..9230b7f4c 100644 --- a/src/builtins/core/duration/normalized.rs +++ b/src/builtins/core/duration/normalized.rs @@ -5,16 +5,14 @@ use core::{num::NonZeroU128, ops::Add}; use num_traits::{AsPrimitive, Euclid, FromPrimitive}; use crate::{ - builtins::core::{ - timezone::{TimeZone, TimeZoneProvider}, - PlainDate, PlainDateTime, - }, + builtins::core::{timezone::TimeZone, PlainDate, PlainDateTime}, iso::{IsoDate, IsoDateTime}, options::{ ArithmeticOverflow, Disambiguation, ResolvedRoundingOptions, TemporalRoundingMode, TemporalUnit, }, primitive::FiniteF64, + provider::TimeZoneProvider, rounding::{IncrementRounder, Round}, TemporalError, TemporalResult, TemporalUnwrap, NS_PER_DAY, }; diff --git a/src/builtins/core/duration/tests.rs b/src/builtins/core/duration/tests.rs index 2d2d4cdd0..857511385 100644 --- a/src/builtins/core/duration/tests.rs +++ b/src/builtins/core/duration/tests.rs @@ -15,4 +15,3 @@ fn partial_duration_values() { let result = Duration::from_partial_duration(partial).unwrap(); assert_eq!(result.years(), 20.0); } - diff --git a/src/builtins/core/instant.rs b/src/builtins/core/instant.rs index 54f316811..30dfec0bc 100644 --- a/src/builtins/core/instant.rs +++ b/src/builtins/core/instant.rs @@ -14,6 +14,7 @@ use crate::{ }, parsers::{parse_instant, IxdtfStringBuilder}, primitive::FiniteF64, + provider::TimeZoneProvider, rounding::{IncrementRounder, Round}, time::EpochNanoseconds, Sign, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, @@ -21,10 +22,7 @@ use crate::{ use ixdtf::parsers::records::UtcOffsetRecordOrZ; -use super::{ - duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, - timezone::TimeZoneProvider, -}; +use super::duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}; const NANOSECONDS_PER_SECOND: f64 = 1e9; const NANOSECONDS_PER_MINUTE: f64 = 60f64 * NANOSECONDS_PER_SECOND; @@ -237,7 +235,7 @@ impl Instant { // ==== Instant Provider API ==== impl Instant { - pub fn to_ixdtf_string_with_provider( + pub fn as_ixdtf_string_with_provider( &self, timezone: Option<&TimeZone>, options: ToStringRoundingOptions, @@ -522,10 +520,10 @@ mod tests { #[cfg(feature = "tzdb")] #[test] fn instant_add_across_epoch() { + use crate::builtins::core::Duration; use crate::{ options::ToStringRoundingOptions, partial::PartialDuration, tzdb::FsTzdbProvider, }; - use crate::builtins::core::Duration; use core::str::FromStr; let instant = Instant::from_str("1969-12-25T12:23:45.678901234Z").unwrap(); @@ -582,13 +580,13 @@ mod tests { // Assert the to_string is valid. let provider = &FsTzdbProvider::default(); let inst_string = instant - .to_ixdtf_string_with_provider(None, ToStringRoundingOptions::default(), provider) + .as_ixdtf_string_with_provider(None, ToStringRoundingOptions::default(), provider) .unwrap(); let one_string = one - .to_ixdtf_string_with_provider(None, ToStringRoundingOptions::default(), provider) + .as_ixdtf_string_with_provider(None, ToStringRoundingOptions::default(), provider) .unwrap(); let two_string = two - .to_ixdtf_string_with_provider(None, ToStringRoundingOptions::default(), provider) + .as_ixdtf_string_with_provider(None, ToStringRoundingOptions::default(), provider) .unwrap(); assert_eq!(&inst_string, "1969-12-25T12:23:45.678901234Z"); diff --git a/src/builtins/core/mod.rs b/src/builtins/core/mod.rs index bf60aa5b2..1ae13dd03 100644 --- a/src/builtins/core/mod.rs +++ b/src/builtins/core/mod.rs @@ -31,7 +31,7 @@ pub use date::{PartialDate, PlainDate}; #[doc(inline)] pub use datetime::{PartialDateTime, PlainDateTime}; #[doc(inline)] -pub use duration::{DateDuration, Duration, TimeDuration, PartialDuration}; +pub use duration::{DateDuration, Duration, PartialDuration, TimeDuration}; #[doc(inline)] pub use instant::Instant; #[doc(inline)] diff --git a/src/builtins/core/now.rs b/src/builtins/core/now.rs index 21b6bcb84..71c92a02c 100644 --- a/src/builtins/core/now.rs +++ b/src/builtins/core/now.rs @@ -1,16 +1,15 @@ //! The Temporal Now component +use crate::provider::TimeZoneProvider; +use crate::{iso::IsoDateTime, time::EpochNanoseconds, TemporalUnwrap}; use crate::{sys, TemporalResult}; use alloc::string::String; use num_traits::FromPrimitive; -use crate::{iso::IsoDateTime, TemporalUnwrap, time::EpochNanoseconds}; - use super::{ - calendar::Calendar, - timezone::{TimeZone, TimeZoneProvider}, - Instant, PlainDate, PlainDateTime, PlainTime, ZonedDateTime, + calendar::Calendar, timezone::TimeZone, Instant, PlainDate, PlainDateTime, PlainTime, + ZonedDateTime, }; /// The Temporal Now object. @@ -111,8 +110,8 @@ mod tests { use std::thread; use std::time::Duration as StdDuration; - use crate::{options::DifferenceSettings, tzdb::FsTzdbProvider}; use crate::builtins::core::Now; + use crate::{options::DifferenceSettings, tzdb::FsTzdbProvider}; #[test] fn now_datetime_test() { diff --git a/src/builtins/core/options.rs b/src/builtins/core/options.rs index 1817c7f50..0929d1e6e 100644 --- a/src/builtins/core/options.rs +++ b/src/builtins/core/options.rs @@ -2,16 +2,13 @@ use alloc::string::String; -use crate::builtins::core::{ - timezone::TimeZoneProvider, zoneddatetime::interpret_isodatetime_offset, -}; +use crate::builtins::core::zoneddatetime::interpret_isodatetime_offset; +use crate::builtins::core::{calendar::Calendar, timezone::TimeZone, PlainDate, ZonedDateTime}; use crate::iso::{IsoDate, IsoTime}; use crate::options::{ArithmeticOverflow, Disambiguation, OffsetDisambiguation}; use crate::parsers::parse_date_time; -use crate::builtins::core::{ZonedDateTime, PlainDate, timezone::TimeZone, calendar::Calendar}; -use crate::{ - TemporalError, TemporalResult, TemporalUnwrap, -}; +use crate::provider::TimeZoneProvider; +use crate::{TemporalError, TemporalResult, TemporalUnwrap}; use ixdtf::parsers::records::{TimeZoneRecord, UtcOffsetRecordOrZ}; @@ -136,5 +133,3 @@ impl RelativeTo { Ok(ZonedDateTime::try_new(epoch_ns.0, calendar, timezone)?.into()) } } - - diff --git a/src/builtins/core/time.rs b/src/builtins/core/time.rs index 80d3b7e3c..8c63e0ed8 100644 --- a/src/builtins/core/time.rs +++ b/src/builtins/core/time.rs @@ -434,7 +434,7 @@ impl PlainTime { Ok(Self::new_unchecked(result)) } - pub fn to_ixdtf_string(&self, options: ToStringRoundingOptions) -> TemporalResult { + pub fn as_ixdtf_string(&self, options: ToStringRoundingOptions) -> TemporalResult { let resolved = options.resolve()?; let (_, result) = self .iso diff --git a/src/builtins/core/timezone.rs b/src/builtins/core/timezone.rs index 101803977..de287e6bd 100644 --- a/src/builtins/core/timezone.rs +++ b/src/builtins/core/timezone.rs @@ -9,6 +9,7 @@ use num_traits::ToPrimitive; use crate::builtins::core::duration::DateDuration; use crate::parsers::{FormattableOffset, FormattableTime, Precision}; +use crate::provider::{TimeZoneOffset, TimeZoneProvider}; use crate::{ builtins::core::{duration::normalized::NormalizedTimeDuration, Instant}, iso::{IsoDate, IsoDateTime, IsoTime}, @@ -18,66 +19,8 @@ use crate::{ }; use crate::{Calendar, Sign}; -#[cfg(feature = "experimental")] -use crate::tzdb::FsTzdbProvider; -#[cfg(feature = "experimental")] -use std::sync::{LazyLock, Mutex}; - -#[cfg(feature = "experimental")] -pub static TZ_PROVIDER: LazyLock> = - LazyLock::new(|| Mutex::new(FsTzdbProvider::default())); - const NS_IN_HOUR: i128 = 60 * 60 * 1000 * 1000 * 1000; -/// `TimeZoneOffset` represents the number of seconds to be added to UT in order to determine local time. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct TimeZoneOffset { - /// The transition time epoch at which the offset needs to be applied. - pub transition_epoch: Option, - /// The time zone offset in seconds. - pub offset: i64, -} - -// NOTE: It may be a good idea to eventually move this into it's -// own individual crate rather than having it tied directly into `temporal_rs` -/// The `TimeZoneProvider` trait provides methods required for a provider -/// to implement in order to source time zone data from that provider. -pub trait TimeZoneProvider { - fn check_identifier(&self, identifier: &str) -> bool; - - fn get_named_tz_epoch_nanoseconds( - &self, - identifier: &str, - local_datetime: IsoDateTime, - ) -> TemporalResult>; - - fn get_named_tz_offset_nanoseconds( - &self, - identifier: &str, - utc_epoch: i128, - ) -> TemporalResult; -} - -pub struct NeverProvider; - -impl TimeZoneProvider for NeverProvider { - fn check_identifier(&self, _: &str) -> bool { - unimplemented!() - } - - fn get_named_tz_epoch_nanoseconds( - &self, - _: &str, - _: IsoDateTime, - ) -> TemporalResult> { - unimplemented!() - } - - fn get_named_tz_offset_nanoseconds(&self, _: &str, _: i128) -> TemporalResult { - unimplemented!() - } -} - // TODO: migrate to Cow<'a, str> #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum TimeZone { @@ -86,32 +29,70 @@ pub enum TimeZone { } impl TimeZone { - #[cfg(feature = "experimental")] + #[cfg(feature = "full")] pub fn try_from_str(source: &str) -> TemporalResult { + use crate::builtins::timezone::TZ_PROVIDER; + let provider = TZ_PROVIDER .lock() .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - Self::try_from_str_with_provider(source, &*provider) + try_timezone_from_str_with_provider(source, &*provider) } /// Parses a `TimeZone` from a provided `&str`. + #[cfg(not(feature = "full"))] pub fn try_from_str_with_provider( source: &str, provider: &impl TimeZoneProvider, ) -> TemporalResult { - if source == "Z" { - return Ok(Self::OffsetMinutes(0)); - } - let mut cursor = source.chars().peekable(); - if cursor.peek().is_some_and(is_ascii_sign) { - return parse_offset(&mut cursor); - } else if provider.check_identifier(source) { - return Ok(Self::IanaIdentifier(source.to_owned())); + try_timezone_from_str_with_provider(source, provider) + } + + /// Returns the current `TimeZoneSlot`'s identifier. + pub fn identifier(&self) -> TemporalResult { + match self { + TimeZone::IanaIdentifier(s) => Ok(s.clone()), + TimeZone::OffsetMinutes(m) => { + let sign = if *m < 0 { + Sign::Negative + } else { + Sign::Positive + }; + let hour = (m.abs() / 60) as u8; + let minute = (m.abs() % 60) as u8; + let formattable_offset = FormattableOffset { + sign, + time: FormattableTime { + hour, + minute, + second: 0, + nanosecond: 0, + precision: Precision::Minute, + include_sep: true, + }, + }; + Ok(formattable_offset.to_string()) + } } - Err(TemporalError::range().with_message("Valid time zone was not provided.")) } } +pub fn try_timezone_from_str_with_provider( + source: &str, + provider: &impl TimeZoneProvider, +) -> TemporalResult { + if source == "Z" { + return Ok(TimeZone::OffsetMinutes(0)); + } + let mut cursor = source.chars().peekable(); + if cursor.peek().is_some_and(is_ascii_sign) { + return parse_offset(&mut cursor); + } else if provider.check_identifier(source) { + return Ok(TimeZone::IanaIdentifier(source.to_owned())); + } + Err(TemporalError::range().with_message("Valid time zone was not provided.")) +} + impl Default for TimeZone { fn default() -> Self { Self::IanaIdentifier("UTC".into()) @@ -133,11 +114,9 @@ impl TimeZone { let nanos = self.get_offset_nanos_for(instant.as_i128(), provider)?; IsoDateTime::from_epoch_nanos(&instant.as_i128(), nanos.to_i64().unwrap_or(0)) } -} -impl TimeZone { /// Get the offset for this current `TimeZoneSlot`. - pub fn get_offset_nanos_for( + pub(crate) fn get_offset_nanos_for( &self, utc_epoch: i128, provider: &impl TimeZoneProvider, @@ -153,7 +132,7 @@ impl TimeZone { } } - pub fn get_epoch_nanoseconds_for( + pub(crate) fn get_epoch_nanoseconds_for( &self, iso: IsoDateTime, disambiguation: Disambiguation, @@ -166,7 +145,7 @@ impl TimeZone { } /// Get the possible `Instant`s for this `TimeZoneSlot`. - pub fn get_possible_epoch_ns_for( + pub(crate) fn get_possible_epoch_ns_for( &self, iso: IsoDateTime, provider: &impl TimeZoneProvider, @@ -219,34 +198,6 @@ impl TimeZone { // 5. Return possibleEpochNanoseconds. Ok(possible_nanoseconds) } - - /// Returns the current `TimeZoneSlot`'s identifier. - pub fn identifier(&self) -> TemporalResult { - match self { - TimeZone::IanaIdentifier(s) => Ok(s.clone()), - TimeZone::OffsetMinutes(m) => { - let sign = if *m < 0 { - Sign::Negative - } else { - Sign::Positive - }; - let hour = (m.abs() / 60) as u8; - let minute = (m.abs() % 60) as u8; - let formattable_offset = FormattableOffset { - sign, - time: FormattableTime { - hour, - minute, - second: 0, - nanosecond: 0, - precision: Precision::Minute, - include_sep: true, - }, - }; - Ok(formattable_offset.to_string()) - } - } - } } impl TimeZone { @@ -504,7 +455,7 @@ fn is_ascii_sign(ch: &char) -> bool { *ch == '+' || *ch == '-' } -#[cfg(all(test, feature = "tzdb"))] +#[cfg(all(test, feature = "tzdb", not(feature = "full")))] mod tests { use super::TimeZone; use crate::tzdb::FsTzdbProvider; diff --git a/src/builtins/core/zoneddatetime.rs b/src/builtins/core/zoneddatetime.rs index f1bdc5aef..0c5462fd5 100644 --- a/src/builtins/core/zoneddatetime.rs +++ b/src/builtins/core/zoneddatetime.rs @@ -7,8 +7,10 @@ use tinystr::TinyAsciiStr; use crate::{ builtins::core::{ + calendar::Calendar, duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, - timezone::{parse_offset, TimeZone, TimeZoneProvider}, calendar::Calendar, Duration, Instant, PlainDate, PlainDateTime, PlainTime, + timezone::{parse_offset, TimeZone}, + Duration, Instant, PlainDate, PlainDateTime, PlainTime, }, iso::{IsoDate, IsoDateTime, IsoTime}, options::{ @@ -18,9 +20,11 @@ use crate::{ }, parsers::{self, IxdtfStringBuilder}, partial::{PartialDate, PartialTime}, + provider::TimeZoneProvider, rounding::{IncrementRounder, Round}, + temporal_assert, time::EpochNanoseconds, - temporal_assert,TemporalError, TemporalResult, Sign + Sign, TemporalError, TemporalResult, }; /// A struct representing a partial `ZonedDateTime`. @@ -942,16 +946,16 @@ pub(crate) fn nanoseconds_to_formattable_offset_minutes( Ok((sign, hour as u8, minute as u8)) } -#[cfg(all(test, feature = "tzdb"))] +#[cfg(all(test, feature = "tzdb", not(feature = "full")))] mod tests { + use super::ZonedDateTime; use crate::{ options::{DifferenceSettings, Disambiguation, OffsetDisambiguation, TemporalUnit}, partial::{PartialDate, PartialTime, PartialZonedDateTime}, primitive::FiniteF64, tzdb::FsTzdbProvider, - Calendar, TimeZone + Calendar, TimeZone, }; - use super::ZonedDateTime; use core::str::FromStr; use tinystr::tinystr; diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index fea9bf348..d74792955 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -1,8 +1,8 @@ pub(crate) mod core; -pub(crate) mod std; +pub(crate) mod native; -#[cfg(not(feature = "experimental"))] +#[cfg(not(feature = "full"))] pub use core::*; -#[cfg(feature = "experimental")] -pub use std::*; +#[cfg(feature = "full")] +pub use native::*; diff --git a/src/builtins/std/date.rs b/src/builtins/native/date.rs similarity index 96% rename from src/builtins/std/date.rs rename to src/builtins/native/date.rs index b7c49b6d7..b6d86d90b 100644 --- a/src/builtins/std/date.rs +++ b/src/builtins/native/date.rs @@ -1,7 +1,10 @@ - +use crate::builtins::native::PlainTime; +use crate::{ + builtins::core, + options::{ArithmeticOverflow, DifferenceSettings, DisplayCalendar}, + Calendar, TemporalResult, +}; use alloc::string::String; -use crate::{builtins::core, options::{ArithmeticOverflow, DifferenceSettings, DisplayCalendar}, Calendar, TemporalResult}; -use crate::builtins::std::PlainTime; use super::{duration::Duration, PartialDate, PlainDateTime, PlainMonthDay, PlainYearMonth}; use tinystr::TinyAsciiStr; @@ -247,7 +250,7 @@ impl PlainDate { /// If no time is provided, then the time will default to midnight. #[inline] pub fn to_date_time(&self, time: Option) -> TemporalResult { - self.0.to_date_time(time.map(Into::into)).map(Into::into) + self.0.to_date_time(time.map(|t| t.0)).map(Into::into) } /// Converts the current `Date` into a `PlainYearMonth` @@ -267,4 +270,3 @@ impl PlainDate { self.0.to_ixdtf_string(display_calendar) } } - diff --git a/src/builtins/native/datetime.rs b/src/builtins/native/datetime.rs new file mode 100644 index 000000000..158fe090e --- /dev/null +++ b/src/builtins/native/datetime.rs @@ -0,0 +1,368 @@ +use crate::{ + builtins::core, + options::{ + ArithmeticOverflow, DifferenceSettings, DisplayCalendar, RoundingOptions, + ToStringRoundingOptions, + }, + Calendar, TemporalResult, +}; +use alloc::string::String; +use tinystr::TinyAsciiStr; + +use super::{Duration, PartialDateTime, PlainDate, PlainTime}; +pub struct PlainDateTime(pub(crate) core::PlainDateTime); + +impl From for PlainDateTime { + fn from(value: core::PlainDateTime) -> Self { + Self(value) + } +} + +impl PlainDateTime { + /// Creates a new `DateTime`, constraining any arguments that into a valid range. + #[allow(clippy::too_many_arguments)] + pub fn new( + year: i32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, + millisecond: u16, + microsecond: u16, + nanosecond: u16, + calendar: Calendar, + ) -> TemporalResult { + core::PlainDateTime::new( + year, + month, + day, + hour, + minute, + second, + millisecond, + microsecond, + nanosecond, + calendar, + ) + .map(Into::into) + } + + /// Creates a new `DateTime`, rejecting any arguments that are not in a valid range. + #[allow(clippy::too_many_arguments)] + pub fn try_new( + year: i32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, + millisecond: u16, + microsecond: u16, + nanosecond: u16, + calendar: Calendar, + ) -> TemporalResult { + core::PlainDateTime::try_new( + year, + month, + day, + hour, + minute, + second, + millisecond, + microsecond, + nanosecond, + calendar, + ) + .map(Into::into) + } + + /// Create a `DateTime` from a `Date` and a `Time`. + pub fn from_date_and_time(date: PlainDate, time: PlainTime) -> TemporalResult { + core::PlainDateTime::from_date_and_time(date.0, time.0).map(Into::into) + } + + /// Creates a `DateTime` from a `PartialDateTime`. + /// + /// ```rust + /// use temporal_rs::{PlainDateTime, partial::{PartialDateTime, PartialTime, PartialDate}}; + /// + /// let date = PartialDate { + /// year: Some(2000), + /// month: Some(13), + /// day: Some(2), + /// ..Default::default() + /// }; + /// + /// let time = PartialTime { + /// hour: Some(4), + /// minute: Some(25), + /// ..Default::default() + /// }; + /// + /// let partial = PartialDateTime { date, time }; + /// + /// let date = PlainDateTime::from_partial(partial, None).unwrap(); + /// + /// assert_eq!(date.year().unwrap(), 2000); + /// assert_eq!(date.month().unwrap(), 12); + /// assert_eq!(date.day().unwrap(), 2); + /// assert_eq!(date.calendar().identifier(), "iso8601"); + /// assert_eq!(date.hour(), 4); + /// assert_eq!(date.minute(), 25); + /// assert_eq!(date.second(), 0); + /// assert_eq!(date.millisecond(), 0); + /// + /// ``` + pub fn from_partial( + partial: PartialDateTime, + overflow: Option, + ) -> TemporalResult { + core::PlainDateTime::from_partial(partial, overflow).map(Into::into) + } + + /// Creates a new `DateTime` with the fields of a `PartialDateTime`. + /// + /// ```rust + /// use temporal_rs::{Calendar, PlainDateTime, partial::{PartialDateTime, PartialTime, PartialDate}}; + /// + /// let initial = PlainDateTime::try_new(2000, 12, 2, 0,0,0,0,0,0, Calendar::default()).unwrap(); + /// + /// let date = PartialDate { + /// month: Some(5), + /// ..Default::default() + /// }; + /// + /// let time = PartialTime { + /// hour: Some(4), + /// second: Some(30), + /// ..Default::default() + /// }; + /// + /// let partial = PartialDateTime { date, time }; + /// + /// let date = initial.with(partial, None).unwrap(); + /// + /// assert_eq!(date.year().unwrap(), 2000); + /// assert_eq!(date.month().unwrap(), 5); + /// assert_eq!(date.day().unwrap(), 2); + /// assert_eq!(date.calendar().identifier(), "iso8601"); + /// assert_eq!(date.hour(), 4); + /// assert_eq!(date.minute(), 0); + /// assert_eq!(date.second(), 30); + /// assert_eq!(date.millisecond(), 0); + /// + /// ``` + #[inline] + pub fn with( + &self, + partial_datetime: PartialDateTime, + overflow: Option, + ) -> TemporalResult { + self.0.with(partial_datetime, overflow).map(Into::into) + } + + /// Creates a new `DateTime` from the current `DateTime` and the provided `Time`. + pub fn with_time(&self, time: PlainTime) -> TemporalResult { + self.0.with_time(time.0).map(Into::into) + } + + /// Creates a new `DateTime` from the current `DateTime` and a provided `Calendar`. + pub fn with_calendar(&self, calendar: Calendar) -> TemporalResult { + self.0.with_calendar(calendar).map(Into::into) + } + + /// Returns this `Date`'s ISO year value. + #[inline] + #[must_use] + pub const fn iso_year(&self) -> i32 { + self.0.iso_year() + } + + /// Returns this `Date`'s ISO month value. + #[inline] + #[must_use] + pub const fn iso_month(&self) -> u8 { + self.0.iso_month() + } + + /// Returns this `Date`'s ISO day value. + #[inline] + #[must_use] + pub const fn iso_day(&self) -> u8 { + self.0.iso_day() + } + + /// Returns the hour value + #[inline] + #[must_use] + pub fn hour(&self) -> u8 { + self.0.hour() + } + + /// Returns the minute value + #[inline] + #[must_use] + pub fn minute(&self) -> u8 { + self.0.minute() + } + + /// Returns the second value + #[inline] + #[must_use] + pub fn second(&self) -> u8 { + self.0.second() + } + + /// Returns the `millisecond` value + #[inline] + #[must_use] + pub fn millisecond(&self) -> u16 { + self.0.millisecond() + } + + /// Returns the `microsecond` value + #[inline] + #[must_use] + pub fn microsecond(&self) -> u16 { + self.0.microsecond() + } + + /// Returns the `nanosecond` value + #[inline] + #[must_use] + pub fn nanosecond(&self) -> u16 { + self.0.nanosecond() + } + + /// Returns the Calendar value. + #[inline] + #[must_use] + pub fn calendar(&self) -> &Calendar { + self.0.calendar() + } +} + +// ==== Calendar-derived public API ==== + +impl PlainDateTime { + /// Returns the calendar year value. + pub fn year(&self) -> TemporalResult { + self.0.year() + } + + /// Returns the calendar month value. + pub fn month(&self) -> TemporalResult { + self.0.month() + } + + /// Returns the calendar month code value. + pub fn month_code(&self) -> TemporalResult> { + self.0.month_code() + } + + /// Returns the calendar day value. + pub fn day(&self) -> TemporalResult { + self.0.day() + } + + /// Returns the calendar day of week value. + pub fn day_of_week(&self) -> TemporalResult { + self.0.day_of_week() + } + + /// Returns the calendar day of year value. + pub fn day_of_year(&self) -> TemporalResult { + self.0.day_of_year() + } + + /// Returns the calendar week of year value. + pub fn week_of_year(&self) -> TemporalResult> { + self.0.week_of_year() + } + + /// Returns the calendar year of week value. + pub fn year_of_week(&self) -> TemporalResult> { + self.0.year_of_week() + } + + /// Returns the calendar days in week value. + pub fn days_in_week(&self) -> TemporalResult { + self.0.days_in_week() + } + + /// Returns the calendar days in month value. + pub fn days_in_month(&self) -> TemporalResult { + self.0.days_in_month() + } + + /// Returns the calendar days in year value. + pub fn days_in_year(&self) -> TemporalResult { + self.0.days_in_year() + } + + /// Returns the calendar months in year value. + pub fn months_in_year(&self) -> TemporalResult { + self.0.months_in_year() + } + + /// Returns returns whether the date in a leap year for the given calendar. + pub fn in_leap_year(&self) -> TemporalResult { + self.0.in_leap_year() + } + + pub fn era(&self) -> TemporalResult>> { + self.0.era() + } + + pub fn era_year(&self) -> TemporalResult> { + self.0.era_year() + } +} + +impl PlainDateTime { + #[inline] + /// Adds a `Duration` to the current `DateTime`. + pub fn add( + &self, + duration: &Duration, + overflow: Option, + ) -> TemporalResult { + self.0.add(&duration.0, overflow).map(Into::into) + } + + #[inline] + /// Subtracts a `Duration` to the current `DateTime`. + pub fn subtract( + &self, + duration: &Duration, + overflow: Option, + ) -> TemporalResult { + self.0.subtract(&duration.0, overflow).map(Into::into) + } + + #[inline] + /// Returns a `Duration` representing the period of time from this `DateTime` until the other `DateTime`. + pub fn until(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult { + self.0.until(&other.0, settings).map(Into::into) + } + + #[inline] + /// Returns a `Duration` representing the period of time from this `DateTime` since the other `DateTime`. + pub fn since(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult { + self.0.since(&other.0, settings).map(Into::into) + } + + /// Rounds the current datetime based on provided options. + pub fn round(&self, options: RoundingOptions) -> TemporalResult { + self.0.round(options).map(Into::into) + } + + pub fn to_ixdtf_string( + &self, + options: ToStringRoundingOptions, + display_calendar: DisplayCalendar, + ) -> TemporalResult { + self.0.to_ixdtf_string(options, display_calendar) + } +} diff --git a/src/builtins/std/duration.rs b/src/builtins/native/duration.rs similarity index 89% rename from src/builtins/std/duration.rs rename to src/builtins/native/duration.rs index e2320e369..77a6d9aa5 100644 --- a/src/builtins/std/duration.rs +++ b/src/builtins/native/duration.rs @@ -1,7 +1,10 @@ - -use crate::{builtins::core, options::{RelativeTo, RoundingOptions}, primitive::FiniteF64, Sign, TemporalError, TemporalResult}; use crate::builtins::core::PartialDuration; -use crate::builtins::core::options::RelativeTo as CoreRelativeTo; +use crate::{ + builtins::core, + options::{RelativeTo, RoundingOptions}, + primitive::FiniteF64, + Sign, TemporalError, TemporalResult, +}; use super::{timezone::TZ_PROVIDER, DateDuration, TimeDuration}; @@ -43,13 +46,25 @@ impl Duration { microseconds: FiniteF64, nanoseconds: FiniteF64, ) -> TemporalResult { - core::Duration::new(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds).map(Into::into) + core::Duration::new( + years, + months, + weeks, + days, + hours, + minutes, + seconds, + milliseconds, + microseconds, + nanoseconds, + ) + .map(Into::into) } /// Creates a `Duration` from a provided `PartialDuration`. pub fn from_partial_duration(partial: PartialDuration) -> TemporalResult { core::Duration::from_partial_duration(partial).map(Into::into) - } + } } // ==== Public `Duration` Getters/Setters ==== @@ -199,8 +214,8 @@ impl Duration { let provider = TZ_PROVIDER .lock() .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.0.round_with_provider(options, relative_to.map(Into::into), &*provider).map(Into::into) + self.0 + .round_with_provider(options, relative_to.map(Into::into), &*provider) + .map(Into::into) } } - - diff --git a/src/builtins/std/duration/tests.rs b/src/builtins/native/duration/tests.rs similarity index 98% rename from src/builtins/std/duration/tests.rs rename to src/builtins/native/duration/tests.rs index f9a0b10d2..a85303ac8 100644 --- a/src/builtins/std/duration/tests.rs +++ b/src/builtins/native/duration/tests.rs @@ -1,9 +1,12 @@ -use alloc::vec::Vec; -use core::str::FromStr; -use crate::builtins::std::PlainDate; +use crate::builtins::native::PlainDate; use crate::{ - builtins::{core::calendar::Calendar, std::zoneddatetime::ZonedDateTime}, options::{RelativeTo, RoundingIncrement, RoundingOptions, TemporalRoundingMode, TemporalUnit}, partial::PartialDuration, primitive::FiniteF64, DateDuration, TimeDuration, TimeZone + builtins::{core::calendar::Calendar, native::zoneddatetime::ZonedDateTime}, + options::{RelativeTo, RoundingIncrement, RoundingOptions, TemporalRoundingMode, TemporalUnit}, + primitive::FiniteF64, + DateDuration, TimeDuration, TimeZone, }; +use alloc::vec::Vec; +use core::str::FromStr; use super::Duration; diff --git a/src/builtins/std/instant.rs b/src/builtins/native/instant.rs similarity index 61% rename from src/builtins/std/instant.rs rename to src/builtins/native/instant.rs index 0cd268d63..3daea0de2 100644 --- a/src/builtins/std/instant.rs +++ b/src/builtins/native/instant.rs @@ -1,25 +1,41 @@ +use crate::{ + builtins::core as temporal_core, + options::{DifferenceSettings, RoundingOptions, ToStringRoundingOptions}, + time::EpochNanoseconds, + TemporalError, TemporalResult, TimeZone, +}; +use alloc::string::String; -use crate::{builtins::core, options::{DifferenceSettings, RoundingOptions}, time::EpochNanoseconds, TemporalError, TemporalResult}; +use super::{duration::Duration, timezone::TZ_PROVIDER, TimeDuration}; -use super::{duration::Duration, TimeDuration}; +#[derive(Debug, Clone)] +pub struct Instant(temporal_core::Instant); -pub struct Instant(core::Instant); - -impl From for Instant { - fn from(value: core::Instant) -> Self { +impl From for Instant { + fn from(value: temporal_core::Instant) -> Self { Self(value) } } +impl core::fmt::Display for Instant { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str( + &self + .as_ixdtf_string(None, ToStringRoundingOptions::default()) + .expect("A valid instant string."), + ) + } +} + impl Instant { /// Create a new validated `Instant`. #[inline] pub fn try_new(nanoseconds: i128) -> TemporalResult { - Ok(core::Instant::from(EpochNanoseconds::try_from(nanoseconds)?).into()) + Ok(temporal_core::Instant::from(EpochNanoseconds::try_from(nanoseconds)?).into()) } pub fn from_epoch_milliseconds(epoch_milliseconds: i128) -> TemporalResult { - core::Instant::from_epoch_milliseconds(epoch_milliseconds).map(Into::into) + temporal_core::Instant::from_epoch_milliseconds(epoch_milliseconds).map(Into::into) } /// Adds a `Duration` to the current `Instant`, returning an error if the `Duration` @@ -76,4 +92,19 @@ impl Instant { pub fn epoch_nanoseconds(&self) -> i128 { self.0.epoch_nanoseconds() } + + /// Returns the RFC9557 (IXDTF) string for this `Instant` with the + /// provided options + pub fn as_ixdtf_string( + &self, + timezone: Option<&TimeZone>, + options: ToStringRoundingOptions, + ) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + + self.0 + .as_ixdtf_string_with_provider(timezone, options, &*provider) + } } diff --git a/src/builtins/native/mod.rs b/src/builtins/native/mod.rs new file mode 100644 index 000000000..5b2d8f1c0 --- /dev/null +++ b/src/builtins/native/mod.rs @@ -0,0 +1,23 @@ +pub(crate) mod timezone; + +mod date; +mod datetime; +mod duration; +mod instant; +mod now; +pub(crate) mod options; +mod time; +mod zoneddatetime; + +pub use date::PlainDate; +pub use datetime::PlainDateTime; +pub use duration::Duration; +pub use instant::Instant; +pub use now::Now; +pub use time::PlainTime; +pub use zoneddatetime::ZonedDateTime; + +pub use crate::builtins::core::{ + calendar, DateDuration, PartialDate, PartialDateTime, PartialTime, PartialZonedDateTime, + PlainMonthDay, PlainYearMonth, TimeDuration, +}; diff --git a/src/builtins/std/now.rs b/src/builtins/native/now.rs similarity index 83% rename from src/builtins/std/now.rs rename to src/builtins/native/now.rs index 824259a14..cf76c0128 100644 --- a/src/builtins/std/now.rs +++ b/src/builtins/native/now.rs @@ -1,7 +1,6 @@ - +use crate::builtins::native::{PlainDate, PlainDateTime, PlainTime, ZonedDateTime}; +use crate::{builtins::core, Instant, TemporalError, TemporalResult, TimeZone}; use alloc::string::String; -use crate::{builtins::core, TemporalError, TemporalResult, TimeZone, Instant}; -use crate::builtins::std::{ZonedDateTime, PlainDateTime, PlainDate, PlainTime}; use super::timezone::TZ_PROVIDER; @@ -10,12 +9,12 @@ pub struct Now; impl Now { /// Returns the current instant pub fn instant() -> TemporalResult { - Now::instant() + core::Now::instant().map(Into::into) } /// Returns the current time zone. pub fn time_zone_id() -> TemporalResult { - Now::time_zone_id() + core::Now::time_zone_id() } /// Returns the current system time as a `ZonedDateTime` with an ISO8601 calendar. @@ -23,7 +22,7 @@ impl Now { /// The time zone will be set to either the `TimeZone` if a value is provided, or /// according to the system timezone if no value is provided. pub fn zoneddatetime_iso(timezone: Option) -> TemporalResult { - Now::zoneddatetime_iso(timezone).map(Into::into) + core::Now::zoneddatetime_iso(timezone).map(Into::into) } pub fn plain_datetime_iso(timezone: Option) -> TemporalResult { @@ -47,6 +46,3 @@ impl Now { core::Now::plain_time_iso_with_provider(timezone, &*provider).map(Into::into) } } - - - diff --git a/src/builtins/std/options.rs b/src/builtins/native/options.rs similarity index 96% rename from src/builtins/std/options.rs rename to src/builtins/native/options.rs index 5eb76b417..699fc34fc 100644 --- a/src/builtins/std/options.rs +++ b/src/builtins/native/options.rs @@ -1,6 +1,6 @@ +use crate::builtins::native::timezone::TZ_PROVIDER; use crate::TemporalError; use crate::{builtins::core, TemporalResult}; -use crate::builtins::std::timezone::TZ_PROVIDER; use super::{date::PlainDate, ZonedDateTime}; @@ -50,4 +50,3 @@ impl RelativeTo { core::options::RelativeTo::try_from_str_with_provider(source, &*provider).map(Into::into) } } - diff --git a/src/builtins/native/time.rs b/src/builtins/native/time.rs new file mode 100644 index 000000000..9ad76d271 --- /dev/null +++ b/src/builtins/native/time.rs @@ -0,0 +1,217 @@ +use crate::{ + builtins::core, + options::{ + ArithmeticOverflow, DifferenceSettings, TemporalRoundingMode, TemporalUnit, + ToStringRoundingOptions, + }, + TemporalResult, +}; +use alloc::string::String; + +use super::{Duration, PartialTime, TimeDuration}; +pub struct PlainTime(pub(crate) core::PlainTime); + +impl From for PlainTime { + fn from(value: core::PlainTime) -> Self { + Self(value) + } +} + +impl PlainTime { + /// Creates a new `PlainTime`, constraining any field into a valid range. + /// + /// ```rust + /// use temporal_rs::PlainTime; + /// + /// let time = PlainTime::new(23, 59, 59, 999, 999, 999).unwrap(); + /// + /// let constrained_time = PlainTime::new(24, 59, 59, 999, 999, 999).unwrap(); + /// assert_eq!(time, constrained_time); + /// ``` + pub fn new( + hour: u8, + minute: u8, + second: u8, + millisecond: u16, + microsecond: u16, + nanosecond: u16, + ) -> TemporalResult { + core::PlainTime::new(hour, minute, second, millisecond, microsecond, nanosecond) + .map(Into::into) + } + + /// Creates a new `PlainTime`, rejecting any field that is not in a valid range. + /// + /// ```rust + /// use temporal_rs::PlainTime; + /// + /// let time = PlainTime::try_new(23, 59, 59, 999, 999, 999).unwrap(); + /// + /// let invalid_time = PlainTime::try_new(24, 59, 59, 999, 999, 999); + /// assert!(invalid_time.is_err()); + /// ``` + pub fn try_new( + hour: u8, + minute: u8, + second: u8, + millisecond: u16, + microsecond: u16, + nanosecond: u16, + ) -> TemporalResult { + core::PlainTime::try_new(hour, minute, second, millisecond, microsecond, nanosecond) + .map(Into::into) + } + + /// Creates a new `PlainTime` from a `PartialTime`. + /// + /// ```rust + /// use temporal_rs::{partial::PartialTime, PlainTime}; + /// + /// let partial_time = PartialTime { + /// hour: Some(22), + /// ..Default::default() + /// }; + /// + /// let time = PlainTime::from_partial(partial_time, None).unwrap(); + /// + /// assert_eq!(time.hour(), 22); + /// assert_eq!(time.minute(), 0); + /// assert_eq!(time.second(), 0); + /// assert_eq!(time.millisecond(), 0); + /// assert_eq!(time.microsecond(), 0); + /// assert_eq!(time.nanosecond(), 0); + /// + /// ``` + pub fn from_partial( + partial: PartialTime, + overflow: Option, + ) -> TemporalResult { + core::PlainTime::from_partial(partial, overflow).map(Into::into) + } + + /// Creates a new `PlainTime` using the current `PlainTime` fields as a fallback. + /// + /// ```rust + /// use temporal_rs::{partial::PartialTime, PlainTime}; + /// + /// let partial_time = PartialTime { + /// hour: Some(22), + /// ..Default::default() + /// }; + /// + /// let initial = PlainTime::try_new(15, 30, 12, 123, 456, 789).unwrap(); + /// + /// let time = initial.with(partial_time, None).unwrap(); + /// + /// assert_eq!(time.hour(), 22); + /// assert_eq!(time.minute(), 30); + /// assert_eq!(time.second(), 12); + /// assert_eq!(time.millisecond(), 123); + /// assert_eq!(time.microsecond(), 456); + /// assert_eq!(time.nanosecond(), 789); + /// + /// ``` + pub fn with( + &self, + partial: PartialTime, + overflow: Option, + ) -> TemporalResult { + self.0.with(partial, overflow).map(Into::into) + } + + /// Returns the internal `hour` field. + #[inline] + #[must_use] + pub const fn hour(&self) -> u8 { + self.0.hour() + } + + /// Returns the internal `minute` field. + #[inline] + #[must_use] + pub const fn minute(&self) -> u8 { + self.0.minute() + } + + /// Returns the internal `second` field. + #[inline] + #[must_use] + pub const fn second(&self) -> u8 { + self.0.second() + } + + /// Returns the internal `millisecond` field. + #[inline] + #[must_use] + pub const fn millisecond(&self) -> u16 { + self.0.millisecond() + } + + /// Returns the internal `microsecond` field. + #[inline] + #[must_use] + pub const fn microsecond(&self) -> u16 { + self.0.microsecond() + } + + /// Returns the internal `nanosecond` field. + #[inline] + #[must_use] + pub const fn nanosecond(&self) -> u16 { + self.0.nanosecond() + } + + /// Add a `Duration` to the current `Time`. + pub fn add(&self, duration: &Duration) -> TemporalResult { + self.0.add(&duration.0).map(Into::into) + } + + /// Adds a `TimeDuration` to the current `Time`. + #[inline] + pub fn add_time_duration(&self, duration: &TimeDuration) -> TemporalResult { + self.0.add_time_duration(duration).map(Into::into) + } + + /// Subtract a `Duration` to the current `Time`. + pub fn subtract(&self, duration: &Duration) -> TemporalResult { + self.0.subtract(&duration.0).map(Into::into) + } + + /// Adds a `TimeDuration` to the current `Time`. + #[inline] + pub fn subtract_time_duration(&self, duration: &TimeDuration) -> TemporalResult { + self.0.subtract_time_duration(duration).map(Into::into) + } + + #[inline] + /// Returns the `Duration` until the provided `Time` from the current `Time`. + /// + /// NOTE: `until` assumes the provided other time will occur in the future relative to the current. + pub fn until(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult { + self.0.until(&other.0, settings).map(Into::into) + } + + #[inline] + /// Returns the `Duration` since the provided `Time` from the current `Time`. + /// + /// NOTE: `since` assumes the provided other time is in the past relative to the current. + pub fn since(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult { + self.0.since(&other.0, settings).map(Into::into) + } + + /// Rounds the current `Time` according to provided options. + pub fn round( + &self, + smallest_unit: TemporalUnit, + rounding_increment: Option, + rounding_mode: Option, + ) -> TemporalResult { + self.0 + .round(smallest_unit, rounding_increment, rounding_mode) + .map(Into::into) + } + + pub fn to_ixdtf_string(&self, options: ToStringRoundingOptions) -> TemporalResult { + self.0.as_ixdtf_string(options) + } +} diff --git a/src/builtins/std/timezone.rs b/src/builtins/native/timezone.rs similarity index 100% rename from src/builtins/std/timezone.rs rename to src/builtins/native/timezone.rs diff --git a/src/builtins/std/zoneddatetime.rs b/src/builtins/native/zoneddatetime.rs similarity index 82% rename from src/builtins/std/zoneddatetime.rs rename to src/builtins/native/zoneddatetime.rs index f55c58653..92be813f0 100644 --- a/src/builtins/std/zoneddatetime.rs +++ b/src/builtins/native/zoneddatetime.rs @@ -1,18 +1,36 @@ -use tinystr::TinyAsciiStr; - use super::timezone::TZ_PROVIDER; use crate::{ - builtins::core, - options::{ArithmeticOverflow, Disambiguation, OffsetDisambiguation}, + builtins::core as temporal_core, + options::{ + ArithmeticOverflow, Disambiguation, DisplayCalendar, DisplayOffset, DisplayTimeZone, + OffsetDisambiguation, ToStringRoundingOptions, + }, Calendar, Duration, PlainDate, PlainDateTime, PlainTime, TemporalError, TemporalResult, TimeZone, }; +use alloc::string::String; +use tinystr::TinyAsciiStr; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct ZonedDateTime(pub(crate) core::ZonedDateTime); +pub struct ZonedDateTime(pub(crate) temporal_core::ZonedDateTime); + +impl core::fmt::Display for ZonedDateTime { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str( + &self + .to_ixdtf_string( + DisplayOffset::Auto, + DisplayTimeZone::Auto, + DisplayCalendar::Auto, + ToStringRoundingOptions::default(), + ) + .expect("A valid ZonedDateTime string with default options."), + ) + } +} -impl From for ZonedDateTime { - fn from(value: core::ZonedDateTime) -> Self { +impl From for ZonedDateTime { + fn from(value: temporal_core::ZonedDateTime) -> Self { Self(value) } } @@ -20,7 +38,7 @@ impl From for ZonedDateTime { impl ZonedDateTime { #[inline] pub fn try_new(nanos: i128, calendar: Calendar, tz: TimeZone) -> TemporalResult { - core::ZonedDateTime::try_new(nanos, calendar, tz).map(Into::into) + temporal_core::ZonedDateTime::try_new(nanos, calendar, tz).map(Into::into) } pub fn calendar(&self) -> &Calendar { @@ -109,7 +127,6 @@ impl ZonedDateTime { // ==== Experimental TZ_PROVIDER calendar method implementations ==== -#[cfg(feature = "experimental")] impl ZonedDateTime { pub fn era(&self) -> TemporalResult>> { let provider = TZ_PROVIDER @@ -215,7 +232,7 @@ impl ZonedDateTime { .lock() .map_err(|_| TemporalError::general("Unable to acquire lock"))?; self.0 - .with_plain_time_and_provider(time.into(), &*provider) + .with_plain_time_and_provider(time.0, &*provider) .map(Into::into) } @@ -227,7 +244,9 @@ impl ZonedDateTime { let provider = TZ_PROVIDER .lock() .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.0.add_with_provider(&duration.0, overflow, &*provider).map(Into::into) + self.0 + .add_with_provider(&duration.0, overflow, &*provider) + .map(Into::into) } pub fn subtract( @@ -238,7 +257,9 @@ impl ZonedDateTime { let provider = TZ_PROVIDER .lock() .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.0.subtract_with_provider(&duration.0, overflow, &*provider).map(Into::into) + self.0 + .subtract_with_provider(&duration.0, overflow, &*provider) + .map(Into::into) } pub fn start_of_day(&self) -> TemporalResult { @@ -250,25 +271,54 @@ impl ZonedDateTime { .map(Into::into) } + /// Creates a new [`PlainDate`] from this `ZonedDateTime`. pub fn to_plain_date(&self) -> TemporalResult { let provider = TZ_PROVIDER .lock() .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.0.to_plain_date_with_provider(&*provider).map(Into::into) + self.0 + .to_plain_date_with_provider(&*provider) + .map(Into::into) } + /// Creates a new [`PlainTime`] from this `ZonedDateTime`. pub fn to_plain_time(&self) -> TemporalResult { let provider = TZ_PROVIDER .lock() .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.0.to_plain_time_with_provider(&*provider).map(Into::into) + self.0 + .to_plain_time_with_provider(&*provider) + .map(Into::into) } + /// Creates a new [`PlainDateTime`] from this `ZonedDateTime`. pub fn to_plain_datetime(&self) -> TemporalResult { let provider = TZ_PROVIDER .lock() .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - self.0.to_plain_datetime_with_provider(&*provider).map(Into::into) + self.0 + .to_plain_datetime_with_provider(&*provider) + .map(Into::into) + } + + /// Returns a RFC9557 (IXDTF) string with the provided options. + pub fn to_ixdtf_string( + &self, + display_offset: DisplayOffset, + display_timezone: DisplayTimeZone, + display_calendar: DisplayCalendar, + options: ToStringRoundingOptions, + ) -> TemporalResult { + let provider = TZ_PROVIDER + .lock() + .map_err(|_| TemporalError::general("Unable to acquire lock"))?; + self.0.to_ixdtf_string_with_provider( + display_offset, + display_timezone, + display_calendar, + options, + &*provider, + ) } pub fn from_str( @@ -279,7 +329,7 @@ impl ZonedDateTime { let provider = TZ_PROVIDER .lock() .map_err(|_| TemporalError::general("Unable to acquire lock"))?; - core::ZonedDateTime::from_str_with_provider( + temporal_core::ZonedDateTime::from_str_with_provider( source, disambiguation, offset_option, @@ -293,8 +343,8 @@ mod tests { #[cfg(not(target_os = "windows"))] #[test] fn static_tzdb_zdt_test() { - use crate::{Calendar, TimeZone}; use super::ZonedDateTime; + use crate::{Calendar, TimeZone}; use core::str::FromStr; let nov_30_2023_utc = 1_701_308_952_000_000_000i128; @@ -345,8 +395,8 @@ mod tests { #[cfg(not(target_os = "windows"))] #[test] fn basic_zdt_add() { - use crate::{Calendar, Duration, TimeZone}; use super::ZonedDateTime; + use crate::{Calendar, Duration, TimeZone}; let zdt = ZonedDateTime::try_new(-560174321098766, Calendar::default(), TimeZone::default()) @@ -372,6 +422,4 @@ mod tests { let result = zdt.add(&d, None).unwrap(); assert_eq!(result, expected); } - - } diff --git a/src/builtins/std/mod.rs b/src/builtins/std/mod.rs deleted file mode 100644 index accfe5ff1..000000000 --- a/src/builtins/std/mod.rs +++ /dev/null @@ -1,55 +0,0 @@ -pub(crate) mod timezone; - -mod date; -mod duration; -mod instant; -mod now; -pub(crate) mod options; -mod zoneddatetime; - - -// TODO: Remove the aliasing - -pub use date::PlainDate; -pub use datetime::PlainDateTime; -pub use zoneddatetime::ZonedDateTime; -pub use duration::Duration; -pub use instant::Instant; -pub use now::Now; -pub use time::PlainTime; - - -pub use crate::builtins::core::{ - DateDuration, PartialDate, PartialDateTime, PartialTime, PartialZonedDateTime, - PlainMonthDay, PlainYearMonth, TimeDuration, - calendar, -}; - -mod time { - use crate::builtins::core; - pub struct PlainTime(pub(crate) core::PlainTime); - - impl From for PlainTime { - fn from(value: core::PlainTime) -> Self { - Self(value) - } - } - - impl From for core::PlainTime { - fn from(value: PlainTime) -> Self { - value.0 - } - } -} - -mod datetime { - use crate::builtins::core; - pub struct PlainDateTime(pub(crate) core::PlainDateTime); - - impl From for PlainDateTime { - fn from(value: core::PlainDateTime) -> Self { - Self(value) - } - } -} - diff --git a/src/epoch_nanoseconds.rs b/src/epoch_nanoseconds.rs index 62bd89ea2..063343ab0 100644 --- a/src/epoch_nanoseconds.rs +++ b/src/epoch_nanoseconds.rs @@ -1,4 +1,3 @@ - use num_traits::FromPrimitive; use crate::{TemporalError, NS_MAX_INSTANT}; @@ -45,4 +44,3 @@ impl TryFrom for EpochNanoseconds { pub(crate) fn is_valid_epoch_nanos(nanos: &i128) -> bool { (crate::NS_MIN_INSTANT..=crate::NS_MAX_INSTANT).contains(nanos) } - diff --git a/src/lib.rs b/src/lib.rs index 2bfb0a451..bcfb50e2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,10 +51,10 @@ pub mod iso; pub mod options; pub mod parsers; pub mod primitive; +pub mod provider; mod epoch_nanoseconds; - pub(crate) mod builtins; #[cfg(feature = "now")] @@ -97,10 +97,8 @@ pub mod time { } pub use crate::builtins::{ - calendar::Calendar, - core::timezone::{TimeZone, TimeZoneProvider}, - DateDuration, Duration, Instant, PlainDate, PlainDateTime, PlainMonthDay, PlainTime, - PlainYearMonth, TimeDuration, ZonedDateTime, + calendar::Calendar, core::timezone::TimeZone, DateDuration, Duration, Instant, PlainDate, + PlainDateTime, PlainMonthDay, PlainTime, PlainYearMonth, TimeDuration, ZonedDateTime, }; #[cfg(feature = "std")] diff --git a/src/options/relative_to.rs b/src/options/relative_to.rs index 8a8522985..a2feb9d6b 100644 --- a/src/options/relative_to.rs +++ b/src/options/relative_to.rs @@ -1,8 +1,7 @@ //! RelativeTo rounding option -#[cfg(not(feature = "experimental"))] +#[cfg(not(feature = "full"))] pub use crate::builtins::core::options::RelativeTo; - -#[cfg(feature = "experimental")] -pub use crate::builtins::std::options::RelativeTo; +#[cfg(feature = "full")] +pub use crate::builtins::native::options::RelativeTo; diff --git a/src/provider.rs b/src/provider.rs new file mode 100644 index 000000000..f1377a779 --- /dev/null +++ b/src/provider.rs @@ -0,0 +1,53 @@ +//! The `TimeZoneProvider` trait. + +use crate::{iso::IsoDateTime, time::EpochNanoseconds, TemporalResult}; +use alloc::vec::Vec; + +/// `TimeZoneOffset` represents the number of seconds to be added to UT in order to determine local time. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct TimeZoneOffset { + /// The transition time epoch at which the offset needs to be applied. + pub transition_epoch: Option, + /// The time zone offset in seconds. + pub offset: i64, +} + +// NOTE: It may be a good idea to eventually move this into it's +// own individual crate rather than having it tied directly into `temporal_rs` +/// The `TimeZoneProvider` trait provides methods required for a provider +/// to implement in order to source time zone data from that provider. +pub trait TimeZoneProvider { + fn check_identifier(&self, identifier: &str) -> bool; + + fn get_named_tz_epoch_nanoseconds( + &self, + identifier: &str, + local_datetime: IsoDateTime, + ) -> TemporalResult>; + + fn get_named_tz_offset_nanoseconds( + &self, + identifier: &str, + utc_epoch: i128, + ) -> TemporalResult; +} + +pub struct NeverProvider; + +impl TimeZoneProvider for NeverProvider { + fn check_identifier(&self, _: &str) -> bool { + unimplemented!() + } + + fn get_named_tz_epoch_nanoseconds( + &self, + _: &str, + _: IsoDateTime, + ) -> TemporalResult> { + unimplemented!() + } + + fn get_named_tz_offset_nanoseconds(&self, _: &str, _: i128) -> TemporalResult { + unimplemented!() + } +} diff --git a/src/tzdb.rs b/src/tzdb.rs index d3d3ecf4a..99bdf402f 100644 --- a/src/tzdb.rs +++ b/src/tzdb.rs @@ -47,10 +47,9 @@ use tzif::{ }, }; -use crate::builtins::core::timezone::TimeZoneOffset; use crate::{ - builtins::core::timezone::TimeZoneProvider, iso::IsoDateTime, + provider::{TimeZoneOffset, TimeZoneProvider}, time::EpochNanoseconds, utils, TemporalError, TemporalResult, }; From 11196eb3d043fc0971686948b16d8ae288aea4a6 Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sat, 18 Jan 2025 00:24:07 -0600 Subject: [PATCH 3/8] Post merge cleanup and add string methods to native Duration --- src/builtins/core/duration.rs | 8 ++++---- src/builtins/core/duration/tests.rs | 31 ++++++++++++++++------------- src/builtins/core/month_day.rs | 2 +- src/builtins/core/year_month.rs | 3 +-- src/builtins/native/duration.rs | 28 ++++++++++++++++++++------ 5 files changed, 45 insertions(+), 27 deletions(-) diff --git a/src/builtins/core/duration.rs b/src/builtins/core/duration.rs index 74c191b9a..942b6936e 100644 --- a/src/builtins/core/duration.rs +++ b/src/builtins/core/duration.rs @@ -4,8 +4,8 @@ use crate::{ builtins::core::{options::RelativeTo, PlainDateTime, PlainTime, ZonedDateTime}, iso::{IsoDateTime, IsoTime}, options::{ - ArithmeticOverflow, ResolvedRoundingOptions, RoundingIncrement, - RoundingOptions, TemporalUnit, ToStringRoundingOptions, + ArithmeticOverflow, ResolvedRoundingOptions, RoundingIncrement, RoundingOptions, + TemporalUnit, ToStringRoundingOptions, }, parsers::{FormattableDuration, Precision}, primitive::FiniteF64, @@ -87,7 +87,7 @@ impl core::fmt::Display for Duration { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str( &self - .to_temporal_string(ToStringRoundingOptions::default()) + .as_temporal_string(ToStringRoundingOptions::default()) .expect("Duration must return a valid string with default options."), ) } @@ -635,7 +635,7 @@ impl Duration { } } - pub fn to_temporal_string(&self, options: ToStringRoundingOptions) -> TemporalResult { + pub fn as_temporal_string(&self, options: ToStringRoundingOptions) -> TemporalResult { if options.smallest_unit == Some(TemporalUnit::Hour) || options.smallest_unit == Some(TemporalUnit::Minute) { diff --git a/src/builtins/core/duration/tests.rs b/src/builtins/core/duration/tests.rs index 6ef0c68b0..a3c5e6922 100644 --- a/src/builtins/core/duration/tests.rs +++ b/src/builtins/core/duration/tests.rs @@ -1,4 +1,7 @@ -use crate::{options::ToStringRoundingOptions, parsers::Precision, partial::PartialDuration, primitive::FiniteF64}; +use crate::{ + options::ToStringRoundingOptions, parsers::Precision, partial::PartialDuration, + primitive::FiniteF64, +}; use super::Duration; @@ -25,7 +28,7 @@ fn default_duration_string() { smallest_unit: None, rounding_mode: None, }; - let result = duration.to_temporal_string(options).unwrap(); + let result = duration.as_temporal_string(options).unwrap(); assert_eq!(&result, "PT0S"); let options = ToStringRoundingOptions { @@ -33,7 +36,7 @@ fn default_duration_string() { smallest_unit: None, rounding_mode: None, }; - let result = duration.to_temporal_string(options).unwrap(); + let result = duration.as_temporal_string(options).unwrap(); assert_eq!(&result, "PT0S"); let options = ToStringRoundingOptions { @@ -41,7 +44,7 @@ fn default_duration_string() { smallest_unit: None, rounding_mode: None, }; - let result = duration.to_temporal_string(options).unwrap(); + let result = duration.as_temporal_string(options).unwrap(); assert_eq!(&result, "PT0.0S"); let options = ToStringRoundingOptions { @@ -49,7 +52,7 @@ fn default_duration_string() { smallest_unit: None, rounding_mode: None, }; - let result = duration.to_temporal_string(options).unwrap(); + let result = duration.as_temporal_string(options).unwrap(); assert_eq!(&result, "PT0.000S"); } @@ -69,7 +72,7 @@ fn duration_to_string_auto_precision() { ) .unwrap(); let result = duration - .to_temporal_string(ToStringRoundingOptions::default()) + .as_temporal_string(ToStringRoundingOptions::default()) .unwrap(); assert_eq!(&result, "P1Y2M3W4DT5H6M7S"); @@ -87,7 +90,7 @@ fn duration_to_string_auto_precision() { ) .unwrap(); let result = duration - .to_temporal_string(ToStringRoundingOptions::default()) + .as_temporal_string(ToStringRoundingOptions::default()) .unwrap(); assert_eq!(&result, "P1Y2M3W4DT5H6M7.98765S"); } @@ -100,7 +103,7 @@ fn empty_date_duration() { }) .unwrap(); let result = duration - .to_temporal_string(ToStringRoundingOptions::default()) + .as_temporal_string(ToStringRoundingOptions::default()) .unwrap(); assert_eq!(&result, "PT1H"); } @@ -121,7 +124,7 @@ fn negative_fields_to_string() { }) .unwrap(); let result = duration - .to_temporal_string(ToStringRoundingOptions::default()) + .as_temporal_string(ToStringRoundingOptions::default()) .unwrap(); assert_eq!(&result, "-P1Y1M1W1DT1H1M1.001001001S"); @@ -131,7 +134,7 @@ fn negative_fields_to_string() { }) .unwrap(); let result = duration - .to_temporal_string(ToStringRoundingOptions::default()) + .as_temporal_string(ToStringRoundingOptions::default()) .unwrap(); assert_eq!(&result, "-PT0.25S"); @@ -141,7 +144,7 @@ fn negative_fields_to_string() { }) .unwrap(); let result = duration - .to_temporal_string(ToStringRoundingOptions::default()) + .as_temporal_string(ToStringRoundingOptions::default()) .unwrap(); assert_eq!(&result, "-PT3.5S"); @@ -151,7 +154,7 @@ fn negative_fields_to_string() { }) .unwrap(); let result = duration - .to_temporal_string(ToStringRoundingOptions::default()) + .as_temporal_string(ToStringRoundingOptions::default()) .unwrap(); assert_eq!(&result, "-PT3.5S"); @@ -162,7 +165,7 @@ fn negative_fields_to_string() { }) .unwrap(); let result = duration - .to_temporal_string(ToStringRoundingOptions::default()) + .as_temporal_string(ToStringRoundingOptions::default()) .unwrap(); assert_eq!(&result, "-P1W1D"); @@ -178,7 +181,7 @@ fn preserve_precision_loss() { }) .unwrap(); let result = duration - .to_temporal_string(ToStringRoundingOptions::default()) + .as_temporal_string(ToStringRoundingOptions::default()) .unwrap(); assert_eq!(&result, "PT9016206453995.731991S"); diff --git a/src/builtins/core/month_day.rs b/src/builtins/core/month_day.rs index 99ead1336..3c18ab3c2 100644 --- a/src/builtins/core/month_day.rs +++ b/src/builtins/core/month_day.rs @@ -9,7 +9,7 @@ use crate::{ iso::IsoDate, options::{ArithmeticOverflow, DisplayCalendar}, parsers::{FormattableCalendar, FormattableDate, FormattableMonthDay}, - TemporalError, TemporalResult, TemporalUnwrap, Calendar + Calendar, TemporalError, TemporalResult, TemporalUnwrap, }; /// The native Rust implementation of `Temporal.PlainMonthDay` diff --git a/src/builtins/core/year_month.rs b/src/builtins/core/year_month.rs index f29b5d08b..6133ed541 100644 --- a/src/builtins/core/year_month.rs +++ b/src/builtins/core/year_month.rs @@ -10,8 +10,7 @@ use crate::{ options::{ArithmeticOverflow, DisplayCalendar}, parsers::{FormattableCalendar, FormattableDate, FormattableYearMonth}, utils::pad_iso_year, - TemporalError, TemporalResult, TemporalUnwrap, - Calendar, + Calendar, TemporalError, TemporalResult, TemporalUnwrap, }; use super::{Duration, PartialDate}; diff --git a/src/builtins/native/duration.rs b/src/builtins/native/duration.rs index 77a6d9aa5..44501eca2 100644 --- a/src/builtins/native/duration.rs +++ b/src/builtins/native/duration.rs @@ -1,20 +1,32 @@ use crate::builtins::core::PartialDuration; +use crate::options::ToStringRoundingOptions; use crate::{ - builtins::core, + builtins::core as temporal_core, options::{RelativeTo, RoundingOptions}, primitive::FiniteF64, Sign, TemporalError, TemporalResult, }; +use alloc::string::String; use super::{timezone::TZ_PROVIDER, DateDuration, TimeDuration}; #[cfg(test)] mod tests; -pub struct Duration(pub(crate) core::Duration); +pub struct Duration(pub(crate) temporal_core::Duration); -impl From for Duration { - fn from(value: core::Duration) -> Self { +impl core::fmt::Display for Duration { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str( + &self + .as_temporal_string(ToStringRoundingOptions::default()) + .expect("Default options on a valid Duration should return a string."), + ) + } +} + +impl From for Duration { + fn from(value: temporal_core::Duration) -> Self { Self(value) } } @@ -46,7 +58,7 @@ impl Duration { microseconds: FiniteF64, nanoseconds: FiniteF64, ) -> TemporalResult { - core::Duration::new( + temporal_core::Duration::new( years, months, weeks, @@ -63,7 +75,7 @@ impl Duration { /// Creates a `Duration` from a provided `PartialDuration`. pub fn from_partial_duration(partial: PartialDuration) -> TemporalResult { - core::Duration::from_partial_duration(partial).map(Into::into) + temporal_core::Duration::from_partial_duration(partial).map(Into::into) } } @@ -218,4 +230,8 @@ impl Duration { .round_with_provider(options, relative_to.map(Into::into), &*provider) .map(Into::into) } + + pub fn as_temporal_string(&self, options: ToStringRoundingOptions) -> TemporalResult { + self.0.as_temporal_string(options) + } } From ca74ec26e77518a6f7990bb7f187cdbf25869695 Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sat, 18 Jan 2025 00:49:12 -0600 Subject: [PATCH 4/8] Fix lints + display impls --- src/builtins/mod.rs | 2 ++ src/builtins/native/date.rs | 24 +++++++++++++++--------- src/builtins/native/datetime.rs | 28 ++++++++++++++++++---------- src/builtins/native/duration.rs | 1 + src/builtins/native/instant.rs | 2 +- src/builtins/native/time.rs | 30 +++++++++++++++++++++--------- 6 files changed, 58 insertions(+), 29 deletions(-) diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index d74792955..33abc08db 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -1,4 +1,6 @@ pub(crate) mod core; + +#[cfg(feature = "full")] pub(crate) mod native; #[cfg(not(feature = "full"))] diff --git a/src/builtins/native/date.rs b/src/builtins/native/date.rs index b6d86d90b..d54b5cc80 100644 --- a/src/builtins/native/date.rs +++ b/src/builtins/native/date.rs @@ -1,6 +1,6 @@ use crate::builtins::native::PlainTime; use crate::{ - builtins::core, + builtins::core as temporal_core, options::{ArithmeticOverflow, DifferenceSettings, DisplayCalendar}, Calendar, TemporalResult, }; @@ -9,11 +9,17 @@ use alloc::string::String; use super::{duration::Duration, PartialDate, PlainDateTime, PlainMonthDay, PlainYearMonth}; use tinystr::TinyAsciiStr; -#[derive(Debug, Clone)] -pub struct PlainDate(pub(crate) core::PlainDate); +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct PlainDate(pub(crate) temporal_core::PlainDate); -impl From for PlainDate { - fn from(value: core::PlainDate) -> Self { +impl core::fmt::Display for PlainDate { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.0.fmt(f) + } +} + +impl From for PlainDate { + fn from(value: temporal_core::PlainDate) -> Self { Self(value) } } @@ -21,12 +27,12 @@ impl From for PlainDate { impl PlainDate { /// Creates a new `PlainDate` automatically constraining any values that may be invalid. pub fn new(year: i32, month: u8, day: u8, calendar: Calendar) -> TemporalResult { - core::PlainDate::new(year, month, day, calendar).map(Into::into) + temporal_core::PlainDate::new(year, month, day, calendar).map(Into::into) } /// Creates a new `PlainDate` rejecting any date that may be invalid. pub fn try_new(year: i32, month: u8, day: u8, calendar: Calendar) -> TemporalResult { - core::PlainDate::try_new(year, month, day, calendar).map(Into::into) + temporal_core::PlainDate::try_new(year, month, day, calendar).map(Into::into) } /// Creates a new `PlainDate` with the specified overflow. @@ -40,7 +46,7 @@ impl PlainDate { calendar: Calendar, overflow: ArithmeticOverflow, ) -> TemporalResult { - core::PlainDate::new_with_overflow(year, month, day, calendar, overflow).map(Into::into) + temporal_core::PlainDate::new_with_overflow(year, month, day, calendar, overflow).map(Into::into) } /// Create a `PlainDate` from a `PartialDate` @@ -68,7 +74,7 @@ impl PlainDate { partial: PartialDate, overflow: Option, ) -> TemporalResult { - core::PlainDate::from_partial(partial, overflow).map(Into::into) + temporal_core::PlainDate::from_partial(partial, overflow).map(Into::into) } /// Creates a date time with values from a `PartialDate`. diff --git a/src/builtins/native/datetime.rs b/src/builtins/native/datetime.rs index 158fe090e..2c2cc31a0 100644 --- a/src/builtins/native/datetime.rs +++ b/src/builtins/native/datetime.rs @@ -1,19 +1,27 @@ + +use alloc::string::String; use crate::{ - builtins::core, + builtins::core as temporal_core, options::{ ArithmeticOverflow, DifferenceSettings, DisplayCalendar, RoundingOptions, ToStringRoundingOptions, }, Calendar, TemporalResult, }; -use alloc::string::String; +use super::{Duration, PartialDateTime, PlainDate, PlainTime}; use tinystr::TinyAsciiStr; -use super::{Duration, PartialDateTime, PlainDate, PlainTime}; -pub struct PlainDateTime(pub(crate) core::PlainDateTime); +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct PlainDateTime(pub(crate) temporal_core::PlainDateTime); + +impl core::fmt::Display for PlainDateTime { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.0.fmt(f) + } +} -impl From for PlainDateTime { - fn from(value: core::PlainDateTime) -> Self { +impl From for PlainDateTime { + fn from(value: temporal_core::PlainDateTime) -> Self { Self(value) } } @@ -33,7 +41,7 @@ impl PlainDateTime { nanosecond: u16, calendar: Calendar, ) -> TemporalResult { - core::PlainDateTime::new( + temporal_core::PlainDateTime::new( year, month, day, @@ -62,7 +70,7 @@ impl PlainDateTime { nanosecond: u16, calendar: Calendar, ) -> TemporalResult { - core::PlainDateTime::try_new( + temporal_core::PlainDateTime::try_new( year, month, day, @@ -79,7 +87,7 @@ impl PlainDateTime { /// Create a `DateTime` from a `Date` and a `Time`. pub fn from_date_and_time(date: PlainDate, time: PlainTime) -> TemporalResult { - core::PlainDateTime::from_date_and_time(date.0, time.0).map(Into::into) + temporal_core::PlainDateTime::from_date_and_time(date.0, time.0).map(Into::into) } /// Creates a `DateTime` from a `PartialDateTime`. @@ -118,7 +126,7 @@ impl PlainDateTime { partial: PartialDateTime, overflow: Option, ) -> TemporalResult { - core::PlainDateTime::from_partial(partial, overflow).map(Into::into) + temporal_core::PlainDateTime::from_partial(partial, overflow).map(Into::into) } /// Creates a new `DateTime` with the fields of a `PartialDateTime`. diff --git a/src/builtins/native/duration.rs b/src/builtins/native/duration.rs index 44501eca2..09dbcba24 100644 --- a/src/builtins/native/duration.rs +++ b/src/builtins/native/duration.rs @@ -13,6 +13,7 @@ use super::{timezone::TZ_PROVIDER, DateDuration, TimeDuration}; #[cfg(test)] mod tests; +#[derive(Debug, Clone)] pub struct Duration(pub(crate) temporal_core::Duration); impl core::fmt::Display for Duration { diff --git a/src/builtins/native/instant.rs b/src/builtins/native/instant.rs index 3daea0de2..5a849f0ef 100644 --- a/src/builtins/native/instant.rs +++ b/src/builtins/native/instant.rs @@ -8,7 +8,7 @@ use alloc::string::String; use super::{duration::Duration, timezone::TZ_PROVIDER, TimeDuration}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Instant(temporal_core::Instant); impl From for Instant { diff --git a/src/builtins/native/time.rs b/src/builtins/native/time.rs index 9ad76d271..55a4a0200 100644 --- a/src/builtins/native/time.rs +++ b/src/builtins/native/time.rs @@ -1,18 +1,30 @@ + +use core::fmt::Debug; + +use alloc::string::String; use crate::{ - builtins::core, + builtins::core as temporal_core, options::{ ArithmeticOverflow, DifferenceSettings, TemporalRoundingMode, TemporalUnit, ToStringRoundingOptions, }, TemporalResult, }; -use alloc::string::String; - use super::{Duration, PartialTime, TimeDuration}; -pub struct PlainTime(pub(crate) core::PlainTime); -impl From for PlainTime { - fn from(value: core::PlainTime) -> Self { + + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct PlainTime(pub(crate) temporal_core::PlainTime); + +impl core::fmt::Display for PlainTime { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.0.fmt(f) + } +} + +impl From for PlainTime { + fn from(value: temporal_core::PlainTime) -> Self { Self(value) } } @@ -36,7 +48,7 @@ impl PlainTime { microsecond: u16, nanosecond: u16, ) -> TemporalResult { - core::PlainTime::new(hour, minute, second, millisecond, microsecond, nanosecond) + temporal_core::PlainTime::new(hour, minute, second, millisecond, microsecond, nanosecond) .map(Into::into) } @@ -58,7 +70,7 @@ impl PlainTime { microsecond: u16, nanosecond: u16, ) -> TemporalResult { - core::PlainTime::try_new(hour, minute, second, millisecond, microsecond, nanosecond) + temporal_core::PlainTime::try_new(hour, minute, second, millisecond, microsecond, nanosecond) .map(Into::into) } @@ -86,7 +98,7 @@ impl PlainTime { partial: PartialTime, overflow: Option, ) -> TemporalResult { - core::PlainTime::from_partial(partial, overflow).map(Into::into) + temporal_core::PlainTime::from_partial(partial, overflow).map(Into::into) } /// Creates a new `PlainTime` using the current `PlainTime` fields as a fallback. From cb9137a341f832f53bc2a3c563d100d741690f18 Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sat, 18 Jan 2025 00:53:10 -0600 Subject: [PATCH 5/8] cargo fmt --- src/builtins/native/date.rs | 3 ++- src/builtins/native/datetime.rs | 5 ++--- src/builtins/native/time.rs | 18 +++++++++++------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/builtins/native/date.rs b/src/builtins/native/date.rs index d54b5cc80..8c74fe8dd 100644 --- a/src/builtins/native/date.rs +++ b/src/builtins/native/date.rs @@ -46,7 +46,8 @@ impl PlainDate { calendar: Calendar, overflow: ArithmeticOverflow, ) -> TemporalResult { - temporal_core::PlainDate::new_with_overflow(year, month, day, calendar, overflow).map(Into::into) + temporal_core::PlainDate::new_with_overflow(year, month, day, calendar, overflow) + .map(Into::into) } /// Create a `PlainDate` from a `PartialDate` diff --git a/src/builtins/native/datetime.rs b/src/builtins/native/datetime.rs index 2c2cc31a0..762385938 100644 --- a/src/builtins/native/datetime.rs +++ b/src/builtins/native/datetime.rs @@ -1,5 +1,4 @@ - -use alloc::string::String; +use super::{Duration, PartialDateTime, PlainDate, PlainTime}; use crate::{ builtins::core as temporal_core, options::{ @@ -8,7 +7,7 @@ use crate::{ }, Calendar, TemporalResult, }; -use super::{Duration, PartialDateTime, PlainDate, PlainTime}; +use alloc::string::String; use tinystr::TinyAsciiStr; #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] diff --git a/src/builtins/native/time.rs b/src/builtins/native/time.rs index 55a4a0200..c71161323 100644 --- a/src/builtins/native/time.rs +++ b/src/builtins/native/time.rs @@ -1,7 +1,6 @@ - use core::fmt::Debug; -use alloc::string::String; +use super::{Duration, PartialTime, TimeDuration}; use crate::{ builtins::core as temporal_core, options::{ @@ -10,9 +9,7 @@ use crate::{ }, TemporalResult, }; -use super::{Duration, PartialTime, TimeDuration}; - - +use alloc::string::String; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct PlainTime(pub(crate) temporal_core::PlainTime); @@ -70,8 +67,15 @@ impl PlainTime { microsecond: u16, nanosecond: u16, ) -> TemporalResult { - temporal_core::PlainTime::try_new(hour, minute, second, millisecond, microsecond, nanosecond) - .map(Into::into) + temporal_core::PlainTime::try_new( + hour, + minute, + second, + millisecond, + microsecond, + nanosecond, + ) + .map(Into::into) } /// Creates a new `PlainTime` from a `PartialTime`. From 93fd1d8ac9037100ab25504d34aa12edb2b9bc58 Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sat, 18 Jan 2025 11:12:14 -0600 Subject: [PATCH 6/8] Core should always be public --- src/builtins/mod.rs | 2 +- src/builtins/native/mod.rs | 9 +++++++++ src/lib.rs | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 33abc08db..64d99c62d 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -1,4 +1,4 @@ -pub(crate) mod core; +pub mod core; #[cfg(feature = "full")] pub(crate) mod native; diff --git a/src/builtins/native/mod.rs b/src/builtins/native/mod.rs index 5b2d8f1c0..331aadb96 100644 --- a/src/builtins/native/mod.rs +++ b/src/builtins/native/mod.rs @@ -1,3 +1,5 @@ +//! This module implements native Rust wrappers for the Temporal builtins. + pub(crate) mod timezone; mod date; @@ -9,12 +11,19 @@ pub(crate) mod options; mod time; mod zoneddatetime; +#[doc(inline)] pub use date::PlainDate; +#[doc(inline)] pub use datetime::PlainDateTime; +#[doc(inline)] pub use duration::Duration; +#[doc(inline)] pub use instant::Instant; +#[doc(inline)] pub use now::Now; +#[doc(inline)] pub use time::PlainTime; +#[doc(inline)] pub use zoneddatetime::ZonedDateTime; pub use crate::builtins::core::{ diff --git a/src/lib.rs b/src/lib.rs index bcfb50e2e..af9d19da9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,6 +96,9 @@ pub mod time { pub use crate::epoch_nanoseconds::EpochNanoseconds; } +#[cfg(feature = "full")] +pub use crate::builtins::core as temporal_core; + pub use crate::builtins::{ calendar::Calendar, core::timezone::TimeZone, DateDuration, Duration, Instant, PlainDate, PlainDateTime, PlainMonthDay, PlainTime, PlainYearMonth, TimeDuration, ZonedDateTime, From a8abb5bf8b2277b85acfd5a9dbe3c784288c82ee Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sun, 19 Jan 2025 23:00:01 -0600 Subject: [PATCH 7/8] Add an lib architercture doc --- docs/architecture.md | 112 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 docs/architecture.md diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 000000000..5fb2fe6d1 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,112 @@ +# Library Architecture + +TODO: FFI docs + +This doc provides an overview of the layout of `temporal_rs`. + +We will go over the Temporal Date/Time builtins, general primitives, and +utiltity crates. + +## `temporal_rs` design considerations + +`temporal_rs` is first and foremost designed to be a fully spec +compliant implementation of ECMAScript's Temporal date/time builtins. + +As such, the main design consideration of `temporal_rs` is that it needs +to be able to service language interpreters / engines. + +Thus, `temporal_rs` aims to provide an API along with tools to implement +Temporal while minimizing issue with integrating Temporal into engines. + +## Date/Time builtins + +The primary date & time builtins/components are located in the +`builtins` directory. + +These builtins are then reexported from `lib.rs` to be available from +`temporal_rs`'s root module. + +### Core vs. Native + +`temporal_rs`'s builtins are split in two distinct directories `core` +and `native`. The core implementation contains the core implementation +of the Temporal builtins; meanwhile, the `native` implementation is a +Rust wrapper around the `core` implementation that simplifies some +"lower" level date/time API that may not be necessary for a general use +case. + +### Core implementation + +The core implementation is always publicly available, but may not be +available to import from the `temporal_rs`'s root. + +The core implementation can be made available from the root by providing +the `--no-default-feautres` flag. + +The core implementation exposes the Provider API that allows the user to +supply a "provider", or any type that implements the `TimeZoneProvider` +trait, for time zone data that the library can use to complete it's +calculations. This is useful from an engine / implementor perspective +because it allows the engine to source time zone data in their preferred +manner without locking them into a library specific implementation that +may or may not have side affects. + +A `TimeZoneProvider` API on a core builtin will look like the below. + +```rust +impl ZonedDateTime { + pub fn day_with_provider(&self, provider: &impl TimeZoneProvider) -> TemporalResult { + // Code goes here. + } +} +``` + +### Native implementation + +The native implementation is only available via the "full" default +feature flag. + +For the same reason that the Provider API is useful for language +implementors, it is a deterent from a general use case perspective. Most +people using a datetime library, outside of the self-proclaimed time +zone nerds, probably won't care from where their time zone data is being +sourced. + +The native Rust wrapper of the core implementation provides a default +provider implementation to remove the need of the user to think or deal +with `TimeZoneProvider`. + +```rust +impl ZonedDateTime { + pub fn day(&self) -> TemporalResult { + // Code goes here. + } +} +``` + +This greatly simplifies the API for general use cases. + +## Primitives + + + +`temporal_rs` has a primitive number implementation `FiniteF64` along +with a few date and time primitives: `IsoDate`, `IsoTime`, +`IsoDateTime`, and `EpochNanoseconds`. + +`FiniteF64` allows an interface to translate between ECMAScript's number +type vs. `temporal_rs`'s strictly typed API. + +Meanwhile the Date and Time primitives allow certain invariants to be +enforced on their records. + +## Utiltiies + +`temporal_rs` provides one implementation of the `TimeZoneProvider` +trait: `FsTzdbProvider`. + +`FsTzdbProvider` reads from the file systems' tzdb and when not +available on the system, `FsTzdbProvider` relies on a prepackaged +`tzdb`. + + From 56b385f7a33e12a5e2e07f646054dbfad85ae3e5 Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Tue, 21 Jan 2025 22:52:06 -0600 Subject: [PATCH 8/8] Fix typos --- docs/architecture.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index 5fb2fe6d1..ba1931d35 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -41,7 +41,7 @@ The core implementation is always publicly available, but may not be available to import from the `temporal_rs`'s root. The core implementation can be made available from the root by providing -the `--no-default-feautres` flag. +the `--no-default-features` flag. The core implementation exposes the Provider API that allows the user to supply a "provider", or any type that implements the `TimeZoneProvider` @@ -49,7 +49,7 @@ trait, for time zone data that the library can use to complete it's calculations. This is useful from an engine / implementor perspective because it allows the engine to source time zone data in their preferred manner without locking them into a library specific implementation that -may or may not have side affects. +may or may not have side effects. A `TimeZoneProvider` API on a core builtin will look like the below.