diff --git a/Cargo.lock b/Cargo.lock index c52fa329dc..13162a8f51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -402,7 +402,6 @@ dependencies = [ "boa_macros", "boa_parser", "boa_profiler", - "boa_temporal", "bytemuck", "cfg-if", "criterion", @@ -447,6 +446,7 @@ dependencies = [ "static_assertions", "sys-locale", "tap", + "temporal_rs", "textwrap", "thin-vec", "thiserror", @@ -571,18 +571,6 @@ dependencies = [ "textwrap", ] -[[package]] -name = "boa_temporal" -version = "0.17.0" -dependencies = [ - "bitflags 2.4.2", - "icu_calendar", - "num-bigint", - "num-traits", - "rustc-hash", - "tinystr", -] - [[package]] name = "boa_tester" version = "0.17.0" @@ -3641,6 +3629,20 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "temporal_rs" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0437239e22d804a1ea7a83e001ae86812afbfc77a5dc12a9dfc579bfa0d9bc" +dependencies = [ + "bitflags 2.4.2", + "icu_calendar", + "num-bigint", + "num-traits", + "rustc-hash", + "tinystr", +] + [[package]] name = "termcolor" version = "1.4.1" diff --git a/Cargo.toml b/Cargo.toml index 9ce5500e70..000000c0c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,6 @@ boa_macros = { version = "~0.17.0", path = "core/macros" } boa_parser = { version = "~0.17.0", path = "core/parser" } boa_profiler = { version = "~0.17.0", path = "core/profiler" } boa_runtime = { version = "~0.17.0", path = "core/runtime" } -boa_temporal = { version = "~0.17.0", path = "core/temporal" } # Shared deps arbitrary = "1" diff --git a/core/engine/Cargo.toml b/core/engine/Cargo.toml index 4673714a02..4d22119329 100644 --- a/core/engine/Cargo.toml +++ b/core/engine/Cargo.toml @@ -64,7 +64,6 @@ boa_profiler.workspace = true boa_macros.workspace = true boa_ast.workspace = true boa_parser.workspace = true -boa_temporal = { workspace = true } serde = { workspace = true, features = ["derive", "rc"] } serde_json.workspace = true rand = "0.8.5" @@ -117,6 +116,8 @@ zerofrom = { workspace = true, optional = true } fixed_decimal = { workspace = true, features = ["ryu", "experimental"], optional = true } tinystr = { workspace = true, optional = true } +# temporal deps +temporal_rs = "0.0.1" [target.'cfg(all(target_family = "wasm", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] web-time = { version = "1.0.0", optional = true } diff --git a/core/engine/src/builtins/temporal/calendar/mod.rs b/core/engine/src/builtins/temporal/calendar/mod.rs index 5a26709948..97a4098a2d 100644 --- a/core/engine/src/builtins/temporal/calendar/mod.rs +++ b/core/engine/src/builtins/temporal/calendar/mod.rs @@ -3,9 +3,9 @@ use std::str::FromStr; use super::{ - create_temporal_date, create_temporal_duration, create_temporal_month_day, - create_temporal_year_month, fields, options::TemporalUnitGroup, PlainDate, PlainDateTime, - PlainMonthDay, PlainYearMonth, ZonedDateTime, + create_temporal_date, create_temporal_month_day, create_temporal_year_month, fields, + options::TemporalUnitGroup, PlainDate, PlainDateTime, PlainMonthDay, PlainYearMonth, + ZonedDateTime, }; use crate::{ builtins::{ @@ -23,7 +23,7 @@ use crate::{ }; use boa_gc::{custom_trace, Finalize, Trace}; use boa_profiler::Profiler; -use boa_temporal::{ +use temporal_rs::{ components::calendar::{ CalendarDateLike, CalendarFieldsType, CalendarProtocol, CalendarSlot, CALENDAR_PROTOCOL_METHODS, @@ -442,9 +442,8 @@ impl Calendar { let overflow = get_option(&options_obj, utf16!("overflow"), context)? .unwrap_or(ArithmeticOverflow::Constrain); - // 8. Let balanceResult be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day"). - duration.balance_time_duration(TemporalUnit::Day)?; - + // 8. Let balanceResult be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], + // duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day"). let result = calendar .slot .date_add(&date.inner, &duration, overflow, context)?; @@ -457,7 +456,7 @@ impl Calendar { // 1. Let calendar be the this value. // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). // 3. Assert: calendar.[[Identifier]] is "iso8601". - let calendar = this + let _calendar = this .as_object() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { @@ -466,16 +465,16 @@ impl Calendar { })?; // 4. Set one to ? ToTemporalDate(one). - let one = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; + let _one = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; // 5. Set two to ? ToTemporalDate(two). - let two = temporal::plain_date::to_temporal_date(args.get_or_undefined(1), None, context)?; + let _two = temporal::plain_date::to_temporal_date(args.get_or_undefined(1), None, context)?; // 6. Set options to ? GetOptionsObject(options). let options = get_options_object(args.get_or_undefined(2))?; // 7. Let largestUnit be ? GetTemporalUnit(options, "largestUnit", date, "auto"). // 8. If largestUnit is "auto", set largestUnit to "day". - let largest_unit = super::options::get_temporal_unit( + let _largest_unit = super::options::get_temporal_unit( &options, utf16!("largestUnit"), TemporalUnitGroup::Date, @@ -484,11 +483,18 @@ impl Calendar { )? .unwrap_or(TemporalUnit::Day); + // TODO: Fix temporal_rs `dateUntil` loop + /* let result = calendar .slot .date_until(&one.inner, &two.inner, largest_unit, context)?; create_temporal_duration(result, None, context).map(Into::into) + */ + + Err(JsNativeError::range() + .with_message("dateUntil not yet implemented.") + .into()) } /// 15.8.2.6 `Temporal.Calendar.prototype.era ( temporalDateLike )` diff --git a/core/engine/src/builtins/temporal/calendar/object.rs b/core/engine/src/builtins/temporal/calendar/object.rs index 04ab9aa3f7..51f0d2c3e3 100644 --- a/core/engine/src/builtins/temporal/calendar/object.rs +++ b/core/engine/src/builtins/temporal/calendar/object.rs @@ -14,7 +14,12 @@ use crate::{ }; use boa_macros::utf16; -use boa_temporal::{ +use num_traits::ToPrimitive; +use plain_date::PlainDate; +use plain_date_time::PlainDateTime; +use plain_month_day::PlainMonthDay; +use plain_year_month::PlainYearMonth; +use temporal_rs::{ components::{ calendar::{CalendarDateLike, CalendarProtocol}, Date, Duration, MonthDay, YearMonth, @@ -22,11 +27,6 @@ use boa_temporal::{ options::ArithmeticOverflow, TemporalError, TemporalFields, TemporalResult, TinyAsciiStr, }; -use num_traits::ToPrimitive; -use plain_date::PlainDate; -use plain_date_time::PlainDateTime; -use plain_month_day::PlainMonthDay; -use plain_year_month::PlainYearMonth; impl CalendarProtocol for JsObject { type Date = JsObject; @@ -194,7 +194,7 @@ impl CalendarProtocol for JsObject { &self, _one: &Date, _two: &Date, - _largest_unit: boa_temporal::options::TemporalUnit, + _largest_unit: temporal_rs::options::TemporalUnit, _context: &mut Context, ) -> TemporalResult { // TODO diff --git a/core/engine/src/builtins/temporal/duration/mod.rs b/core/engine/src/builtins/temporal/duration/mod.rs index 57c40760f3..1a80879b32 100644 --- a/core/engine/src/builtins/temporal/duration/mod.rs +++ b/core/engine/src/builtins/temporal/duration/mod.rs @@ -3,7 +3,6 @@ use crate::{ builtins::{ options::{get_option, get_options_object, RoundingMode}, - temporal::validate_temporal_rounding_increment, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, @@ -16,12 +15,11 @@ use crate::{ }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; -use boa_temporal::{components::Duration as InnerDuration, options::TemporalUnit}; +use temporal_rs::{components::Duration as InnerDuration, options::TemporalUnit}; use super::{ options::{get_temporal_rounding_increment, get_temporal_unit, TemporalUnitGroup}, - plain_date::PlainDate, - to_integer_if_integral, DateTimeValues, PlainDateTime, + to_integer_if_integral, DateTimeValues, }; #[cfg(test)] @@ -307,16 +305,16 @@ impl Duration { let inner = &duration.inner; match field { - DateTimeValues::Year => Ok(JsValue::Rational(inner.date().years())), - DateTimeValues::Month => Ok(JsValue::Rational(inner.date().months())), - DateTimeValues::Week => Ok(JsValue::Rational(inner.date().weeks())), - DateTimeValues::Day => Ok(JsValue::Rational(inner.date().days())), - DateTimeValues::Hour => Ok(JsValue::Rational(inner.time().hours())), - DateTimeValues::Minute => Ok(JsValue::Rational(inner.time().minutes())), - DateTimeValues::Second => Ok(JsValue::Rational(inner.time().seconds())), - DateTimeValues::Millisecond => Ok(JsValue::Rational(inner.time().milliseconds())), - DateTimeValues::Microsecond => Ok(JsValue::Rational(inner.time().microseconds())), - DateTimeValues::Nanosecond => Ok(JsValue::Rational(inner.time().nanoseconds())), + DateTimeValues::Year => Ok(JsValue::Rational(inner.years())), + DateTimeValues::Month => Ok(JsValue::Rational(inner.months())), + DateTimeValues::Week => Ok(JsValue::Rational(inner.weeks())), + DateTimeValues::Day => Ok(JsValue::Rational(inner.days())), + DateTimeValues::Hour => Ok(JsValue::Rational(inner.hours())), + DateTimeValues::Minute => Ok(JsValue::Rational(inner.minutes())), + DateTimeValues::Second => Ok(JsValue::Rational(inner.seconds())), + DateTimeValues::Millisecond => Ok(JsValue::Rational(inner.milliseconds())), + DateTimeValues::Microsecond => Ok(JsValue::Rational(inner.microseconds())), + DateTimeValues::Nanosecond => Ok(JsValue::Rational(inner.nanoseconds())), DateTimeValues::MonthCode => unreachable!( "Any other DateTimeValue fields on Duration would be an implementation error." ), @@ -387,7 +385,7 @@ impl Duration { // 3. Return 𝔽(! DurationSign(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], // duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], // duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]])). - Ok(duration.inner.duration_sign().into()) + Ok(duration.inner.sign().into()) } /// 7.3.14 get Temporal.Duration.prototype.blank @@ -404,14 +402,9 @@ impl Duration { // 3. Let sign be ! DurationSign(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], // duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], // duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]). - let sign = duration.inner.duration_sign(); - // 4. If sign = 0, return true. // 5. Return false. - match sign { - 0 => Ok(true.into()), - _ => Ok(false.into()), - } + Ok(duration.inner.is_zero().into()) } } @@ -441,100 +434,100 @@ impl Duration { // a. Let years be temporalDurationLike.[[Years]]. // 5. Else, // a. Let years be duration.[[Years]]. - let years = if temporal_duration_like.date().years().is_nan() { - duration.inner.date().years() + let years = if temporal_duration_like.years().is_nan() { + duration.inner.years() } else { - temporal_duration_like.date().years() + temporal_duration_like.years() }; // 6. If temporalDurationLike.[[Months]] is not undefined, then // a. Let months be temporalDurationLike.[[Months]]. // 7. Else, // a. Let months be duration.[[Months]]. - let months = if temporal_duration_like.date().months().is_nan() { - duration.inner.date().months() + let months = if temporal_duration_like.months().is_nan() { + duration.inner.months() } else { - temporal_duration_like.date().months() + temporal_duration_like.months() }; // 8. If temporalDurationLike.[[Weeks]] is not undefined, then // a. Let weeks be temporalDurationLike.[[Weeks]]. // 9. Else, // a. Let weeks be duration.[[Weeks]]. - let weeks = if temporal_duration_like.date().weeks().is_nan() { - duration.inner.date().weeks() + let weeks = if temporal_duration_like.weeks().is_nan() { + duration.inner.weeks() } else { - temporal_duration_like.date().weeks() + temporal_duration_like.weeks() }; // 10. If temporalDurationLike.[[Days]] is not undefined, then // a. Let days be temporalDurationLike.[[Days]]. // 11. Else, // a. Let days be duration.[[Days]]. - let days = if temporal_duration_like.date().days().is_nan() { - duration.inner.date().days() + let days = if temporal_duration_like.days().is_nan() { + duration.inner.days() } else { - temporal_duration_like.date().days() + temporal_duration_like.days() }; // 12. If temporalDurationLike.[[Hours]] is not undefined, then // a. Let hours be temporalDurationLike.[[Hours]]. // 13. Else, // a. Let hours be duration.[[Hours]]. - let hours = if temporal_duration_like.time().hours().is_nan() { - duration.inner.time().hours() + let hours = if temporal_duration_like.hours().is_nan() { + duration.inner.hours() } else { - temporal_duration_like.time().hours() + temporal_duration_like.hours() }; // 14. If temporalDurationLike.[[Minutes]] is not undefined, then // a. Let minutes be temporalDurationLike.[[Minutes]]. // 15. Else, // a. Let minutes be duration.[[Minutes]]. - let minutes = if temporal_duration_like.time().minutes().is_nan() { - duration.inner.time().minutes() + let minutes = if temporal_duration_like.minutes().is_nan() { + duration.inner.minutes() } else { - temporal_duration_like.time().minutes() + temporal_duration_like.minutes() }; // 16. If temporalDurationLike.[[Seconds]] is not undefined, then // a. Let seconds be temporalDurationLike.[[Seconds]]. // 17. Else, // a. Let seconds be duration.[[Seconds]]. - let seconds = if temporal_duration_like.time().seconds().is_nan() { - duration.inner.time().seconds() + let seconds = if temporal_duration_like.seconds().is_nan() { + duration.inner.seconds() } else { - temporal_duration_like.time().seconds() + temporal_duration_like.seconds() }; // 18. If temporalDurationLike.[[Milliseconds]] is not undefined, then // a. Let milliseconds be temporalDurationLike.[[Milliseconds]]. // 19. Else, // a. Let milliseconds be duration.[[Milliseconds]]. - let milliseconds = if temporal_duration_like.time().milliseconds().is_nan() { - duration.inner.time().milliseconds() + let milliseconds = if temporal_duration_like.milliseconds().is_nan() { + duration.inner.milliseconds() } else { - temporal_duration_like.time().milliseconds() + temporal_duration_like.milliseconds() }; // 20. If temporalDurationLike.[[Microseconds]] is not undefined, then // a. Let microseconds be temporalDurationLike.[[Microseconds]]. // 21. Else, // a. Let microseconds be duration.[[Microseconds]]. - let microseconds = if temporal_duration_like.time().microseconds().is_nan() { - duration.inner.time().microseconds() + let microseconds = if temporal_duration_like.microseconds().is_nan() { + duration.inner.microseconds() } else { - temporal_duration_like.time().microseconds() + temporal_duration_like.microseconds() }; // 22. If temporalDurationLike.[[Nanoseconds]] is not undefined, then // a. Let nanoseconds be temporalDurationLike.[[Nanoseconds]]. // 23. Else, // a. Let nanoseconds be duration.[[Nanoseconds]]. - let nanoseconds = if temporal_duration_like.time().nanoseconds().is_nan() { - duration.inner.time().nanoseconds() + let nanoseconds = if temporal_duration_like.nanoseconds().is_nan() { + duration.inner.nanoseconds() } else { - temporal_duration_like.time().nanoseconds() + temporal_duration_like.nanoseconds() }; // 24. Return ? CreateTemporalDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). @@ -598,7 +591,7 @@ impl Duration { .into()) } - // TODO: Update needed. + // TODO: Migrate to `temporal_rs's Duration::round` /// 7.3.20 `Temporal.Duration.prototype.round ( roundTo )` pub(crate) fn round( this: &JsValue, @@ -607,7 +600,7 @@ impl Duration { ) -> JsResult { // 1. Let duration be the this value. // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). - let duration = this + let _duration = this .as_object() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { @@ -659,11 +652,11 @@ impl Duration { // 10. Let relativeToRecord be ? ToRelativeTemporalObject(roundTo). // 11. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]]. // 12. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]]. - let (plain_relative_to, zoned_relative_to) = + let (_plain_relative_to, _zoned_relative_to) = super::to_relative_temporal_object(&round_to, context)?; // 13. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo). - let rounding_increment = get_temporal_rounding_increment(&round_to, context)?; + let _rounding_increment = get_temporal_rounding_increment(&round_to, context)?; // 14. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand"). let _rounding_mode = get_option(&round_to, utf16!("roundingMode"), context)? @@ -687,133 +680,7 @@ impl Duration { .into()); } - // 16. If smallestUnit is undefined, then - let smallest_unit = if let Some(unit) = smallest_unit { - unit - } else { - // a. Set smallestUnitPresent to false. - // b. Set smallestUnit to "nanosecond". - TemporalUnit::Nanosecond - }; - - // 17. Let existingLargestUnit be ! DefaultTemporalLargestUnit(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]]). - let existing_largest_unit = duration.inner.default_temporal_largest_unit(); - - // 18. Set defaultLargestUnit to ! LargerOfTwoTemporalUnits(defaultLargestUnit, smallestUnit). - let default_largest_unit = core::cmp::max(existing_largest_unit, smallest_unit); - - // 19. If largestUnit is undefined, then - let largest_unit = match largest_unit { - // 20. Else if largestUnit is "auto", then - // a. Set largestUnit to defaultLargestUnit. - Some(TemporalUnit::Auto) => default_largest_unit, - Some(u) => u, - None => { - // a. Set largestUnitPresent to false. - // b. Set largestUnit to defaultLargestUnit. - default_largest_unit - } - }; - - // 22. If LargerOfTwoTemporalUnits(largestUnit, smallestUnit) is not largestUnit, throw a RangeError exception. - if core::cmp::max(largest_unit, smallest_unit) != largest_unit { - return Err(JsNativeError::range() - .with_message("largestUnit must be larger than smallestUnit") - .into()); - } - - // 23. Let maximum be ! MaximumTemporalDurationRoundingIncrement(smallestUnit). - let maximum = smallest_unit.to_maximum_rounding_increment(); - - // 24. If maximum is not undefined, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false). - if let Some(max) = maximum { - validate_temporal_rounding_increment(rounding_increment.into(), max.into(), false)?; - } - - // 25. Let hoursToDaysConversionMayOccur be false. - // 26. If duration.[[Days]] ≠ 0 and zonedRelativeTo is not undefined, set hoursToDaysConversionMayOccur to true. - // 27. Else if abs(duration.[[Hours]]) ≥ 24, set hoursToDaysConversionMayOccur to true. - let conversion_may_occur = - if duration.inner.date().days() != 0.0 && zoned_relative_to.is_some() { - true - } else { - 24f64 <= duration.inner.time().hours().abs() - }; - - // 28. If smallestUnit is "nanosecond" and roundingIncrement = 1, let roundingGranularityIsNoop be true; else let roundingGranularityIsNoop be false. - let is_noop = smallest_unit == TemporalUnit::Nanosecond && rounding_increment == 1; - - // 29. If duration.[[Years]] = 0 and duration.[[Months]] = 0 and duration.[[Weeks]] = 0, let calendarUnitsPresent be false; else let calendarUnitsPresent be true. - let calendar_units_present = !(duration.inner.date().years() == 0f64 - || duration.inner.date().months() == 0f64 - || duration.inner.date().weeks() == 0f64); - - // 30. If roundingGranularityIsNoop is true, and largestUnit is existingLargestUnit, - // and calendarUnitsPresent is false, and hoursToDaysConversionMayOccur is false, - // and abs(duration.[[Minutes]]) < 60, and abs(duration.[[Seconds]]) < 60, - // and abs(duration.[[Milliseconds]]) < 1000, and abs(duration.[[Microseconds]]) < 1000, - // and abs(duration.[[Nanoseconds]]) < 1000, then - if is_noop - && largest_unit == existing_largest_unit - && !calendar_units_present - && !conversion_may_occur - && duration.inner.is_time_within_range() - { - // a. NOTE: The above conditions mean that the operation will have no effect: the smallest unit and - // rounding increment will leave the total duration unchanged, and it can be determined without - // calling a calendar or time zone method that no balancing will take place. - // b. Return ! CreateTemporalDuration(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]). - } - - // 31. Let precalculatedPlainDateTime be undefined. - // let mut precalc_datetime = None; - - // 32. If roundingGranularityIsNoop is false, or largestUnit is "year", or largestUnit is "month", - // or largestUnit is "week", or largestUnit is "day", or calendarUnitsPresent is true, or duration.[[Days]] ≠ 0, - // let plainDateTimeOrRelativeToWillBeUsed be true; else let plainDateTimeOrRelativeToWillBeUsed be false. - let pdt_or_rel_will_be_used = !is_noop - || largest_unit == TemporalUnit::Year - || largest_unit == TemporalUnit::Month - || largest_unit == TemporalUnit::Week - || largest_unit == TemporalUnit::Day - || calendar_units_present - || duration.inner.date().days() != 0f64; - - // 33. If zonedRelativeTo is not undefined and plainDateTimeOrRelativeToWillBeUsed is true, then - let (_plain_relative_to, _precalc_pdt) = if zoned_relative_to.is_some() - && pdt_or_rel_will_be_used - { - // TODO(TimeZone): Implement GetPlainDateTimeFor - // TODO(ZonedDateTime): Implement ZonedDateTime related methods. - return Err(JsNativeError::range() - .with_message("not yet implemented.") - .into()); - - // a. NOTE: The above conditions mean that the corresponding Temporal.PlainDateTime or Temporal.PlainDate for zonedRelativeTo will be used in one of the operations below. - // b. Let instant be ! CreateTemporalInstant(zonedRelativeTo.[[Nanoseconds]]). - // c. Set precalculatedPlainDateTime to ? GetPlainDateTimeFor(zonedRelativeTo.[[TimeZone]], instant, zonedRelativeTo.[[Calendar]]). - // d. Set plainRelativeTo to ! CreateTemporalDate(precalculatedPlainDateTime.[[ISOYear]], precalculatedPlainDateTime.[[ISOMonth]], precalculatedPlainDateTime.[[ISODay]], zonedRelativeTo.[[Calendar]]). - } else { - // TODO: remove after ZonedDateTime is implemented - let non_zoned: (Option, Option) = (plain_relative_to, None); - non_zoned - }; - - // 34. Let unbalanceResult be ? UnbalanceDateDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], largestUnit, plainRelativeTo). - // 35. Let roundRecord be ? RoundDuration(unbalanceResult.[[Years]], unbalanceResult.[[Months]], - // unbalanceResult.[[Weeks]], unbalanceResult.[[Days]], duration.[[Hours]], duration.[[Minutes]], - // duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], - // roundingIncrement, smallestUnit, roundingMode, plainRelativeTo, zonedRelativeTo, precalculatedPlainDateTime). - // 36. Let roundResult be roundRecord.[[DurationRecord]]. - // 37. If zonedRelativeTo is not undefined, then - // a. Set roundResult to ? AdjustRoundedDurationDays(roundResult.[[Years]], roundResult.[[Months]], roundResult.[[Weeks]], roundResult.[[Days]], roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode, zonedRelativeTo, precalculatedPlainDateTime). - // b. Let balanceResult be ? BalanceTimeDurationRelative(roundResult.[[Days]], roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]], largestUnit, zonedRelativeTo, precalculatedPlainDateTime). - // 38. Else, - // a. Let balanceResult be ? BalanceTimeDuration(roundResult.[[Days]], roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]], largestUnit). - // 39. Let result be ? BalanceDateDurationRelative(roundResult.[[Years]], roundResult.[[Months]], roundResult.[[Weeks]], balanceResult.[[Days]], largestUnit, plainRelativeTo). - // 40. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], balanceResult.[[Hours]], balanceResult.[[Minutes]], balanceResult.[[Seconds]], balanceResult.[[Milliseconds]], balanceResult.[[Microseconds]], balanceResult.[[Nanoseconds]]). - - // NOTE: Below is currently incorrect: Handling of zonedRelativeTo and precalculatedPlainDateTime is needed. + // NOTE: Err(JsNativeError::range() .with_message("not yet implemented.") .into()) @@ -1099,10 +966,23 @@ pub(crate) fn to_temporal_partial_duration( result.set_days(f64::from(to_integer_if_integral(&years, context)?)); } - // 24. If years is undefined, and months is undefined, and weeks is undefined, and days is undefined, and hours is undefined, and minutes is undefined, and seconds is undefined, and milliseconds is undefined, and microseconds is undefined, and nanoseconds is undefined, throw a TypeError exception. - if result.into_iter().all(f64::is_nan) { + // TODO: Implement this functionality better in `temporal_rs`. + // 24. If years is undefined, and months is undefined, and weeks is undefined, and days + // is undefined, and hours is undefined, and minutes is undefined, and seconds is + // undefined, and milliseconds is undefined, and microseconds is undefined, and + // nanoseconds is undefined, throw a TypeError exception. + if result.years().is_nan() + && result.months().is_nan() + && result.weeks().is_nan() + && result.days().is_nan() + && result.minutes().is_nan() + && result.seconds().is_nan() + && result.milliseconds().is_nan() + && result.microseconds().is_nan() + && result.nanoseconds().is_nan() + { return Err(JsNativeError::typ() - .with_message("no valid Duration fields on temporalDurationLike.") + .with_message("PartialDurationRecord must have a defined field.") .into()); } diff --git a/core/engine/src/builtins/temporal/error.rs b/core/engine/src/builtins/temporal/error.rs index 0781b6bd31..22757acdf9 100644 --- a/core/engine/src/builtins/temporal/error.rs +++ b/core/engine/src/builtins/temporal/error.rs @@ -1,4 +1,4 @@ -use boa_temporal::error::{ErrorKind, TemporalError}; +use temporal_rs::error::{ErrorKind, TemporalError}; use crate::{JsError, JsNativeError}; diff --git a/core/engine/src/builtins/temporal/fields.rs b/core/engine/src/builtins/temporal/fields.rs index 6c6b00d655..08bc820161 100644 --- a/core/engine/src/builtins/temporal/fields.rs +++ b/core/engine/src/builtins/temporal/fields.rs @@ -9,7 +9,7 @@ use crate::{ use rustc_hash::FxHashSet; -use boa_temporal::fields::{FieldConversion, FieldValue, TemporalFields}; +use temporal_rs::fields::{FieldConversion, FieldValue, TemporalFields}; use super::{to_integer_with_truncation, to_positive_integer_with_trunc}; diff --git a/core/engine/src/builtins/temporal/instant/mod.rs b/core/engine/src/builtins/temporal/instant/mod.rs index de62f8c621..c1259c637a 100644 --- a/core/engine/src/builtins/temporal/instant/mod.rs +++ b/core/engine/src/builtins/temporal/instant/mod.rs @@ -20,7 +20,7 @@ use crate::{ }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; -use boa_temporal::{ +use temporal_rs::{ components::Instant as InnerInstant, options::{TemporalRoundingMode, TemporalUnit}, }; diff --git a/core/engine/src/builtins/temporal/mod.rs b/core/engine/src/builtins/temporal/mod.rs index 4147370764..9ef7c5a97f 100644 --- a/core/engine/src/builtins/temporal/mod.rs +++ b/core/engine/src/builtins/temporal/mod.rs @@ -38,14 +38,14 @@ use crate::{ Context, JsBigInt, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use boa_profiler::Profiler; -use boa_temporal::NS_PER_DAY; +use temporal_rs::NS_PER_DAY; -// TODO: Remove in favor of `boa_temporal` +// TODO: Remove in favor of `temporal_rs` pub(crate) fn ns_max_instant() -> JsBigInt { JsBigInt::from(i128::from(NS_PER_DAY) * 100_000_000_i128) } -// TODO: Remove in favor of `boa_temporal` +// TODO: Remove in favor of `temporal_rs` pub(crate) fn ns_min_instant() -> JsBigInt { JsBigInt::from(i128::from(NS_PER_DAY) * -100_000_000_i128) } @@ -211,11 +211,8 @@ pub(crate) fn _iterator_to_list_of_types( Ok(values) } -/// 13.2 `ISODateToEpochDays ( year, month, date )` -// Note: implemented on IsoDateRecord. - // Abstract Operation 13.3 `EpochDaysToEpochMs` -// Migrated to `boa_temporal` +// Migrated to `temporal_rs` // 13.4 Date Equations // implemented in temporal/date_equations.rs @@ -238,41 +235,8 @@ pub(crate) fn _iterator_to_list_of_types( // 13.16 `ToTemporalRoundingIncrement ( normalizedOptions )` // Now implemented in temporal/options.rs -/// 13.17 `ValidateTemporalRoundingIncrement ( increment, dividend, inclusive )` -#[inline] -pub(crate) fn validate_temporal_rounding_increment( - increment: u64, - dividend: u64, - inclusive: bool, -) -> JsResult<()> { - // 1. If inclusive is true, then - let maximum = if inclusive { - // a. Let maximum be dividend. - dividend - // 2. Else, - } else { - // a. Assert: dividend > 1. - assert!(dividend > 1); - // b. Let maximum be dividend - 1. - dividend - 1 - }; - - // 3. If increment > maximum, throw a RangeError exception. - if increment > maximum { - return Err(JsNativeError::range() - .with_message("increment is exceeds the range of the allowed maximum.") - .into()); - } - // 4. If dividend modulo increment ≠ 0, then - if dividend % increment != 0 { - // a. Throw a RangeError exception. - return Err(JsNativeError::range() - .with_message("Temporal rounding increment is not valid.") - .into()); - } - // 5. Return unused. - Ok(()) -} +// 13.17 `ValidateTemporalRoundingIncrement ( increment, dividend, inclusive )` +// Moved to temporal_rs /// 13.21 `ToRelativeTemporalObject ( options )` pub(crate) fn to_relative_temporal_object( @@ -294,13 +258,13 @@ pub(crate) fn to_relative_temporal_object( // Implemented on RoundingMode in builtins/options.rs // 13.27 `ApplyUnsignedRoundingMode ( x, r1, r2, unsignedRoundingMode )` -// Migrated to `boa_temporal` +// Migrated to `temporal_rs` // 13.28 `RoundNumberToIncrement ( x, increment, roundingMode )` -// Migrated to `boa_temporal` +// Migrated to `temporal_rs` // 13.29 `RoundNumberToIncrementAsIfPositive ( x, increment, roundingMode )` -// Migrated to `boa_temporal` +// Migrated to `temporal_rs` /// 13.43 `ToPositiveIntegerWithTruncation ( argument )` #[inline] @@ -355,12 +319,12 @@ pub(crate) fn to_integer_if_integral(arg: &JsValue, context: &mut Context) -> Js // NOTE: op -> true == until | false == since // 13.47 `GetDifferenceSettings ( operation, options, unitGroup, disallowedUnits, fallbackSmallestUnit, smallestLargestDefaultUnit )` -// Migrated to `boa_temporal` +// Migrated to `temporal_rs` // NOTE: used for MergeFields methods. Potentially can be omitted in favor of `TemporalFields`. // 14.6 `CopyDataProperties ( target, source, excludedKeys [ , excludedValues ] )` -// Migrated or repurposed to `boa_temporal`/`fields.rs` +// Migrated or repurposed to `temporal_rs`/`fields.rs` // Note: Deviates from Proposal spec -> proto appears to be always null across the specification. // 14.7 `SnapshotOwnProperties ( source, proto [ , excludedKeys [ , excludedValues ] ] )` -// Migrated or repurposed to `boa_temporal`/`fields.rs` +// Migrated or repurposed to `temporal_rs`/`fields.rs` diff --git a/core/engine/src/builtins/temporal/options.rs b/core/engine/src/builtins/temporal/options.rs index 5c9a04a822..9059ed793b 100644 --- a/core/engine/src/builtins/temporal/options.rs +++ b/core/engine/src/builtins/temporal/options.rs @@ -12,7 +12,7 @@ use crate::{ builtins::options::{get_option, ParsableOptionType}, js_string, Context, JsNativeError, JsObject, JsResult, }; -use boa_temporal::options::{ +use temporal_rs::options::{ ArithmeticOverflow, DurationOverflow, InstantDisambiguation, OffsetDisambiguation, TemporalRoundingMode, TemporalUnit, }; diff --git a/core/engine/src/builtins/temporal/plain_date/mod.rs b/core/engine/src/builtins/temporal/plain_date/mod.rs index 06abf25a6c..43306a7555 100644 --- a/core/engine/src/builtins/temporal/plain_date/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date/mod.rs @@ -18,7 +18,7 @@ use crate::{ }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; -use boa_temporal::{ +use temporal_rs::{ components::{ calendar::{CalendarSlot, GetCalendarSlot}, Date as InnerDate, DateTime, @@ -43,8 +43,8 @@ impl PlainDate { } impl IsoDateSlots for JsObject { - fn iso_date(&self) -> boa_temporal::iso::IsoDate { - self.borrow().data().inner.iso() + fn iso_date(&self) -> temporal_rs::iso::IsoDate { + self.borrow().data().inner.iso_date() } } diff --git a/core/engine/src/builtins/temporal/plain_date_time/mod.rs b/core/engine/src/builtins/temporal/plain_date_time/mod.rs index 9fb73aea04..dceb7c452b 100644 --- a/core/engine/src/builtins/temporal/plain_date_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date_time/mod.rs @@ -20,7 +20,7 @@ use boa_profiler::Profiler; #[cfg(test)] mod tests; -use boa_temporal::{ +use temporal_rs::{ components::{ calendar::{CalendarSlot, GetCalendarSlot}, DateTime as InnerDateTime, diff --git a/core/engine/src/builtins/temporal/plain_month_day/mod.rs b/core/engine/src/builtins/temporal/plain_month_day/mod.rs index 51a2b0978f..3cdc17652b 100644 --- a/core/engine/src/builtins/temporal/plain_month_day/mod.rs +++ b/core/engine/src/builtins/temporal/plain_month_day/mod.rs @@ -12,7 +12,7 @@ use crate::{ use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; -use boa_temporal::{ +use temporal_rs::{ components::{ calendar::{CalendarSlot, GetCalendarSlot}, DateTime, MonthDay as InnerMonthDay, @@ -34,7 +34,7 @@ impl PlainMonthDay { } impl IsoDateSlots for JsObject { - fn iso_date(&self) -> boa_temporal::iso::IsoDate { + fn iso_date(&self) -> temporal_rs::iso::IsoDate { self.borrow().data().inner.iso_date() } } diff --git a/core/engine/src/builtins/temporal/plain_time/mod.rs b/core/engine/src/builtins/temporal/plain_time/mod.rs index b0cf38caf9..7f5af85725 100644 --- a/core/engine/src/builtins/temporal/plain_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_time/mod.rs @@ -16,7 +16,7 @@ use crate::{ use boa_gc::{Finalize, Trace}; use boa_macros::utf16; use boa_profiler::Profiler; -use boa_temporal::{ +use temporal_rs::{ components::Time, options::{ArithmeticOverflow, TemporalRoundingMode}, }; diff --git a/core/engine/src/builtins/temporal/plain_year_month/mod.rs b/core/engine/src/builtins/temporal/plain_year_month/mod.rs index 1f47fcd05a..4fdd69a000 100644 --- a/core/engine/src/builtins/temporal/plain_year_month/mod.rs +++ b/core/engine/src/builtins/temporal/plain_year_month/mod.rs @@ -15,7 +15,7 @@ use boa_profiler::Profiler; use super::calendar::to_temporal_calendar_slot_value; -use boa_temporal::{ +use temporal_rs::{ iso::IsoDateSlots, { components::{ @@ -40,7 +40,7 @@ impl PlainYearMonth { } impl IsoDateSlots for JsObject { - fn iso_date(&self) -> boa_temporal::iso::IsoDate { + fn iso_date(&self) -> temporal_rs::iso::IsoDate { self.borrow().data().inner.iso_date() } } diff --git a/core/engine/src/builtins/temporal/time_zone/custom.rs b/core/engine/src/builtins/temporal/time_zone/custom.rs index 344943b140..9734b3ab6c 100644 --- a/core/engine/src/builtins/temporal/time_zone/custom.rs +++ b/core/engine/src/builtins/temporal/time_zone/custom.rs @@ -2,11 +2,11 @@ use crate::{property::PropertyKey, string::utf16, Context, JsObject, JsValue}; use boa_gc::{Finalize, Trace}; -use boa_temporal::{ +use num_bigint::BigInt; +use temporal_rs::{ components::{tz::TzProtocol, Instant}, TemporalError, TemporalResult, }; -use num_bigint::BigInt; #[derive(Debug, Clone, Trace, Finalize)] pub(crate) struct JsCustomTimeZone { diff --git a/core/engine/src/builtins/temporal/time_zone/mod.rs b/core/engine/src/builtins/temporal/time_zone/mod.rs index d906b54e8a..c152364951 100644 --- a/core/engine/src/builtins/temporal/time_zone/mod.rs +++ b/core/engine/src/builtins/temporal/time_zone/mod.rs @@ -16,7 +16,7 @@ use crate::{ }; use boa_gc::{custom_trace, Finalize, Trace}; use boa_profiler::Profiler; -use boa_temporal::components::tz::TimeZoneSlot; +use temporal_rs::components::tz::TimeZoneSlot; mod custom; @@ -348,7 +348,7 @@ pub(super) fn create_temporal_time_zone( /// [spec]: https://tc39.es/ecma262/#sec-parsetimezoneoffsetstring #[allow(clippy::unnecessary_wraps, unused)] fn parse_timezone_offset_string(offset_string: &str, context: &mut Context) -> JsResult { - use boa_temporal::parser::{Cursor, TemporalTimeZoneString}; + use temporal_rs::parser::{Cursor, TemporalTimeZoneString}; // 1. Let parseResult be ParseText(StringToCodePoints(offsetString), UTCOffset). let parse_result = TemporalTimeZoneString::parse(&mut Cursor::new(offset_string))?; diff --git a/core/engine/src/builtins/temporal/zoned_date_time/mod.rs b/core/engine/src/builtins/temporal/zoned_date_time/mod.rs index d023e04c48..d95f7de02f 100644 --- a/core/engine/src/builtins/temporal/zoned_date_time/mod.rs +++ b/core/engine/src/builtins/temporal/zoned_date_time/mod.rs @@ -9,7 +9,7 @@ use crate::{ }; use boa_gc::{custom_trace, Finalize, Trace}; use boa_profiler::Profiler; -use boa_temporal::components::{ +use temporal_rs::components::{ calendar::CalendarSlot, tz::TimeZoneSlot, Duration as TemporalDuration, ZonedDateTime as InnerZdt, }; diff --git a/core/temporal/ABOUT.md b/core/temporal/ABOUT.md deleted file mode 100644 index 0deb7c9a49..0000000000 --- a/core/temporal/ABOUT.md +++ /dev/null @@ -1,33 +0,0 @@ -# About Boa - -Boa is an open-source, experimental ECMAScript Engine written in Rust for -lexing, parsing and executing ECMAScript/JavaScript. Currently, Boa supports some -of the [language][boa-conformance]. More information can be viewed at [Boa's -website][boa-web]. - -Try out the most recent release with Boa's live demo -[playground][boa-playground]. - -# Boa Crates - -- [**`boa_ast`**][ast] - Boa's ECMAScript Abstract Syntax Tree. -- [**`boa_engine`**][engine] - Boa's implementation of ECMAScript builtin objects and - execution. -- [**`boa_gc`**][gc] - Boa's garbage collector. -- [**`boa_interner`**][interner] - Boa's string interner. -- [**`boa_parser`**][parser] - Boa's lexer and parser. -- [**`boa_profiler`**][profiler] - Boa's code profiler. -- [**`boa_icu_provider`**][icu] - Boa's ICU4X data provider. -- [**`boa_runtime`**][runtime] - Boa's WebAPI features. - -[boa-conformance]: https://boajs.dev/boa/test262/ -[boa-web]: https://boajs.dev/ -[boa-playground]: https://boajs.dev/boa/playground/ -[ast]: https://boajs.dev/boa/doc/boa_ast/index.html -[engine]: https://boajs.dev/boa/doc/boa_engine/index.html -[gc]: https://boajs.dev/boa/doc/boa_gc/index.html -[interner]: https://boajs.dev/boa/doc/boa_interner/index.html -[parser]: https://boajs.dev/boa/doc/boa_parser/index.html -[profiler]: https://boajs.dev/boa/doc/boa_profiler/index.html -[icu]: https://boajs.dev/boa/doc/boa_icu_provider/index.html -[runtime]: https://boajs.dev/boa/doc/boa_runtime/index.html diff --git a/core/temporal/Cargo.toml b/core/temporal/Cargo.toml deleted file mode 100644 index 75f47b33d0..0000000000 --- a/core/temporal/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "boa_temporal" -keywords = ["javascript", "js", "compiler", "temporal", "calendar", "date", "time"] -categories = ["date", "time", "calendars"] -readme = "./README.md" -description.workspace = true -version.workspace = true -edition.workspace = true -authors.workspace = true -license.workspace = true -repository.workspace = true -rust-version.workspace = true - -[dependencies] -tinystr = "0.7.4" -icu_calendar = { workspace = true, default-features = true } -rustc-hash = { workspace = true, features = ["std"] } -num-bigint = { workspace = true, features = ["serde"] } -bitflags.workspace = true -num-traits.workspace = true - -[lints] -workspace = true diff --git a/core/temporal/README.md b/core/temporal/README.md deleted file mode 100644 index 2211542e0f..0000000000 --- a/core/temporal/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Temporal in Rust - -Provides a standard API for working with dates and time. - -IMPORTANT NOTE: The Temporal Proposal is still in Stage 3. As such, this crate should be viewed -as highly experimental until the proposal has been completely standardized and released. - -## Goal - -The intended goal of this crate is to provide an engine agnostic -implementation of `ECMAScript`'s Temporal algorithms. diff --git a/core/temporal/src/components/calendar.rs b/core/temporal/src/components/calendar.rs deleted file mode 100644 index 9543cb8062..0000000000 --- a/core/temporal/src/components/calendar.rs +++ /dev/null @@ -1,1011 +0,0 @@ -//! This module implements the calendar traits and related components. -//! -//! The goal of the calendar module of `boa_temporal` is to provide -//! Temporal compatible calendar implementations. -//! -//! The implementation will only be of calendar's prexisting calendars. This library -//! does not come with a pre-existing `CustomCalendar` (i.e., an object that implements -//! the calendar protocol), but it does aim to provide the necessary tools and API for -//! implementing one. - -use std::str::FromStr; - -use crate::{ - components::{Date, DateTime, Duration, MonthDay, YearMonth}, - iso::{IsoDate, IsoDateSlots}, - options::{ArithmeticOverflow, TemporalUnit}, - TemporalError, TemporalFields, TemporalResult, -}; - -use icu_calendar::{ - types::{Era, MonthCode}, - week::{RelativeUnit, WeekCalculator}, - AnyCalendar, AnyCalendarKind, Calendar, Iso, -}; -use tinystr::TinyAsciiStr; - -/// The ECMAScript defined protocol methods -pub const CALENDAR_PROTOCOL_METHODS: [&str; 21] = [ - "dateAdd", - "dateFromFields", - "dateUntil", - "day", - "dayOfWeek", - "dayOfYear", - "daysInMonth", - "daysInWeek", - "daysInYear", - "fields", - "id", - "inLeapYear", - "mergeFields", - "month", - "monthCode", - "monthDayFromFields", - "monthsInYear", - "weekOfYear", - "year", - "yearMonthFromFields", - "yearOfWeek", -]; - -/// Designate the type of `CalendarFields` needed -#[derive(Debug, Clone, Copy)] -pub enum CalendarFieldsType { - /// Whether the Fields should return for a Date. - Date, - /// Whether the Fields should return for a YearMonth. - YearMonth, - /// Whether the Fields should return for a MonthDay. - MonthDay, -} - -// TODO: Optimize to TinyStr or &str. -impl From<&[String]> for CalendarFieldsType { - fn from(value: &[String]) -> Self { - let year_present = value.contains(&"year".to_owned()); - let day_present = value.contains(&"day".to_owned()); - - if year_present && day_present { - CalendarFieldsType::Date - } else if year_present { - CalendarFieldsType::YearMonth - } else { - CalendarFieldsType::MonthDay - } - } -} - -// NOTE (nekevss): May be worth switching the below to "Custom" `DateLikes`, and -// allow the non-custom to be engine specific types. -// -// enum CalendarDateLike> { -// Date(Date), -// CustomDate(D::Date), -// ... -// } -/// The `DateLike` objects that can be provided to the `CalendarProtocol`. -#[derive(Debug)] -pub enum CalendarDateLike { - /// Represents a user-defined `Date` datelike - CustomDate(C::Date), - /// Represents a user-defined `DateTime` datelike - CustomDateTime(C::DateTime), - /// Represents a user-defined `YearMonth` datelike - CustomYearMonth(C::YearMonth), - /// Represents a user-defined `MonthDay` datelike - CustomMonthDay(C::MonthDay), - /// Represents a `DateTime`. - DateTime(DateTime), - /// Represents a `Date`. - Date(Date), -} - -impl CalendarDateLike { - /// Retrieves the internal `IsoDate` field. - #[inline] - #[must_use] - pub fn as_iso_date(&self) -> IsoDate { - match self { - CalendarDateLike::CustomDate(d) => d.iso_date(), - CalendarDateLike::CustomMonthDay(md) => md.iso_date(), - CalendarDateLike::CustomYearMonth(ym) => ym.iso_date(), - CalendarDateLike::CustomDateTime(dt) => dt.iso_date(), - CalendarDateLike::DateTime(dt) => dt.iso_date(), - CalendarDateLike::Date(d) => d.iso_date(), - } - } -} - -// ==== CalendarProtocol trait ==== - -/// A trait for implementing a Builtin Calendar's Calendar Protocol in Rust. -pub trait CalendarProtocol: Clone { - /// A Custom `Date` Type for an associated `CalendarProtocol`. Default `Date` - type Date: IsoDateSlots + GetCalendarSlot + Clone + core::fmt::Debug; - /// A Custom `DateTime` Type for an associated `CalendarProtocol`. Default `DateTime` - type DateTime: IsoDateSlots + GetCalendarSlot + Clone + core::fmt::Debug; - /// A Custom `YearMonth` Type for an associated `CalendarProtocol`. Default `YearMonth` - type YearMonth: IsoDateSlots + GetCalendarSlot + Clone + core::fmt::Debug; - /// A Custom `MonthDay` Type for an associated `CalendarProtocol`. Default `MonthDay` - type MonthDay: IsoDateSlots + GetCalendarSlot + Clone + core::fmt::Debug; - /// The context passed to every method of the `CalendarProtocol`. - type Context; - - /// Creates a `Temporal.PlainDate` object from provided fields. - fn date_from_fields( - &self, - fields: &mut TemporalFields, - overflow: ArithmeticOverflow, - context: &mut Self::Context, - ) -> TemporalResult>; - /// Creates a `Temporal.PlainYearMonth` object from the provided fields. - fn year_month_from_fields( - &self, - fields: &mut TemporalFields, - overflow: ArithmeticOverflow, - context: &mut Self::Context, - ) -> TemporalResult>; - /// Creates a `Temporal.PlainMonthDay` object from the provided fields. - fn month_day_from_fields( - &self, - fields: &mut TemporalFields, - overflow: ArithmeticOverflow, - context: &mut Self::Context, - ) -> TemporalResult>; - /// Returns a `Temporal.PlainDate` based off an added date. - fn date_add( - &self, - date: &Date, - duration: &Duration, - overflow: ArithmeticOverflow, - context: &mut Self::Context, - ) -> TemporalResult>; - /// Returns a `Temporal.Duration` representing the duration between two dates. - fn date_until( - &self, - one: &Date, - two: &Date, - largest_unit: TemporalUnit, - context: &mut Self::Context, - ) -> TemporalResult; - /// Returns the era for a given `temporaldatelike`. - fn era( - &self, - date_like: &CalendarDateLike, - context: &mut Self::Context, - ) -> TemporalResult>>; - /// Returns the era year for a given `temporaldatelike` - fn era_year( - &self, - date_like: &CalendarDateLike, - context: &mut Self::Context, - ) -> TemporalResult>; - /// Returns the `year` for a given `temporaldatelike` - fn year( - &self, - date_like: &CalendarDateLike, - context: &mut Self::Context, - ) -> TemporalResult; - /// Returns the `month` for a given `temporaldatelike` - fn month( - &self, - date_like: &CalendarDateLike, - context: &mut Self::Context, - ) -> TemporalResult; - // Note: Best practice would probably be to switch to a MonthCode enum after extraction. - /// Returns the `monthCode` for a given `temporaldatelike` - fn month_code( - &self, - date_like: &CalendarDateLike, - context: &mut Self::Context, - ) -> TemporalResult>; - /// Returns the `day` for a given `temporaldatelike` - fn day( - &self, - date_like: &CalendarDateLike, - context: &mut Self::Context, - ) -> TemporalResult; - /// Returns a value representing the day of the week for a date. - fn day_of_week( - &self, - date_like: &CalendarDateLike, - context: &mut Self::Context, - ) -> TemporalResult; - /// Returns a value representing the day of the year for a given calendar. - fn day_of_year( - &self, - date_like: &CalendarDateLike, - context: &mut Self::Context, - ) -> TemporalResult; - /// Returns a value representing the week of the year for a given calendar. - fn week_of_year( - &self, - date_like: &CalendarDateLike, - context: &mut Self::Context, - ) -> TemporalResult; - /// Returns the year of a given week. - fn year_of_week( - &self, - date_like: &CalendarDateLike, - context: &mut Self::Context, - ) -> TemporalResult; - /// Returns the days in a week for a given calendar. - fn days_in_week( - &self, - date_like: &CalendarDateLike, - context: &mut Self::Context, - ) -> TemporalResult; - /// Returns the days in a month for a given calendar. - fn days_in_month( - &self, - date_like: &CalendarDateLike, - context: &mut Self::Context, - ) -> TemporalResult; - /// Returns the days in a year for a given calendar. - fn days_in_year( - &self, - date_like: &CalendarDateLike, - context: &mut Self::Context, - ) -> TemporalResult; - /// Returns the months in a year for a given calendar. - fn months_in_year( - &self, - date_like: &CalendarDateLike, - context: &mut Self::Context, - ) -> TemporalResult; - /// Returns whether a value is within a leap year according to the designated calendar. - fn in_leap_year( - &self, - date_like: &CalendarDateLike, - context: &mut Self::Context, - ) -> TemporalResult; - /// Return the fields for a value. - fn fields( - &self, - fields: Vec, - context: &mut Self::Context, - ) -> TemporalResult>; - /// Merge fields based on the calendar and provided values. - fn merge_fields( - &self, - fields: &TemporalFields, - additional_fields: &TemporalFields, - context: &mut Self::Context, - ) -> TemporalResult; - /// Debug name - fn identifier(&self, context: &mut Self::Context) -> TemporalResult; -} - -/// A trait for retrieving an internal calendar slice. -pub trait GetCalendarSlot { - /// Returns the `CalendarSlot` value of the implementor. - fn get_calendar(&self) -> CalendarSlot; -} - -// NOTE(nekevss): Builtin could be `Rc`, but doing so may -// have an effect on the pattern matching for `CalendarSlot`'s methods. -/// The `[[Calendar]]` field slot of a Temporal Object. -#[derive(Debug)] -pub enum CalendarSlot { - /// The calendar identifier string. - Builtin(AnyCalendar), - /// A `CalendarProtocol` implementation. - Protocol(C), -} - -impl Clone for CalendarSlot { - fn clone(&self) -> Self { - match self { - Self::Builtin(any) => { - let clone = match any { - AnyCalendar::Buddhist(c) => AnyCalendar::Buddhist(*c), - AnyCalendar::Chinese(c) => AnyCalendar::Chinese(c.clone()), - AnyCalendar::Coptic(c) => AnyCalendar::Coptic(*c), - AnyCalendar::Dangi(c) => AnyCalendar::Dangi(c.clone()), - AnyCalendar::Ethiopian(c) => AnyCalendar::Ethiopian(*c), - AnyCalendar::Gregorian(c) => AnyCalendar::Gregorian(*c), - AnyCalendar::Hebrew(c) => AnyCalendar::Hebrew(c.clone()), - AnyCalendar::Indian(c) => AnyCalendar::Indian(*c), - AnyCalendar::IslamicCivil(c) => AnyCalendar::IslamicCivil(c.clone()), - AnyCalendar::IslamicObservational(c) => { - AnyCalendar::IslamicObservational(c.clone()) - } - AnyCalendar::IslamicTabular(c) => AnyCalendar::IslamicTabular(c.clone()), - AnyCalendar::IslamicUmmAlQura(c) => AnyCalendar::IslamicUmmAlQura(c.clone()), - AnyCalendar::Iso(c) => AnyCalendar::Iso(*c), - AnyCalendar::Japanese(c) => AnyCalendar::Japanese(c.clone()), - AnyCalendar::JapaneseExtended(c) => AnyCalendar::JapaneseExtended(c.clone()), - AnyCalendar::Persian(c) => AnyCalendar::Persian(*c), - AnyCalendar::Roc(c) => AnyCalendar::Roc(*c), - _ => unimplemented!("There is a calendar that is missing a clone impl."), - }; - Self::Builtin(clone) - } - - Self::Protocol(proto) => CalendarSlot::Protocol(proto.clone()), - } - } -} - -// `FromStr` essentially serves as a stand in for `IsBuiltinCalendar`. -impl FromStr for CalendarSlot { - type Err = TemporalError; - - fn from_str(s: &str) -> Result { - // NOTE(nekesss): Catch the iso identifier here, as `iso8601` is not a valid ID below. - if s == "iso8601" { - return Ok(CalendarSlot::Builtin(AnyCalendar::Iso(Iso))); - } - - let Some(cal) = AnyCalendarKind::get_for_bcp47_bytes(s.as_bytes()) else { - return Err(TemporalError::range().with_message("Not a builtin calendar.")); - }; - - let any_calendar = AnyCalendar::new(cal); - - Ok(CalendarSlot::Builtin(any_calendar)) - } -} - -impl Default for CalendarSlot { - fn default() -> Self { - Self::Builtin(AnyCalendar::Iso(Iso)) - } -} - -// ==== Public `CalendarSlot` methods ==== - -impl CalendarSlot { - /// Returns whether the current calendar is `ISO` - pub fn is_iso(&self) -> bool { - matches!(self, CalendarSlot::Builtin(AnyCalendar::Iso(_))) - } -} - -// ==== Abstract `CalendarProtocol` Methods ==== - -// NOTE: Below is functionally the `CalendarProtocol` implementation on `CalendarSlot`. - -impl CalendarSlot { - /// `CalendarDateFromFields` - pub fn date_from_fields( - &self, - fields: &mut TemporalFields, - overflow: ArithmeticOverflow, - context: &mut C::Context, - ) -> TemporalResult> { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { - // Resolve month and monthCode; - fields.iso_resolve_month()?; - Date::new( - fields.year().unwrap_or(0), - fields.month().unwrap_or(0), - fields.day().unwrap_or(0), - self.clone(), - overflow, - ) - } - CalendarSlot::Builtin(builtin) => { - // NOTE: This might preemptively throw as `ICU4X` does not support constraining. - // Resolve month and monthCode; - let calendar_date = builtin.date_from_codes( - Era::from(fields.era()), - fields.year().unwrap_or(0), - MonthCode(fields.month_code()), - fields.day().unwrap_or(0) as u8, - )?; - let iso = builtin.date_to_iso(&calendar_date); - Date::new( - iso.year().number, - iso.month().ordinal as i32, - iso.day_of_month().0 as i32, - self.clone(), - overflow, - ) - } - CalendarSlot::Protocol(protocol) => { - protocol.date_from_fields(fields, overflow, context) - } - } - } - - /// `CalendarMonthDayFromFields` - pub fn month_day_from_fields( - &self, - fields: &mut TemporalFields, - overflow: ArithmeticOverflow, - context: &mut C::Context, - ) -> TemporalResult> { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { - fields.iso_resolve_month()?; - MonthDay::new( - fields.month().unwrap_or(0), - fields.day().unwrap_or(0), - self.clone(), - overflow, - ) - } - CalendarSlot::Builtin(_) => { - // TODO: This may get complicated... - // For reference: https://github.com/tc39/proposal-temporal/blob/main/polyfill/lib/calendar.mjs#L1275. - Err(TemporalError::range().with_message("Not yet implemented/supported.")) - } - CalendarSlot::Protocol(protocol) => { - protocol.month_day_from_fields(fields, overflow, context) - } - } - } - - /// `CalendarYearMonthFromFields` - pub fn year_month_from_fields( - &self, - fields: &mut TemporalFields, - overflow: ArithmeticOverflow, - context: &mut C::Context, - ) -> TemporalResult> { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { - fields.iso_resolve_month()?; - YearMonth::new( - fields.year().unwrap_or(0), - fields.month().unwrap_or(0), - fields.day(), - self.clone(), - overflow, - ) - } - CalendarSlot::Builtin(builtin) => { - // NOTE: This might preemptively throw as `ICU4X` does not support regulating. - let calendar_date = builtin.date_from_codes( - Era::from(fields.era()), - fields.year().unwrap_or(0), - MonthCode(fields.month_code()), - fields.day().unwrap_or(1) as u8, - )?; - let iso = builtin.date_to_iso(&calendar_date); - YearMonth::new( - iso.year().number, - iso.month().ordinal as i32, - Some(iso.day_of_month().0 as i32), - self.clone(), - overflow, - ) - } - CalendarSlot::Protocol(protocol) => { - protocol.year_month_from_fields(fields, overflow, context) - } - } - } - - /// `CalendarDateAdd` - pub fn date_add( - &self, - date: &Date, - duration: &Duration, - overflow: ArithmeticOverflow, - context: &mut C::Context, - ) -> TemporalResult> { - match self { - CalendarSlot::Builtin(_) => { - Err(TemporalError::range().with_message("Not yet implemented.")) - } - CalendarSlot::Protocol(protocol) => { - protocol.date_add(date, duration, overflow, context) - } - } - } - - /// `CalendarDateUntil` - pub fn date_until( - &self, - one: &Date, - two: &Date, - largest_unit: TemporalUnit, - context: &mut C::Context, - ) -> TemporalResult { - match self { - CalendarSlot::Builtin(_) => { - Err(TemporalError::range().with_message("Not yet implemented.")) - } - CalendarSlot::Protocol(protocol) => { - protocol.date_until(one, two, largest_unit, context) - } - } - } - - /// `CalendarEra` - pub fn era( - &self, - date_like: &CalendarDateLike, - context: &mut C::Context, - ) -> TemporalResult>> { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(None), - CalendarSlot::Builtin(builtin) => { - let calendar_date = builtin.date_from_iso(date_like.as_iso_date().as_icu4x()?); - Ok(Some(builtin.year(&calendar_date).era.0)) - } - CalendarSlot::Protocol(protocol) => protocol.era(date_like, context), - } - } - - /// `CalendarEraYear` - pub fn era_year( - &self, - date_like: &CalendarDateLike, - context: &mut C::Context, - ) -> TemporalResult> { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(None), - CalendarSlot::Builtin(builtin) => { - let calendar_date = builtin.date_from_iso(date_like.as_iso_date().as_icu4x()?); - Ok(Some(builtin.year(&calendar_date).number)) - } - CalendarSlot::Protocol(protocol) => protocol.era_year(date_like, context), - } - } - - /// `CalendarYear` - pub fn year( - &self, - date_like: &CalendarDateLike, - context: &mut C::Context, - ) -> TemporalResult { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(date_like.as_iso_date().year), - CalendarSlot::Builtin(builtin) => { - let calendar_date = builtin.date_from_iso(date_like.as_iso_date().as_icu4x()?); - Ok(builtin.year(&calendar_date).number) - } - CalendarSlot::Protocol(protocol) => protocol.year(date_like, context), - } - } - - /// `CalendarMonth` - pub fn month( - &self, - date_like: &CalendarDateLike, - context: &mut C::Context, - ) -> TemporalResult { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(date_like.as_iso_date().month), - CalendarSlot::Builtin(_) => { - Err(TemporalError::range().with_message("Not yet implemented.")) - } - CalendarSlot::Protocol(protocol) => protocol.month(date_like, context), - } - } - - /// `CalendarMonthCode` - pub fn month_code( - &self, - date_like: &CalendarDateLike, - context: &mut C::Context, - ) -> TemporalResult> { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { - Ok(date_like.as_iso_date().as_icu4x()?.month().code.0) - } - CalendarSlot::Builtin(_) => { - Err(TemporalError::range().with_message("Not yet implemented.")) - } - CalendarSlot::Protocol(protocol) => protocol.month_code(date_like, context), - } - } - - /// `CalendarDay` - pub fn day( - &self, - date_like: &CalendarDateLike, - context: &mut C::Context, - ) -> TemporalResult { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(date_like.as_iso_date().day), - CalendarSlot::Builtin(_) => { - Err(TemporalError::range().with_message("Not yet implemented.")) - } - CalendarSlot::Protocol(protocol) => protocol.day(date_like, context), - } - } - - /// `CalendarDayOfWeek` - pub fn day_of_week( - &self, - date_like: &CalendarDateLike, - context: &mut C::Context, - ) -> TemporalResult { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { - Ok(date_like.as_iso_date().as_icu4x()?.day_of_week() as u16) - } - CalendarSlot::Builtin(_) => { - Err(TemporalError::range().with_message("Not yet implemented.")) - } - CalendarSlot::Protocol(protocol) => protocol.day_of_week(date_like, context), - } - } - - /// `CalendarDayOfYear` - pub fn day_of_year( - &self, - date_like: &CalendarDateLike, - context: &mut C::Context, - ) -> TemporalResult { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(date_like - .as_iso_date() - .as_icu4x()? - .day_of_year_info() - .day_of_year), - CalendarSlot::Builtin(_) => { - Err(TemporalError::range().with_message("Not yet implemented.")) - } - CalendarSlot::Protocol(protocol) => protocol.day_of_year(date_like, context), - } - } - - /// `CalendarWeekOfYear` - pub fn week_of_year( - &self, - date_like: &CalendarDateLike, - context: &mut C::Context, - ) -> TemporalResult { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { - let date = date_like.as_iso_date().as_icu4x()?; - - let week_calculator = WeekCalculator::default(); - - let week_of = date - .week_of_year(&week_calculator) - .map_err(|err| TemporalError::range().with_message(err.to_string()))?; - - Ok(week_of.week) - } - CalendarSlot::Builtin(_) => { - Err(TemporalError::range().with_message("Not yet implemented.")) - } - CalendarSlot::Protocol(protocol) => protocol.week_of_year(date_like, context), - } - } - - /// `CalendarYearOfWeek` - pub fn year_of_week( - &self, - date_like: &CalendarDateLike, - context: &mut C::Context, - ) -> TemporalResult { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { - let date = date_like.as_iso_date().as_icu4x()?; - - let week_calculator = WeekCalculator::default(); - - let week_of = date - .week_of_year(&week_calculator) - .map_err(|err| TemporalError::range().with_message(err.to_string()))?; - - match week_of.unit { - RelativeUnit::Previous => Ok(date.year().number - 1), - RelativeUnit::Current => Ok(date.year().number), - RelativeUnit::Next => Ok(date.year().number + 1), - } - } - CalendarSlot::Builtin(_) => { - Err(TemporalError::range().with_message("Not yet implemented.")) - } - CalendarSlot::Protocol(protocol) => protocol.year_of_week(date_like, context), - } - } - - /// `CalendarDaysInWeek` - pub fn days_in_week( - &self, - date_like: &CalendarDateLike, - context: &mut C::Context, - ) -> TemporalResult { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(7), - CalendarSlot::Builtin(_) => { - Err(TemporalError::range().with_message("Not yet implemented.")) - } - CalendarSlot::Protocol(protocol) => protocol.days_in_week(date_like, context), - } - } - - /// `CalendarDaysInMonth` - pub fn days_in_month( - &self, - date_like: &CalendarDateLike, - context: &mut C::Context, - ) -> TemporalResult { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { - // NOTE: Cast shouldn't fail in this instance. - Ok(date_like.as_iso_date().as_icu4x()?.day_of_month().0 as u16) - } - CalendarSlot::Builtin(_) => { - Err(TemporalError::range().with_message("Not yet implemented.")) - } - CalendarSlot::Protocol(protocol) => protocol.days_in_month(date_like, context), - } - } - - /// `CalendarDaysInYear` - pub fn days_in_year( - &self, - date_like: &CalendarDateLike, - context: &mut C::Context, - ) -> TemporalResult { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { - Ok(date_like.as_iso_date().as_icu4x()?.days_in_year()) - } - CalendarSlot::Builtin(_) => { - Err(TemporalError::range().with_message("Not yet implemented.")) - } - CalendarSlot::Protocol(protocol) => protocol.days_in_year(date_like, context), - } - } - - /// `CalendarMonthsInYear` - pub fn months_in_year( - &self, - date_like: &CalendarDateLike, - context: &mut C::Context, - ) -> TemporalResult { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(12), - CalendarSlot::Builtin(_) => { - Err(TemporalError::range().with_message("Not yet implemented.")) - } - CalendarSlot::Protocol(protocol) => protocol.months_in_year(date_like, context), - } - } - - /// `CalendarInLeapYear` - pub fn in_leap_year( - &self, - date_like: &CalendarDateLike, - context: &mut C::Context, - ) -> TemporalResult { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { - Ok(date_like.as_iso_date().as_icu4x()?.is_in_leap_year()) - } - CalendarSlot::Builtin(_) => { - Err(TemporalError::range().with_message("Not yet implemented.")) - } - CalendarSlot::Protocol(protocol) => protocol.in_leap_year(date_like, context), - } - } - - /// `CalendarFields` - pub fn fields( - &self, - fields: Vec, - context: &mut C::Context, - ) -> TemporalResult> { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(fields), - CalendarSlot::Builtin(_) => { - Err(TemporalError::range().with_message("Not yet implemented.")) - } - CalendarSlot::Protocol(protocol) => protocol.fields(fields, context), - } - } - - /// `CalendarMergeFields` - pub fn merge_fields( - &self, - fields: &TemporalFields, - additional_fields: &TemporalFields, - context: &mut C::Context, - ) -> TemporalResult { - match self { - CalendarSlot::Builtin(_) => fields.merge_fields(additional_fields, self), - CalendarSlot::Protocol(protocol) => { - protocol.merge_fields(fields, additional_fields, context) - } - } - } - - /// Returns the identifier of this calendar slot. - pub fn identifier(&self, context: &mut C::Context) -> TemporalResult { - match self { - CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(String::from("iso8601")), - CalendarSlot::Builtin(builtin) => Ok(String::from(builtin.debug_name())), - CalendarSlot::Protocol(protocol) => protocol.identifier(context), - } - } -} - -impl CalendarSlot { - /// Returns the designated field descriptors for builtin calendars. - pub fn field_descriptors( - &self, - _fields_type: CalendarFieldsType, - ) -> TemporalResult> { - // NOTE(nekevss): Can be called on a custom. - if let CalendarSlot::Protocol(_) = self { - return Ok(Vec::default()); - } - - // TODO: Research and implement the appropriate descriptors for all `BuiltinCalendars.` - Err(TemporalError::range().with_message("FieldDescriptors is not yet implemented.")) - } - - /// Provides field keys to be ignored depending on the calendar. - pub fn field_keys_to_ignore(&self, _keys: &[String]) -> TemporalResult> { - // TODO: Research and implement the appropriate KeysToIgnore for all `BuiltinCalendars.` - Err(TemporalError::range().with_message("FieldKeysToIgnore is not yet implemented.")) - } - - /// `CalendarResolveFields` - pub fn resolve_fields( - &self, - _fields: &mut TemporalFields, - _typ: CalendarFieldsType, - ) -> TemporalResult<()> { - // TODO: Research and implement the appropriate ResolveFields for all `BuiltinCalendars.` - Err(TemporalError::range().with_message("CalendarResolveFields is not yet implemented.")) - } -} - -impl IsoDateSlots for () { - fn iso_date(&self) -> IsoDate { - unreachable!() - } -} - -/// An empty `CalendarProtocol` implementation on `()`. -/// -/// # Panics -/// -/// Attempting to use this empty calendar implementation as a valid calendar is an error and will cause a panic. -impl CalendarProtocol for () { - type Date = Date<()>; - - type DateTime = DateTime<()>; - - type YearMonth = YearMonth<()>; - - type MonthDay = MonthDay<()>; - - type Context = (); - - fn date_from_fields( - &self, - _: &mut TemporalFields, - _: ArithmeticOverflow, - (): &mut (), - ) -> TemporalResult> { - unreachable!(); - } - - fn month_day_from_fields( - &self, - _: &mut TemporalFields, - _: ArithmeticOverflow, - (): &mut (), - ) -> TemporalResult> { - unreachable!(); - } - - fn year_month_from_fields( - &self, - _: &mut TemporalFields, - _: ArithmeticOverflow, - (): &mut (), - ) -> TemporalResult> { - unreachable!() - } - - fn date_add( - &self, - _: &Date, - _: &Duration, - _: ArithmeticOverflow, - (): &mut (), - ) -> TemporalResult> { - unreachable!(); - } - - fn date_until( - &self, - _: &Date<()>, - _: &Date<()>, - _: TemporalUnit, - (): &mut (), - ) -> TemporalResult { - unreachable!(); - } - - fn era( - &self, - _: &CalendarDateLike, - (): &mut (), - ) -> TemporalResult>> { - unreachable!(); - } - - fn era_year(&self, _: &CalendarDateLike, (): &mut ()) -> TemporalResult> { - unreachable!(); - } - - fn year(&self, _: &CalendarDateLike, (): &mut ()) -> TemporalResult { - unreachable!(); - } - - fn month(&self, _: &CalendarDateLike, (): &mut ()) -> TemporalResult { - unreachable!(); - } - - fn month_code( - &self, - _: &CalendarDateLike, - (): &mut (), - ) -> TemporalResult> { - unreachable!(); - } - - fn day(&self, _: &CalendarDateLike, (): &mut ()) -> TemporalResult { - unreachable!(); - } - - fn day_of_week(&self, _: &CalendarDateLike, (): &mut ()) -> TemporalResult { - unreachable!(); - } - - fn day_of_year(&self, _: &CalendarDateLike, (): &mut ()) -> TemporalResult { - unreachable!(); - } - - fn week_of_year(&self, _: &CalendarDateLike, (): &mut ()) -> TemporalResult { - unreachable!(); - } - - fn year_of_week(&self, _: &CalendarDateLike, (): &mut ()) -> TemporalResult { - unreachable!(); - } - - fn days_in_week(&self, _: &CalendarDateLike, (): &mut ()) -> TemporalResult { - unreachable!(); - } - - fn days_in_month(&self, _: &CalendarDateLike, (): &mut ()) -> TemporalResult { - unreachable!(); - } - - fn days_in_year(&self, _: &CalendarDateLike, (): &mut ()) -> TemporalResult { - unreachable!(); - } - - fn months_in_year(&self, _: &CalendarDateLike, (): &mut ()) -> TemporalResult { - unreachable!(); - } - - fn in_leap_year(&self, _: &CalendarDateLike, (): &mut ()) -> TemporalResult { - unreachable!(); - } - - fn fields(&self, _: Vec, (): &mut ()) -> TemporalResult> { - unreachable!(); - } - - fn merge_fields( - &self, - _: &TemporalFields, - _: &TemporalFields, - (): &mut (), - ) -> TemporalResult { - unreachable!(); - } - - fn identifier(&self, (): &mut ()) -> TemporalResult { - unreachable!(); - } -} diff --git a/core/temporal/src/components/date.rs b/core/temporal/src/components/date.rs deleted file mode 100644 index d6c80eb265..0000000000 --- a/core/temporal/src/components/date.rs +++ /dev/null @@ -1,433 +0,0 @@ -//! This module implements `Date` and any directly related algorithms. - -use tinystr::TinyAsciiStr; - -use crate::{ - components::{ - calendar::{CalendarProtocol, CalendarSlot}, - duration::DateDuration, - DateTime, Duration, - }, - iso::{IsoDate, IsoDateSlots}, - options::{ArithmeticOverflow, TemporalUnit}, - parser::parse_date_time, - TemporalError, TemporalResult, -}; -use std::str::FromStr; - -use super::{ - calendar::{CalendarDateLike, GetCalendarSlot}, - duration::TimeDuration, -}; - -/// The native Rust implementation of `Temporal.PlainDate`. -#[derive(Debug, Default, Clone)] -pub struct Date { - iso: IsoDate, - calendar: CalendarSlot, -} - -// ==== Private API ==== - -impl Date { - /// Create a new `Date` with the date values and calendar slot. - #[inline] - #[must_use] - pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { - Self { iso, calendar } - } - - #[inline] - /// Returns a new moved date and the days associated with that adjustment - pub(crate) fn move_relative_date( - &self, - duration: &Duration, - context: &mut C::Context, - ) -> TemporalResult<(Self, f64)> { - let new_date = - self.contextual_add_date(duration, ArithmeticOverflow::Constrain, context)?; - let days = f64::from(self.days_until(&new_date)); - Ok((new_date, days)) - } -} - -// ==== Public API ==== - -impl Date { - /// Creates a new `Date` while checking for validity. - pub fn new( - year: i32, - month: i32, - day: i32, - calendar: CalendarSlot, - overflow: ArithmeticOverflow, - ) -> TemporalResult { - let iso = IsoDate::new(year, month, day, overflow)?; - Ok(Self::new_unchecked(iso, calendar)) - } - - #[must_use] - /// Creates a `Date` from a `DateTime`. - pub fn from_datetime(dt: &DateTime) -> Self { - Self { - iso: dt.iso_date(), - calendar: dt.calendar().clone(), - } - } - - #[inline] - #[must_use] - /// Returns this `Date`'s ISO year value. - pub const fn iso_year(&self) -> i32 { - self.iso.year - } - - #[inline] - #[must_use] - /// Returns this `Date`'s ISO month value. - pub const fn iso_month(&self) -> u8 { - self.iso.month - } - - #[inline] - #[must_use] - /// Returns this `Date`'s ISO day value. - pub const fn iso_day(&self) -> u8 { - self.iso.day - } - - #[inline] - #[must_use] - /// Returns the `Date`'s inner `IsoDate` record. - pub const fn iso(&self) -> IsoDate { - self.iso - } - - #[inline] - #[must_use] - /// Returns a reference to this `Date`'s calendar slot. - pub fn calendar(&self) -> &CalendarSlot { - &self.calendar - } - - /// 3.5.7 `IsValidISODate` - /// - /// Checks if the current date is a valid `ISODate`. - #[must_use] - pub fn is_valid(&self) -> bool { - self.iso.is_valid() - } - - /// `DaysUntil` - /// - /// Calculates the epoch days between two `Date`s - #[inline] - #[must_use] - pub fn days_until(&self, other: &Self) -> i32 { - other.iso.to_epoch_days() - self.iso.to_epoch_days() - } -} - -// ==== Calendar-derived Public API ==== - -impl Date<()> { - /// Returns the calendar year value. - pub fn year(&self) -> TemporalResult { - self.calendar - .year(&CalendarDateLike::Date(self.clone()), &mut ()) - } - - /// Returns the calendar month value. - pub fn month(&self) -> TemporalResult { - self.calendar - .month(&CalendarDateLike::Date(self.clone()), &mut ()) - } - - /// Returns the calendar month code value. - pub fn month_code(&self) -> TemporalResult> { - self.calendar - .month_code(&CalendarDateLike::Date(self.clone()), &mut ()) - } - - /// Returns the calendar day value. - pub fn day(&self) -> TemporalResult { - self.calendar - .day(&CalendarDateLike::Date(self.clone()), &mut ()) - } - - /// Returns the calendar day of week value. - pub fn day_of_week(&self) -> TemporalResult { - self.calendar - .day_of_week(&CalendarDateLike::Date(self.clone()), &mut ()) - } - - /// Returns the calendar day of year value. - pub fn day_of_year(&self) -> TemporalResult { - self.calendar - .day_of_year(&CalendarDateLike::Date(self.clone()), &mut ()) - } - - /// Returns the calendar week of year value. - pub fn week_of_year(&self) -> TemporalResult { - self.calendar - .week_of_year(&CalendarDateLike::Date(self.clone()), &mut ()) - } - - /// Returns the calendar year of week value. - pub fn year_of_week(&self) -> TemporalResult { - self.calendar - .year_of_week(&CalendarDateLike::Date(self.clone()), &mut ()) - } - - /// Returns the calendar days in week value. - pub fn days_in_week(&self) -> TemporalResult { - self.calendar - .days_in_week(&CalendarDateLike::Date(self.clone()), &mut ()) - } - - /// Returns the calendar days in month value. - pub fn days_in_month(&self) -> TemporalResult { - self.calendar - .days_in_month(&CalendarDateLike::Date(self.clone()), &mut ()) - } - - /// Returns the calendar days in year value. - pub fn days_in_year(&self) -> TemporalResult { - self.calendar - .days_in_year(&CalendarDateLike::Date(self.clone()), &mut ()) - } - - /// Returns the calendar months in year value. - pub fn months_in_year(&self) -> TemporalResult { - self.calendar - .months_in_year(&CalendarDateLike::Date(self.clone()), &mut ()) - } - - /// Returns returns whether the date in a leap year for the given calendar. - pub fn in_leap_year(&self) -> TemporalResult { - self.calendar - .in_leap_year(&CalendarDateLike::Date(self.clone()), &mut ()) - } -} - -// NOTE(nekevss): The clone below should ideally not change the memory address, but that may -// not be true across all cases. I.e., it should be fine as long as the clone is simply a -// reference count increment. Need to test. -impl Date { - /// Returns the calendar year value with provided context. - pub fn contextual_year(this: &C::Date, context: &mut C::Context) -> TemporalResult { - this.get_calendar() - .year(&CalendarDateLike::CustomDate(this.clone()), context) - } - - /// Returns the calendar month value with provided context. - pub fn contextual_month(this: &C::Date, context: &mut C::Context) -> TemporalResult { - this.get_calendar() - .month(&CalendarDateLike::CustomDate(this.clone()), context) - } - - /// Returns the calendar month code value with provided context. - pub fn contextual_month_code( - this: &C::Date, - context: &mut C::Context, - ) -> TemporalResult> { - this.get_calendar() - .month_code(&CalendarDateLike::CustomDate(this.clone()), context) - } - - /// Returns the calendar day value with provided context. - pub fn contextual_day(this: &C::Date, context: &mut C::Context) -> TemporalResult { - this.get_calendar() - .day(&CalendarDateLike::CustomDate(this.clone()), context) - } - - /// Returns the calendar day of week value with provided context. - pub fn contextual_day_of_week(this: &C::Date, context: &mut C::Context) -> TemporalResult { - this.get_calendar() - .day_of_week(&CalendarDateLike::CustomDate(this.clone()), context) - } - - /// Returns the calendar day of year value with provided context. - pub fn contextual_day_of_year(this: &C::Date, context: &mut C::Context) -> TemporalResult { - this.get_calendar() - .day_of_year(&CalendarDateLike::CustomDate(this.clone()), context) - } - - /// Returns the calendar week of year value with provided context. - pub fn contextual_week_of_year( - this: &C::Date, - context: &mut C::Context, - ) -> TemporalResult { - this.get_calendar() - .week_of_year(&CalendarDateLike::CustomDate(this.clone()), context) - } - - /// Returns the calendar year of week value with provided context. - pub fn contextual_year_of_week( - this: &C::Date, - context: &mut C::Context, - ) -> TemporalResult { - this.get_calendar() - .year_of_week(&CalendarDateLike::CustomDate(this.clone()), context) - } - - /// Returns the calendar days in week value with provided context. - pub fn contextual_days_in_week( - this: &C::Date, - context: &mut C::Context, - ) -> TemporalResult { - this.get_calendar() - .days_in_week(&CalendarDateLike::CustomDate(this.clone()), context) - } - - /// Returns the calendar days in month value with provided context. - pub fn contextual_days_in_month( - this: &C::Date, - context: &mut C::Context, - ) -> TemporalResult { - this.get_calendar() - .days_in_month(&CalendarDateLike::CustomDate(this.clone()), context) - } - - /// Returns the calendar days in year value with provided context. - pub fn contextual_days_in_year( - this: &C::Date, - context: &mut C::Context, - ) -> TemporalResult { - this.get_calendar() - .days_in_year(&CalendarDateLike::CustomDate(this.clone()), context) - } - - /// Returns the calendar months in year value with provided context. - pub fn contextual_months_in_year( - this: &C::Date, - context: &mut C::Context, - ) -> TemporalResult { - this.get_calendar() - .months_in_year(&CalendarDateLike::CustomDate(this.clone()), context) - } - - /// Returns whether the date is in a leap year for the given calendar with provided context. - pub fn contextual_in_leap_year( - this: &C::Date, - context: &mut C::Context, - ) -> TemporalResult { - this.get_calendar() - .in_leap_year(&CalendarDateLike::CustomDate(this.clone()), context) - } -} - -impl GetCalendarSlot for Date { - fn get_calendar(&self) -> CalendarSlot { - self.calendar.clone() - } -} - -impl IsoDateSlots for Date { - /// Returns the structs `IsoDate` - fn iso_date(&self) -> IsoDate { - self.iso - } -} - -// ==== Context based API ==== - -impl Date { - /// Returns the date after adding the given duration to date with a provided context. - /// - /// Temporal Equivalent: 3.5.13 `AddDate ( calendar, plainDate, duration [ , options [ , dateAdd ] ] )` - #[inline] - pub fn contextual_add_date( - &self, - duration: &Duration, - overflow: ArithmeticOverflow, - context: &mut C::Context, - ) -> TemporalResult { - // 1. If options is not present, set options to undefined. - // 2. If duration.[[Years]] ≠ 0, or duration.[[Months]] ≠ 0, or duration.[[Weeks]] ≠ 0, then - if duration.date().years() != 0.0 - || duration.date().months() != 0.0 - || duration.date().weeks() != 0.0 - { - // a. If dateAdd is not present, then - // i. Set dateAdd to unused. - // ii. If calendar is an Object, set dateAdd to ? GetMethod(calendar, "dateAdd"). - // b. Return ? CalendarDateAdd(calendar, plainDate, duration, options, dateAdd). - return self.calendar().date_add(self, duration, overflow, context); - } - - // 3. Let overflow be ? ToTemporalOverflow(options). - // 4. Let days be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day").[[Days]]. - let (days, _) = TimeDuration::new_unchecked( - duration.hours(), - duration.minutes(), - duration.seconds(), - duration.milliseconds(), - duration.microseconds(), - duration.nanoseconds(), - ) - .balance(duration.days(), TemporalUnit::Day)?; - - // 5. Let result be ? AddISODate(plainDate.[[ISOYear]], plainDate.[[ISOMonth]], plainDate.[[ISODay]], 0, 0, 0, days, overflow). - let result = self - .iso - .add_iso_date(&DateDuration::new(0f64, 0f64, 0f64, days)?, overflow)?; - - Ok(Self::new_unchecked(result, self.calendar().clone())) - } - - /// Returns a duration representing the difference between the dates one and two with a provided context. - /// - /// Temporal Equivalent: 3.5.6 `DifferenceDate ( calendar, one, two, options )` - #[inline] - pub fn contextual_difference_date( - &self, - other: &Self, - largest_unit: TemporalUnit, - context: &mut C::Context, - ) -> TemporalResult { - if self.iso.year == other.iso.year - && self.iso.month == other.iso.month - && self.iso.day == other.iso.day - { - return Ok(Duration::default()); - } - - if largest_unit == TemporalUnit::Day { - let days = self.days_until(other); - return Ok(Duration::from_date_duration(DateDuration::new( - 0f64, - 0f64, - 0f64, - f64::from(days), - )?)); - } - - self.calendar() - .date_until(self, other, largest_unit, context) - } -} - -// ==== Trait impls ==== - -impl FromStr for Date { - type Err = TemporalError; - - fn from_str(s: &str) -> Result { - let parse_record = parse_date_time(s)?; - - let calendar = parse_record.calendar.unwrap_or("iso8601".to_owned()); - - let date = IsoDate::new( - parse_record.date.year, - parse_record.date.month, - parse_record.date.day, - ArithmeticOverflow::Reject, - )?; - - Ok(Self::new_unchecked( - date, - CalendarSlot::from_str(&calendar)?, - )) - } -} diff --git a/core/temporal/src/components/datetime.rs b/core/temporal/src/components/datetime.rs deleted file mode 100644 index 1a5197c6fb..0000000000 --- a/core/temporal/src/components/datetime.rs +++ /dev/null @@ -1,447 +0,0 @@ -//! This module implements `DateTime` any directly related algorithms. - -use crate::{ - components::{ - calendar::{CalendarProtocol, CalendarSlot}, - Instant, - }, - iso::{IsoDate, IsoDateSlots, IsoDateTime, IsoTime}, - options::ArithmeticOverflow, - parser::parse_date_time, - TemporalError, TemporalResult, -}; - -use std::str::FromStr; -use tinystr::TinyAsciiStr; - -use super::calendar::{CalendarDateLike, GetCalendarSlot}; - -/// The native Rust implementation of `Temporal.PlainDateTime` -#[derive(Debug, Default, Clone)] -pub struct DateTime { - iso: IsoDateTime, - calendar: CalendarSlot, -} - -// ==== Private DateTime API ==== - -impl DateTime { - /// Creates a new unchecked `DateTime`. - #[inline] - #[must_use] - pub(crate) fn new_unchecked(iso: IsoDateTime, calendar: CalendarSlot) -> Self { - Self { iso, calendar } - } - - #[inline] - #[must_use] - /// Utility function for validating `IsoDate`s - fn validate_iso(iso: IsoDate) -> bool { - IsoDateTime::new_unchecked(iso, IsoTime::noon()).is_within_limits() - } - - /// Create a new `DateTime` from an `Instant`. - #[inline] - pub(crate) fn from_instant( - instant: &Instant, - offset: f64, - calendar: CalendarSlot, - ) -> TemporalResult { - let iso = IsoDateTime::from_epoch_nanos(&instant.nanos, offset)?; - Ok(Self { iso, calendar }) - } -} - -// ==== Public DateTime API ==== - -impl DateTime { - /// Creates a new validated `DateTime`. - #[inline] - #[allow(clippy::too_many_arguments)] - pub fn new( - year: i32, - month: i32, - day: i32, - hour: i32, - minute: i32, - second: i32, - millisecond: i32, - microsecond: i32, - nanosecond: i32, - calendar: CalendarSlot, - ) -> TemporalResult { - let iso_date = IsoDate::new(year, month, day, ArithmeticOverflow::Reject)?; - let iso_time = IsoTime::new( - hour, - minute, - second, - millisecond, - microsecond, - nanosecond, - ArithmeticOverflow::Reject, - )?; - Ok(Self::new_unchecked( - IsoDateTime::new(iso_date, iso_time)?, - calendar, - )) - } - - /// Validates whether ISO date slots are within iso limits at noon. - #[inline] - pub fn validate(target: &T) -> bool { - Self::validate_iso(target.iso_date()) - } - - /// Returns this `Date`'s ISO year value. - #[inline] - #[must_use] - pub const fn iso_year(&self) -> i32 { - self.iso.date().year - } - - /// Returns this `Date`'s ISO month value. - #[inline] - #[must_use] - pub const fn iso_month(&self) -> u8 { - self.iso.date().month - } - - /// Returns this `Date`'s ISO day value. - #[inline] - #[must_use] - pub const fn iso_day(&self) -> u8 { - self.iso.date().day - } - - /// Returns the hour value - #[inline] - #[must_use] - pub fn hour(&self) -> u8 { - self.iso.time().hour - } - - /// Returns the minute value - #[inline] - #[must_use] - pub fn minute(&self) -> u8 { - self.iso.time().minute - } - - /// Returns the second value - #[inline] - #[must_use] - pub fn second(&self) -> u8 { - self.iso.time().second - } - - /// Returns the `millisecond` value - #[inline] - #[must_use] - pub fn millisecond(&self) -> u16 { - self.iso.time().millisecond - } - - /// Returns the `microsecond` value - #[inline] - #[must_use] - pub fn microsecond(&self) -> u16 { - self.iso.time().microsecond - } - - /// Returns the `nanosecond` value - #[inline] - #[must_use] - pub fn nanosecond(&self) -> u16 { - self.iso.time().nanosecond - } - - /// Returns the Calendar value. - #[inline] - #[must_use] - pub fn calendar(&self) -> &CalendarSlot { - &self.calendar - } -} - -// ==== Calendar-derived public API ==== - -// TODO: Revert to `DateTime`. -impl DateTime<()> { - /// Returns the calendar year value. - pub fn year(&self) -> TemporalResult { - self.calendar - .year(&CalendarDateLike::DateTime(self.clone()), &mut ()) - } - - /// Returns the calendar month value. - pub fn month(&self) -> TemporalResult { - self.calendar - .month(&CalendarDateLike::DateTime(self.clone()), &mut ()) - } - - /// Returns the calendar month code value. - pub fn month_code(&self) -> TemporalResult> { - self.calendar - .month_code(&CalendarDateLike::DateTime(self.clone()), &mut ()) - } - - /// Returns the calendar day value. - pub fn day(&self) -> TemporalResult { - self.calendar - .day(&CalendarDateLike::DateTime(self.clone()), &mut ()) - } - - /// Returns the calendar day of week value. - pub fn day_of_week(&self) -> TemporalResult { - self.calendar - .day_of_week(&CalendarDateLike::DateTime(self.clone()), &mut ()) - } - - /// Returns the calendar day of year value. - pub fn day_of_year(&self) -> TemporalResult { - self.calendar - .day_of_year(&CalendarDateLike::DateTime(self.clone()), &mut ()) - } - - /// Returns the calendar week of year value. - pub fn week_of_year(&self) -> TemporalResult { - self.calendar - .week_of_year(&CalendarDateLike::DateTime(self.clone()), &mut ()) - } - - /// Returns the calendar year of week value. - pub fn year_of_week(&self) -> TemporalResult { - self.calendar - .year_of_week(&CalendarDateLike::DateTime(self.clone()), &mut ()) - } - - /// Returns the calendar days in week value. - pub fn days_in_week(&self) -> TemporalResult { - self.calendar - .days_in_week(&CalendarDateLike::DateTime(self.clone()), &mut ()) - } - - /// Returns the calendar days in month value. - pub fn days_in_month(&self) -> TemporalResult { - self.calendar - .days_in_month(&CalendarDateLike::DateTime(self.clone()), &mut ()) - } - - /// Returns the calendar days in year value. - pub fn days_in_year(&self) -> TemporalResult { - self.calendar - .days_in_year(&CalendarDateLike::DateTime(self.clone()), &mut ()) - } - - /// Returns the calendar months in year value. - pub fn months_in_year(&self) -> TemporalResult { - self.calendar - .months_in_year(&CalendarDateLike::DateTime(self.clone()), &mut ()) - } - - /// Returns returns whether the date in a leap year for the given calendar. - pub fn in_leap_year(&self) -> TemporalResult { - self.calendar - .in_leap_year(&CalendarDateLike::DateTime(self.clone()), &mut ()) - } -} - -impl DateTime { - /// Returns the calendar year value with provided context. - pub fn contextual_year(this: &C::DateTime, context: &mut C::Context) -> TemporalResult { - this.get_calendar() - .year(&CalendarDateLike::CustomDateTime(this.clone()), context) - } - - /// Returns the calendar month value with provided context. - pub fn contextual_month(this: &C::DateTime, context: &mut C::Context) -> TemporalResult { - this.get_calendar() - .month(&CalendarDateLike::CustomDateTime(this.clone()), context) - } - - /// Returns the calendar month code value with provided context. - pub fn contextual_month_code( - this: &C::DateTime, - context: &mut C::Context, - ) -> TemporalResult> { - this.get_calendar() - .month_code(&CalendarDateLike::CustomDateTime(this.clone()), context) - } - - /// Returns the calendar day value with provided context. - pub fn contextual_day(this: &C::DateTime, context: &mut C::Context) -> TemporalResult { - this.get_calendar() - .day(&CalendarDateLike::CustomDateTime(this.clone()), context) - } - - /// Returns the calendar day of week value with provided context. - pub fn contextual_day_of_week( - this: &C::DateTime, - context: &mut C::Context, - ) -> TemporalResult { - this.get_calendar() - .day_of_week(&CalendarDateLike::CustomDateTime(this.clone()), context) - } - - /// Returns the calendar day of year value with provided context. - pub fn contextual_day_of_year( - this: &C::DateTime, - context: &mut C::Context, - ) -> TemporalResult { - this.get_calendar() - .day_of_year(&CalendarDateLike::CustomDateTime(this.clone()), context) - } - - /// Returns the calendar week of year value with provided context. - pub fn contextual_week_of_year( - this: &C::DateTime, - context: &mut C::Context, - ) -> TemporalResult { - this.get_calendar() - .week_of_year(&CalendarDateLike::CustomDateTime(this.clone()), context) - } - - /// Returns the calendar year of week value with provided context. - pub fn contextual_year_of_week( - this: &C::DateTime, - context: &mut C::Context, - ) -> TemporalResult { - this.get_calendar() - .year_of_week(&CalendarDateLike::CustomDateTime(this.clone()), context) - } - - /// Returns the calendar days in week value with provided context. - pub fn contextual_days_in_week( - this: &C::DateTime, - context: &mut C::Context, - ) -> TemporalResult { - this.get_calendar() - .days_in_week(&CalendarDateLike::CustomDateTime(this.clone()), context) - } - - /// Returns the calendar days in month value with provided context. - pub fn contextual_days_in_month( - this: &C::DateTime, - context: &mut C::Context, - ) -> TemporalResult { - this.get_calendar() - .days_in_month(&CalendarDateLike::CustomDateTime(this.clone()), context) - } - - /// Returns the calendar days in year value with provided context. - pub fn contextual_days_in_year( - this: &C::DateTime, - context: &mut C::Context, - ) -> TemporalResult { - this.get_calendar() - .days_in_year(&CalendarDateLike::CustomDateTime(this.clone()), context) - } - - /// Returns the calendar months in year value with provided context. - pub fn contextual_months_in_year( - this: &C::DateTime, - context: &mut C::Context, - ) -> TemporalResult { - this.get_calendar() - .months_in_year(&CalendarDateLike::CustomDateTime(this.clone()), context) - } - - /// Returns whether the date is in a leap year for the given calendar with provided context. - pub fn contextual_in_leap_year( - this: &C::DateTime, - context: &mut C::Context, - ) -> TemporalResult { - this.get_calendar() - .in_leap_year(&CalendarDateLike::CustomDateTime(this.clone()), context) - } -} - -// ==== Trait impls ==== - -impl GetCalendarSlot for DateTime { - fn get_calendar(&self) -> CalendarSlot { - self.calendar.clone() - } -} - -impl IsoDateSlots for DateTime { - fn iso_date(&self) -> IsoDate { - *self.iso.date() - } -} - -impl FromStr for DateTime { - type Err = TemporalError; - - fn from_str(s: &str) -> Result { - let parse_record = parse_date_time(s)?; - - let calendar = parse_record.calendar.unwrap_or("iso8601".to_owned()); - - let time = if let Some(time) = parse_record.time { - IsoTime::from_components( - i32::from(time.hour), - i32::from(time.minute), - i32::from(time.second), - time.fraction, - )? - } else { - IsoTime::default() - }; - - let date = IsoDate::new( - parse_record.date.year, - parse_record.date.month, - parse_record.date.day, - ArithmeticOverflow::Reject, - )?; - - Ok(Self::new_unchecked( - IsoDateTime::new(date, time)?, - CalendarSlot::from_str(&calendar)?, - )) - } -} - -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use crate::components::calendar::CalendarSlot; - - use super::DateTime; - - #[test] - #[allow(clippy::float_cmp)] - fn plain_date_time_limits() { - // This test is primarily to assert that the `expect` in the epoch methods is - // valid, i.e., a valid instant is within the range of an f64. - let negative_limit = DateTime::<()>::new( - -271_821, - 4, - 19, - 0, - 0, - 0, - 0, - 0, - 0, - CalendarSlot::from_str("iso8601").unwrap(), - ); - let positive_limit = DateTime::<()>::new( - 275_760, - 9, - 14, - 0, - 0, - 0, - 0, - 0, - 0, - CalendarSlot::from_str("iso8601").unwrap(), - ); - - assert!(negative_limit.is_err()); - assert!(positive_limit.is_err()); - } -} diff --git a/core/temporal/src/components/duration.rs b/core/temporal/src/components/duration.rs deleted file mode 100644 index 13205dc465..0000000000 --- a/core/temporal/src/components/duration.rs +++ /dev/null @@ -1,1022 +0,0 @@ -//! This module implements `Duration` along with it's methods and components. - -use crate::{ - components::{Date, DateTime, ZonedDateTime}, - options::{ArithmeticOverflow, TemporalRoundingMode, TemporalUnit}, - parser::{duration::parse_duration, Cursor}, - TemporalError, TemporalResult, -}; -use std::str::FromStr; - -use super::{calendar::CalendarProtocol, tz::TzProtocol}; - -mod date; -mod time; - -#[doc(inline)] -pub use date::DateDuration; -#[doc(inline)] -pub use time::TimeDuration; - -/// The native Rust implementation of `Temporal.Duration`. -/// -/// `Duration` is made up of a `DateDuration` and `TimeDuration` as primarily -/// defined by Abtract Operation 7.5.1-5. -#[derive(Debug, Clone, Copy, Default)] -pub struct Duration { - date: DateDuration, - time: TimeDuration, -} - -// NOTE(nekevss): Structure of the below is going to be a little convoluted, -// but intended to section everything based on the below -// -// Notation - [section](sub-section(s)). -// -// Sections: -// - Creation (private/public) -// - Getters/Setters -// - Methods (private/public/feature) -// - -// ==== Private Creation methods ==== - -impl Duration { - /// Creates a new `Duration` from a `DateDuration` and `TimeDuration`. - pub(crate) const fn new_unchecked(date: DateDuration, time: TimeDuration) -> Self { - Self { date, time } - } - - /// Utility function to create a year duration. - pub(crate) fn one_year(year_value: f64) -> Self { - Self::from_date_duration(DateDuration::new_unchecked(year_value, 0f64, 0f64, 0f64)) - } - - /// Utility function to create a month duration. - pub(crate) fn one_month(month_value: f64) -> Self { - Self::from_date_duration(DateDuration::new_unchecked(0f64, month_value, 0f64, 0f64)) - } - - /// Utility function to create a week duration. - pub(crate) fn one_week(week_value: f64) -> Self { - Self::from_date_duration(DateDuration::new_unchecked(0f64, 0f64, week_value, 0f64)) - } -} - -// ==== Public Duration API ==== - -impl Duration { - /// Creates a new validated `Duration`. - #[allow(clippy::too_many_arguments)] - pub fn new( - years: f64, - months: f64, - weeks: f64, - days: f64, - hours: f64, - minutes: f64, - seconds: f64, - milliseconds: f64, - microseconds: f64, - nanoseconds: f64, - ) -> TemporalResult { - let duration = Self::new_unchecked( - DateDuration::new_unchecked(years, months, weeks, days), - TimeDuration::new_unchecked( - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - ), - ); - if !is_valid_duration(&duration.into_iter().collect()) { - return Err(TemporalError::range().with_message("Duration was not valid.")); - } - Ok(duration) - } - - /// Creates a partial `Duration` with all fields set to `NaN`. - #[must_use] - pub const fn partial() -> Self { - Self { - date: DateDuration::partial(), - time: TimeDuration::partial(), - } - } - - /// Creates a `Duration` from only a `DateDuration`. - #[must_use] - pub fn from_date_duration(date: DateDuration) -> Self { - Self { - date, - time: TimeDuration::default(), - } - } - - /// Creates a `Duration` from a provided a day and a `TimeDuration`. - /// - /// Note: `TimeDuration` records can store a day value to deal with overflow. - #[must_use] - pub fn from_day_and_time(day: f64, time: TimeDuration) -> Self { - Self { - date: DateDuration::new_unchecked(0.0, 0.0, 0.0, day), - time, - } - } - - /// Creates a new valid `Duration` from a partial `Duration`. - pub fn from_partial(partial: &Duration) -> TemporalResult { - let duration = Self { - date: DateDuration::from_partial(partial.date()), - time: TimeDuration::from_partial(partial.time()), - }; - if !is_valid_duration(&duration.into_iter().collect()) { - return Err(TemporalError::range().with_message("Duration was not valid.")); - } - Ok(duration) - } - - /// Return if the Durations values are within their valid ranges. - #[inline] - #[must_use] - pub fn is_time_within_range(&self) -> bool { - self.time.is_within_range() - } - - /// Returns whether `Duration`'s `DateDuration` isn't empty and is therefore a `DateDuration` or `Duration`. - #[inline] - #[must_use] - pub fn is_date_duration(&self) -> bool { - self.date().iter().any(|x| x != 0.0) && self.time().iter().all(|x| x == 0.0) - } - - /// Returns whether `Duration`'s `DateDuration` is empty and is therefore a `TimeDuration`. - #[inline] - #[must_use] - pub fn is_time_duration(&self) -> bool { - self.time().iter().any(|x| x != 0.0) && self.date().iter().all(|x| x == 0.0) - } -} - -// ==== Public `Duration` Getters/Setters ==== - -impl Duration { - /// Returns a reference to the inner `TimeDuration` - #[inline] - #[must_use] - pub fn time(&self) -> &TimeDuration { - &self.time - } - - /// Returns a reference to the inner `DateDuration` - #[inline] - #[must_use] - pub fn date(&self) -> &DateDuration { - &self.date - } - - /// Set this `DurationRecord`'s `TimeDuration`. - #[inline] - pub fn set_time_duration(&mut self, time: TimeDuration) { - self.time = time; - } - - /// Set the value for `years`. - #[inline] - pub fn set_years(&mut self, y: f64) { - self.date.years = y; - } - - /// Returns the `years` field of duration. - #[inline] - #[must_use] - pub const fn years(&self) -> f64 { - self.date.years - } - - /// Set the value for `months`. - #[inline] - pub fn set_months(&mut self, mo: f64) { - self.date.months = mo; - } - - /// Returns the `months` field of duration. - #[inline] - #[must_use] - pub const fn months(&self) -> f64 { - self.date.months - } - - /// Set the value for `weeks`. - #[inline] - pub fn set_weeks(&mut self, w: f64) { - self.date.weeks = w; - } - - /// Returns the `weeks` field of duration. - #[inline] - #[must_use] - pub const fn weeks(&self) -> f64 { - self.date.weeks - } - - /// Set the value for `days`. - #[inline] - pub fn set_days(&mut self, d: f64) { - self.date.days = d; - } - - /// Returns the `weeks` field of duration. - #[inline] - #[must_use] - pub const fn days(&self) -> f64 { - self.date.days - } - - /// Set the value for `hours`. - #[inline] - pub fn set_hours(&mut self, h: f64) { - self.time.hours = h; - } - - /// Returns the `hours` field of duration. - #[inline] - #[must_use] - pub const fn hours(&self) -> f64 { - self.time.hours - } - - /// Set the value for `minutes`. - #[inline] - pub fn set_minutes(&mut self, m: f64) { - self.time.minutes = m; - } - - /// Returns the `hours` field of duration. - #[inline] - #[must_use] - pub const fn minutes(&self) -> f64 { - self.time.minutes - } - - /// Set the value for `seconds`. - #[inline] - pub fn set_seconds(&mut self, s: f64) { - self.time.seconds = s; - } - - /// Returns the `seconds` field of duration. - #[inline] - #[must_use] - pub const fn seconds(&self) -> f64 { - self.time.seconds - } - - /// Set the value for `milliseconds`. - #[inline] - pub fn set_milliseconds(&mut self, ms: f64) { - self.time.milliseconds = ms; - } - - /// Returns the `hours` field of duration. - #[inline] - #[must_use] - pub const fn milliseconds(&self) -> f64 { - self.time.milliseconds - } - - /// Set the value for `microseconds`. - #[inline] - pub fn set_microseconds(&mut self, mis: f64) { - self.time.microseconds = mis; - } - - /// Returns the `microseconds` field of duration. - #[inline] - #[must_use] - pub const fn microseconds(&self) -> f64 { - self.time.microseconds - } - - /// Set the value for `nanoseconds`. - #[inline] - pub fn set_nanoseconds(&mut self, ns: f64) { - self.time.nanoseconds = ns; - } - - /// Returns the `nanoseconds` field of duration. - #[inline] - #[must_use] - pub const fn nanoseconds(&self) -> f64 { - self.time.nanoseconds - } - - /// Returns `Duration`'s iterator - #[must_use] - pub fn iter(&self) -> DurationIter<'_> { - <&Self as IntoIterator>::into_iter(self) - } -} - -impl<'a> IntoIterator for &'a Duration { - type Item = f64; - type IntoIter = DurationIter<'a>; - - fn into_iter(self) -> Self::IntoIter { - DurationIter { - duration: self, - index: 0, - } - } -} - -/// A Duration iterator that iterates through all duration fields. -#[derive(Debug)] -pub struct DurationIter<'a> { - duration: &'a Duration, - index: usize, -} - -impl Iterator for DurationIter<'_> { - type Item = f64; - - fn next(&mut self) -> Option { - let result = match self.index { - 0 => Some(self.duration.date.years()), - 1 => Some(self.duration.date.months()), - 2 => Some(self.duration.date.weeks()), - 3 => Some(self.duration.date.days()), - 4 => Some(self.duration.time.hours()), - 5 => Some(self.duration.time.minutes()), - 6 => Some(self.duration.time.seconds()), - 7 => Some(self.duration.time.milliseconds()), - 8 => Some(self.duration.time.microseconds()), - 9 => Some(self.duration.time.nanoseconds()), - _ => None, - }; - self.index += 1; - result - } -} - -// ==== Private Duration methods ==== - -impl Duration { - /// 7.5.21 `UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit, plainRelativeTo )` - #[allow(dead_code)] - pub(crate) fn unbalance_duration_relative( - &self, - largest_unit: TemporalUnit, - plain_relative_to: Option<&Date>, - context: &mut C::Context, - ) -> TemporalResult { - // 1. Let allZero be false. - // 2. If years = 0, and months = 0, and weeks = 0, and days = 0, set allZero to true. - let all_zero = self.date.years == 0_f64 - && self.date.months == 0_f64 - && self.date.weeks == 0_f64 - && self.date.days == 0_f64; - - // 3. If largestUnit is "year" or allZero is true, then - if largest_unit == TemporalUnit::Year || all_zero { - // a. Return ! CreateDateDurationRecord(years, months, weeks, days). - return Ok(self.date); - }; - - // 4. Let sign be ! DurationSign(years, months, weeks, days, 0, 0, 0, 0, 0, 0). - // 5. Assert: sign ≠ 0. - let sign = f64::from(self.duration_sign()); - - // 6. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0). - let one_year = Self::one_year(sign); - // 7. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). - let one_month = Self::one_month(sign); - - // 9. If plainRelativeTo is not undefined, then - // a. Let calendar be plainRelativeTo.[[Calendar]]. - // 10. Else, - // a. Let calendar be undefined. - - // 11. If largestUnit is "month", then - if largest_unit == TemporalUnit::Month { - // a. If years = 0, return ! CreateDateDurationRecord(0, months, weeks, days). - if self.date.years == 0f64 { - return DateDuration::new(0f64, self.date.months, self.date.weeks, self.date.days); - } - - // b. If calendar is undefined, then - let Some(mut plain_relative_to) = plain_relative_to.map(Clone::clone) else { - // i. Throw a RangeError exception. - return Err(TemporalError::range().with_message("Calendar cannot be undefined.")); - }; - - // c. If calendar is an Object, then - // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // ii. Let dateUntil be ? GetMethod(calendar, "dateUntil"). - // d. Else, - // i. Let dateAdd be unused. - // ii. Let dateUntil be unused. - - let mut years = self.date.years; - let mut months = self.date.months; - // e. Repeat, while years ≠ 0, - while years != 0f64 { - // i. Let newRelativeTo be ? CalendarDateAdd(calendar, plainRelativeTo, oneYear, undefined, dateAdd). - let new_relative_to = plain_relative_to.calendar().date_add( - &plain_relative_to, - &one_year, - ArithmeticOverflow::Constrain, - context, - )?; - - // ii. Let untilOptions be OrdinaryObjectCreate(null). - // iii. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "month"). - // iv. Let untilResult be ? CalendarDateUntil(calendar, plainRelativeTo, newRelativeTo, untilOptions, dateUntil). - let until_result = plain_relative_to.calendar().date_until( - &plain_relative_to, - &new_relative_to, - TemporalUnit::Month, - context, - )?; - - // v. Let oneYearMonths be untilResult.[[Months]]. - let one_year_months = until_result.date.months; - - // vi. Set plainRelativeTo to newRelativeTo. - plain_relative_to = new_relative_to; - - // vii. Set years to years - sign. - years -= sign; - // viii. Set months to months + oneYearMonths. - months += one_year_months; - } - // f. Return ? CreateDateDurationRecord(0, months, weeks, days). - return DateDuration::new(years, months, self.date.weeks, self.date.days); - - // 12. If largestUnit is "week", then - } else if largest_unit == TemporalUnit::Week { - // a. If years = 0 and months = 0, return ! CreateDateDurationRecord(0, 0, weeks, days). - if self.date.years == 0f64 && self.date.months == 0f64 { - return DateDuration::new(0f64, 0f64, self.date.weeks, self.date.days); - } - - // b. If calendar is undefined, then - let Some(mut plain_relative_to) = plain_relative_to.map(Clone::clone) else { - // i. Throw a RangeError exception. - return Err(TemporalError::range().with_message("Calendar cannot be undefined.")); - }; - - // c. If calendar is an Object, then - // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // d. Else, - // i. Let dateAdd be unused. - - let mut years = self.date.years; - let mut days = self.date.days; - // e. Repeat, while years ≠ 0, - while years != 0f64 { - // i. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneYear, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_year, context)?; - - // ii. Set plainRelativeTo to moveResult.[[RelativeTo]]. - plain_relative_to = move_result.0; - // iii. Set days to days + moveResult.[[Days]]. - days += move_result.1; - // iv. Set years to years - sign. - years -= sign; - } - - let mut months = self.date.months; - // f. Repeat, while months ≠ 0, - while months != 0f64 { - // i. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_month, context)?; - // ii. Set plainRelativeTo to moveResult.[[RelativeTo]]. - plain_relative_to = move_result.0; - // iii. Set days to days + moveResult.[[Days]]. - days += move_result.1; - // iv. Set months to months - sign. - months -= sign; - } - // g. Return ? CreateDateDurationRecord(0, 0, weeks, days). - return DateDuration::new(0f64, 0f64, self.date.weeks(), days); - } - - // 13. If years = 0, and months = 0, and weeks = 0, return ! CreateDateDurationRecord(0, 0, 0, days). - if self.date.years == 0f64 && self.date.months == 0f64 && self.date.weeks == 0f64 { - return DateDuration::new(0f64, 0f64, 0f64, self.date.days); - } - - // NOTE: Move 8 down to past 13 as we only use one_week after making it past 13. - // 8. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0). - let one_week = Self::one_week(sign); - - // 14. If calendar is undefined, then - let Some(mut plain_relative_to) = plain_relative_to.map(Clone::clone) else { - // a. Throw a RangeError exception. - return Err(TemporalError::range().with_message("Calendar cannot be undefined.")); - }; - - // 15. If calendar is an Object, then - // a. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // 16. Else, - // a. Let dateAdd be unused. - - let mut years = self.date.years; - let mut days = self.date.days; - // a. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneYear, dateAdd). - while years != 0f64 { - // a. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneYear, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_year, context)?; - - // b. Set plainRelativeTo to moveResult.[[RelativeTo]]. - plain_relative_to = move_result.0; - // c. Set days to days + moveResult.[[Days]]. - days += move_result.1; - // d. Set years to years - sign. - years -= sign; - } - - let mut months = self.date.months; - // 18. Repeat, while months ≠ 0, - while months != 0f64 { - // a. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_month, context)?; - // b. Set plainRelativeTo to moveResult.[[RelativeTo]]. - plain_relative_to = move_result.0; - // c. Set days to days +moveResult.[[Days]]. - days += move_result.1; - // d. Set months to months - sign. - months -= sign; - } - - let mut weeks = self.date.weeks; - // 19. Repeat, while weeks ≠ 0, - while weeks != 0f64 { - // a. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneWeek, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_week, context)?; - // b. Set plainRelativeTo to moveResult.[[RelativeTo]]. - plain_relative_to = move_result.0; - // c. Set days to days + moveResult.[[Days]]. - days += move_result.1; - // d. Set weeks to weeks - sign. - weeks -= sign; - } - - // 20. Return ? CreateDateDurationRecord(0, 0, 0, days). - DateDuration::new(0f64, 0f64, 0f64, days) - } - - // TODO: Move to DateDuration - /// `BalanceDateDurationRelative` - #[allow(unused)] - pub fn balance_date_duration_relative( - &self, - largest_unit: TemporalUnit, - plain_relative_to: Option<&Date>, - context: &mut C::Context, - ) -> TemporalResult { - let mut result = self.date; - - // 1. Let allZero be false. - // 2. If years = 0, and months = 0, and weeks = 0, and days = 0, set allZero to true. - let all_zero = self.date.years == 0.0 - && self.date.months == 0.0 - && self.date.weeks == 0.0 - && self.date.days == 0.0; - - // 3. If largestUnit is not one of "year", "month", or "week", or allZero is true, then - match largest_unit { - TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week if !all_zero => {} - _ => { - // a. Return ! CreateDateDurationRecord(years, months, weeks, days). - return Ok(result); - } - } - - // 4. If plainRelativeTo is undefined, then - let Some(mut plain_relative_to) = plain_relative_to.map(Clone::clone) else { - // a. Throw a RangeError exception. - return Err(TemporalError::range().with_message("relativeTo cannot be undefined.")); - }; - - // 5. Let sign be ! DurationSign(years, months, weeks, days, 0, 0, 0, 0, 0, 0). - // 6. Assert: sign ≠ 0. - let sign = f64::from(self.duration_sign()); - - // 7. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0). - let one_year = Self::one_year(sign); - // 8. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). - let one_month = Self::one_month(sign); - // 9. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0). - let one_week = Self::one_week(sign); - - // 10. Let calendar be relativeTo.[[Calendar]]. - - match largest_unit { - // 12. If largestUnit is "year", then - TemporalUnit::Year => { - // a. If calendar is an Object, then - // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // b. Else, - // i. Let dateAdd be unused. - - // c. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd). - // d. Let newRelativeTo be moveResult.[[RelativeTo]]. - // e. Let oneYearDays be moveResult.[[Days]]. - let (mut new_relative_to, mut one_year_days) = - plain_relative_to.move_relative_date(&one_year, context)?; - - // f. Repeat, while abs(days) ≥ abs(oneYearDays), - while result.days().abs() >= one_year_days.abs() { - // i. Set days to days - oneYearDays. - result.days -= one_year_days; - - // ii. Set years to years + sign. - result.years += sign; - - // iii. Set relativeTo to newRelativeTo. - plain_relative_to = new_relative_to; - // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_year, context)?; - - // v. Set newRelativeTo to moveResult.[[RelativeTo]]. - new_relative_to = move_result.0; - // vi. Set oneYearDays to moveResult.[[Days]]. - one_year_days = move_result.1; - } - - // g. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). - // h. Set newRelativeTo to moveResult.[[RelativeTo]]. - // i. Let oneMonthDays be moveResult.[[Days]]. - let (mut new_relative_to, mut one_month_days) = - plain_relative_to.move_relative_date(&one_month, context)?; - - // j. Repeat, while abs(days) ≥ abs(oneMonthDays), - while result.days().abs() >= one_month_days.abs() { - // i. Set days to days - oneMonthDays. - result.days -= one_month_days; - // ii. Set months to months + sign. - result.months += sign; - - // iii. Set relativeTo to newRelativeTo. - plain_relative_to = new_relative_to; - // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_month, context)?; - - // v. Set newRelativeTo to moveResult.[[RelativeTo]]. - new_relative_to = move_result.0; - // vi. Set oneMonthDays to moveResult.[[Days]]. - one_month_days = move_result.1; - } - - // k. Set newRelativeTo to ? CalendarDateAdd(calendar, relativeTo, oneYear, undefined, dateAdd). - new_relative_to = plain_relative_to.calendar().date_add( - &plain_relative_to, - &one_year, - ArithmeticOverflow::Constrain, - context, - )?; - - // l. If calendar is an Object, then - // i. Let dateUntil be ? GetMethod(calendar, "dateUntil"). - // m. Else, - // i. Let dateUntil be unused. - // n. Let untilOptions be OrdinaryObjectCreate(null). - // o. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "month"). - - // p. Let untilResult be ? CalendarDateUntil(calendar, relativeTo, newRelativeTo, untilOptions, dateUntil). - let mut until_result = plain_relative_to.calendar().date_until( - &plain_relative_to, - &new_relative_to, - TemporalUnit::Month, - context, - )?; - - // q. Let oneYearMonths be untilResult.[[Months]]. - let mut one_year_months = until_result.date.months(); - - // r. Repeat, while abs(months) ≥ abs(oneYearMonths), - while result.months().abs() >= one_year_months.abs() { - // i. Set months to months - oneYearMonths. - result.months -= one_year_months; - // ii. Set years to years + sign. - result.years += sign; - - // iii. Set relativeTo to newRelativeTo. - plain_relative_to = new_relative_to; - - // iv. Set newRelativeTo to ? CalendarDateAdd(calendar, relativeTo, oneYear, undefined, dateAdd). - new_relative_to = plain_relative_to.calendar().date_add( - &plain_relative_to, - &one_year, - ArithmeticOverflow::Constrain, - context, - )?; - - // v. Set untilOptions to OrdinaryObjectCreate(null). - // vi. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "month"). - // vii. Set untilResult to ? CalendarDateUntil(calendar, relativeTo, newRelativeTo, untilOptions, dateUntil). - until_result = plain_relative_to.calendar().date_until( - &plain_relative_to, - &new_relative_to, - TemporalUnit::Month, - context, - )?; - - // viii. Set oneYearMonths to untilResult.[[Months]]. - one_year_months = until_result.date.months(); - } - } - // 13. Else if largestUnit is "month", then - TemporalUnit::Month => { - // a. If calendar is an Object, then - // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // b. Else, - // i. Let dateAdd be unused. - - // c. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). - // d. Let newRelativeTo be moveResult.[[RelativeTo]]. - // e. Let oneMonthDays be moveResult.[[Days]]. - let (mut new_relative_to, mut one_month_days) = - plain_relative_to.move_relative_date(&one_month, context)?; - - // f. Repeat, while abs(days) ≥ abs(oneMonthDays), - while result.days().abs() >= one_month_days.abs() { - // i. Set days to days - oneMonthDays. - result.days -= one_month_days; - - // ii. Set months to months + sign. - result.months += sign; - - // iii. Set relativeTo to newRelativeTo. - plain_relative_to = new_relative_to; - - // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_month, context)?; - - // v. Set newRelativeTo to moveResult.[[RelativeTo]]. - new_relative_to = move_result.0; - // vi. Set oneMonthDays to moveResult.[[Days]]. - one_month_days = move_result.1; - } - } - // 14. Else, - TemporalUnit::Week => { - // a. Assert: largestUnit is "week". - // b. If calendar is an Object, then - // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // c. Else, - // i. Let dateAdd be unused. - - // d. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd). - // e. Let newRelativeTo be moveResult.[[RelativeTo]]. - // f. Let oneWeekDays be moveResult.[[Days]]. - let (mut new_relative_to, mut one_week_days) = - plain_relative_to.move_relative_date(&one_week, context)?; - - // g. Repeat, while abs(days) ≥ abs(oneWeekDays), - while result.days().abs() >= one_week_days.abs() { - // i. Set days to days - oneWeekDays. - result.days -= one_week_days; - // ii. Set weeks to weeks + sign. - result.weeks += sign; - // iii. Set relativeTo to newRelativeTo. - plain_relative_to = new_relative_to; - // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_week, context)?; - - // v. Set newRelativeTo to moveResult.[[RelativeTo]]. - new_relative_to = move_result.0; - // vi. Set oneWeekDays to moveResult.[[Days]]. - one_week_days = move_result.1; - } - } - _ => unreachable!(), - } - - // 15. Return ! CreateDateDurationRecord(years, months, weeks, days). - Ok(result) - } - - // TODO: Refactor relative_to's into a RelativeTo struct? - /// Abstract Operation 7.5.26 `RoundDuration ( years, months, weeks, days, hours, minutes, - /// seconds, milliseconds, microseconds, nanoseconds, increment, unit, - /// roundingMode [ , plainRelativeTo [, zonedRelativeTo [, precalculatedDateTime]]] )` - #[allow(clippy::type_complexity)] - pub fn round_duration( - &self, - increment: f64, - unit: TemporalUnit, - rounding_mode: TemporalRoundingMode, - relative_targets: ( - Option<&Date>, - Option<&ZonedDateTime>, - Option<&DateTime>, - ), - context: &mut C::Context, - ) -> TemporalResult<(Self, f64)> { - match unit { - TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week | TemporalUnit::Day => { - let round_result = self.date().round( - Some(self.time), - increment, - unit, - rounding_mode, - relative_targets, - context, - )?; - let result = Self::from_date_duration(round_result.0); - Ok((result, round_result.1)) - } - TemporalUnit::Hour - | TemporalUnit::Minute - | TemporalUnit::Second - | TemporalUnit::Millisecond - | TemporalUnit::Microsecond - | TemporalUnit::Nanosecond => { - let round_result = self.time().round(increment, unit, rounding_mode)?; - let result = Self { - date: self.date, - time: round_result.0, - }; - Ok((result, round_result.1)) - } - TemporalUnit::Auto => { - Err(TemporalError::range().with_message("Invalid TemporalUnit for Duration.round")) - } - } - - // 18. Let duration be ? CreateDurationRecord(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - // 19. Return the Record { [[DurationRecord]]: duration, [[Total]]: total }. - } -} - -// ==== Public Duration methods ==== - -impl Duration { - /// Returns the absolute value of `Duration`. - #[inline] - #[must_use] - pub fn abs(&self) -> Self { - Self { - date: self.date().abs(), - time: self.time().abs(), - } - } - - /// 7.5.10 `DurationSign ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds )` - /// - /// Determines the sign for the current self. - #[inline] - #[must_use] - pub fn duration_sign(&self) -> i32 { - duration_sign(&self.into_iter().collect()) - } - - /// 7.5.12 `DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds )` - #[inline] - #[must_use] - pub fn default_temporal_largest_unit(&self) -> TemporalUnit { - for (index, value) in self.into_iter().enumerate() { - if value == 0f64 { - continue; - } - - match index { - 0 => return TemporalUnit::Year, - 1 => return TemporalUnit::Month, - 2 => return TemporalUnit::Week, - 3 => return TemporalUnit::Day, - 4 => return TemporalUnit::Hour, - 5 => return TemporalUnit::Minute, - 6 => return TemporalUnit::Second, - 7 => return TemporalUnit::Millisecond, - 8 => return TemporalUnit::Microsecond, - _ => {} - } - } - - TemporalUnit::Nanosecond - } - - /// Calls `TimeDuration`'s balance method on the current `Duration`. - #[inline] - pub fn balance_time_duration(&self, unit: TemporalUnit) -> TemporalResult<(f64, TimeDuration)> { - TimeDuration::new_unchecked( - self.hours(), - self.minutes(), - self.seconds(), - self.milliseconds(), - self.microseconds(), - self.nanoseconds(), - ) - .balance(self.days(), unit) - } -} - -/// Utility function to check whether the `Duration` fields are valid. -#[inline] -#[must_use] -pub(crate) fn is_valid_duration(set: &Vec) -> bool { - // 1. Let sign be ! DurationSign(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - let sign = duration_sign(set); - // 2. For each value v of « years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds », do - for v in set { - // a. If 𝔽(v) is not finite, return false. - if !v.is_finite() { - return false; - } - // b. If v < 0 and sign > 0, return false. - if *v < 0f64 && sign > 0 { - return false; - } - // c. If v > 0 and sign < 0, return false. - if *v > 0f64 && sign < 0 { - return false; - } - } - // 3. Return true. - true -} - -/// Utility function for determining the sign for the current set of `Duration` fields. -#[inline] -#[must_use] -fn duration_sign(set: &Vec) -> i32 { - // 1. For each value v of « years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds », do - for v in set { - // a. If v < 0, return -1. - if *v < 0f64 { - return -1; - // b. If v > 0, return 1. - } else if *v > 0f64 { - return 1; - } - } - // 2. Return 0. - 0 -} - -impl From for Duration { - fn from(value: TimeDuration) -> Self { - Self { - time: value, - date: DateDuration::default(), - } - } -} - -// ==== FromStr trait impl ==== - -impl FromStr for Duration { - type Err = TemporalError; - - fn from_str(s: &str) -> Result { - let parse_record = parse_duration(&mut Cursor::new(s))?; - - let minutes = if parse_record.time.fhours > 0.0 { - parse_record.time.fhours * 60.0 - } else { - f64::from(parse_record.time.minutes) - }; - - let seconds = if parse_record.time.fminutes > 0.0 { - parse_record.time.fminutes * 60.0 - } else if parse_record.time.seconds > 0 { - f64::from(parse_record.time.seconds) - } else { - minutes.rem_euclid(1.0) * 60.0 - }; - - let milliseconds = if parse_record.time.fseconds > 0.0 { - parse_record.time.fseconds * 1000.0 - } else { - seconds.rem_euclid(1.0) * 1000.0 - }; - - let micro = milliseconds.rem_euclid(1.0) * 1000.0; - let nano = micro.rem_euclid(1.0) * 1000.0; - - let sign = if parse_record.sign { 1f64 } else { -1f64 }; - - Ok(Self { - date: DateDuration::new( - f64::from(parse_record.date.years) * sign, - f64::from(parse_record.date.months) * sign, - f64::from(parse_record.date.weeks) * sign, - f64::from(parse_record.date.days) * sign, - )?, - time: TimeDuration::new( - f64::from(parse_record.time.hours) * sign, - minutes.floor() * sign, - seconds.floor() * sign, - milliseconds.floor() * sign, - micro.floor() * sign, - nano.floor() * sign, - )?, - }) - } -} diff --git a/core/temporal/src/components/duration/date.rs b/core/temporal/src/components/duration/date.rs deleted file mode 100644 index 9f83097928..0000000000 --- a/core/temporal/src/components/duration/date.rs +++ /dev/null @@ -1,486 +0,0 @@ -//! Implementation of a `DateDuration` - -use crate::{ - components::{ - calendar::CalendarProtocol, duration::TimeDuration, tz::TzProtocol, Date, DateTime, - Duration, ZonedDateTime, - }, - options::{ArithmeticOverflow, TemporalRoundingMode, TemporalUnit}, - utils, TemporalError, TemporalResult, NS_PER_DAY, -}; - -/// `DateDuration` represents the [date duration record][spec] of the `Duration.` -/// -/// These fields are laid out in the [Temporal Proposal][field spec] as 64-bit floating point numbers. -/// -/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-date-duration-records -/// [field spec]: https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances -#[derive(Debug, Default, Clone, Copy)] -pub struct DateDuration { - pub(crate) years: f64, - pub(crate) months: f64, - pub(crate) weeks: f64, - pub(crate) days: f64, -} - -impl DateDuration { - /// Creates a new, non-validated `DateDuration`. - #[inline] - #[must_use] - pub(crate) const fn new_unchecked(years: f64, months: f64, weeks: f64, days: f64) -> Self { - Self { - years, - months, - weeks, - days, - } - } -} - -impl DateDuration { - /// Creates a new `DateDuration` with provided values. - pub fn new(years: f64, months: f64, weeks: f64, days: f64) -> TemporalResult { - let result = Self::new_unchecked(years, months, weeks, days); - if !super::is_valid_duration(&result.into_iter().collect()) { - return Err(TemporalError::range().with_message("Invalid DateDuration.")); - } - Ok(result) - } - - /// Returns a `PartialDateDuration` with all fields set to `NaN`. - #[must_use] - pub const fn partial() -> Self { - Self { - years: f64::NAN, - months: f64::NAN, - weeks: f64::NAN, - days: f64::NAN, - } - } - - /// Creates a `DateDuration` from a provided partial `DateDuration`. - #[must_use] - pub fn from_partial(partial: &DateDuration) -> Self { - Self { - years: if partial.years.is_nan() { - 0.0 - } else { - partial.years - }, - months: if partial.months.is_nan() { - 0.0 - } else { - partial.months - }, - weeks: if partial.weeks.is_nan() { - 0.0 - } else { - partial.weeks - }, - days: if partial.days.is_nan() { - 0.0 - } else { - partial.days - }, - } - } - - /// Returns a new `DateDuration` representing the absolute value of the current. - #[inline] - #[must_use] - pub fn abs(&self) -> Self { - Self { - years: self.years.abs(), - months: self.months.abs(), - weeks: self.weeks.abs(), - days: self.days.abs(), - } - } - - /// Returns the `[[years]]` value. - #[must_use] - pub const fn years(&self) -> f64 { - self.years - } - - /// Returns the `[[months]]` value. - #[must_use] - pub const fn months(&self) -> f64 { - self.months - } - - /// Returns the `[[weeks]]` value. - #[must_use] - pub const fn weeks(&self) -> f64 { - self.weeks - } - - /// Returns the `[[days]]` value. - #[must_use] - pub const fn days(&self) -> f64 { - self.days - } - - /// Returns the iterator for `DateDuration` - #[must_use] - pub fn iter(&self) -> DateIter<'_> { - <&Self as IntoIterator>::into_iter(self) - } -} - -// ==== DateDuration Operations ==== - -impl DateDuration { - /// Rounds the current `DateDuration` returning a tuple of the rounded `DateDuration` and - /// the `total` value of the smallest unit prior to rounding. - #[allow(clippy::type_complexity, clippy::let_and_return)] - pub fn round( - &self, - additional_time: Option, - increment: f64, - unit: TemporalUnit, - rounding_mode: TemporalRoundingMode, - relative_targets: ( - Option<&Date>, - Option<&ZonedDateTime>, - Option<&DateTime>, - ), - context: &mut C::Context, - ) -> TemporalResult<(Self, f64)> { - // 1. If plainRelativeTo is not present, set plainRelativeTo to undefined. - let plain_relative_to = relative_targets.0; - // 2. If zonedRelativeTo is not present, set zonedRelativeTo to undefined. - let zoned_relative_to = relative_targets.1; - // 3. If precalculatedPlainDateTime is not present, set precalculatedPlainDateTime to undefined. - let _ = relative_targets.2; - - let mut fractional_days = match unit { - // 4. If unit is "year", "month", or "week", and plainRelativeTo is undefined, then - TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week - if plain_relative_to.is_none() => - { - // a. Throw a RangeError exception. - return Err(TemporalError::range() - .with_message("plainRelativeTo canot be undefined with given TemporalUnit")); - } - // 5. If unit is one of "year", "month", "week", or "day", then - TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week | TemporalUnit::Day => { - // a. Let nanoseconds be TotalDurationNanoseconds(hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - let nanoseconds = additional_time.unwrap_or_default().as_nanos(); - - // b. If zonedRelativeTo is not undefined, then - // i. Let intermediate be ? MoveRelativeZonedDateTime(zonedRelativeTo, years, months, weeks, days, precalculatedPlainDateTime). - // ii. Let result be ? NanosecondsToDays(nanoseconds, intermediate). - // iii. Let fractionalDays be days + result.[[Days]] + result.[[Nanoseconds]] / result.[[DayLength]]. - // c. Else, - // i. Let fractionalDays be days + nanoseconds / nsPerDay. - // d. Set days, hours, minutes, seconds, milliseconds, microseconds, and nanoseconds to 0. - // e. Assert: fractionalSeconds is not used below. - if zoned_relative_to.is_none() { - self.days + nanoseconds / NS_PER_DAY as f64 - } else { - // implementation of b: i-iii needed. - return Err(TemporalError::range().with_message("Not yet implemented.")); - } - } - _ => { - return Err(TemporalError::range() - .with_message("Invalid TemporalUnit provided to DateDuration.round")) - } - }; - - // 7. let total be unset. - // We begin matching against unit and return the remainder value. - match unit { - // 8. If unit is "year", then - TemporalUnit::Year => { - let plain_relative_to = plain_relative_to.expect("this must exist."); - // a. Let calendar be plainRelativeTo.[[Calendar]]. - let calendar = plain_relative_to.calendar(); - - // b. Let yearsDuration be ! CreateTemporalDuration(years, 0, 0, 0, 0, 0, 0, 0, 0, 0). - let years = DateDuration::new_unchecked(self.years, 0.0, 0.0, 0.0); - let years_duration = Duration::new_unchecked(years, TimeDuration::default()); - - // c. If calendar is an Object, then - // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // d. Else, - // i. Let dateAdd be unused. - - // e. Let yearsLater be ? AddDate(calendar, plainRelativeTo, yearsDuration, undefined, dateAdd). - let years_later = plain_relative_to.contextual_add_date( - &years_duration, - ArithmeticOverflow::Constrain, - context, - )?; - - // f. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). - let years_months_weeks = Duration::new_unchecked( - Self::new_unchecked(self.years, self.months, self.weeks, 0.0), - TimeDuration::default(), - ); - - // g. Let yearsMonthsWeeksLater be ? AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd). - let years_months_weeks_later = plain_relative_to.contextual_add_date( - &years_months_weeks, - ArithmeticOverflow::Constrain, - context, - )?; - - // h. Let monthsWeeksInDays be DaysUntil(yearsLater, yearsMonthsWeeksLater). - let months_weeks_in_days = years_later.days_until(&years_months_weeks_later); - - // i. Set plainRelativeTo to yearsLater. - let plain_relative_to = years_later; - - // j. Set fractionalDays to fractionalDays + monthsWeeksInDays. - fractional_days += f64::from(months_weeks_in_days); - - // k. Let isoResult be ! AddISODate(plainRelativeTo.[[ISOYear]]. plainRelativeTo.[[ISOMonth]], plainRelativeTo.[[ISODay]], 0, 0, 0, truncate(fractionalDays), "constrain"). - let iso_result = plain_relative_to.iso().add_iso_date( - &DateDuration::new_unchecked(0.0, 0.0, 0.0, fractional_days.trunc()), - ArithmeticOverflow::Constrain, - )?; - - // l. Let wholeDaysLater be ? CreateDate(isoResult.[[Year]], isoResult.[[Month]], isoResult.[[Day]], calendar). - let whole_days_later = Date::new_unchecked(iso_result, calendar.clone()); - - // m. Let untilOptions be OrdinaryObjectCreate(null). - // n. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "year"). - // o. Let timePassed be ? DifferenceDate(calendar, plainRelativeTo, wholeDaysLater, untilOptions). - let time_passed = plain_relative_to.contextual_difference_date( - &whole_days_later, - TemporalUnit::Year, - context, - )?; - - // p. Let yearsPassed be timePassed.[[Years]]. - let years_passed = time_passed.date.years(); - - // q. Set years to years + yearsPassed. - let years = self.years() + years_passed; - - // r. Let yearsDuration be ! CreateTemporalDuration(yearsPassed, 0, 0, 0, 0, 0, 0, 0, 0, 0). - let years_duration = Duration::one_year(years_passed); - - // s. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, yearsDuration, dateAdd). - // t. Set plainRelativeTo to moveResult.[[RelativeTo]]. - // u. Let daysPassed be moveResult.[[Days]]. - let (plain_relative_to, days_passed) = - plain_relative_to.move_relative_date(&years_duration, context)?; - - // v. Set fractionalDays to fractionalDays - daysPassed. - fractional_days -= days_passed; - - // w. If fractionalDays < 0, let sign be -1; else, let sign be 1. - let sign = if fractional_days < 0.0 { -1 } else { 1 }; - - // x. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0). - let one_year = Duration::one_year(f64::from(sign)); - - // y. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneYear, dateAdd). - // z. Let oneYearDays be moveResult.[[Days]]. - let (_, one_year_days) = - plain_relative_to.move_relative_date(&one_year, context)?; - - // aa. Let fractionalYears be years + fractionalDays / abs(oneYearDays). - let frac_years = years + (fractional_days / one_year_days.abs()); - - // ab. Set years to RoundNumberToIncrement(fractionalYears, increment, roundingMode). - let rounded_years = - utils::round_number_to_increment(frac_years, increment, rounding_mode); - - // ac. Set total to fractionalYears. - // ad. Set months and weeks to 0. - let result = Self::new(rounded_years, 0f64, 0f64, 0f64)?; - Ok((result, frac_years)) - } - // 9. Else if unit is "month", then - TemporalUnit::Month => { - // a. Let calendar be plainRelativeTo.[[Calendar]]. - let plain_relative_to = plain_relative_to.expect("this must exist."); - - // b. Let yearsMonths be ! CreateTemporalDuration(years, months, 0, 0, 0, 0, 0, 0, 0, 0). - let years_months = Duration::from_date_duration(DateDuration::new_unchecked( - self.years(), - self.months(), - 0.0, - 0.0, - )); - - // c. If calendar is an Object, then - // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // d. Else, - // i. Let dateAdd be unused. - - // e. Let yearsMonthsLater be ? AddDate(calendar, plainRelativeTo, yearsMonths, undefined, dateAdd). - let years_months_later = plain_relative_to.contextual_add_date( - &years_months, - ArithmeticOverflow::Constrain, - context, - )?; - - // f. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). - let years_months_weeks = Duration::from_date_duration(DateDuration::new_unchecked( - self.years(), - self.months(), - self.weeks(), - 0.0, - )); - - // g. Let yearsMonthsWeeksLater be ? AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd). - let years_months_weeks_later = plain_relative_to.contextual_add_date( - &years_months_weeks, - ArithmeticOverflow::Constrain, - context, - )?; - - // h. Let weeksInDays be DaysUntil(yearsMonthsLater, yearsMonthsWeeksLater). - let weeks_in_days = years_months_later.days_until(&years_months_weeks_later); - - // i. Set plainRelativeTo to yearsMonthsLater. - let plain_relative_to = years_months_later; - - // j. Set fractionalDays to fractionalDays + weeksInDays. - fractional_days += f64::from(weeks_in_days); - - // k. If fractionalDays < 0, let sign be -1; else, let sign be 1. - let sign = if fractional_days < 0.0 { -1f64 } else { 1f64 }; - - // l. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). - let one_month = Duration::one_month(sign); - - // m. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd). - // n. Set plainRelativeTo to moveResult.[[RelativeTo]]. - // o. Let oneMonthDays be moveResult.[[Days]]. - let (mut plain_relative_to, mut one_month_days) = - plain_relative_to.move_relative_date(&one_month, context)?; - - let mut months = self.months; - // p. Repeat, while abs(fractionalDays) ≥ abs(oneMonthDays), - while fractional_days.abs() >= one_month_days.abs() { - // i. Set months to months + sign. - months += sign; - - // ii. Set fractionalDays to fractionalDays - oneMonthDays. - fractional_days -= one_month_days; - - // iii. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_month, context)?; - - // iv. Set plainRelativeTo to moveResult.[[RelativeTo]]. - plain_relative_to = move_result.0; - // v. Set oneMonthDays to moveResult.[[Days]]. - one_month_days = move_result.1; - } - - // q. Let fractionalMonths be months + fractionalDays / abs(oneMonthDays). - let frac_months = months + fractional_days / one_month_days.abs(); - - // r. Set months to RoundNumberToIncrement(fractionalMonths, increment, roundingMode). - let rounded_months = - utils::round_number_to_increment(frac_months, increment, rounding_mode); - - // s. Set total to fractionalMonths. - // t. Set weeks to 0. - let result = Self::new(self.years, rounded_months, 0f64, 0f64)?; - Ok((result, frac_months)) - } - // 10. Else if unit is "week", then - TemporalUnit::Week => { - // a. Let calendar be plainRelativeTo.[[Calendar]]. - let plain_relative_to = plain_relative_to.expect("date must exist given Week"); - - // b. If fractionalDays < 0, let sign be -1; else, let sign be 1. - let sign = if fractional_days < 0.0 { -1f64 } else { 1f64 }; - - // c. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0). - let one_week = Duration::one_week(sign); - - // d. If calendar is an Object, then - // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // e. Else, - // i. Let dateAdd be unused. - - // f. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneWeek, dateAdd). - // g. Set plainRelativeTo to moveResult.[[RelativeTo]]. - // h. Let oneWeekDays be moveResult.[[Days]]. - let (mut plain_relative_to, mut one_week_days) = - plain_relative_to.move_relative_date(&one_week, context)?; - - let mut weeks = self.weeks; - // i. Repeat, while abs(fractionalDays) ≥ abs(oneWeekDays), - while fractional_days.abs() >= one_week_days.abs() { - // i. Set weeks to weeks + sign. - weeks += sign; - - // ii. Set fractionalDays to fractionalDays - oneWeekDays. - fractional_days -= one_week_days; - - // iii. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneWeek, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_week, context)?; - - // iv. Set plainRelativeTo to moveResult.[[RelativeTo]]. - plain_relative_to = move_result.0; - // v. Set oneWeekDays to moveResult.[[Days]]. - one_week_days = move_result.1; - } - - // j. Let fractionalWeeks be weeks + fractionalDays / abs(oneWeekDays). - let frac_weeks = weeks + fractional_days / one_week_days.abs(); - - // k. Set weeks to RoundNumberToIncrement(fractionalWeeks, increment, roundingMode). - let rounded_weeks = - utils::round_number_to_increment(frac_weeks, increment, rounding_mode); - // l. Set total to fractionalWeeks. - let result = Self::new(self.years, self.months, rounded_weeks, 0f64)?; - Ok((result, frac_weeks)) - } - // 11. Else if unit is "day", then - TemporalUnit::Day => { - // a. Set days to RoundNumberToIncrement(fractionalDays, increment, roundingMode). - let rounded_days = - utils::round_number_to_increment(fractional_days, increment, rounding_mode); - // b. Set total to fractionalDays. - let result = Self::new(self.years, self.months, self.weeks, rounded_days)?; - Ok((result, fractional_days)) - } - _ => unreachable!("All other TemporalUnits were returned early as invalid."), - } - } -} - -impl<'a> IntoIterator for &'a DateDuration { - type Item = f64; - type IntoIter = DateIter<'a>; - - fn into_iter(self) -> Self::IntoIter { - DateIter { - date: self, - index: 0, - } - } -} - -/// An iterator over the `DateDuration` -#[derive(Debug)] -pub struct DateIter<'a> { - date: &'a DateDuration, - index: usize, -} - -impl Iterator for DateIter<'_> { - type Item = f64; - - fn next(&mut self) -> Option { - let result = match self.index { - 0 => Some(self.date.years), - 1 => Some(self.date.months), - 2 => Some(self.date.weeks), - 3 => Some(self.date.days), - _ => None, - }; - self.index += 1; - result - } -} diff --git a/core/temporal/src/components/duration/time.rs b/core/temporal/src/components/duration/time.rs deleted file mode 100644 index 9553de4ccf..0000000000 --- a/core/temporal/src/components/duration/time.rs +++ /dev/null @@ -1,596 +0,0 @@ -//! An implementation of `TimeDuration` and it's methods. - -use crate::{ - options::{TemporalRoundingMode, TemporalUnit}, - utils, TemporalError, TemporalResult, -}; - -use super::is_valid_duration; - -/// `TimeDuration` represents the [Time Duration record][spec] of the `Duration.` -/// -/// These fields are laid out in the [Temporal Proposal][field spec] as 64-bit floating point numbers. -/// -/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-time-duration-records -/// [field spec]: https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances -#[derive(Debug, Default, Clone, Copy)] -pub struct TimeDuration { - pub(crate) hours: f64, - pub(crate) minutes: f64, - pub(crate) seconds: f64, - pub(crate) milliseconds: f64, - pub(crate) microseconds: f64, - pub(crate) nanoseconds: f64, -} -// ==== TimeDuration Private API ==== - -impl TimeDuration { - /// Creates a new `TimeDuration`. - #[must_use] - pub(crate) const fn new_unchecked( - hours: f64, - minutes: f64, - seconds: f64, - milliseconds: f64, - microseconds: f64, - nanoseconds: f64, - ) -> Self { - Self { - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - } - } - - /// Returns the current `TimeDuration` as nanoseconds. - #[inline] - pub(crate) fn as_nanos(&self) -> f64 { - self.hours - .mul_add(60_f64, self.minutes) - .mul_add(60_f64, self.seconds) - .mul_add(1_000_f64, self.milliseconds) - .mul_add(1_000_f64, self.microseconds) - .mul_add(1_000_f64, self.nanoseconds) - } - - /// Abstract Operation 7.5.18 `BalancePossiblyInfiniteDuration ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit )` - /// - /// This function will balance the current `TimeDuration`. It returns the balanced `day` and `TimeDuration` value. - #[allow(clippy::too_many_arguments)] - pub(crate) fn balance_possibly_infinite_time_duration( - days: f64, - hours: f64, - minutes: f64, - seconds: f64, - milliseconds: f64, - microseconds: f64, - nanoseconds: f64, - largest_unit: TemporalUnit, - ) -> TemporalResult<(f64, Option)> { - // 1. Set hours to hours + days × 24. - let hours = hours + (days * 24f64); - - // 2. Set nanoseconds to TotalDurationNanoseconds(hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - let mut nanoseconds = Self::new_unchecked( - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - ) - .as_nanos(); - - // 3. Set days, hours, minutes, seconds, milliseconds, and microseconds to 0. - let mut days = 0f64; - let mut hours = 0f64; - let mut minutes = 0f64; - let mut seconds = 0f64; - let mut milliseconds = 0f64; - let mut microseconds = 0f64; - - // 4. If nanoseconds < 0, let sign be -1; else, let sign be 1. - let sign = if nanoseconds < 0f64 { -1 } else { 1 }; - // 5. Set nanoseconds to abs(nanoseconds). - nanoseconds = nanoseconds.abs(); - - match largest_unit { - // 9. If largestUnit is "year", "month", "week", "day", or "hour", then - TemporalUnit::Year - | TemporalUnit::Month - | TemporalUnit::Week - | TemporalUnit::Day - | TemporalUnit::Hour => { - // a. Set microseconds to floor(nanoseconds / 1000). - microseconds = (nanoseconds / 1000f64).floor(); - // b. Set nanoseconds to nanoseconds modulo 1000. - nanoseconds %= 1000f64; - - // c. Set milliseconds to floor(microseconds / 1000). - milliseconds = (microseconds / 1000f64).floor(); - // d. Set microseconds to microseconds modulo 1000. - microseconds %= 1000f64; - - // e. Set seconds to floor(milliseconds / 1000). - seconds = (milliseconds / 1000f64).floor(); - // f. Set milliseconds to milliseconds modulo 1000. - milliseconds %= 1000f64; - - // g. Set minutes to floor(seconds / 60). - minutes = (seconds / 60f64).floor(); - // h. Set seconds to seconds modulo 60. - seconds %= 60f64; - - // i. Set hours to floor(minutes / 60). - hours = (minutes / 60f64).floor(); - // j. Set minutes to minutes modulo 60. - minutes %= 60f64; - - // k. Set days to floor(hours / 24). - days = (hours / 24f64).floor(); - // l. Set hours to hours modulo 24. - hours %= 24f64; - } - // 10. Else if largestUnit is "minute", then - TemporalUnit::Minute => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - microseconds = (nanoseconds / 1000f64).floor(); - nanoseconds %= 1000f64; - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - milliseconds = (microseconds / 1000f64).floor(); - microseconds %= 1000f64; - - // e. Set seconds to floor(milliseconds / 1000). - // f. Set milliseconds to milliseconds modulo 1000. - seconds = (milliseconds / 1000f64).floor(); - milliseconds %= 1000f64; - - // g. Set minutes to floor(seconds / 60). - // h. Set seconds to seconds modulo 60. - minutes = (seconds / 60f64).floor(); - seconds %= 60f64; - } - // 11. Else if largestUnit is "second", then - TemporalUnit::Second => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - microseconds = (nanoseconds / 1000f64).floor(); - nanoseconds %= 1000f64; - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - milliseconds = (microseconds / 1000f64).floor(); - microseconds %= 1000f64; - - // e. Set seconds to floor(milliseconds / 1000). - // f. Set milliseconds to milliseconds modulo 1000. - seconds = (milliseconds / 1000f64).floor(); - milliseconds %= 1000f64; - } - // 12. Else if largestUnit is "millisecond", then - TemporalUnit::Millisecond => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - microseconds = (nanoseconds / 1000f64).floor(); - nanoseconds %= 1000f64; - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - milliseconds = (microseconds / 1000f64).floor(); - microseconds %= 1000f64; - } - // 13. Else if largestUnit is "microsecond", then - TemporalUnit::Microsecond => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - microseconds = (nanoseconds / 1000f64).floor(); - nanoseconds %= 1000f64; - } - // 14. Else, - // a. Assert: largestUnit is "nanosecond". - _ => debug_assert!(largest_unit == TemporalUnit::Nanosecond), - } - - let result_values = Vec::from(&[ - days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - ]); - // 15. For each value v of « days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds », do - for value in result_values { - // a. If 𝔽(v) is not finite, then - if !value.is_finite() { - // i. If sign = 1, then - if sign == 1 { - // 1. Return positive overflow. - return Ok((f64::INFINITY, None)); - } - // ii. Else if sign = -1, then - // 1. Return negative overflow. - return Ok((f64::NEG_INFINITY, None)); - } - } - - let sign = f64::from(sign); - - // 16. Return ? CreateTimeDurationRecord(days, hours × sign, minutes × sign, seconds × sign, milliseconds × sign, microseconds × sign, nanoseconds × sign). - let result = Self::new( - hours * sign, - minutes * sign, - seconds * sign, - milliseconds * sign, - microseconds * sign, - nanoseconds * sign, - )?; - - Ok((days, Some(result))) - } -} - -// ==== TimeDuration's public API ==== - -impl TimeDuration { - /// Creates a new validated `TimeDuration`. - pub fn new( - hours: f64, - minutes: f64, - seconds: f64, - milliseconds: f64, - microseconds: f64, - nanoseconds: f64, - ) -> TemporalResult { - let result = Self::new_unchecked( - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - ); - if !is_valid_duration(&result.into_iter().collect()) { - return Err( - TemporalError::range().with_message("Attempted to create an invalid TimeDuration.") - ); - } - Ok(result) - } - - /// Creates a partial `TimeDuration` with all values set to `NaN`. - #[must_use] - pub const fn partial() -> Self { - Self { - hours: f64::NAN, - minutes: f64::NAN, - seconds: f64::NAN, - milliseconds: f64::NAN, - microseconds: f64::NAN, - nanoseconds: f64::NAN, - } - } - - /// Creates a `TimeDuration` from a provided partial `TimeDuration`. - #[must_use] - pub fn from_partial(partial: &TimeDuration) -> Self { - Self { - hours: if partial.hours.is_nan() { - 0.0 - } else { - partial.hours - }, - minutes: if partial.minutes.is_nan() { - 0.0 - } else { - partial.minutes - }, - seconds: if partial.seconds.is_nan() { - 0.0 - } else { - partial.seconds - }, - milliseconds: if partial.milliseconds.is_nan() { - 0.0 - } else { - partial.milliseconds - }, - microseconds: if partial.microseconds.is_nan() { - 0.0 - } else { - partial.microseconds - }, - nanoseconds: if partial.nanoseconds.is_nan() { - 0.0 - } else { - partial.nanoseconds - }, - } - } - - /// Returns a new `TimeDuration` representing the absolute value of the current. - #[inline] - #[must_use] - pub fn abs(&self) -> Self { - Self { - hours: self.hours.abs(), - minutes: self.minutes.abs(), - seconds: self.seconds.abs(), - milliseconds: self.milliseconds.abs(), - microseconds: self.microseconds.abs(), - nanoseconds: self.nanoseconds.abs(), - } - } - - /// Returns a negated `TimeDuration`. - #[inline] - #[must_use] - pub fn neg(&self) -> Self { - Self { - hours: self.hours * -1f64, - minutes: self.minutes * -1f64, - seconds: self.seconds * -1f64, - milliseconds: self.milliseconds * -1f64, - microseconds: self.microseconds * -1f64, - nanoseconds: self.nanoseconds * -1f64, - } - } - - /// Balances a `TimeDuration` given a day value and the largest unit. `balance` will return - /// the balanced `day` and `TimeDuration`. - /// - /// # Errors: - /// - Will error if provided duration is invalid - pub fn balance(&self, days: f64, largest_unit: TemporalUnit) -> TemporalResult<(f64, Self)> { - let result = Self::balance_possibly_infinite_time_duration( - days, - self.hours, - self.minutes, - self.seconds, - self.milliseconds, - self.microseconds, - self.nanoseconds, - largest_unit, - )?; - let Some(time_duration) = result.1 else { - return Err(TemporalError::range().with_message("Invalid balance TimeDuration.")); - }; - Ok((result.0, time_duration)) - } - - /// Utility function for returning if values in a valid range. - #[inline] - #[must_use] - pub fn is_within_range(&self) -> bool { - self.hours.abs() < 24f64 - && self.minutes.abs() < 60f64 - && self.seconds.abs() < 60f64 - && self.milliseconds.abs() < 1000f64 - && self.milliseconds.abs() < 1000f64 - && self.milliseconds.abs() < 1000f64 - } - - /// Returns the `[[hours]]` value. - #[must_use] - pub const fn hours(&self) -> f64 { - self.hours - } - - /// Returns the `[[minutes]]` value. - #[must_use] - pub const fn minutes(&self) -> f64 { - self.minutes - } - - /// Returns the `[[seconds]]` value. - #[must_use] - pub const fn seconds(&self) -> f64 { - self.seconds - } - - /// Returns the `[[milliseconds]]` value. - #[must_use] - pub const fn milliseconds(&self) -> f64 { - self.milliseconds - } - - /// Returns the `[[microseconds]]` value. - #[must_use] - pub const fn microseconds(&self) -> f64 { - self.microseconds - } - - /// Returns the `[[nanoseconds]]` value. - #[must_use] - pub const fn nanoseconds(&self) -> f64 { - self.nanoseconds - } - - /// Returns the `TimeDuration`'s iterator. - #[must_use] - pub fn iter(&self) -> TimeIter<'_> { - <&Self as IntoIterator>::into_iter(self) - } -} - -// ==== TimeDuration method impls ==== - -impl TimeDuration { - /// Rounds the current `TimeDuration` given a rounding increment, unit and rounding mode. `round` will return a tuple of the rounded `TimeDuration` and - /// the `total` value of the smallest unit prior to rounding. - #[inline] - pub fn round( - &self, - increment: f64, - unit: TemporalUnit, - rounding_mode: TemporalRoundingMode, - ) -> TemporalResult<(Self, f64)> { - let fraction_seconds = match unit { - TemporalUnit::Year - | TemporalUnit::Month - | TemporalUnit::Week - | TemporalUnit::Day - | TemporalUnit::Auto => { - return Err(TemporalError::r#type() - .with_message("Invalid unit provided to for TimeDuration to round.")) - } - _ => self.nanoseconds().mul_add( - 1_000_000_000f64, - self.microseconds().mul_add( - 1_000_000f64, - self.milliseconds().mul_add(1000f64, self.seconds()), - ), - ), - }; - - match unit { - // 12. Else if unit is "hour", then - TemporalUnit::Hour => { - // a. Let fractionalHours be (fractionalSeconds / 60 + minutes) / 60 + hours. - let frac_hours = (fraction_seconds / 60f64 + self.minutes) / 60f64 + self.hours; - // b. Set hours to RoundNumberToIncrement(fractionalHours, increment, roundingMode). - let rounded_hours = - utils::round_number_to_increment(frac_hours, increment, rounding_mode); - // c. Set total to fractionalHours. - // d. Set minutes, seconds, milliseconds, microseconds, and nanoseconds to 0. - let result = Self::new(rounded_hours, 0f64, 0f64, 0f64, 0f64, 0f64)?; - Ok((result, frac_hours)) - } - // 13. Else if unit is "minute", then - TemporalUnit::Minute => { - // a. Let fractionalMinutes be fractionalSeconds / 60 + minutes. - let frac_minutes = fraction_seconds / 60f64 + self.minutes; - // b. Set minutes to RoundNumberToIncrement(fractionalMinutes, increment, roundingMode). - let rounded_minutes = - utils::round_number_to_increment(frac_minutes, increment, rounding_mode); - // c. Set total to fractionalMinutes. - // d. Set seconds, milliseconds, microseconds, and nanoseconds to 0. - let result = Self::new(self.hours, rounded_minutes, 0f64, 0f64, 0f64, 0f64)?; - - Ok((result, frac_minutes)) - } - // 14. Else if unit is "second", then - TemporalUnit::Second => { - // a. Set seconds to RoundNumberToIncrement(fractionalSeconds, increment, roundingMode). - let rounded_seconds = - utils::round_number_to_increment(fraction_seconds, increment, rounding_mode); - // b. Set total to fractionalSeconds. - // c. Set milliseconds, microseconds, and nanoseconds to 0. - let result = - Self::new(self.hours, self.minutes, rounded_seconds, 0f64, 0f64, 0f64)?; - - Ok((result, fraction_seconds)) - } - // 15. Else if unit is "millisecond", then - TemporalUnit::Millisecond => { - // a. Let fractionalMilliseconds be nanoseconds × 10-6 + microseconds × 10-3 + milliseconds. - let fraction_millis = self.nanoseconds.mul_add( - 1_000_000f64, - self.microseconds.mul_add(1_000f64, self.milliseconds), - ); - - // b. Set milliseconds to RoundNumberToIncrement(fractionalMilliseconds, increment, roundingMode). - let rounded_millis = - utils::round_number_to_increment(fraction_millis, increment, rounding_mode); - - // c. Set total to fractionalMilliseconds. - // d. Set microseconds and nanoseconds to 0. - let result = Self::new( - self.hours, - self.minutes, - self.seconds, - rounded_millis, - 0f64, - 0f64, - )?; - Ok((result, fraction_millis)) - } - // 16. Else if unit is "microsecond", then - TemporalUnit::Microsecond => { - // a. Let fractionalMicroseconds be nanoseconds × 10-3 + microseconds. - let frac_micros = self.nanoseconds.mul_add(1_000f64, self.microseconds); - - // b. Set microseconds to RoundNumberToIncrement(fractionalMicroseconds, increment, roundingMode). - let rounded_micros = - utils::round_number_to_increment(frac_micros, increment, rounding_mode); - - // c. Set total to fractionalMicroseconds. - // d. Set nanoseconds to 0. - let result = Self::new( - self.hours, - self.minutes, - self.seconds, - self.milliseconds, - rounded_micros, - 0f64, - )?; - Ok((result, frac_micros)) - } - // 17. Else, - TemporalUnit::Nanosecond => { - // a. Assert: unit is "nanosecond". - // b. Set total to nanoseconds. - let total = self.nanoseconds; - // c. Set nanoseconds to RoundNumberToIncrement(nanoseconds, increment, roundingMode). - let rounded_nanos = - utils::round_number_to_increment(self.nanoseconds, increment, rounding_mode); - - let result = Self::new( - self.hours, - self.minutes, - self.seconds, - self.milliseconds, - self.microseconds, - rounded_nanos, - )?; - - Ok((result, total)) - } - _ => unreachable!("All other units early return error."), - } - } -} - -impl<'a> IntoIterator for &'a TimeDuration { - type Item = f64; - type IntoIter = TimeIter<'a>; - - fn into_iter(self) -> Self::IntoIter { - TimeIter { - time: self, - index: 0, - } - } -} - -/// An iterator over a `TimeDuration`. -#[derive(Debug, Clone)] -pub struct TimeIter<'a> { - time: &'a TimeDuration, - index: usize, -} - -impl Iterator for TimeIter<'_> { - type Item = f64; - - fn next(&mut self) -> Option { - let result = match self.index { - 0 => Some(self.time.hours), - 1 => Some(self.time.minutes), - 2 => Some(self.time.seconds), - 3 => Some(self.time.milliseconds), - 4 => Some(self.time.microseconds), - 5 => Some(self.time.nanoseconds), - _ => None, - }; - self.index += 1; - result - } -} diff --git a/core/temporal/src/components/instant.rs b/core/temporal/src/components/instant.rs deleted file mode 100644 index 41c2a1da95..0000000000 --- a/core/temporal/src/components/instant.rs +++ /dev/null @@ -1,318 +0,0 @@ -//! An implementation of the Temporal Instant. - -use crate::{ - components::{duration::TimeDuration, Duration}, - options::{TemporalRoundingMode, TemporalUnit}, - utils, TemporalError, TemporalResult, MS_PER_DAY, NS_PER_DAY, -}; - -use num_bigint::BigInt; -use num_traits::{FromPrimitive, ToPrimitive}; - -const NANOSECONDS_PER_SECOND: f64 = 1e9; -const NANOSECONDS_PER_MINUTE: f64 = 60f64 * NANOSECONDS_PER_SECOND; -const NANOSECONDS_PER_HOUR: f64 = 60f64 * NANOSECONDS_PER_MINUTE; - -/// The native Rust implementation of `Temporal.Instant` -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Instant { - pub(crate) nanos: BigInt, -} - -// ==== Private API ==== - -impl Instant { - /// Adds a `TimeDuration` to the current `Instant`. - /// - /// Temporal-Proposal equivalent: `AddDurationToOrSubtractDurationFrom`. - pub(crate) fn add_to_instant(&self, duration: &TimeDuration) -> TemporalResult { - let result = self.epoch_nanoseconds() - + duration.nanoseconds - + (duration.microseconds * 1000f64) - + (duration.milliseconds * 1_000_000f64) - + (duration.seconds * NANOSECONDS_PER_SECOND) - + (duration.minutes * NANOSECONDS_PER_MINUTE) - + (duration.hours * NANOSECONDS_PER_HOUR); - let nanos = BigInt::from_f64(result).ok_or_else(|| { - TemporalError::range().with_message("Duration added to instant exceeded valid range.") - })?; - Self::new(nanos) - } - - // TODO: Add test for `diff_instant`. - // NOTE(nekevss): As the below is internal, op will be left as a boolean - // with a `since` op being true and `until` being false. - /// Internal operation to handle `since` and `until` difference ops. - #[allow(unused)] - pub(crate) fn diff_instant( - &self, - op: bool, - other: &Self, - rounding_mode: Option, - rounding_increment: Option, - largest_unit: Option, - smallest_unit: Option, - ) -> TemporalResult { - // diff the instant and determine its component values. - let diff = self.to_f64() - other.to_f64(); - let nanos = diff.rem_euclid(1000f64); - let micros = (diff / 1000f64).trunc().rem_euclid(1000f64); - let millis = (diff / 1_000_000f64).trunc().rem_euclid(1000f64); - let secs = (diff / NANOSECONDS_PER_SECOND).trunc(); - - // Handle the settings provided to `diff_instant` - let rounding_increment = rounding_increment.unwrap_or(1.0); - let rounding_mode = if op { - rounding_mode - .unwrap_or(TemporalRoundingMode::Trunc) - .negate() - } else { - rounding_mode.unwrap_or(TemporalRoundingMode::Trunc) - }; - let smallest_unit = smallest_unit.unwrap_or(TemporalUnit::Nanosecond); - // Use the defaultlargestunit which is max smallestlargestdefault and smallestunit - let largest_unit = largest_unit.unwrap_or(smallest_unit.max(TemporalUnit::Second)); - - // TODO: validate roundingincrement - // Steps 11-13 of 13.47 GetDifferenceSettings - - if smallest_unit == TemporalUnit::Nanosecond { - let (_, result) = TimeDuration::new_unchecked(0f64, 0f64, secs, millis, micros, nanos) - .balance(0f64, largest_unit)?; - return Ok(result); - } - - let (round_result, _) = TimeDuration::new(0f64, 0f64, secs, millis, micros, nanos)?.round( - rounding_increment, - smallest_unit, - rounding_mode, - )?; - let (_, result) = round_result.balance(0f64, largest_unit)?; - Ok(result) - } - - /// Rounds a current `Instant` given the resolved options, returning a `BigInt` result. - pub(crate) fn round_instant( - &self, - increment: f64, - unit: TemporalUnit, - rounding_mode: TemporalRoundingMode, - ) -> TemporalResult { - let increment_nanos = match unit { - TemporalUnit::Hour => increment * NANOSECONDS_PER_HOUR, - TemporalUnit::Minute => increment * NANOSECONDS_PER_MINUTE, - TemporalUnit::Second => increment * NANOSECONDS_PER_SECOND, - TemporalUnit::Millisecond => increment * 1_000_000f64, - TemporalUnit::Microsecond => increment * 1_000f64, - TemporalUnit::Nanosecond => increment, - _ => { - return Err(TemporalError::range() - .with_message("Invalid unit provided for Instant::round.")) - } - }; - - let rounded = utils::round_number_to_increment_as_if_positive( - self.to_f64(), - increment_nanos, - rounding_mode, - ); - - BigInt::from_f64(rounded) - .ok_or_else(|| TemporalError::range().with_message("Invalid rounded Instant value.")) - } - - /// Utility for converting `Instant` to f64. - /// - /// # Panics - /// - /// This function will panic if called on an invalid `Instant`. - pub(crate) fn to_f64(&self) -> f64 { - self.nanos - .to_f64() - .expect("A valid instant is representable by f64.") - } -} - -// ==== Public API ==== - -impl Instant { - /// Create a new validated `Instant`. - #[inline] - pub fn new(nanos: BigInt) -> TemporalResult { - if !is_valid_epoch_nanos(&nanos) { - return Err(TemporalError::range() - .with_message("Instant nanoseconds are not within a valid epoch range.")); - } - Ok(Self { nanos }) - } - - /// Adds a `Duration` to the current `Instant`, returning an error if the `Duration` - /// contains a `DateDuration`. - #[inline] - pub fn add(&self, duration: Duration) -> TemporalResult { - if !duration.is_time_duration() { - return Err(TemporalError::range() - .with_message("DateDuration values cannot be added to instant.")); - } - self.add_time_duration(duration.time()) - } - - /// Adds a `TimeDuration` to `Instant`. - #[inline] - pub fn add_time_duration(&self, duration: &TimeDuration) -> TemporalResult { - self.add_to_instant(duration) - } - - /// Subtract a `Duration` to the current `Instant`, returning an error if the `Duration` - /// contains a `DateDuration`. - #[inline] - pub fn subtract(&self, duration: Duration) -> TemporalResult { - if !duration.is_time_duration() { - return Err(TemporalError::range() - .with_message("DateDuration values cannot be added to instant.")); - } - self.subtract_time_duration(duration.time()) - } - - /// Subtracts a `TimeDuration` to `Instant`. - #[inline] - pub fn subtract_time_duration(&self, duration: &TimeDuration) -> TemporalResult { - self.add_to_instant(&duration.neg()) - } - - /// Returns a `TimeDuration` representing the duration since provided `Instant` - #[inline] - pub fn since( - &self, - other: &Self, - rounding_mode: Option, - rounding_increment: Option, - largest_unit: Option, - smallest_unit: Option, - ) -> TemporalResult { - self.diff_instant( - true, - other, - rounding_mode, - rounding_increment, - largest_unit, - smallest_unit, - ) - } - - /// Returns a `TimeDuration` representing the duration until provided `Instant` - #[inline] - pub fn until( - &self, - other: &Self, - rounding_mode: Option, - rounding_increment: Option, - largest_unit: Option, - smallest_unit: Option, - ) -> TemporalResult { - self.diff_instant( - false, - other, - rounding_mode, - rounding_increment, - largest_unit, - smallest_unit, - ) - } - - /// Returns an `Instant` by rounding the current `Instant` according to the provided settings. - pub fn round( - &self, - increment: Option, - unit: TemporalUnit, // smallestUnit is required on Instant::round - rounding_mode: Option, - ) -> TemporalResult { - let increment = utils::to_rounding_increment(increment)?; - let mode = rounding_mode.unwrap_or(TemporalRoundingMode::HalfExpand); - let maximum = match unit { - TemporalUnit::Hour => 24u64, - TemporalUnit::Minute => 24 * 60, - TemporalUnit::Second => 24 * 3600, - TemporalUnit::Millisecond => MS_PER_DAY as u64, - TemporalUnit::Microsecond => MS_PER_DAY as u64 * 1000, - TemporalUnit::Nanosecond => NS_PER_DAY as u64, - _ => return Err(TemporalError::range().with_message("Invalid roundTo unit provided.")), - }; - // NOTE: to_rounding_increment returns an f64 within a u32 range. - utils::validate_temporal_rounding_increment(increment as u32, maximum, true)?; - - let round_result = self.round_instant(increment, unit, mode)?; - Self::new(round_result) - } - - /// Returns the `epochSeconds` value for this `Instant`. - #[must_use] - pub fn epoch_seconds(&self) -> f64 { - (&self.nanos / BigInt::from(1_000_000_000)) - .to_f64() - .expect("A validated Instant should be within a valid f64") - .floor() - } - - /// Returns the `epochMilliseconds` value for this `Instant`. - #[must_use] - pub fn epoch_milliseconds(&self) -> f64 { - (&self.nanos / BigInt::from(1_000_000)) - .to_f64() - .expect("A validated Instant should be within a valid f64") - .floor() - } - - /// Returns the `epochMicroseconds` value for this `Instant`. - #[must_use] - pub fn epoch_microseconds(&self) -> f64 { - (&self.nanos / BigInt::from(1_000)) - .to_f64() - .expect("A validated Instant should be within a valid f64") - .floor() - } - - /// Returns the `epochNanoseconds` value for this `Instant`. - #[must_use] - pub fn epoch_nanoseconds(&self) -> f64 { - self.to_f64() - } -} - -// ==== Utility Functions ==== - -/// Utility for determining if the nanos are within a valid range. -#[inline] -#[must_use] -pub(crate) fn is_valid_epoch_nanos(nanos: &BigInt) -> bool { - nanos <= &BigInt::from(crate::NS_MAX_INSTANT) && nanos >= &BigInt::from(crate::NS_MIN_INSTANT) -} - -// ==== Instant Tests ==== - -#[cfg(test)] -mod tests { - use crate::{components::Instant, NS_MAX_INSTANT, NS_MIN_INSTANT}; - use num_bigint::BigInt; - use num_traits::ToPrimitive; - - #[test] - #[allow(clippy::float_cmp)] - fn max_and_minimum_instant_bounds() { - // This test is primarily to assert that the `expect` in the epoch methods is - // valid, i.e., a valid instant is within the range of an f64. - let max = BigInt::from(NS_MAX_INSTANT); - let min = BigInt::from(NS_MIN_INSTANT); - let max_instant = Instant::new(max.clone()).unwrap(); - let min_instant = Instant::new(min.clone()).unwrap(); - - assert_eq!(max_instant.epoch_nanoseconds(), max.to_f64().unwrap()); - assert_eq!(min_instant.epoch_nanoseconds(), min.to_f64().unwrap()); - - let max_plus_one = BigInt::from(NS_MAX_INSTANT + 1); - let min_minus_one = BigInt::from(NS_MIN_INSTANT - 1); - - assert!(Instant::new(max_plus_one).is_err()); - assert!(Instant::new(min_minus_one).is_err()); - } -} diff --git a/core/temporal/src/components/mod.rs b/core/temporal/src/components/mod.rs deleted file mode 100644 index 3d0d811f3a..0000000000 --- a/core/temporal/src/components/mod.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! The primary date-time components provided by Temporal. -//! -//! The below components are the main primitives of the `Temporal` specification: -//! - `Date` -> `PlainDate` -//! - `DateTime` -> `PlainDateTime` -//! - `Time` -> `PlainTime` -//! - `Duration` -> `Duration` -//! - `Instant` -> `Instant` -//! - `MonthDay` -> `PlainMonthDay` -//! - `YearMonth` -> `PlainYearMonth` -//! - `ZonedDateTime` -> `ZonedDateTime` -//! -//! The Temporal specification, along with this implementation aims to provide -//! full support for time zones and non-gregorian calendars that are compliant -//! with standards like ISO 8601, RFC 3339, and RFC 5545. - -// TODO: Expand upon above introduction. - -pub mod calendar; -pub mod duration; -pub mod tz; - -mod date; -mod datetime; -mod instant; -mod month_day; -mod time; -mod year_month; -mod zoneddatetime; - -#[doc(inline)] -pub use date::Date; -#[doc(inline)] -pub use datetime::DateTime; -#[doc(inline)] -pub use duration::Duration; -#[doc(inline)] -pub use instant::Instant; -#[doc(inline)] -pub use month_day::MonthDay; -#[doc(inline)] -pub use time::Time; -#[doc(inline)] -pub use year_month::YearMonth; -#[doc(inline)] -pub use zoneddatetime::ZonedDateTime; diff --git a/core/temporal/src/components/month_day.rs b/core/temporal/src/components/month_day.rs deleted file mode 100644 index 92a64de000..0000000000 --- a/core/temporal/src/components/month_day.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! This module implements `MonthDay` and any directly related algorithms. - -use std::str::FromStr; - -use crate::{ - components::calendar::CalendarSlot, - iso::{IsoDate, IsoDateSlots}, - options::ArithmeticOverflow, - TemporalError, TemporalResult, -}; - -use super::calendar::{CalendarProtocol, GetCalendarSlot}; - -/// The native Rust implementation of `Temporal.PlainMonthDay` -#[derive(Debug, Default, Clone)] -pub struct MonthDay { - iso: IsoDate, - calendar: CalendarSlot, -} - -impl MonthDay { - /// Creates a new unchecked `MonthDay` - #[inline] - #[must_use] - pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { - Self { iso, calendar } - } - - /// Creates a new valid `MonthDay`. - #[inline] - pub fn new( - month: i32, - day: i32, - calendar: CalendarSlot, - overflow: ArithmeticOverflow, - ) -> TemporalResult { - let iso = IsoDate::new(1972, month, day, overflow)?; - Ok(Self::new_unchecked(iso, calendar)) - } - - /// Returns the `month` value of `MonthDay`. - #[inline] - #[must_use] - pub fn month(&self) -> u8 { - self.iso.month - } - - /// Returns the `day` value of `MonthDay`. - #[inline] - #[must_use] - pub fn day(&self) -> u8 { - self.iso.day - } - - /// Returns a reference to `MonthDay`'s `CalendarSlot` - #[inline] - #[must_use] - pub fn calendar(&self) -> &CalendarSlot { - &self.calendar - } -} - -impl GetCalendarSlot for MonthDay { - fn get_calendar(&self) -> CalendarSlot { - self.calendar.clone() - } -} - -impl IsoDateSlots for MonthDay { - #[inline] - /// Returns this structs `IsoDate`. - fn iso_date(&self) -> IsoDate { - self.iso - } -} - -impl FromStr for MonthDay { - type Err = TemporalError; - - fn from_str(s: &str) -> Result { - let record = crate::parser::parse_month_day(s)?; - - let calendar = record.calendar.unwrap_or("iso8601".into()); - - Self::new( - record.date.month, - record.date.day, - CalendarSlot::from_str(&calendar)?, - ArithmeticOverflow::Reject, - ) - } -} diff --git a/core/temporal/src/components/time.rs b/core/temporal/src/components/time.rs deleted file mode 100644 index a1e4d70298..0000000000 --- a/core/temporal/src/components/time.rs +++ /dev/null @@ -1,274 +0,0 @@ -//! This module implements `Time` and any directly related algorithms. - -use crate::{ - components::{duration::TimeDuration, Duration}, - iso::IsoTime, - options::{ArithmeticOverflow, TemporalRoundingMode, TemporalUnit}, - utils, TemporalError, TemporalResult, -}; - -/// The native Rust implementation of `Temporal.PlainTime`. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Time { - iso: IsoTime, -} - -// ==== Private API ==== - -impl Time { - #[inline] - #[must_use] - pub(crate) fn new_unchecked(iso: IsoTime) -> Self { - Self { iso } - } - - /// Returns true if a valid `Time`. - #[allow(dead_code)] - pub(crate) fn is_valid(&self) -> bool { - self.iso.is_valid() - } - - /// Adds a `TimeDuration` to the current `Time`. - /// - /// Spec Equivalent: `AddDurationToOrSubtractDurationFromPlainTime` AND `AddTime`. - pub(crate) fn add_to_time(&self, duration: &TimeDuration) -> Self { - let (_, result) = IsoTime::balance( - f64::from(self.hour()) + duration.hours(), - f64::from(self.minute()) + duration.minutes(), - f64::from(self.second()) + duration.seconds(), - f64::from(self.millisecond()) + duration.milliseconds(), - f64::from(self.microsecond()) + duration.microseconds(), - f64::from(self.nanosecond()) + duration.nanoseconds(), - ); - - // NOTE (nekevss): IsoTime::balance should never return an invalid `IsoTime` - - Self::new_unchecked(result) - } -} - -// ==== Public API ==== - -impl Time { - /// Creates a new `IsoTime` value. - pub fn new( - hour: i32, - minute: i32, - second: i32, - millisecond: i32, - microsecond: i32, - nanosecond: i32, - overflow: ArithmeticOverflow, - ) -> TemporalResult { - let time = IsoTime::new( - hour, - minute, - second, - millisecond, - microsecond, - nanosecond, - overflow, - )?; - Ok(Self::new_unchecked(time)) - } - - /// Returns the internal `hour` field. - #[inline] - #[must_use] - pub const fn hour(&self) -> u8 { - self.iso.hour - } - - /// Returns the internal `minute` field. - #[inline] - #[must_use] - pub const fn minute(&self) -> u8 { - self.iso.minute - } - - /// Returns the internal `second` field. - #[inline] - #[must_use] - pub const fn second(&self) -> u8 { - self.iso.second - } - - /// Returns the internal `millisecond` field. - #[inline] - #[must_use] - pub const fn millisecond(&self) -> u16 { - self.iso.millisecond - } - - /// Returns the internal `microsecond` field. - #[inline] - #[must_use] - pub const fn microsecond(&self) -> u16 { - self.iso.microsecond - } - - /// Returns the internal `nanosecond` field. - #[inline] - #[must_use] - pub const fn nanosecond(&self) -> u16 { - self.iso.nanosecond - } - - /// Add a `Duration` to the current `Time`. - pub fn add(&self, duration: &Duration) -> TemporalResult { - if !duration.is_time_duration() { - return Err(TemporalError::range() - .with_message("DateDuration values cannot be added to `Time`.")); - } - Ok(self.add_time_duration(duration.time())) - } - - /// Adds a `TimeDuration` to the current `Time`. - #[inline] - #[must_use] - pub fn add_time_duration(&self, duration: &TimeDuration) -> Self { - self.add_to_time(duration) - } - - /// Subtract a `Duration` to the current `Time`. - pub fn subtract(&self, duration: &Duration) -> TemporalResult { - if !duration.is_time_duration() { - return Err(TemporalError::range() - .with_message("DateDuration values cannot be added to `Time` component.")); - } - Ok(self.add_time_duration(duration.time())) - } - - /// Adds a `TimeDuration` to the current `Time`. - #[inline] - #[must_use] - pub fn subtract_time_duration(&self, duration: &TimeDuration) -> Self { - self.add_to_time(&duration.neg()) - } - - // TODO (nekevss): optimize and test rounding_increment type (f64 vs. u64). - /// Rounds the current `Time` according to provided options. - pub fn round( - &self, - smallest_unit: TemporalUnit, - rounding_increment: Option, - rounding_mode: Option, - ) -> TemporalResult { - let increment = utils::to_rounding_increment(rounding_increment)?; - let mode = rounding_mode.unwrap_or(TemporalRoundingMode::HalfExpand); - - let max = smallest_unit - .to_maximum_rounding_increment() - .ok_or_else(|| { - TemporalError::range().with_message("smallestUnit must be a time value.") - })?; - - // Safety (nekevss): to_rounding_increment returns a value in the range of a u32. - utils::validate_temporal_rounding_increment(increment as u32, u64::from(max), false)?; - - let (_, result) = self.iso.round(increment, smallest_unit, mode, None)?; - - Ok(Self::new_unchecked(result)) - } -} - -// ==== Test land ==== - -#[cfg(test)] -mod tests { - use crate::{components::Duration, iso::IsoTime, options::TemporalUnit}; - - use super::Time; - - fn assert_time(result: Time, values: (u8, u8, u8, u16, u16, u16)) { - assert!(result.hour() == values.0); - assert!(result.minute() == values.1); - assert!(result.second() == values.2); - assert!(result.millisecond() == values.3); - assert!(result.microsecond() == values.4); - assert!(result.nanosecond() == values.5); - } - - #[test] - fn time_round_millisecond() { - let base = Time::new_unchecked(IsoTime::new_unchecked(3, 34, 56, 987, 654, 321)); - - let result_1 = base - .round(TemporalUnit::Millisecond, Some(1.0), None) - .unwrap(); - assert_time(result_1, (3, 34, 56, 988, 0, 0)); - - let result_2 = base - .round(TemporalUnit::Millisecond, Some(2.0), None) - .unwrap(); - assert_time(result_2, (3, 34, 56, 988, 0, 0)); - - let result_3 = base - .round(TemporalUnit::Millisecond, Some(4.0), None) - .unwrap(); - assert_time(result_3, (3, 34, 56, 988, 0, 0)); - - let result_4 = base - .round(TemporalUnit::Millisecond, Some(5.0), None) - .unwrap(); - assert_time(result_4, (3, 34, 56, 990, 0, 0)); - } - - #[test] - fn time_round_microsecond() { - let base = Time::new_unchecked(IsoTime::new_unchecked(3, 34, 56, 987, 654, 321)); - - let result_1 = base - .round(TemporalUnit::Microsecond, Some(1.0), None) - .unwrap(); - assert_time(result_1, (3, 34, 56, 987, 654, 0)); - - let result_2 = base - .round(TemporalUnit::Microsecond, Some(2.0), None) - .unwrap(); - assert_time(result_2, (3, 34, 56, 987, 654, 0)); - - let result_3 = base - .round(TemporalUnit::Microsecond, Some(4.0), None) - .unwrap(); - assert_time(result_3, (3, 34, 56, 987, 656, 0)); - - let result_4 = base - .round(TemporalUnit::Microsecond, Some(5.0), None) - .unwrap(); - assert_time(result_4, (3, 34, 56, 987, 655, 0)); - } - - #[test] - fn time_round_nanoseconds() { - let base = Time::new_unchecked(IsoTime::new_unchecked(3, 34, 56, 987, 654, 321)); - - let result_1 = base - .round(TemporalUnit::Nanosecond, Some(1.0), None) - .unwrap(); - assert_time(result_1, (3, 34, 56, 987, 654, 321)); - - let result_2 = base - .round(TemporalUnit::Nanosecond, Some(2.0), None) - .unwrap(); - assert_time(result_2, (3, 34, 56, 987, 654, 322)); - - let result_3 = base - .round(TemporalUnit::Nanosecond, Some(4.0), None) - .unwrap(); - assert_time(result_3, (3, 34, 56, 987, 654, 320)); - - let result_4 = base - .round(TemporalUnit::Nanosecond, Some(5.0), None) - .unwrap(); - assert_time(result_4, (3, 34, 56, 987, 654, 320)); - } - - #[test] - fn add_duration_basic() { - let base = Time::new_unchecked(IsoTime::new_unchecked(15, 23, 30, 123, 456, 789)); - let result = base.add(&"PT16H".parse::().unwrap()).unwrap(); - - assert_time(result, (7, 23, 30, 123, 456, 789)); - } -} diff --git a/core/temporal/src/components/tz.rs b/core/temporal/src/components/tz.rs deleted file mode 100644 index 4deb4b0674..0000000000 --- a/core/temporal/src/components/tz.rs +++ /dev/null @@ -1,118 +0,0 @@ -//! This module implements the Temporal `TimeZone` and components. - -use num_bigint::BigInt; -use num_traits::ToPrimitive; - -use crate::{ - components::{calendar::CalendarSlot, DateTime, Instant}, - TemporalError, TemporalResult, -}; - -use super::calendar::CalendarProtocol; - -/// Any object that implements the `TzProtocol` must implement the below methods/properties. -pub const TIME_ZONE_PROPERTIES: [&str; 3] = - ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "id"]; - -/// The Time Zone Protocol that must be implemented for time zones. -pub trait TzProtocol: Clone { - /// The context passed to every method of the `TzProtocol`. - type Context; - /// Get the Offset nanoseconds for this `TimeZone` - fn get_offset_nanos_for(&self, context: &mut Self::Context) -> TemporalResult; - /// Get the possible Instant for this `TimeZone` - fn get_possible_instant_for(&self, context: &mut Self::Context) - -> TemporalResult>; // TODO: Implement Instant - /// Get the `TimeZone`'s identifier. - fn id(&self, context: &mut Self::Context) -> TemporalResult; -} - -/// A Temporal `TimeZone`. -#[derive(Debug, Clone)] -#[allow(unused)] -pub struct TimeZone { - pub(crate) iana: Option, // TODO: ICU4X IANA TimeZone support. - pub(crate) offset: Option, -} - -/// The `TimeZoneSlot` represents a `[[TimeZone]]` internal slot value. -#[derive(Clone)] -pub enum TimeZoneSlot { - /// A native `TimeZone` representation. - Tz(TimeZone), - /// A Custom `TimeZone` that implements the `TzProtocol`. - Protocol(Z), -} - -impl core::fmt::Debug for TimeZoneSlot { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Tz(tz) => write!(f, "{tz:?}"), - Self::Protocol(_) => write!(f, "TzProtocol"), - } - } -} - -impl TimeZoneSlot { - pub(crate) fn get_datetime_for( - &self, - instant: &Instant, - calendar: &CalendarSlot, - context: &mut Z::Context, - ) -> TemporalResult> { - let nanos = self.get_offset_nanos_for(context)?; - DateTime::from_instant(instant, nanos.to_f64().unwrap_or(0.0), calendar.clone()) - } -} - -impl TimeZoneSlot { - /// Get the offset for this current `TimeZoneSlot`. - pub fn get_offset_nanos_for(&self, context: &mut Z::Context) -> TemporalResult { - // 1. Let timeZone be the this value. - // 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimeZone]]). - // 3. Set instant to ? ToTemporalInstant(instant). - match self { - Self::Tz(tz) => { - // 4. If timeZone.[[OffsetMinutes]] is not empty, return 𝔽(timeZone.[[OffsetMinutes]] × (60 × 10^9)). - if let Some(offset) = &tz.offset { - return Ok(BigInt::from(i64::from(*offset) * 60_000_000_000i64)); - } - // 5. Return 𝔽(GetNamedTimeZoneOffsetNanoseconds(timeZone.[[Identifier]], instant.[[Nanoseconds]])). - Err(TemporalError::range().with_message("IANA TimeZone names not yet implemented.")) - } - // Call any custom implemented TimeZone. - Self::Protocol(p) => p.get_offset_nanos_for(context), - } - } - - /// Get the possible `Instant`s for this `TimeZoneSlot`. - pub fn get_possible_instant_for( - &self, - _context: &mut Z::Context, - ) -> TemporalResult> { - Err(TemporalError::general("Not yet implemented.")) - } - - /// Returns the current `TimeZoneSlot`'s identifier. - pub fn id(&self, context: &mut Z::Context) -> TemporalResult { - match self { - Self::Tz(_) => Err(TemporalError::range().with_message("Not yet implemented.")), // TODO: Implement Display for Time Zone. - Self::Protocol(tz) => tz.id(context), - } - } -} - -impl TzProtocol for () { - type Context = (); - fn get_offset_nanos_for(&self, (): &mut ()) -> TemporalResult { - unreachable!() - } - - fn get_possible_instant_for(&self, (): &mut ()) -> TemporalResult> { - unreachable!() - } - - fn id(&self, (): &mut ()) -> TemporalResult { - Ok("() TimeZone".to_owned()) - } -} diff --git a/core/temporal/src/components/year_month.rs b/core/temporal/src/components/year_month.rs deleted file mode 100644 index 9686c784ea..0000000000 --- a/core/temporal/src/components/year_month.rs +++ /dev/null @@ -1,96 +0,0 @@ -//! This module implements `YearMonth` and any directly related algorithms. - -use std::str::FromStr; - -use crate::{ - components::calendar::CalendarSlot, - iso::{IsoDate, IsoDateSlots}, - options::ArithmeticOverflow, - TemporalError, TemporalResult, -}; - -use super::calendar::{CalendarProtocol, GetCalendarSlot}; - -/// The native Rust implementation of `Temporal.YearMonth`. -#[derive(Debug, Default, Clone)] -pub struct YearMonth { - iso: IsoDate, - calendar: CalendarSlot, -} - -impl YearMonth { - /// Creates an unvalidated `YearMonth`. - #[inline] - #[must_use] - pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { - Self { iso, calendar } - } - - /// Creates a new valid `YearMonth`. - #[inline] - pub fn new( - year: i32, - month: i32, - reference_day: Option, - calendar: CalendarSlot, - overflow: ArithmeticOverflow, - ) -> TemporalResult { - let day = reference_day.unwrap_or(1); - let iso = IsoDate::new(year, month, day, overflow)?; - Ok(Self::new_unchecked(iso, calendar)) - } - - /// Returns the `year` value for this `YearMonth`. - #[inline] - #[must_use] - pub fn year(&self) -> i32 { - self.iso.year - } - - /// Returns the `month` value for this `YearMonth`. - #[inline] - #[must_use] - pub fn month(&self) -> u8 { - self.iso.month - } - - /// Returns the Calendar value. - #[inline] - #[must_use] - pub fn calendar(&self) -> &CalendarSlot { - &self.calendar - } -} - -impl GetCalendarSlot for YearMonth { - /// Returns a reference to `YearMonth`'s `CalendarSlot` - fn get_calendar(&self) -> CalendarSlot { - self.calendar.clone() - } -} - -impl IsoDateSlots for YearMonth { - #[inline] - /// Returns this `YearMonth`'s `IsoDate` - fn iso_date(&self) -> IsoDate { - self.iso - } -} - -impl FromStr for YearMonth { - type Err = TemporalError; - - fn from_str(s: &str) -> Result { - let record = crate::parser::parse_year_month(s)?; - - let calendar = record.calendar.unwrap_or("iso8601".into()); - - Self::new( - record.date.year, - record.date.month, - None, - CalendarSlot::from_str(&calendar)?, - ArithmeticOverflow::Reject, - ) - } -} diff --git a/core/temporal/src/components/zoneddatetime.rs b/core/temporal/src/components/zoneddatetime.rs deleted file mode 100644 index 04d72941af..0000000000 --- a/core/temporal/src/components/zoneddatetime.rs +++ /dev/null @@ -1,234 +0,0 @@ -//! This module implements `ZonedDateTime` and any directly related algorithms. - -use num_bigint::BigInt; -use tinystr::TinyStr4; - -use crate::{ - components::{ - calendar::{CalendarDateLike, CalendarProtocol, CalendarSlot}, - tz::TimeZoneSlot, - Instant, - }, - TemporalResult, -}; - -use super::tz::TzProtocol; - -/// The native Rust implementation of `Temporal.ZonedDateTime`. -#[derive(Debug, Clone)] -pub struct ZonedDateTime { - instant: Instant, - calendar: CalendarSlot, - tz: TimeZoneSlot, -} - -// ==== Private API ==== - -impl ZonedDateTime { - /// Creates a `ZonedDateTime` without validating the input. - #[inline] - #[must_use] - pub(crate) fn new_unchecked( - instant: Instant, - calendar: CalendarSlot, - tz: TimeZoneSlot, - ) -> Self { - Self { - instant, - calendar, - tz, - } - } -} - -// ==== Public API ==== - -impl ZonedDateTime { - /// Creates a new valid `ZonedDateTime`. - #[inline] - pub fn new( - nanos: BigInt, - calendar: CalendarSlot, - tz: TimeZoneSlot, - ) -> TemporalResult { - let instant = Instant::new(nanos)?; - Ok(Self::new_unchecked(instant, calendar, tz)) - } - - /// Returns `ZonedDateTime`'s Calendar. - #[inline] - #[must_use] - pub fn calendar(&self) -> &CalendarSlot { - &self.calendar - } - - /// Returns `ZonedDateTime`'s `TimeZone` slot. - #[inline] - #[must_use] - pub fn tz(&self) -> &TimeZoneSlot { - &self.tz - } - - /// Returns the `epochSeconds` value of this `ZonedDateTime`. - #[must_use] - pub fn epoch_seconds(&self) -> f64 { - self.instant.epoch_seconds() - } - - /// Returns the `epochMilliseconds` value of this `ZonedDateTime`. - #[must_use] - pub fn epoch_milliseconds(&self) -> f64 { - self.instant.epoch_milliseconds() - } - - /// Returns the `epochMicroseconds` value of this `ZonedDateTime`. - #[must_use] - pub fn epoch_microseconds(&self) -> f64 { - self.instant.epoch_microseconds() - } - - /// Returns the `epochNanoseconds` value of this `ZonedDateTime`. - #[must_use] - pub fn epoch_nanoseconds(&self) -> f64 { - self.instant.epoch_nanoseconds() - } -} - -// ==== Context based API ==== - -impl ZonedDateTime -where - C: CalendarProtocol, -{ - /// Returns the `year` value for this `ZonedDateTime`. - #[inline] - pub fn contextual_year(&self, context: &mut C::Context) -> TemporalResult { - let dt = self - .tz - .get_datetime_for(&self.instant, &self.calendar, context)?; - self.calendar.year(&CalendarDateLike::DateTime(dt), context) - } - - /// Returns the `month` value for this `ZonedDateTime`. - pub fn contextual_month(&self, context: &mut C::Context) -> TemporalResult { - let dt = self - .tz - .get_datetime_for(&self.instant, &self.calendar, context)?; - self.calendar - .month(&CalendarDateLike::DateTime(dt), context) - } - - /// Returns the `monthCode` value for this `ZonedDateTime`. - pub fn contextual_month_code(&self, context: &mut C::Context) -> TemporalResult { - let dt = self - .tz - .get_datetime_for(&self.instant, &self.calendar, context)?; - self.calendar - .month_code(&CalendarDateLike::DateTime(dt), context) - } - - /// Returns the `day` value for this `ZonedDateTime`. - pub fn contextual_day(&self, context: &mut C::Context) -> TemporalResult { - let dt = self - .tz - .get_datetime_for(&self.instant, &self.calendar, context)?; - self.calendar.day(&CalendarDateLike::DateTime(dt), context) - } - - /// Returns the `hour` value for this `ZonedDateTime`. - pub fn contextual_hour(&self, context: &mut C::Context) -> TemporalResult { - let dt = self - .tz - .get_datetime_for(&self.instant, &self.calendar, context)?; - Ok(dt.hour()) - } - - /// Returns the `minute` value for this `ZonedDateTime`. - pub fn contextual_minute(&self, context: &mut C::Context) -> TemporalResult { - let dt = self - .tz - .get_datetime_for(&self.instant, &self.calendar, context)?; - Ok(dt.minute()) - } - - /// Returns the `second` value for this `ZonedDateTime`. - pub fn contextual_second(&self, context: &mut C::Context) -> TemporalResult { - let dt = self - .tz - .get_datetime_for(&self.instant, &self.calendar, context)?; - Ok(dt.second()) - } - - /// Returns the `millisecond` value for this `ZonedDateTime`. - pub fn contextual_millisecond(&self, context: &mut C::Context) -> TemporalResult { - let dt = self - .tz - .get_datetime_for(&self.instant, &self.calendar, context)?; - Ok(dt.millisecond()) - } - - /// Returns the `microsecond` value for this `ZonedDateTime`. - pub fn contextual_microsecond(&self, context: &mut C::Context) -> TemporalResult { - let dt = self - .tz - .get_datetime_for(&self.instant, &self.calendar, context)?; - Ok(dt.millisecond()) - } - - /// Returns the `nanosecond` value for this `ZonedDateTime`. - pub fn contextual_nanosecond(&self, context: &mut C::Context) -> TemporalResult { - let dt = self - .tz - .get_datetime_for(&self.instant, &self.calendar, context)?; - Ok(dt.nanosecond()) - } -} - -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use crate::components::tz::TimeZone; - use num_bigint::BigInt; - - use super::{CalendarSlot, TimeZoneSlot, ZonedDateTime}; - - #[test] - fn basic_zdt_test() { - let nov_30_2023_utc = BigInt::from(1_701_308_952_000_000_000i64); - - let zdt = ZonedDateTime::<(), ()>::new( - nov_30_2023_utc.clone(), - CalendarSlot::from_str("iso8601").unwrap(), - TimeZoneSlot::Tz(TimeZone { - iana: None, - offset: Some(0), - }), - ) - .unwrap(); - - assert_eq!(zdt.contextual_year(&mut ()).unwrap(), 2023); - assert_eq!(zdt.contextual_month(&mut ()).unwrap(), 11); - assert_eq!(zdt.contextual_day(&mut ()).unwrap(), 30); - assert_eq!(zdt.contextual_hour(&mut ()).unwrap(), 1); - assert_eq!(zdt.contextual_minute(&mut ()).unwrap(), 49); - assert_eq!(zdt.contextual_second(&mut ()).unwrap(), 12); - - let zdt_minus_five = ZonedDateTime::<(), ()>::new( - nov_30_2023_utc, - CalendarSlot::from_str("iso8601").unwrap(), - TimeZoneSlot::Tz(TimeZone { - iana: None, - offset: Some(-300), - }), - ) - .unwrap(); - - assert_eq!(zdt_minus_five.contextual_year(&mut ()).unwrap(), 2023); - assert_eq!(zdt_minus_five.contextual_month(&mut ()).unwrap(), 11); - assert_eq!(zdt_minus_five.contextual_day(&mut ()).unwrap(), 29); - assert_eq!(zdt_minus_five.contextual_hour(&mut ()).unwrap(), 20); - assert_eq!(zdt_minus_five.contextual_minute(&mut ()).unwrap(), 49); - assert_eq!(zdt_minus_five.contextual_second(&mut ()).unwrap(), 12); - } -} diff --git a/core/temporal/src/error.rs b/core/temporal/src/error.rs deleted file mode 100644 index 6aee99a8d1..0000000000 --- a/core/temporal/src/error.rs +++ /dev/null @@ -1,121 +0,0 @@ -//! This module implements `TemporalError`. - -use core::fmt; - -use icu_calendar::CalendarError; - -/// `TemporalError`'s error type. -#[derive(Debug, Default, Clone, Copy)] -pub enum ErrorKind { - /// Error. - #[default] - Generic, - /// TypeError - Type, - /// RangeError - Range, - /// SyntaxError - Syntax, -} - -impl fmt::Display for ErrorKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Generic => "Error", - Self::Type => "TypeError", - Self::Range => "RangeError", - Self::Syntax => "SyntaxError", - } - .fmt(f) - } -} - -/// The error type for `boa_temporal`. -#[derive(Debug, Clone)] -pub struct TemporalError { - kind: ErrorKind, - msg: Box, -} - -impl TemporalError { - fn new(kind: ErrorKind) -> Self { - Self { - kind, - msg: Box::default(), - } - } - - /// Create a generic error - #[must_use] - pub fn general(msg: S) -> Self - where - S: Into>, - { - Self::new(ErrorKind::Generic).with_message(msg) - } - - /// Create a range error. - #[must_use] - pub fn range() -> Self { - Self::new(ErrorKind::Range) - } - - /// Create a type error. - #[must_use] - pub fn r#type() -> Self { - Self::new(ErrorKind::Type) - } - - /// Create a syntax error. - #[must_use] - pub fn syntax() -> Self { - Self::new(ErrorKind::Syntax) - } - - /// Create an abrupt end error. - #[must_use] - pub fn abrupt_end() -> Self { - Self::syntax().with_message("Abrupt end to parsing target.") - } - - /// Add a message to the error. - #[must_use] - pub fn with_message(mut self, msg: S) -> Self - where - S: Into>, - { - self.msg = msg.into(); - self - } - - /// Returns this error's kind. - #[must_use] - pub fn kind(&self) -> ErrorKind { - self.kind - } - - /// Returns the error message. - #[must_use] - pub fn message(&self) -> &str { - &self.msg - } -} - -impl fmt::Display for TemporalError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.kind)?; - - let msg = self.msg.trim(); - if !msg.is_empty() { - write!(f, ": {msg}")?; - } - - Ok(()) - } -} - -impl From for TemporalError { - fn from(value: CalendarError) -> Self { - TemporalError::general(value.to_string()) - } -} diff --git a/core/temporal/src/fields.rs b/core/temporal/src/fields.rs deleted file mode 100644 index a02047a5dc..0000000000 --- a/core/temporal/src/fields.rs +++ /dev/null @@ -1,640 +0,0 @@ -//! This module implements a native Rust `TemporalField` and components. - -use std::{fmt, str::FromStr}; - -use crate::{ - components::calendar::{CalendarProtocol, CalendarSlot}, - error::TemporalError, - TemporalResult, -}; - -use bitflags::bitflags; -// use rustc_hash::FxHashSet; -use tinystr::{TinyAsciiStr, TinyStr16, TinyStr4}; - -bitflags! { - /// FieldMap maps the currently active fields on the `TemporalField` - #[derive(Debug, PartialEq, Eq)] - pub struct FieldMap: u16 { - /// Represents an active `year` field - const YEAR = 0b0000_0000_0000_0001; - /// Represents an active `month` field - const MONTH = 0b0000_0000_0000_0010; - /// Represents an active `monthCode` field - const MONTH_CODE = 0b0000_0000_0000_0100; - /// Represents an active `day` field - const DAY = 0b0000_0000_0000_1000; - /// Represents an active `hour` field - const HOUR = 0b0000_0000_0001_0000; - /// Represents an active `minute` field - const MINUTE = 0b0000_0000_0010_0000; - /// Represents an active `second` field - const SECOND = 0b0000_0000_0100_0000; - /// Represents an active `millisecond` field - const MILLISECOND = 0b0000_0000_1000_0000; - /// Represents an active `microsecond` field - const MICROSECOND = 0b0000_0001_0000_0000; - /// Represents an active `nanosecond` field - const NANOSECOND = 0b0000_0010_0000_0000; - /// Represents an active `offset` field - const OFFSET = 0b0000_0100_0000_0000; - /// Represents an active `era` field - const ERA = 0b0000_1000_0000_0000; - /// Represents an active `eraYear` field - const ERA_YEAR = 0b0001_0000_0000_0000; - /// Represents an active `timeZone` field - const TIME_ZONE = 0b0010_0000_0000_0000; - // NOTE(nekevss): Two bits preserved if needed. - } -} - -/// The post conversion field value. -#[derive(Debug)] -#[allow(variant_size_differences)] -pub enum FieldValue { - /// Designates the values as an integer. - Integer(i32), - /// Designates that the value is undefined. - Undefined, - /// Designates the value as a string. - String(String), -} - -impl From for FieldValue { - fn from(value: i32) -> Self { - FieldValue::Integer(value) - } -} - -/// The Conversion type of a field. -#[derive(Debug, Clone, Copy)] -pub enum FieldConversion { - /// Designates the Conversion type is `ToIntegerWithTruncation` - ToIntegerWithTruncation, - /// Designates the Conversion type is `ToPositiveIntegerWithTruncation` - ToPositiveIntegerWithTruncation, - /// Designates the Conversion type is `ToPrimitiveRequireString` - ToPrimativeAndRequireString, - /// Designates the Conversion type is nothing - None, -} - -impl FromStr for FieldConversion { - type Err = TemporalError; - - fn from_str(s: &str) -> Result { - match s { - "year" | "hour" | "minute" | "second" | "millisecond" | "microsecond" - | "nanosecond" => Ok(Self::ToIntegerWithTruncation), - "month" | "day" => Ok(Self::ToPositiveIntegerWithTruncation), - "monthCode" | "offset" | "eraYear" => Ok(Self::ToPrimativeAndRequireString), - _ => Err(TemporalError::range() - .with_message(format!("{s} is not a valid TemporalField Property"))), - } - } -} - -/// `TemporalFields` acts as a native Rust implementation of the `fields` object -/// -/// The temporal fields are laid out in the Temporal proposal under section 13.46 `PrepareTemporalFields` -/// with conversion and defaults laid out by Table 17 (displayed below). -/// -/// ## Table 17: Temporal field requirements -/// -/// | Property | Conversion | Default | -/// | -------------|-----------------------------------|------------| -/// | "year" | `ToIntegerWithTruncation` | undefined | -/// | "month" | `ToPositiveIntegerWithTruncation` | undefined | -/// | "monthCode" | `ToPrimitiveAndRequireString` | undefined | -/// | "day" | `ToPositiveIntegerWithTruncation` | undefined | -/// | "hour" | `ToIntegerWithTruncation` | +0𝔽 | -/// | "minute" | `ToIntegerWithTruncation` | +0𝔽 | -/// | "second" | `ToIntegerWithTruncation` | +0𝔽 | -/// | "millisecond"| `ToIntegerWithTruncation` | +0𝔽 | -/// | "microsecond"| `ToIntegerWithTruncation` | +0𝔽 | -/// | "nanosecond" | `ToIntegerWithTruncation` | +0𝔽 | -/// | "offset" | `ToPrimitiveAndRequireString` | undefined | -/// | "era" | `ToPrimitiveAndRequireString` | undefined | -/// | "eraYear" | `ToIntegerWithTruncation` | undefined | -/// | "timeZone" | `None` | undefined | -#[derive(Debug)] -pub struct TemporalFields { - bit_map: FieldMap, - year: Option, - month: Option, - month_code: Option, // TODO: Switch to icu compatible value. - day: Option, - hour: i32, - minute: i32, - second: i32, - millisecond: i32, - microsecond: i32, - nanosecond: i32, - offset: Option, // TODO: Switch to tinystr? - era: Option, // TODO: switch to icu compatible value. - era_year: Option, // TODO: switch to icu compatible value. - time_zone: Option, // TODO: figure out the identifier for TimeZone. -} - -impl Default for TemporalFields { - fn default() -> Self { - Self { - bit_map: FieldMap::empty(), - year: None, - month: None, - month_code: None, - day: None, - hour: 0, - minute: 0, - second: 0, - millisecond: 0, - microsecond: 0, - nanosecond: 0, - offset: None, - era: None, - era_year: None, - time_zone: None, - } - } -} - -impl TemporalFields { - pub(crate) fn era(&self) -> TinyAsciiStr<16> { - self.era.unwrap_or("default".parse().expect("less than 8")) - } - - pub(crate) const fn year(&self) -> Option { - self.year - } - - pub(crate) const fn month(&self) -> Option { - self.month - } - - pub(crate) fn month_code(&self) -> TinyAsciiStr<4> { - // Passing along an invalid MonthCode to ICU...might be better to figure out a different approach...TBD. - self.month_code - .unwrap_or("M00".parse().expect("less than 4")) - } - - pub(crate) const fn day(&self) -> Option { - self.day - } -} - -// TODO: Update the below. -impl TemporalFields { - /// Flags a field as being required. - #[inline] - pub fn require_field(&mut self, field: &str) { - match field { - "year" => self.bit_map.set(FieldMap::YEAR, true), - "month" => self.bit_map.set(FieldMap::MONTH, true), - "monthCode" => self.bit_map.set(FieldMap::MONTH_CODE, true), - "day" => self.bit_map.set(FieldMap::DAY, true), - "hour" => self.bit_map.set(FieldMap::HOUR, true), - "minute" => self.bit_map.set(FieldMap::MINUTE, true), - "second" => self.bit_map.set(FieldMap::SECOND, true), - "millisecond" => self.bit_map.set(FieldMap::MILLISECOND, true), - "microsecond" => self.bit_map.set(FieldMap::MICROSECOND, true), - "nanosecond" => self.bit_map.set(FieldMap::NANOSECOND, true), - "offset" => self.bit_map.set(FieldMap::OFFSET, true), - "era" => self.bit_map.set(FieldMap::ERA, true), - "eraYear" => self.bit_map.set(FieldMap::ERA_YEAR, true), - "timeZone" => self.bit_map.set(FieldMap::TIME_ZONE, true), - _ => {} - } - } - - #[inline] - /// A generic field setter for `TemporalFields` - /// - /// This method will not run any `JsValue` conversion. `FieldValue` is - /// expected to contain a preconverted value. - pub fn set_field_value(&mut self, field: &str, value: &FieldValue) -> TemporalResult<()> { - match field { - "year" => self.set_year(value)?, - "month" => self.set_month(value)?, - "monthCode" => self.set_month_code(value)?, - "day" => self.set_day(value)?, - "hour" => self.set_hour(value)?, - "minute" => self.set_minute(value)?, - "second" => self.set_second(value)?, - "millisecond" => self.set_milli(value)?, - "microsecond" => self.set_micro(value)?, - "nanosecond" => self.set_nano(value)?, - "offset" => self.set_offset(value)?, - "era" => self.set_era(value)?, - "eraYear" => self.set_era_year(value)?, - "timeZone" => self.set_time_zone(value)?, - _ => unreachable!(), - } - - Ok(()) - } - - /// Retrieves a field value if set, else None. - pub fn get(&self, field: &str) -> Option { - if !self.is_set_field(field) { - return None; - } - match field { - "year" => self.year.map(FieldValue::Integer), - "month" => self.month.map(FieldValue::Integer), - "monthCode" => self.month_code.map(|s| FieldValue::String(s.to_string())), - "day" => self.day.map(FieldValue::from), - "hour" => Some(FieldValue::Integer(self.hour)), - "minute" => Some(FieldValue::Integer(self.minute)), - "second" => Some(FieldValue::Integer(self.second)), - "millisecond" => Some(FieldValue::Integer(self.millisecond)), - "microsecond" => Some(FieldValue::Integer(self.microsecond)), - "nanosecond" => Some(FieldValue::Integer(self.nanosecond)), - "offset" => self.offset.as_ref().map(|s| FieldValue::String(s.clone())), - "era" => self.era.map(|s| FieldValue::String(s.to_string())), - "eraYear" => self.era_year.map(FieldValue::Integer), - "timeZone" => self - .time_zone - .as_ref() - .map(|s| FieldValue::String(s.clone())), - _ => unreachable!(), - } - } - - fn is_set_field(&self, field: &str) -> bool { - match field { - "year" => self.bit_map.contains(FieldMap::YEAR), - "month" => self.bit_map.contains(FieldMap::MONTH), - "monthCode" => self.bit_map.contains(FieldMap::MONTH_CODE), - "day" => self.bit_map.contains(FieldMap::DAY), - "hour" => self.bit_map.contains(FieldMap::HOUR), - "minute" => self.bit_map.contains(FieldMap::MINUTE), - "second" => self.bit_map.contains(FieldMap::SECOND), - "millisecond" => self.bit_map.contains(FieldMap::MILLISECOND), - "microsecond" => self.bit_map.contains(FieldMap::MICROSECOND), - "nanosecond" => self.bit_map.contains(FieldMap::NANOSECOND), - "offset" => self.bit_map.contains(FieldMap::OFFSET), - "era" => self.bit_map.contains(FieldMap::ERA), - "eraYear" => self.bit_map.contains(FieldMap::ERA_YEAR), - "timeZone" => self.bit_map.contains(FieldMap::TIME_ZONE), - _ => unreachable!(), - } - } - - #[inline] - fn set_year(&mut self, value: &FieldValue) -> TemporalResult<()> { - let FieldValue::Integer(y) = value else { - return Err(TemporalError::r#type().with_message("Year must be an integer.")); - }; - self.year = Some(*y); - self.bit_map.set(FieldMap::YEAR, true); - Ok(()) - } - - #[inline] - fn set_month(&mut self, value: &FieldValue) -> TemporalResult<()> { - let FieldValue::Integer(mo) = value else { - return Err(TemporalError::r#type().with_message("Month must be an integer.")); - }; - self.year = Some(*mo); - self.bit_map.set(FieldMap::MONTH, true); - Ok(()) - } - - #[inline] - fn set_month_code(&mut self, value: &FieldValue) -> TemporalResult<()> { - let FieldValue::String(mc) = value else { - return Err(TemporalError::r#type().with_message("monthCode must be string.")); - }; - self.month_code = - Some(TinyStr4::from_bytes(mc.as_bytes()).expect("monthCode must be less than 4 chars")); - self.bit_map.set(FieldMap::MONTH_CODE, true); - Ok(()) - } - - #[inline] - fn set_day(&mut self, value: &FieldValue) -> TemporalResult<()> { - let FieldValue::Integer(d) = value else { - return Err(TemporalError::r#type().with_message("day must be an integer.")); - }; - self.day = Some(*d); - self.bit_map.set(FieldMap::DAY, true); - Ok(()) - } - - #[inline] - fn set_hour(&mut self, value: &FieldValue) -> TemporalResult<()> { - let FieldValue::Integer(h) = value else { - return Err(TemporalError::r#type().with_message("hour must be an integer.")); - }; - self.hour = *h; - self.bit_map.set(FieldMap::HOUR, true); - Ok(()) - } - - #[inline] - fn set_minute(&mut self, value: &FieldValue) -> TemporalResult<()> { - let FieldValue::Integer(min) = value else { - return Err(TemporalError::r#type().with_message("minute must be an integer.")); - }; - self.minute = *min; - self.bit_map.set(FieldMap::MINUTE, true); - Ok(()) - } - - #[inline] - fn set_second(&mut self, value: &FieldValue) -> TemporalResult<()> { - let FieldValue::Integer(sec) = value else { - return Err(TemporalError::r#type().with_message("Second must be an integer.")); - }; - self.second = *sec; - self.bit_map.set(FieldMap::SECOND, true); - Ok(()) - } - - #[inline] - fn set_milli(&mut self, value: &FieldValue) -> TemporalResult<()> { - let FieldValue::Integer(milli) = value else { - return Err(TemporalError::r#type().with_message("Second must be an integer.")); - }; - self.millisecond = *milli; - self.bit_map.set(FieldMap::MILLISECOND, true); - Ok(()) - } - - #[inline] - fn set_micro(&mut self, value: &FieldValue) -> TemporalResult<()> { - let FieldValue::Integer(micro) = value else { - return Err(TemporalError::r#type().with_message("microsecond must be an integer.")); - }; - self.microsecond = *micro; - self.bit_map.set(FieldMap::MICROSECOND, true); - Ok(()) - } - - #[inline] - fn set_nano(&mut self, value: &FieldValue) -> TemporalResult<()> { - let FieldValue::Integer(nano) = value else { - return Err(TemporalError::r#type().with_message("nanosecond must be an integer.")); - }; - self.nanosecond = *nano; - self.bit_map.set(FieldMap::NANOSECOND, true); - Ok(()) - } - - #[inline] - fn set_offset(&mut self, value: &FieldValue) -> TemporalResult<()> { - let FieldValue::String(offset) = value else { - return Err(TemporalError::r#type().with_message("offset must be string.")); - }; - self.offset = Some(offset.to_string()); - self.bit_map.set(FieldMap::OFFSET, true); - - Ok(()) - } - - #[inline] - fn set_era(&mut self, value: &FieldValue) -> TemporalResult<()> { - let FieldValue::String(era) = value else { - return Err(TemporalError::r#type().with_message("era must be string.")); - }; - self.era = - Some(TinyStr16::from_bytes(era.as_bytes()).expect("era should not exceed 16 bytes.")); - self.bit_map.set(FieldMap::ERA, true); - - Ok(()) - } - - #[inline] - fn set_era_year(&mut self, value: &FieldValue) -> TemporalResult<()> { - let FieldValue::Integer(era_year) = value else { - return Err(TemporalError::r#type().with_message("eraYear must be an integer.")); - }; - self.era_year = Some(*era_year); - self.bit_map.set(FieldMap::ERA_YEAR, true); - Ok(()) - } - - #[inline] - fn set_time_zone(&mut self, value: &FieldValue) -> TemporalResult<()> { - let FieldValue::String(tz) = value else { - return Err(TemporalError::r#type().with_message("tz must be string.")); - }; - self.time_zone = Some(tz.to_string()); - self.bit_map.set(FieldMap::TIME_ZONE, true); - Ok(()) - } -} - -impl TemporalFields { - /// Returns a vector filled with the key-value pairs marked as active. - #[must_use] - pub fn active_kvs(&self) -> Vec<(String, FieldValue)> { - self.keys().zip(self.values()).collect() - } - - /// Returns an iterator over the current keys. - #[must_use] - pub fn keys(&self) -> Keys { - Keys { - iter: self.bit_map.iter(), - } - } - - /// Returns an iterator over the current values. - #[must_use] - pub fn values(&self) -> Values<'_> { - Values { - fields: self, - iter: self.bit_map.iter(), - } - } - - /// Resolve `TemporalFields` month and monthCode fields. - pub(crate) fn iso_resolve_month(&mut self) -> TemporalResult<()> { - if self.month_code.is_none() { - if self.month.is_some() { - return Ok(()); - } - - return Err(TemporalError::range() - .with_message("month and MonthCode values cannot both be undefined.")); - } - - let unresolved_month_code = self - .month_code - .as_ref() - .expect("monthCode must exist at this point."); - - let month_code_integer = month_code_to_integer(*unresolved_month_code)?; - - let new_month = match self.month { - Some(month) if month != month_code_integer => { - return Err( - TemporalError::range().with_message("month and monthCode cannot be resolved.") - ) - } - _ => month_code_integer, - }; - - self.month = Some(new_month); - - Ok(()) - } - - /// Merges two `TemporalFields` values given a specific `CalendarSlot`. - pub fn merge_fields( - &self, - other: &Self, - calendar: &CalendarSlot, - ) -> TemporalResult { - let add_keys = other.keys().collect::>(); - let overridden_keys = calendar.field_keys_to_ignore(&add_keys)?; - - let mut result = Self::default(); - - for key in self.keys() { - let value = if overridden_keys.contains(&key) { - other.get(&key) - } else { - self.get(&key) - }; - - if let Some(value) = value { - result.set_field_value(&key, &value)?; - } - } - - Ok(result) - } -} - -/// Iterator over `TemporalFields` keys. -pub struct Keys { - iter: bitflags::iter::Iter, -} - -impl fmt::Debug for Keys { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "TemporalFields KeyIterator") - } -} - -impl Iterator for Keys { - type Item = String; - - fn next(&mut self) -> Option { - let Some(field) = self.iter.next() else { - return None; - }; - - match field { - FieldMap::YEAR => Some("year".to_owned()), - FieldMap::MONTH => Some("month".to_owned()), - FieldMap::MONTH_CODE => Some("monthCode".to_owned()), - FieldMap::DAY => Some("day".to_owned()), - FieldMap::HOUR => Some("hour".to_owned()), - FieldMap::MINUTE => Some("minute".to_owned()), - FieldMap::SECOND => Some("second".to_owned()), - FieldMap::MILLISECOND => Some("millisecond".to_owned()), - FieldMap::MICROSECOND => Some("microsecond".to_owned()), - FieldMap::NANOSECOND => Some("nanosecond".to_owned()), - FieldMap::OFFSET => Some("offset".to_owned()), - FieldMap::ERA => Some("era".to_owned()), - FieldMap::ERA_YEAR => Some("eraYear".to_owned()), - FieldMap::TIME_ZONE => Some("timeZone".to_owned()), - _ => None, - } - } -} - -/// An iterator over `TemporalFields`'s values. -pub struct Values<'a> { - fields: &'a TemporalFields, - iter: bitflags::iter::Iter, -} - -impl fmt::Debug for Values<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "TemporalFields Values Iterator") - } -} - -impl Iterator for Values<'_> { - type Item = FieldValue; - - fn next(&mut self) -> Option { - let Some(field) = self.iter.next() else { - return None; - }; - match field { - FieldMap::YEAR => Some( - self.fields - .year - .map_or(FieldValue::Undefined, FieldValue::Integer), - ), - FieldMap::MONTH => Some( - self.fields - .month - .map_or(FieldValue::Undefined, FieldValue::Integer), - ), - FieldMap::MONTH_CODE => Some( - self.fields - .month_code - .map_or(FieldValue::Undefined, |s| FieldValue::String(s.to_string())), - ), - FieldMap::DAY => Some( - self.fields - .day - .map_or(FieldValue::Undefined, FieldValue::Integer), - ), - FieldMap::HOUR => Some(FieldValue::Integer(self.fields.hour)), - FieldMap::MINUTE => Some(FieldValue::Integer(self.fields.minute)), - FieldMap::SECOND => Some(FieldValue::Integer(self.fields.second)), - FieldMap::MILLISECOND => Some(FieldValue::Integer(self.fields.millisecond)), - FieldMap::MICROSECOND => Some(FieldValue::Integer(self.fields.microsecond)), - FieldMap::NANOSECOND => Some(FieldValue::Integer(self.fields.nanosecond)), - FieldMap::OFFSET => Some( - self.fields - .offset - .clone() - .map_or(FieldValue::Undefined, FieldValue::String), - ), - FieldMap::ERA => Some( - self.fields - .era - .map_or(FieldValue::Undefined, |s| FieldValue::String(s.to_string())), - ), - FieldMap::ERA_YEAR => Some( - self.fields - .era_year - .map_or(FieldValue::Undefined, FieldValue::Integer), - ), - FieldMap::TIME_ZONE => Some( - self.fields - .time_zone - .clone() - .map_or(FieldValue::Undefined, FieldValue::String), - ), - _ => None, - } - } -} - -fn month_code_to_integer(mc: TinyAsciiStr<4>) -> TemporalResult { - match mc.as_str() { - "M01" => Ok(1), - "M02" => Ok(2), - "M03" => Ok(3), - "M04" => Ok(4), - "M05" => Ok(5), - "M06" => Ok(6), - "M07" => Ok(7), - "M08" => Ok(8), - "M09" => Ok(9), - "M10" => Ok(10), - "M11" => Ok(11), - "M12" => Ok(12), - "M13" => Ok(13), - _ => Err(TemporalError::range().with_message("monthCode is not within the valid values.")), - } -} diff --git a/core/temporal/src/iso.rs b/core/temporal/src/iso.rs deleted file mode 100644 index 96bec11c0b..0000000000 --- a/core/temporal/src/iso.rs +++ /dev/null @@ -1,654 +0,0 @@ -//! This module implements the internal ISO field slots. -//! -//! The three main types of slots are: -//! - `IsoDateTime` -//! - `IsoDate` -//! - `IsoTime` -//! -//! An `IsoDate` represents the `[[ISOYear]]`, `[[ISOMonth]]`, and `[[ISODay]]` internal slots. -//! -//! An `IsoTime` represents the `[[ISOHour]]`, `[[ISOMinute]]`, `[[ISOsecond]]`, `[[ISOmillisecond]]`, -//! `[[ISOmicrosecond]]`, and `[[ISOnanosecond]]` internal slots. -//! -//! An `IsoDateTime` has the internal slots of both an `IsoDate` and `IsoTime`. - -use crate::{ - components::duration::DateDuration, - error::TemporalError, - options::{ArithmeticOverflow, TemporalRoundingMode, TemporalUnit}, - utils, TemporalResult, NS_PER_DAY, -}; -use icu_calendar::{Date as IcuDate, Iso}; -use num_bigint::BigInt; -use num_traits::{cast::FromPrimitive, ToPrimitive}; - -/// `IsoDateTime` is the record of the `IsoDate` and `IsoTime` internal slots. -#[derive(Debug, Default, Clone, Copy)] -pub struct IsoDateTime { - date: IsoDate, - time: IsoTime, -} - -impl IsoDateTime { - /// Creates a new `IsoDateTime` without any validaiton. - pub(crate) fn new_unchecked(date: IsoDate, time: IsoTime) -> Self { - Self { date, time } - } - - /// Creates a new validated `IsoDateTime` that is within valid limits. - pub(crate) fn new(date: IsoDate, time: IsoTime) -> TemporalResult { - if !iso_dt_within_valid_limits(date, &time) { - return Err( - TemporalError::range().with_message("IsoDateTime not within a valid range.") - ); - } - Ok(Self::new_unchecked(date, time)) - } - - // NOTE: The below assumes that nanos is from an `Instant` and thus in a valid range. -> Needs validation. - /// Creates an `IsoDateTime` from a `BigInt` of epochNanoseconds. - pub(crate) fn from_epoch_nanos(nanos: &BigInt, offset: f64) -> TemporalResult { - // Skip the assert as nanos should be validated by Instant. - // TODO: Determine whether value needs to be validated as integral. - // Get the component ISO parts - let mathematical_nanos = nanos.to_f64().ok_or_else(|| { - TemporalError::range().with_message("nanos was not within a valid range.") - })?; - - // 2. Let remainderNs be epochNanoseconds modulo 10^6. - let remainder_nanos = mathematical_nanos % 1_000_000f64; - - // 3. Let epochMilliseconds be 𝔽((epochNanoseconds - remainderNs) / 10^6). - let epoch_millis = ((mathematical_nanos - remainder_nanos) / 1_000_000f64).floor(); - - let year = utils::epoch_time_to_epoch_year(epoch_millis); - let month = utils::epoch_time_to_month_in_year(epoch_millis) + 1; - let day = utils::epoch_time_to_date(epoch_millis); - - // 7. Let hour be ℝ(! HourFromTime(epochMilliseconds)). - let hour = (epoch_millis / 3_600_000f64).floor() % 24f64; - // 8. Let minute be ℝ(! MinFromTime(epochMilliseconds)). - let minute = (epoch_millis / 60_000f64).floor() % 60f64; - // 9. Let second be ℝ(! SecFromTime(epochMilliseconds)). - let second = (epoch_millis / 1000f64).floor() % 60f64; - // 10. Let millisecond be ℝ(! msFromTime(epochMilliseconds)). - let millis = (epoch_millis % 1000f64).floor() % 1000f64; - - // 11. Let microsecond be floor(remainderNs / 1000). - let micros = (remainder_nanos / 1000f64).floor(); - // 12. Assert: microsecond < 1000. - debug_assert!(micros < 1000f64); - // 13. Let nanosecond be remainderNs modulo 1000. - let nanos = (remainder_nanos % 1000f64).floor(); - - Ok(Self::balance( - year, - i32::from(month), - i32::from(day), - hour, - minute, - second, - millis, - micros, - nanos + offset, - )) - } - - #[allow(clippy::too_many_arguments)] - fn balance( - year: i32, - month: i32, - day: i32, - hour: f64, - minute: f64, - second: f64, - millisecond: f64, - microsecond: f64, - nanosecond: f64, - ) -> Self { - let (overflow_day, time) = - IsoTime::balance(hour, minute, second, millisecond, microsecond, nanosecond); - let date = IsoDate::balance(year, month, day + overflow_day); - Self::new_unchecked(date, time) - } - - /// Returns whether the `IsoDateTime` is within valid limits. - pub(crate) fn is_within_limits(&self) -> bool { - iso_dt_within_valid_limits(self.date, &self.time) - } - - pub(crate) const fn date(&self) -> &IsoDate { - &self.date - } - - pub(crate) const fn time(&self) -> &IsoTime { - &self.time - } -} - -// ==== `IsoDate` section ==== - -// TODO: Figure out `ICU4X` interop / replacement? - -/// A trait for accessing the `IsoDate` across the various Temporal objects -pub trait IsoDateSlots { - /// Returns the target's internal `IsoDate`. - fn iso_date(&self) -> IsoDate; -} - -/// `IsoDate` serves as a record for the `[[ISOYear]]`, `[[ISOMonth]]`, -/// and `[[ISODay]]` internal fields. -/// -/// These fields are used for the `Temporal.PlainDate` object, the -/// `Temporal.YearMonth` object, and the `Temporal.MonthDay` object. -#[derive(Debug, Clone, Copy, Default)] -pub struct IsoDate { - pub(crate) year: i32, - pub(crate) month: u8, - pub(crate) day: u8, -} - -impl IsoDate { - /// Creates a new `IsoDate` without determining the validity. - pub(crate) const fn new_unchecked(year: i32, month: u8, day: u8) -> Self { - Self { year, month, day } - } - - pub(crate) fn new( - year: i32, - month: i32, - day: i32, - overflow: ArithmeticOverflow, - ) -> TemporalResult { - match overflow { - ArithmeticOverflow::Constrain => { - let month = month.clamp(1, 12); - let days_in_month = utils::iso_days_in_month(year, month); - let d = day.clamp(1, days_in_month); - // NOTE: Values are clamped in a u8 range. - Ok(Self::new_unchecked(year, month as u8, d as u8)) - } - ArithmeticOverflow::Reject => { - if !is_valid_date(year, month, day) { - return Err(TemporalError::range().with_message("not a valid ISO date.")); - } - // NOTE: Values have been verified to be in a u8 range. - Ok(Self::new_unchecked(year, month as u8, day as u8)) - } - } - } - - /// Create a balanced `IsoDate` - /// - /// Equivalent to `BalanceISODate`. - fn balance(year: i32, month: i32, day: i32) -> Self { - let epoch_days = iso_date_to_epoch_days(year, month - 1, day); - let ms = utils::epoch_days_to_epoch_ms(epoch_days, 0f64); - Self::new_unchecked( - utils::epoch_time_to_epoch_year(ms), - utils::epoch_time_to_month_in_year(ms) + 1, - utils::epoch_time_to_date(ms), - ) - } - - /// Functionally the same as Date's abstract operation `MakeDay` - /// - /// Equivalent to `IsoDateToEpochDays` - pub(crate) fn to_epoch_days(self) -> i32 { - iso_date_to_epoch_days(self.year, self.month.into(), self.day.into()) - } - - /// Returns if the current `IsoDate` is valid. - pub(crate) fn is_valid(self) -> bool { - is_valid_date(self.year, self.month.into(), self.day.into()) - } - - /// Returns the resulting `IsoDate` from adding a provided `Duration` to this `IsoDate` - pub(crate) fn add_iso_date( - self, - duration: &DateDuration, - overflow: ArithmeticOverflow, - ) -> TemporalResult { - // 1. Assert: year, month, day, years, months, weeks, and days are integers. - // 2. Assert: overflow is either "constrain" or "reject". - // 3. Let intermediate be ! BalanceISOYearMonth(year + years, month + months). - let mut intermediate_year = self.year + duration.years() as i32; - let mut intermediate_month = i32::from(self.month) + duration.months() as i32; - - intermediate_year += (intermediate_month - 1) / 12; - intermediate_month = (intermediate_month - 1) % 12 + 1; - - // 4. Let intermediate be ? RegulateISODate(intermediate.[[Year]], intermediate.[[Month]], day, overflow). - let intermediate = Self::new( - intermediate_year, - intermediate_month, - i32::from(self.day), - overflow, - )?; - - // 5. Set days to days + 7 × weeks. - // 6. Let d be intermediate.[[Day]] + days. - let additional_days = duration.days() as i32 + (duration.weeks() as i32 * 7); - let d = i32::from(intermediate.day) + additional_days; - - // 7. Return BalanceISODate(intermediate.[[Year]], intermediate.[[Month]], d). - Ok(Self::balance( - intermediate.year, - intermediate.month.into(), - d, - )) - } -} - -impl IsoDate { - /// Creates `[[ISOYear]]`, `[[isoMonth]]`, `[[isoDay]]` fields from `ICU4X`'s `Date` struct. - pub(crate) fn as_icu4x(self) -> TemporalResult> { - IcuDate::try_new_iso_date(self.year, self.month, self.day) - .map_err(|e| TemporalError::range().with_message(e.to_string())) - } -} - -// ==== `IsoTime` section ==== - -/// An `IsoTime` record that contains `Temporal`'s -/// time slots. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct IsoTime { - pub(crate) hour: u8, // 0..=23 - pub(crate) minute: u8, // 0..=59 - pub(crate) second: u8, // 0..=59 - pub(crate) millisecond: u16, // 0..=999 - pub(crate) microsecond: u16, // 0..=999 - pub(crate) nanosecond: u16, // 0..=999 -} - -impl IsoTime { - /// Creates a new `IsoTime` without any validation. - pub(crate) fn new_unchecked( - hour: u8, - minute: u8, - second: u8, - millisecond: u16, - microsecond: u16, - nanosecond: u16, - ) -> Self { - Self { - hour, - minute, - second, - millisecond, - microsecond, - nanosecond, - } - } - - /// Creates a new regulated `IsoTime`. - pub fn new( - hour: i32, - minute: i32, - second: i32, - millisecond: i32, - microsecond: i32, - nanosecond: i32, - overflow: ArithmeticOverflow, - ) -> TemporalResult { - match overflow { - ArithmeticOverflow::Constrain => { - let h = hour.clamp(0, 23) as u8; - let min = minute.clamp(0, 59) as u8; - let sec = second.clamp(0, 59) as u8; - let milli = millisecond.clamp(0, 999) as u16; - let micro = microsecond.clamp(0, 999) as u16; - let nano = nanosecond.clamp(0, 999) as u16; - Ok(Self::new_unchecked(h, min, sec, milli, micro, nano)) - } - ArithmeticOverflow::Reject => { - if !is_valid_time(hour, minute, second, millisecond, microsecond, nanosecond) { - return Err(TemporalError::range().with_message("IsoTime is not valid")); - }; - Ok(Self::new_unchecked( - hour as u8, - minute as u8, - second as u8, - millisecond as u16, - microsecond as u16, - nanosecond as u16, - )) - } - } - } - - /// Returns an `IsoTime` set to 12:00:00 - pub(crate) const fn noon() -> Self { - Self { - hour: 12, - minute: 0, - second: 0, - millisecond: 0, - microsecond: 0, - nanosecond: 0, - } - } - - /// Returns an `IsoTime` based off parse components. - pub(crate) fn from_components( - hour: i32, - minute: i32, - second: i32, - fraction: f64, - ) -> TemporalResult { - let millisecond = fraction * 1000f64; - let micros = millisecond.rem_euclid(1f64) * 1000f64; - let nanos = micros.rem_euclid(1f64).mul_add(1000f64, 0.5).floor(); - - Self::new( - hour, - minute, - second, - millisecond as i32, - micros as i32, - nanos as i32, - ArithmeticOverflow::Reject, - ) - } - - // NOTE(nekevss): f64 is needed here as values could exceed i32 when input. - /// Balances and creates a new `IsoTime` with `day` overflow from the provided values. - pub(crate) fn balance( - hour: f64, - minute: f64, - second: f64, - millisecond: f64, - microsecond: f64, - nanosecond: f64, - ) -> (i32, Self) { - // 1. Set microsecond to microsecond + floor(nanosecond / 1000). - // 2. Set nanosecond to nanosecond modulo 1000. - let (quotient, nanosecond) = div_mod(nanosecond, 1000f64); - let microsecond = microsecond + quotient; - - // 3. Set millisecond to millisecond + floor(microsecond / 1000). - // 4. Set microsecond to microsecond modulo 1000. - let (quotient, microsecond) = div_mod(microsecond, 1000f64); - let millisecond = millisecond + quotient; - - // 5. Set second to second + floor(millisecond / 1000). - // 6. Set millisecond to millisecond modulo 1000. - let (quotient, millisecond) = div_mod(millisecond, 1000f64); - let second = second + quotient; - - // 7. Set minute to minute + floor(second / 60). - // 8. Set second to second modulo 60. - let (quotient, second) = div_mod(second, 60f64); - let minute = minute + quotient; - - // 9. Set hour to hour + floor(minute / 60). - // 10. Set minute to minute modulo 60. - let (quotient, minute) = div_mod(minute, 60f64); - let hour = hour + quotient; - - // 11. Let days be floor(hour / 24). - // 12. Set hour to hour modulo 24. - let (days, hour) = div_mod(hour, 24f64); - - let time = Self::new_unchecked( - hour as u8, - minute as u8, - second as u8, - millisecond as u16, - microsecond as u16, - nanosecond as u16, - ); - - (days as i32, time) - } - - // NOTE (nekevss): Specification seemed to be off / not entirely working, so the below was adapted from the - // temporal-polyfill - // TODO: DayLengthNS can probably be a u64, but keep as is for now and optimize. - /// Rounds the current `IsoTime` according to the provided settings. - pub(crate) fn round( - &self, - increment: f64, - unit: TemporalUnit, - mode: TemporalRoundingMode, - day_length_ns: Option, - ) -> TemporalResult<(i32, Self)> { - // 1. Let fractionalSecond be nanosecond × 10-9 + microsecond × 10-6 + millisecond × 10-3 + second. - - let quantity = match unit { - // 2. If unit is "day", then - // a. If dayLengthNs is not present, set dayLengthNs to nsPerDay. - // b. Let quantity be (((((hour × 60 + minute) × 60 + second) × 1000 + millisecond) × 1000 + microsecond) × 1000 + nanosecond) / dayLengthNs. - // 3. Else if unit is "hour", then - // a. Let quantity be (fractionalSecond / 60 + minute) / 60 + hour. - TemporalUnit::Hour | TemporalUnit::Day => { - u64::from(self.nanosecond) - + u64::from(self.microsecond) * 1_000 - + u64::from(self.millisecond) * 1_000_000 - + u64::from(self.second) * 1_000_000_000 - + u64::from(self.minute) * 60 * 1_000_000_000 - + u64::from(self.hour) * 60 * 60 * 1_000_000_000 - } - // 4. Else if unit is "minute", then - // a. Let quantity be fractionalSecond / 60 + minute. - TemporalUnit::Minute => { - u64::from(self.nanosecond) - + u64::from(self.microsecond) * 1_000 - + u64::from(self.millisecond) * 1_000_000 - + u64::from(self.second) * 1_000_000_000 - + u64::from(self.minute) * 60 - } - // 5. Else if unit is "second", then - // a. Let quantity be fractionalSecond. - TemporalUnit::Second => { - u64::from(self.nanosecond) - + u64::from(self.microsecond) * 1_000 - + u64::from(self.millisecond) * 1_000_000 - + u64::from(self.second) * 1_000_000_000 - } - // 6. Else if unit is "millisecond", then - // a. Let quantity be nanosecond × 10-6 + microsecond × 10-3 + millisecond. - TemporalUnit::Millisecond => { - u64::from(self.nanosecond) - + u64::from(self.microsecond) * 1_000 - + u64::from(self.millisecond) * 1_000_000 - } - // 7. Else if unit is "microsecond", then - // a. Let quantity be nanosecond × 10-3 + microsecond. - TemporalUnit::Microsecond => { - u64::from(self.nanosecond) + 1_000 * u64::from(self.microsecond) - } - // 8. Else, - // a. Assert: unit is "nanosecond". - // b. Let quantity be nanosecond. - TemporalUnit::Nanosecond => u64::from(self.nanosecond), - _ => { - return Err(TemporalError::range() - .with_message("Invalid temporal unit provided to Time.round.")) - } - }; - - let ns_per_unit = if unit == TemporalUnit::Day { - day_length_ns.unwrap_or(NS_PER_DAY) as f64 - } else { - unit.as_nanoseconds().expect("Only valid time values are ") - }; - - // TODO: Verify validity of cast or handle better. - // 9. Let result be RoundNumberToIncrement(quantity, increment, roundingMode). - let result = - utils::round_number_to_increment(quantity as f64, ns_per_unit * increment, mode) - / ns_per_unit; - - let result = match unit { - // 10. If unit is "day", then - // a. Return the Record { [[Days]]: result, [[Hour]]: 0, [[Minute]]: 0, [[Second]]: 0, [[Millisecond]]: 0, [[Microsecond]]: 0, [[Nanosecond]]: 0 }. - TemporalUnit::Day => (result as i32, IsoTime::default()), - // 11. If unit is "hour", then - // a. Return BalanceTime(result, 0, 0, 0, 0, 0). - TemporalUnit::Hour => IsoTime::balance(result, 0.0, 0.0, 0.0, 0.0, 0.0), - // 12. If unit is "minute", then - // a. Return BalanceTime(hour, result, 0, 0, 0, 0). - TemporalUnit::Minute => { - IsoTime::balance(f64::from(self.hour), result, 0.0, 0.0, 0.0, 0.0) - } - // 13. If unit is "second", then - // a. Return BalanceTime(hour, minute, result, 0, 0, 0). - TemporalUnit::Second => IsoTime::balance( - f64::from(self.hour), - f64::from(self.minute), - result, - 0.0, - 0.0, - 0.0, - ), - // 14. If unit is "millisecond", then - // a. Return BalanceTime(hour, minute, second, result, 0, 0). - TemporalUnit::Millisecond => IsoTime::balance( - f64::from(self.hour), - f64::from(self.minute), - f64::from(self.second), - result, - 0.0, - 0.0, - ), - // 15. If unit is "microsecond", then - // a. Return BalanceTime(hour, minute, second, millisecond, result, 0). - TemporalUnit::Microsecond => IsoTime::balance( - f64::from(self.hour), - f64::from(self.minute), - f64::from(self.second), - f64::from(self.millisecond), - result, - 0.0, - ), - // 16. Assert: unit is "nanosecond". - // 17. Return BalanceTime(hour, minute, second, millisecond, microsecond, result). - TemporalUnit::Nanosecond => IsoTime::balance( - f64::from(self.hour), - f64::from(self.minute), - f64::from(self.second), - f64::from(self.millisecond), - f64::from(self.microsecond), - result, - ), - _ => unreachable!("Error is thrown in previous match."), - }; - - Ok(result) - } - - /// Checks if the time is a valid `IsoTime` - pub(crate) fn is_valid(&self) -> bool { - if !(0..=23).contains(&self.hour) { - return false; - } - - let min_sec = 0..=59; - if !min_sec.contains(&self.minute) || !min_sec.contains(&self.second) { - return false; - } - - let sub_second = 0..=999; - sub_second.contains(&self.millisecond) - && sub_second.contains(&self.microsecond) - && sub_second.contains(&self.nanosecond) - } - - /// `IsoTimeToEpochMs` - /// - /// Note: This method is library specific and not in spec - /// - /// Functionally the same as Date's `MakeTime` - pub(crate) fn to_epoch_ms(self) -> f64 { - ((f64::from(self.hour) * utils::MS_PER_HOUR - + f64::from(self.minute) * utils::MS_PER_MINUTE) - + f64::from(self.second) * 1000f64) - + f64::from(self.millisecond) - } -} - -// ==== `IsoDateTime` specific utility functions ==== - -#[inline] -/// Utility function to determine if a `DateTime`'s components create a `DateTime` within valid limits -fn iso_dt_within_valid_limits(date: IsoDate, time: &IsoTime) -> bool { - if iso_date_to_epoch_days(date.year, (date.month).into(), date.day.into()).abs() > 100_000_001 { - return false; - } - let Some(ns) = utc_epoch_nanos(date, time, 0.0) else { - return false; - }; - - let max = BigInt::from(crate::NS_MAX_INSTANT + i128::from(NS_PER_DAY)); - let min = BigInt::from(crate::NS_MIN_INSTANT - i128::from(NS_PER_DAY)); - - min < ns && max > ns -} - -#[inline] -/// Utility function to convert a `IsoDate` and `IsoTime` values into epoch nanoseconds -fn utc_epoch_nanos(date: IsoDate, time: &IsoTime, offset: f64) -> Option { - let ms = time.to_epoch_ms(); - let epoch_ms = utils::epoch_days_to_epoch_ms(date.to_epoch_days(), ms); - - let epoch_nanos = epoch_ms.mul_add( - 1_000_000f64, - f64::from(time.microsecond).mul_add(1_000f64, f64::from(time.nanosecond)), - ); - - BigInt::from_f64(epoch_nanos - offset) -} - -// ==== `IsoDate` specific utiltiy functions ==== - -/// Returns the Epoch days based off the given year, month, and day. -#[inline] -fn iso_date_to_epoch_days(year: i32, month: i32, day: i32) -> i32 { - // 1. Let resolvedYear be year + floor(month / 12). - let resolved_year = year + (f64::from(month) / 12_f64).floor() as i32; - // 2. Let resolvedMonth be month modulo 12. - let resolved_month = month % 12; - - // 3. Find a time t such that EpochTimeToEpochYear(t) is resolvedYear, EpochTimeToMonthInYear(t) is resolvedMonth, and EpochTimeToDate(t) is 1. - let year_t = utils::epoch_time_for_year(resolved_year); - let month_t = utils::epoch_time_for_month_given_year(resolved_month, resolved_year); - - // 4. Return EpochTimeToDayNumber(t) + date - 1. - utils::epoch_time_to_day_number((year_t.abs() + month_t).copysign(year_t)) + day - 1 -} - -#[inline] -// Determines if the month and day are valid for the given year. -fn is_valid_date(year: i32, month: i32, day: i32) -> bool { - if !(1..=12).contains(&month) { - return false; - } - - let days_in_month = utils::iso_days_in_month(year, month); - (1..=days_in_month).contains(&day) -} - -// ==== `IsoTime` specific utilities ==== - -#[inline] -fn is_valid_time(hour: i32, minute: i32, second: i32, ms: i32, mis: i32, ns: i32) -> bool { - if !(0..=23).contains(&hour) { - return false; - } - - let min_sec = 0..=59; - if !min_sec.contains(&minute) || !min_sec.contains(&second) { - return false; - } - - let sub_second = 0..=999; - sub_second.contains(&ms) && sub_second.contains(&mis) && sub_second.contains(&ns) -} - -// NOTE(nekevss): Considering the below: Balance can probably be altered from f64. -#[inline] -fn div_mod(dividend: f64, divisor: f64) -> (f64, f64) { - (dividend.div_euclid(divisor), dividend.rem_euclid(divisor)) -} diff --git a/core/temporal/src/lib.rs b/core/temporal/src/lib.rs deleted file mode 100644 index 24dbf5f357..0000000000 --- a/core/temporal/src/lib.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! Boa's `boa_temporal` crate is an engine agnostic implementation of ECMAScript's Temporal. -//! -//! IMPORTANT NOTE: Please note that this library is actively being developed and is very -//! much experimental and NOT STABLE. -//! -//! [`Temporal`][proposal] is the Stage 3 proposal for ECMAScript that provides new JS objects and functions -//! for working with dates and times that fully supports time zones and non-gregorian calendars. -//! -//! This library's primary source is the Temporal Proposal [specification][spec]. -//! -//! [proposal]: https://github.com/tc39/proposal-temporal -//! [spec]: https://tc39.es/proposal-temporal/ -#![doc = include_str!("../ABOUT.md")] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg" -)] -#![cfg_attr(not(test), forbid(clippy::unwrap_used))] -#![allow( - // Currently throws a false positive regarding dependencies that are only used in benchmarks. - unused_crate_dependencies, - clippy::module_name_repetitions, - clippy::redundant_pub_crate, - clippy::too_many_lines, - clippy::cognitive_complexity, - clippy::missing_errors_doc, - clippy::let_unit_value, - clippy::option_if_let_else, - - // It may be worth to look if we can fix the issues highlighted by these lints. - clippy::cast_possible_truncation, - clippy::cast_sign_loss, - clippy::cast_precision_loss, - clippy::cast_possible_wrap, - - // Add temporarily - Needs addressing - clippy::missing_panics_doc, -)] - -pub mod components; -pub mod error; -pub mod fields; -pub mod iso; -pub mod options; -pub mod parser; - -#[doc(hidden)] -pub(crate) mod utils; - -// TODO: evaluate positives and negatives of using tinystr. -// Re-exporting tinystr as a convenience, as it is currently tied into the API. -pub use tinystr::TinyAsciiStr; - -#[doc(inline)] -pub use error::TemporalError; -#[doc(inline)] -pub use fields::TemporalFields; - -/// The `Temporal` result type -pub type TemporalResult = Result; - -// Relevant numeric constants -/// Nanoseconds per day constant: 8.64e+13 -pub const NS_PER_DAY: i64 = MS_PER_DAY as i64 * 1_000_000; -/// Milliseconds per day constant: 8.64e+7 -pub const MS_PER_DAY: i32 = 24 * 60 * 60 * 1000; -/// Max Instant nanosecond constant -#[doc(hidden)] -pub(crate) const NS_MAX_INSTANT: i128 = NS_PER_DAY as i128 * 100_000_000i128; -/// Min Instant nanosecond constant -#[doc(hidden)] -pub(crate) const NS_MIN_INSTANT: i128 = -NS_MAX_INSTANT; diff --git a/core/temporal/src/options.rs b/core/temporal/src/options.rs deleted file mode 100644 index 75192802a1..0000000000 --- a/core/temporal/src/options.rs +++ /dev/null @@ -1,437 +0,0 @@ -//! Native implementation of the `Temporal` options. -//! -//! Temporal has various instances where user's can define options for how an -//! operation may be completed. - -use core::{fmt, str::FromStr}; - -use crate::TemporalError; - -// ==== Options enums and methods ==== - -/// The relevant unit that should be used for the operation that -/// this option is provided as a value. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum TemporalUnit { - /// The `Auto` unit - Auto = 0, - /// The `Nanosecond` unit - Nanosecond, - /// The `Microsecond` unit - Microsecond, - /// The `Millisecond` unit - Millisecond, - /// The `Second` unit - Second, - /// The `Minute` unit - Minute, - /// The `Hour` unit - Hour, - /// The `Day` unit - Day, - /// The `Week` unit - Week, - /// The `Month` unit - Month, - /// The `Year` unit - Year, -} - -impl TemporalUnit { - #[inline] - #[must_use] - /// Returns the `MaximumRoundingIncrement` for the current `TemporalUnit`. - pub fn to_maximum_rounding_increment(self) -> Option { - use TemporalUnit::{ - Auto, Day, Hour, Microsecond, Millisecond, Minute, Month, Nanosecond, Second, Week, - Year, - }; - // 1. If unit is "year", "month", "week", or "day", then - // a. Return undefined. - // 2. If unit is "hour", then - // a. Return 24. - // 3. If unit is "minute" or "second", then - // a. Return 60. - // 4. Assert: unit is one of "millisecond", "microsecond", or "nanosecond". - // 5. Return 1000. - match self { - Year | Month | Week | Day => None, - Hour => Some(24), - Minute | Second => Some(60), - Millisecond | Microsecond | Nanosecond => Some(1000), - Auto => unreachable!(), - } - } - - // TODO: potentiall use a u64 - /// Returns the `Nanosecond amount for any given value.` - #[must_use] - pub fn as_nanoseconds(&self) -> Option { - use TemporalUnit::{ - Auto, Day, Hour, Microsecond, Millisecond, Minute, Month, Nanosecond, Second, Week, - Year, - }; - match self { - Year | Month | Week | Day | Auto => None, - Hour => Some(3600e9), - Minute => Some(60e9), - Second => Some(1e9), - Millisecond => Some(1e6), - Microsecond => Some(1e3), - Nanosecond => Some(1f64), - } - } -} - -/// A parsing error for `TemporalUnit` -#[derive(Debug, Clone, Copy)] -pub struct ParseTemporalUnitError; - -impl fmt::Display for ParseTemporalUnitError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("provided string was not a valid TemporalUnit") - } -} - -impl FromStr for TemporalUnit { - type Err = ParseTemporalUnitError; - - fn from_str(s: &str) -> Result { - match s { - "auto" => Ok(Self::Auto), - "year" | "years" => Ok(Self::Year), - "month" | "months" => Ok(Self::Month), - "week" | "weeks" => Ok(Self::Week), - "day" | "days" => Ok(Self::Day), - "hour" | "hours" => Ok(Self::Hour), - "minute" | "minutes" => Ok(Self::Minute), - "second" | "seconds" => Ok(Self::Second), - "millisecond" | "milliseconds" => Ok(Self::Millisecond), - "microsecond" | "microseconds" => Ok(Self::Microsecond), - "nanosecond" | "nanoseconds" => Ok(Self::Nanosecond), - _ => Err(ParseTemporalUnitError), - } - } -} - -impl fmt::Display for TemporalUnit { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Auto => "auto", - Self::Year => "constrain", - Self::Month => "month", - Self::Week => "week", - Self::Day => "day", - Self::Hour => "hour", - Self::Minute => "minute", - Self::Second => "second", - Self::Millisecond => "millsecond", - Self::Microsecond => "microsecond", - Self::Nanosecond => "nanosecond", - } - .fmt(f) - } -} - -/// `ArithmeticOverflow` can also be used as an -/// assignment overflow and consists of the "constrain" -/// and "reject" options. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ArithmeticOverflow { - /// Constrain option - Constrain, - /// Constrain option - Reject, -} - -/// A parsing error for `ArithemeticOverflow` -#[derive(Debug, Clone, Copy)] -pub struct ParseArithmeticOverflowError; - -impl fmt::Display for ParseArithmeticOverflowError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("provided string was not a valid overflow value") - } -} - -impl FromStr for ArithmeticOverflow { - type Err = ParseArithmeticOverflowError; - - fn from_str(s: &str) -> Result { - match s { - "constrain" => Ok(Self::Constrain), - "reject" => Ok(Self::Reject), - _ => Err(ParseArithmeticOverflowError), - } - } -} - -impl fmt::Display for ArithmeticOverflow { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Constrain => "constrain", - Self::Reject => "reject", - } - .fmt(f) - } -} - -/// `Duration` overflow options. -#[derive(Debug, Clone, Copy)] -pub enum DurationOverflow { - /// Constrain option - Constrain, - /// Balance option - Balance, -} - -/// A parsing error for `DurationOverflow`. -#[derive(Debug, Clone, Copy)] -pub struct ParseDurationOverflowError; - -impl fmt::Display for ParseDurationOverflowError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("provided string was not a valid duration overflow value") - } -} - -impl FromStr for DurationOverflow { - type Err = ParseDurationOverflowError; - - fn from_str(s: &str) -> Result { - match s { - "constrain" => Ok(Self::Constrain), - "balance" => Ok(Self::Balance), - _ => Err(ParseDurationOverflowError), - } - } -} - -impl fmt::Display for DurationOverflow { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Constrain => "constrain", - Self::Balance => "balance", - } - .fmt(f) - } -} - -/// The disambiguation options for an instant. -#[derive(Debug, Clone, Copy)] -pub enum InstantDisambiguation { - /// Compatible option - Compatible, - /// Earlier option - Earlier, - /// Later option - Later, - /// Reject option - Reject, -} - -/// A parsing error on `InstantDisambiguation` options. -#[derive(Debug, Clone, Copy)] -pub struct ParseInstantDisambiguationError; - -impl fmt::Display for ParseInstantDisambiguationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("provided string was not a valid instant disambiguation value") - } -} - -impl FromStr for InstantDisambiguation { - type Err = ParseInstantDisambiguationError; - - fn from_str(s: &str) -> Result { - match s { - "compatible" => Ok(Self::Compatible), - "earlier" => Ok(Self::Earlier), - "later" => Ok(Self::Later), - "reject" => Ok(Self::Reject), - _ => Err(ParseInstantDisambiguationError), - } - } -} - -impl fmt::Display for InstantDisambiguation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Compatible => "compatible", - Self::Earlier => "earlier", - Self::Later => "later", - Self::Reject => "reject", - } - .fmt(f) - } -} - -/// Offset disambiguation options. -#[derive(Debug, Clone, Copy)] -pub enum OffsetDisambiguation { - /// Use option - Use, - /// Prefer option - Prefer, - /// Ignore option - Ignore, - /// Reject option - Reject, -} - -/// A parsing error for `OffsetDisambiguation` parsing. -#[derive(Debug, Clone, Copy)] -pub struct ParseOffsetDisambiguationError; - -impl fmt::Display for ParseOffsetDisambiguationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("provided string was not a valid offset disambiguation value") - } -} - -impl FromStr for OffsetDisambiguation { - type Err = ParseOffsetDisambiguationError; - - fn from_str(s: &str) -> Result { - match s { - "use" => Ok(Self::Use), - "prefer" => Ok(Self::Prefer), - "ignore" => Ok(Self::Ignore), - "reject" => Ok(Self::Reject), - _ => Err(ParseOffsetDisambiguationError), - } - } -} - -impl fmt::Display for OffsetDisambiguation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Use => "use", - Self::Prefer => "prefer", - Self::Ignore => "ignore", - Self::Reject => "reject", - } - .fmt(f) - } -} - -// TODO: Figure out what to do with intl's RoundingMode - -/// Declares the specified `RoundingMode` for the operation. -#[derive(Debug, Copy, Clone, Default)] -pub enum TemporalRoundingMode { - /// Ceil RoundingMode - Ceil, - /// Floor RoundingMode - Floor, - /// Expand RoundingMode - Expand, - /// Truncate RoundingMode - Trunc, - /// HalfCeil RoundingMode - HalfCeil, - /// HalfFloor RoundingMode - HalfFloor, - /// HalfExpand RoundingMode - Default - #[default] - HalfExpand, - /// HalfTruncate RoundingMode - HalfTrunc, - /// HalfEven RoundingMode - HalfEven, -} - -/// The `UnsignedRoundingMode` -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum TemporalUnsignedRoundingMode { - /// `Infinity` `RoundingMode` - Infinity, - /// `Zero` `RoundingMode` - Zero, - /// `HalfInfinity` `RoundingMode` - HalfInfinity, - /// `HalfZero` `RoundingMode` - HalfZero, - /// `HalfEven` `RoundingMode` - HalfEven, -} - -impl TemporalRoundingMode { - #[inline] - #[must_use] - /// Negates the current `RoundingMode`. - pub const fn negate(self) -> Self { - use TemporalRoundingMode::{ - Ceil, Expand, Floor, HalfCeil, HalfEven, HalfExpand, HalfFloor, HalfTrunc, Trunc, - }; - - match self { - Ceil => Self::Floor, - Floor => Self::Ceil, - HalfCeil => Self::HalfFloor, - HalfFloor => Self::HalfCeil, - Trunc => Self::Trunc, - Expand => Self::Expand, - HalfTrunc => Self::HalfTrunc, - HalfExpand => Self::HalfExpand, - HalfEven => Self::HalfEven, - } - } - - #[inline] - #[must_use] - /// Returns the `UnsignedRoundingMode` - pub const fn get_unsigned_round_mode(self, is_negative: bool) -> TemporalUnsignedRoundingMode { - use TemporalRoundingMode::{ - Ceil, Expand, Floor, HalfCeil, HalfEven, HalfExpand, HalfFloor, HalfTrunc, Trunc, - }; - - match self { - Ceil if !is_negative => TemporalUnsignedRoundingMode::Infinity, - Ceil => TemporalUnsignedRoundingMode::Zero, - Floor if !is_negative => TemporalUnsignedRoundingMode::Zero, - Floor | Trunc | Expand => TemporalUnsignedRoundingMode::Infinity, - HalfCeil if !is_negative => TemporalUnsignedRoundingMode::HalfInfinity, - HalfCeil | HalfTrunc => TemporalUnsignedRoundingMode::HalfZero, - HalfFloor if !is_negative => TemporalUnsignedRoundingMode::HalfZero, - HalfFloor | HalfExpand => TemporalUnsignedRoundingMode::HalfInfinity, - HalfEven => TemporalUnsignedRoundingMode::HalfEven, - } - } -} - -impl FromStr for TemporalRoundingMode { - type Err = TemporalError; - - fn from_str(s: &str) -> Result { - match s { - "ceil" => Ok(Self::Ceil), - "floor" => Ok(Self::Floor), - "expand" => Ok(Self::Expand), - "trunc" => Ok(Self::Trunc), - "halfCeil" => Ok(Self::HalfCeil), - "halfFloor" => Ok(Self::HalfFloor), - "halfExpand" => Ok(Self::HalfExpand), - "halfTrunc" => Ok(Self::HalfTrunc), - "halfEven" => Ok(Self::HalfEven), - _ => Err(TemporalError::range().with_message("RoundingMode not an accepted value.")), - } - } -} - -impl fmt::Display for TemporalRoundingMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Ceil => "ceil", - Self::Floor => "floor", - Self::Expand => "expand", - Self::Trunc => "trunc", - Self::HalfCeil => "halfCeil", - Self::HalfFloor => "halfFloor", - Self::HalfExpand => "halfExpand", - Self::HalfTrunc => "halfTrunc", - Self::HalfEven => "halfEven", - } - .fmt(f) - } -} diff --git a/core/temporal/src/parser/annotations.rs b/core/temporal/src/parser/annotations.rs deleted file mode 100644 index 50785bf5f4..0000000000 --- a/core/temporal/src/parser/annotations.rs +++ /dev/null @@ -1,180 +0,0 @@ -/// Parsing for Temporal's `Annotations`. -use crate::{ - assert_syntax, - parser::{ - grammar::{ - is_a_key_char, is_a_key_leading_char, is_annotation_close, - is_annotation_key_value_separator, is_annotation_value_component, is_critical_flag, - }, - time_zone, - time_zone::TimeZoneAnnotation, - Cursor, - }, - TemporalError, TemporalResult, -}; - -use super::grammar::{is_annotation_open, is_hyphen}; - -/// A `KeyValueAnnotation` Parse Node. -#[derive(Debug, Clone)] -pub(crate) struct KeyValueAnnotation { - /// An `Annotation`'s Key. - pub(crate) key: String, - /// An `Annotation`'s value. - pub(crate) value: String, - /// Whether the annotation was flagged as critical. - pub(crate) critical: bool, -} - -/// Strictly a Parsing Intermediary for the checking the common annotation backing. -pub(crate) struct AnnotationSet { - pub(crate) tz: Option, - pub(crate) calendar: Option, -} - -/// Parse a `TimeZoneAnnotation` `Annotations` set -pub(crate) fn parse_annotation_set( - zoned: bool, - cursor: &mut Cursor, -) -> TemporalResult { - // Parse the first annotation. - let tz_annotation = time_zone::parse_ambiguous_tz_annotation(cursor)?; - if tz_annotation.is_none() && zoned { - return Err( - TemporalError::syntax().with_message("ZonedDateTime must have a TimeZone annotation.") - ); - } - - // Parse any `Annotations` - let annotations = cursor.check_or(false, is_annotation_open); - - if annotations { - let annotations = parse_annotations(cursor)?; - return Ok(AnnotationSet { - tz: tz_annotation, - calendar: annotations.calendar, - }); - } - - Ok(AnnotationSet { - tz: tz_annotation, - calendar: None, - }) -} - -/// An internal crate type to house any recognized annotations that are found. -#[derive(Default)] -pub(crate) struct RecognizedAnnotations { - pub(crate) calendar: Option, -} - -/// Parse any number of `KeyValueAnnotation`s -pub(crate) fn parse_annotations(cursor: &mut Cursor) -> TemporalResult { - let mut annotations = RecognizedAnnotations::default(); - - let mut calendar_crit = false; - while cursor.check_or(false, is_annotation_open) { - let kv = parse_kv_annotation(cursor)?; - - if &kv.key == "u-ca" { - if annotations.calendar.is_none() { - annotations.calendar = Some(kv.value); - calendar_crit = kv.critical; - continue; - } - - if calendar_crit || kv.critical { - return Err(TemporalError::syntax().with_message( - "Cannot have critical flag with duplicate calendar annotations", - )); - } - } else if kv.critical { - return Err(TemporalError::syntax().with_message("Unrecognized critical annotation.")); - } - } - - Ok(annotations) -} - -/// Parse an annotation with an `AnnotationKey`=`AnnotationValue` pair. -fn parse_kv_annotation(cursor: &mut Cursor) -> TemporalResult { - assert_syntax!( - is_annotation_open(cursor.abrupt_next()?), - "Invalid annotation open character." - ); - - let critical = cursor.check_or(false, is_critical_flag); - cursor.advance_if(critical); - - // Parse AnnotationKey. - let annotation_key = parse_annotation_key(cursor)?; - assert_syntax!( - is_annotation_key_value_separator(cursor.abrupt_next()?), - "Invalid annotation key-value separator" - ); - - // Parse AnnotationValue. - let annotation_value = parse_annotation_value(cursor)?; - assert_syntax!( - is_annotation_close(cursor.abrupt_next()?), - "Invalid annotion closing character" - ); - - Ok(KeyValueAnnotation { - key: annotation_key, - value: annotation_value, - critical, - }) -} - -/// Parse an `AnnotationKey`. -fn parse_annotation_key(cursor: &mut Cursor) -> TemporalResult { - let key_start = cursor.pos(); - assert_syntax!( - is_a_key_leading_char(cursor.abrupt_next()?), - "Invalid key leading character." - ); - - while let Some(potential_key_char) = cursor.next() { - // End of key. - if cursor.check_or(false, is_annotation_key_value_separator) { - // Return found key - return Ok(cursor.slice(key_start, cursor.pos())); - } - - assert_syntax!( - is_a_key_char(potential_key_char), - "Invalid annotation key character." - ); - } - - Err(TemporalError::abrupt_end()) -} - -/// Parse an `AnnotationValue`. -fn parse_annotation_value(cursor: &mut Cursor) -> TemporalResult { - let value_start = cursor.pos(); - cursor.advance(); - while let Some(potential_value_char) = cursor.next() { - if cursor.check_or(false, is_annotation_close) { - // Return the determined AnnotationValue. - return Ok(cursor.slice(value_start, cursor.pos())); - } - - if is_hyphen(potential_value_char) { - assert_syntax!( - cursor.peek().map_or(false, is_annotation_value_component), - "Missing annotation value compoenent after '-'" - ); - cursor.advance(); - continue; - } - - assert_syntax!( - is_annotation_value_component(potential_value_char), - "Invalid annotation value component character." - ); - } - - Err(TemporalError::abrupt_end()) -} diff --git a/core/temporal/src/parser/datetime.rs b/core/temporal/src/parser/datetime.rs deleted file mode 100644 index 8061257373..0000000000 --- a/core/temporal/src/parser/datetime.rs +++ /dev/null @@ -1,311 +0,0 @@ -//! Parsing for Temporal's ISO8601 `Date` and `DateTime`. - -use crate::{ - assert_syntax, - parser::{ - annotations, - grammar::{is_date_time_separator, is_sign, is_utc_designator}, - nodes::TimeZone, - time, - time::TimeSpec, - time_zone, Cursor, IsoParseRecord, - }, - TemporalError, TemporalResult, -}; - -use super::grammar::{is_annotation_open, is_hyphen}; -use bitflags::bitflags; - -#[derive(Debug, Default, Clone)] -/// A `DateTime` Parse Node that contains the date, time, and offset info. -pub(crate) struct DateTimeRecord { - /// Date - pub(crate) date: DateRecord, - /// Time - pub(crate) time: Option, - /// Tz Offset - pub(crate) time_zone: Option, -} - -#[derive(Default, Debug, Clone, Copy)] -/// The record of a parsed date. -pub(crate) struct DateRecord { - /// Date Year - pub(crate) year: i32, - /// Date Month - pub(crate) month: i32, - /// Date Day - pub(crate) day: i32, -} - -bitflags! { - /// Parsing flags for `AnnotatedDateTime` parsing. - #[derive(Debug, Clone, Copy)] - pub struct DateTimeFlags: u8 { - const ZONED = 0b0000_0001; - const TIME_REQ = 0b0000_0010; - const UTC_REQ = 0b0000_0100; - } -} - -/// This function handles parsing for [`AnnotatedDateTime`][datetime], -/// [`AnnotatedDateTimeTimeRequred`][time], and -/// [`TemporalInstantString.`][instant] according to the requirements -/// provided via Spec. -/// -/// [datetime]: https://tc39.es/proposal-temporal/#prod-AnnotatedDateTime -/// [time]: https://tc39.es/proposal-temporal/#prod-AnnotatedDateTimeTimeRequired -/// [instant]: https://tc39.es/proposal-temporal/#prod-TemporalInstantString -pub(crate) fn parse_annotated_date_time( - flags: DateTimeFlags, - cursor: &mut Cursor, -) -> TemporalResult { - let date_time = parse_date_time( - flags.contains(DateTimeFlags::TIME_REQ), - flags.contains(DateTimeFlags::UTC_REQ), - cursor, - )?; - - // Peek Annotation presence - // Throw error if annotation does not exist and zoned is true, else return. - if !cursor.check_or(false, is_annotation_open) { - if flags.contains(DateTimeFlags::ZONED) { - return Err(TemporalError::syntax() - .with_message("ZonedDateTime must have a TimeZoneAnnotation.")); - } - - cursor.close()?; - - return Ok(IsoParseRecord { - date: date_time.date, - time: date_time.time, - tz: date_time.time_zone, - calendar: None, - }); - } - - let mut tz = TimeZone::default(); - - if let Some(tz_info) = date_time.time_zone { - tz = tz_info; - } - - let annotation_set = - annotations::parse_annotation_set(flags.contains(DateTimeFlags::ZONED), cursor)?; - - if let Some(annotated_tz) = annotation_set.tz { - tz = annotated_tz.tz; - } - - let tz = if tz.name.is_some() || tz.offset.is_some() { - Some(tz) - } else { - None - }; - - cursor.close()?; - - Ok(IsoParseRecord { - date: date_time.date, - time: date_time.time, - tz, - calendar: annotation_set.calendar, - }) -} - -/// Parses a `DateTime` record. -fn parse_date_time( - time_required: bool, - utc_required: bool, - cursor: &mut Cursor, -) -> TemporalResult { - let date = parse_date(cursor)?; - - // If there is no `DateTimeSeparator`, return date early. - if !cursor.check_or(false, is_date_time_separator) { - if time_required { - return Err(TemporalError::syntax().with_message("Missing a required Time target.")); - } - - return Ok(DateTimeRecord { - date, - time: None, - time_zone: None, - }); - } - - cursor.advance(); - - let time = time::parse_time_spec(cursor)?; - - let time_zone = if cursor.check_or(false, |ch| is_sign(ch) || is_utc_designator(ch)) { - Some(time_zone::parse_date_time_utc(cursor)?) - } else { - if utc_required { - return Err(TemporalError::syntax().with_message("DateTimeUTCOffset is required.")); - } - None - }; - - Ok(DateTimeRecord { - date, - time: Some(time), - time_zone, - }) -} - -/// Parses `Date` record. -fn parse_date(cursor: &mut Cursor) -> TemporalResult { - let year = parse_date_year(cursor)?; - let hyphenated = cursor - .check(is_hyphen) - .ok_or_else(TemporalError::abrupt_end)?; - - cursor.advance_if(hyphenated); - - let month = parse_date_month(cursor)?; - - if hyphenated { - assert_syntax!(cursor.check_or(false, is_hyphen), "Invalid hyphen usage."); - } - cursor.advance_if(cursor.check_or(false, is_hyphen)); - - let day = parse_date_day(cursor)?; - - Ok(DateRecord { year, month, day }) -} - -// ==== `YearMonth` and `MonthDay` parsing functions ==== - -/// Parses a `DateSpecYearMonth` -pub(crate) fn parse_year_month(cursor: &mut Cursor) -> TemporalResult<(i32, i32)> { - let year = parse_date_year(cursor)?; - - cursor.advance_if(cursor.check_or(false, is_hyphen)); - - let month = parse_date_month(cursor)?; - - assert_syntax!( - cursor.check_or(true, is_annotation_open), - "Expected an end or AnnotationOpen" - ); - - Ok((year, month)) -} - -/// Parses a `DateSpecMonthDay` -pub(crate) fn parse_month_day(cursor: &mut Cursor) -> TemporalResult<(i32, i32)> { - let dash_one = cursor - .check(is_hyphen) - .ok_or_else(TemporalError::abrupt_end)?; - let dash_two = cursor - .peek() - .map(is_hyphen) - .ok_or_else(TemporalError::abrupt_end)?; - - if dash_two && dash_one { - cursor.advance_n(2); - } else if dash_two && !dash_one { - return Err(TemporalError::syntax().with_message("MonthDay requires two dashes")); - } - - let month = parse_date_month(cursor)?; - - cursor.advance_if(cursor.check_or(false, is_hyphen)); - - let day = parse_date_day(cursor)?; - - assert_syntax!( - cursor.check_or(true, is_annotation_open), - "Expected an end or AnnotationOpen" - ); - - Ok((month, day)) -} - -// ==== Unit Parsers ==== - -fn parse_date_year(cursor: &mut Cursor) -> TemporalResult { - if cursor.check_or(false, is_sign) { - let sign = if cursor.expect_next() == '+' { 1 } else { -1 }; - let year_start = cursor.pos(); - - for _ in 0..6 { - let year_digit = cursor.abrupt_next()?; - assert_syntax!( - year_digit.is_ascii_digit(), - "Year must be made up of digits." - ); - } - - let year_value = cursor - .slice(year_start, cursor.pos()) - .parse::() - .map_err(|e| TemporalError::syntax().with_message(e.to_string()))?; - - // 13.30.1 Static Semantics: Early Errors - // - // It is a Syntax Error if DateYear is "-000000" or "−000000" (U+2212 MINUS SIGN followed by 000000). - if sign == -1 && year_value == 0 { - return Err(TemporalError::syntax().with_message("Cannot have negative 0 years.")); - } - - let year = sign * year_value; - - if !(-271_820..=275_760).contains(&year) { - return Err(TemporalError::range() - .with_message("Year is outside of the minimum supported range.")); - } - - return Ok(year); - } - - let year_start = cursor.pos(); - - for _ in 0..4 { - let year_digit = cursor.abrupt_next()?; - assert_syntax!( - year_digit.is_ascii_digit(), - "Year must be made up of digits." - ); - } - - let year_value = cursor - .slice(year_start, cursor.pos()) - .parse::() - .map_err(|e| TemporalError::syntax().with_message(e.to_string()))?; - - Ok(year_value) -} - -fn parse_date_month(cursor: &mut Cursor) -> TemporalResult { - let start = cursor.pos(); - for _ in 0..2 { - let digit = cursor.abrupt_next()?; - assert_syntax!(digit.is_ascii_digit(), "Month must be a digit"); - } - let month_value = cursor - .slice(start, cursor.pos()) - .parse::() - .map_err(|e| TemporalError::syntax().with_message(e.to_string()))?; - if !(1..=12).contains(&month_value) { - return Err(TemporalError::syntax().with_message("DateMonth must be in a range of 1-12")); - } - Ok(month_value) -} - -fn parse_date_day(cursor: &mut Cursor) -> TemporalResult { - let start = cursor.pos(); - for _ in 0..2 { - let digit = cursor.abrupt_next()?; - assert_syntax!(digit.is_ascii_digit(), "Date must be a digit"); - } - let day_value = cursor - .slice(start, cursor.pos()) - .parse::() - .map_err(|e| TemporalError::syntax().with_message(e.to_string()))?; - if !(1..=31).contains(&day_value) { - return Err(TemporalError::syntax().with_message("DateDay must be in a range of 1-31")); - } - Ok(day_value) -} diff --git a/core/temporal/src/parser/duration.rs b/core/temporal/src/parser/duration.rs deleted file mode 100644 index 10d182fcc2..0000000000 --- a/core/temporal/src/parser/duration.rs +++ /dev/null @@ -1,247 +0,0 @@ -use crate::{ - assert_syntax, - parser::{ - grammar::{ - is_day_designator, is_decimal_separator, is_duration_designator, is_hour_designator, - is_minute_designator, is_month_designator, is_second_designator, is_sign, - is_time_designator, is_week_designator, is_year_designator, - }, - time::parse_fraction, - Cursor, - }, - TemporalError, TemporalResult, -}; - -/// An ISO8601 `DurationRecord` Parse Node. -#[derive(Debug, Clone, Copy)] -pub(crate) struct DurationParseRecord { - /// Duration Sign - pub(crate) sign: bool, - /// A `DateDuration` record. - pub(crate) date: DateDuration, - /// A `TimeDuration` record. - pub(crate) time: TimeDuration, -} - -/// A `DateDuration` Parse Node. -#[derive(Default, Debug, Clone, Copy)] -pub(crate) struct DateDuration { - /// Years value. - pub(crate) years: i32, - /// Months value. - pub(crate) months: i32, - /// Weeks value. - pub(crate) weeks: i32, - /// Days value. - pub(crate) days: i32, -} - -/// A `TimeDuration` Parse Node -#[derive(Default, Debug, Clone, Copy)] -pub(crate) struct TimeDuration { - /// Hours value. - pub(crate) hours: i32, - /// Hours fraction value. - pub(crate) fhours: f64, - /// Minutes value with fraction. - pub(crate) minutes: i32, - /// Minutes fraction value. - pub(crate) fminutes: f64, - /// Seconds value with fraction. - pub(crate) seconds: i32, - /// Seconds fraction value, - pub(crate) fseconds: f64, -} - -pub(crate) fn parse_duration(cursor: &mut Cursor) -> TemporalResult { - let sign = if cursor - .check(is_sign) - .ok_or_else(TemporalError::abrupt_end)? - { - cursor.expect_next() == '+' - } else { - true - }; - - assert_syntax!( - is_duration_designator(cursor.abrupt_next()?), - "DurationDisgnator is missing." - ); - - let date = if cursor.check_or(false, is_time_designator) { - Some(DateDuration::default()) - } else { - Some(parse_date_duration(cursor)?) - }; - - let time = if cursor.check_or(false, is_time_designator) { - cursor.advance(); - Some(parse_time_duration(cursor)?) - } else { - None - }; - - cursor.close()?; - - Ok(DurationParseRecord { - sign, - date: date.unwrap_or_default(), - time: time.unwrap_or_default(), - }) -} - -#[derive(PartialEq, PartialOrd, Eq, Ord)] -enum DateUnit { - None = 0, - Year, - Month, - Week, - Day, -} - -pub(crate) fn parse_date_duration(cursor: &mut Cursor) -> TemporalResult { - let mut date = DateDuration::default(); - - let mut previous_unit = DateUnit::None; - while cursor.check_or(false, |ch| ch.is_ascii_digit()) { - let digit_start = cursor.pos(); - - while cursor.next().is_some() { - if !cursor.check_or(false, |ch| ch.is_ascii_digit()) { - break; - } - } - - let value = cursor - .slice(digit_start, cursor.pos()) - .parse::() - .map_err(|err| TemporalError::syntax().with_message(err.to_string()))?; - - match cursor.next() { - Some(ch) if is_year_designator(ch) => { - if previous_unit > DateUnit::Year { - return Err( - TemporalError::syntax().with_message("Not a valid DateDuration order") - ); - } - date.years = value; - previous_unit = DateUnit::Year; - } - Some(ch) if is_month_designator(ch) => { - if previous_unit > DateUnit::Month { - return Err( - TemporalError::syntax().with_message("Not a valid DateDuration order") - ); - } - date.months = value; - previous_unit = DateUnit::Month; - } - Some(ch) if is_week_designator(ch) => { - if previous_unit > DateUnit::Week { - return Err( - TemporalError::syntax().with_message("Not a valid DateDuration order") - ); - } - date.weeks = value; - previous_unit = DateUnit::Week; - } - Some(ch) if is_day_designator(ch) => { - if previous_unit > DateUnit::Day { - return Err( - TemporalError::syntax().with_message("Not a valid DateDuration order") - ); - } - date.days = value; - previous_unit = DateUnit::Day; - } - Some(_) | None => return Err(TemporalError::abrupt_end()), - } - } - - Ok(date) -} - -#[derive(PartialEq, PartialOrd, Eq, Ord)] -enum TimeUnit { - None = 0, - Hour, - Minute, - Second, -} - -pub(crate) fn parse_time_duration(cursor: &mut Cursor) -> TemporalResult { - let mut time = TimeDuration::default(); - - assert_syntax!( - cursor.check_or(false, |ch| ch.is_ascii_digit()), - "TimeDuration designator must have values after." - ); - - let mut previous_unit = TimeUnit::None; - let mut fraction_present = false; - while cursor.check_or(false, |ch| ch.is_ascii_digit()) { - let digit_start = cursor.pos(); - - while cursor.next().is_some() { - if !cursor.check_or(false, |ch| ch.is_ascii_digit()) { - break; - } - } - - let value = cursor - .slice(digit_start, cursor.pos()) - .parse::() - .map_err(|err| TemporalError::syntax().with_message(err.to_string()))?; - - let fraction = if cursor.check_or(false, is_decimal_separator) { - fraction_present = true; - parse_fraction(cursor)? - } else { - 0.0 - }; - - match cursor.next() { - Some(ch) if is_hour_designator(ch) => { - if previous_unit > TimeUnit::Hour { - return Err( - TemporalError::syntax().with_message("Not a valid DateDuration order") - ); - } - time.hours = value; - time.fhours = fraction; - previous_unit = TimeUnit::Hour; - } - Some(ch) if is_minute_designator(ch) => { - if previous_unit > TimeUnit::Minute { - return Err( - TemporalError::syntax().with_message("Not a valid DateDuration order") - ); - } - time.minutes = value; - time.fminutes = fraction; - previous_unit = TimeUnit::Minute; - } - Some(ch) if is_second_designator(ch) => { - if previous_unit > TimeUnit::Second { - return Err( - TemporalError::syntax().with_message("Not a valid DateDuration order") - ); - } - time.seconds = value; - time.fseconds = fraction; - previous_unit = TimeUnit::Second; - } - Some(_) | None => return Err(TemporalError::abrupt_end()), - } - - if fraction_present { - assert_syntax!( - cursor.check_or(true, |ch| !ch.is_ascii_digit()), - "Invalid duration value provided after fraction." - ); - break; - } - } - - Ok(time) -} diff --git a/core/temporal/src/parser/grammar.rs b/core/temporal/src/parser/grammar.rs deleted file mode 100644 index ae7a28eb8b..0000000000 --- a/core/temporal/src/parser/grammar.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! ISO8601 specific grammar checks. - -/// Checks if char is a `AKeyLeadingChar`. -#[inline] -pub(crate) const fn is_a_key_leading_char(ch: char) -> bool { - ch.is_ascii_lowercase() || ch == '_' -} - -/// Checks if char is an `AKeyChar`. -#[inline] -pub(crate) const fn is_a_key_char(ch: char) -> bool { - is_a_key_leading_char(ch) || ch.is_ascii_digit() || ch == '-' -} - -/// Checks if char is an `AnnotationValueComponent`. -pub(crate) const fn is_annotation_value_component(ch: char) -> bool { - ch.is_ascii_digit() || ch.is_ascii_alphabetic() -} - -/// Checks if char is a `TZLeadingChar`. -#[inline] -pub(crate) const fn is_tz_leading_char(ch: char) -> bool { - ch.is_ascii_alphabetic() || ch == '_' || ch == '.' -} - -/// Checks if char is a `TZChar`. -#[inline] -pub(crate) const fn is_tz_char(ch: char) -> bool { - is_tz_leading_char(ch) || ch.is_ascii_digit() || ch == '-' || ch == '+' -} - -/// Checks if char is a `TimeZoneIANAName` Separator. -pub(crate) const fn is_tz_name_separator(ch: char) -> bool { - ch == '/' -} - -/// Checks if char is an ascii sign. -pub(crate) const fn is_ascii_sign(ch: char) -> bool { - ch == '+' || ch == '-' -} - -/// Checks if char is an ascii sign or U+2212 -pub(crate) const fn is_sign(ch: char) -> bool { - is_ascii_sign(ch) || ch == '\u{2212}' -} - -/// Checks if char is a `TimeSeparator`. -pub(crate) const fn is_time_separator(ch: char) -> bool { - ch == ':' -} - -/// Checks if char is a `TimeDesignator`. -pub(crate) const fn is_time_designator(ch: char) -> bool { - ch == 'T' || ch == 't' -} - -/// Checks if char is a `DateTimeSeparator`. -pub(crate) const fn is_date_time_separator(ch: char) -> bool { - is_time_designator(ch) || ch == '\u{0020}' -} - -/// Checks if char is a `UtcDesignator`. -pub(crate) const fn is_utc_designator(ch: char) -> bool { - ch == 'Z' || ch == 'z' -} - -/// Checks if char is a `DurationDesignator`. -pub(crate) const fn is_duration_designator(ch: char) -> bool { - ch == 'P' || ch == 'p' -} - -/// Checks if char is a `YearDesignator`. -pub(crate) const fn is_year_designator(ch: char) -> bool { - ch == 'Y' || ch == 'y' -} - -/// Checks if char is a `MonthsDesignator`. -pub(crate) const fn is_month_designator(ch: char) -> bool { - ch == 'M' || ch == 'm' -} - -/// Checks if char is a `WeekDesignator`. -pub(crate) const fn is_week_designator(ch: char) -> bool { - ch == 'W' || ch == 'w' -} - -/// Checks if char is a `DayDesignator`. -pub(crate) const fn is_day_designator(ch: char) -> bool { - ch == 'D' || ch == 'd' -} - -/// checks if char is a `DayDesignator`. -pub(crate) const fn is_hour_designator(ch: char) -> bool { - ch == 'H' || ch == 'h' -} - -/// Checks if char is a `MinuteDesignator`. -pub(crate) const fn is_minute_designator(ch: char) -> bool { - is_month_designator(ch) -} - -/// checks if char is a `SecondDesignator`. -pub(crate) const fn is_second_designator(ch: char) -> bool { - ch == 'S' || ch == 's' -} - -/// Checks if char is a `DecimalSeparator`. -pub(crate) const fn is_decimal_separator(ch: char) -> bool { - ch == '.' || ch == ',' -} - -/// Checks if char is an `AnnotationOpen`. -pub(crate) const fn is_annotation_open(ch: char) -> bool { - ch == '[' -} - -/// Checks if char is an `AnnotationClose`. -pub(crate) const fn is_annotation_close(ch: char) -> bool { - ch == ']' -} - -/// Checks if char is an `CriticalFlag`. -pub(crate) const fn is_critical_flag(ch: char) -> bool { - ch == '!' -} - -/// Checks if char is the `AnnotationKeyValueSeparator`. -pub(crate) const fn is_annotation_key_value_separator(ch: char) -> bool { - ch == '=' -} - -/// Checks if char is a hyphen. Hyphens are used as a Date separator -/// and as a `AttributeValueComponent` separator. -pub(crate) const fn is_hyphen(ch: char) -> bool { - ch == '-' -} diff --git a/core/temporal/src/parser/mod.rs b/core/temporal/src/parser/mod.rs deleted file mode 100644 index 48bfee3ba3..0000000000 --- a/core/temporal/src/parser/mod.rs +++ /dev/null @@ -1,290 +0,0 @@ -//! This module implements parsing for ISO 8601 grammar. - -use crate::{TemporalError, TemporalResult}; - -use datetime::DateRecord; -use nodes::{IsoDate, IsoDateTime, IsoTime, TimeZone}; -use time::TimeSpec; - -mod annotations; -pub(crate) mod datetime; -pub(crate) mod duration; -mod grammar; -mod nodes; -mod time; -pub(crate) mod time_zone; - -use self::{datetime::DateTimeFlags, grammar::is_annotation_open}; - -#[cfg(test)] -mod tests; - -// TODO: optimize where possible. - -/// `assert_syntax!` is a parser specific utility macro for asserting a syntax test, and returning a -/// `SyntaxError` with the provided message if the test fails. -#[macro_export] -macro_rules! assert_syntax { - ($cond:expr, $msg:literal) => { - if !$cond { - return Err(TemporalError::syntax().with_message($msg)); - } - }; -} - -/// A utility function for parsing a `DateTime` string -pub(crate) fn parse_date_time(target: &str) -> TemporalResult { - datetime::parse_annotated_date_time(DateTimeFlags::empty(), &mut Cursor::new(target)) -} - -/// A utility function for parsing an `Instant` string -#[allow(unused)] -pub(crate) fn parse_instant(target: &str) -> TemporalResult { - datetime::parse_annotated_date_time( - DateTimeFlags::UTC_REQ | DateTimeFlags::TIME_REQ, - &mut Cursor::new(target), - ) -} - -/// A utility function for parsing a `YearMonth` string -pub(crate) fn parse_year_month(target: &str) -> TemporalResult { - let mut cursor = Cursor::new(target); - let ym = datetime::parse_year_month(&mut cursor); - - let Ok(year_month) = ym else { - cursor.pos = 0; - return datetime::parse_annotated_date_time(DateTimeFlags::empty(), &mut cursor); - }; - - let calendar = if cursor.check_or(false, is_annotation_open) { - let set = annotations::parse_annotation_set(false, &mut cursor)?; - set.calendar - } else { - None - }; - - cursor.close()?; - - Ok(IsoParseRecord { - date: DateRecord { - year: year_month.0, - month: year_month.1, - day: 1, - }, - time: None, - tz: None, - calendar, - }) -} - -/// A utilty function for parsing a `MonthDay` String. -pub(crate) fn parse_month_day(target: &str) -> TemporalResult { - let mut cursor = Cursor::new(target); - let md = datetime::parse_month_day(&mut cursor); - - let Ok(month_day) = md else { - cursor.pos = 0; - return datetime::parse_annotated_date_time(DateTimeFlags::empty(), &mut cursor); - }; - - let calendar = if cursor.check_or(false, is_annotation_open) { - let set = annotations::parse_annotation_set(false, &mut cursor)?; - set.calendar - } else { - None - }; - - cursor.close()?; - - Ok(IsoParseRecord { - date: DateRecord { - year: 0, - month: month_day.0, - day: month_day.1, - }, - time: None, - tz: None, - calendar, - }) -} - -/// An `IsoParseRecord` is an intermediary record returned by ISO parsing functions. -/// -/// `IsoParseRecord` is converted into the ISO AST Nodes. -#[derive(Default, Debug)] -pub(crate) struct IsoParseRecord { - /// Parsed Date Record - pub(crate) date: DateRecord, - /// Parsed Time - pub(crate) time: Option, - /// Parsed `TimeZone` data (UTCOffset | IANA name) - pub(crate) tz: Option, - /// The parsed calendar value. - pub(crate) calendar: Option, -} - -// TODO: Phase out the below and integrate parsing with Temporal components. - -/// Parse a [`TemporalTimeZoneString`][proposal]. -/// -/// [proposal]: https://tc39.es/proposal-temporal/#prod-TemporalTimeZoneString -#[derive(Debug, Clone, Copy)] -pub struct TemporalTimeZoneString; - -impl TemporalTimeZoneString { - /// Parses a targeted string as a `TimeZone`. - /// - /// # Errors - /// - /// The parse will error if the provided target is not valid - /// Iso8601 grammar. - pub fn parse(cursor: &mut Cursor) -> TemporalResult { - time_zone::parse_time_zone(cursor) - } -} - -/// Parser for a [`TemporalInstantString`][proposal]. -/// -/// [proposal]: https://tc39.es/proposal-temporal/#prod-TemporalInstantString -#[derive(Debug, Clone, Copy)] -pub struct TemporalInstantString; - -impl TemporalInstantString { - /// Parses a targeted string as an `Instant`. - /// - /// # Errors - /// - /// The parse will error if the provided target is not valid - /// Iso8601 grammar. - pub fn parse(cursor: &mut Cursor) -> TemporalResult { - let parse_record = datetime::parse_annotated_date_time( - DateTimeFlags::UTC_REQ | DateTimeFlags::TIME_REQ, - cursor, - )?; - - let date = IsoDate { - year: parse_record.date.year, - month: parse_record.date.month, - day: parse_record.date.day, - calendar: parse_record.calendar, - }; - - let time = parse_record.time.map_or_else(IsoTime::default, |time| { - IsoTime::from_components(time.hour, time.minute, time.second, time.fraction) - }); - - Ok(IsoDateTime { - date, - time, - tz: parse_record.tz, - }) - } -} - -// ==== Mini cursor implementation for Iso8601 targets ==== - -/// `Cursor` is a small cursor implementation for parsing Iso8601 grammar. -#[derive(Debug)] -pub struct Cursor { - pos: u32, - source: Vec, -} - -impl Cursor { - /// Create a new cursor from a source `String` value. - #[must_use] - pub fn new(source: &str) -> Self { - Self { - pos: 0, - source: source.chars().collect(), - } - } - - /// Returns a string value from a slice of the cursor. - fn slice(&self, start: u32, end: u32) -> String { - self.source[start as usize..end as usize].iter().collect() - } - - /// Get current position - const fn pos(&self) -> u32 { - self.pos - } - - /// Peek the value at next position (current + 1). - fn peek(&self) -> Option { - self.peek_n(1) - } - - /// Peek the value at n len from current. - fn peek_n(&self, n: u32) -> Option { - let target = (self.pos + n) as usize; - if target < self.source.len() { - Some(self.source[target]) - } else { - None - } - } - - /// Runs the provided check on the current position. - fn check(&self, f: F) -> Option - where - F: FnOnce(char) -> bool, - { - self.peek_n(0).map(f) - } - - /// Runs the provided check on current position returns the default value if None. - fn check_or(&self, default: bool, f: F) -> bool - where - F: FnOnce(char) -> bool, - { - self.peek_n(0).map_or(default, f) - } - - /// Returns `Cursor`'s current char and advances to the next position. - fn next(&mut self) -> Option { - let result = self.peek_n(0); - self.advance(); - result - } - - /// Utility method that returns next charactor unwrapped char - /// - /// # Panics - /// - /// This will panic if the next value has not been confirmed to exist. - fn expect_next(&mut self) -> char { - self.next().expect("Invalid use of expect_next.") - } - - /// A utility next method that returns an `AbruptEnd` error if invalid. - fn abrupt_next(&mut self) -> TemporalResult { - self.next().ok_or_else(TemporalError::abrupt_end) - } - - /// Advances the cursor's position by 1. - fn advance(&mut self) { - self.pos += 1; - } - - /// Utility function to advance when a condition is true - fn advance_if(&mut self, condition: bool) { - if condition { - self.advance(); - } - } - - /// Advances the cursor's position by `n`. - fn advance_n(&mut self, n: u32) { - self.pos += n; - } - - /// Closes the current cursor by checking if all contents have been consumed. If not, returns an error for invalid syntax. - fn close(&mut self) -> TemporalResult<()> { - if (self.pos as usize) < self.source.len() { - return Err(TemporalError::syntax() - .with_message("Unexpected syntax at the end of an ISO target.")); - } - Ok(()) - } -} diff --git a/core/temporal/src/parser/nodes.rs b/core/temporal/src/parser/nodes.rs deleted file mode 100644 index 184ab750ec..0000000000 --- a/core/temporal/src/parser/nodes.rs +++ /dev/null @@ -1,90 +0,0 @@ -//! AST nodes for Temporal's implementation of ISO8601 grammar. - -// TODO: Slowly remove the below nodes in favor of Temporal components. - -/// An ISO Date Node consisting of non-validated date fields and calendar value. -#[derive(Default, Debug)] -pub struct IsoDate { - /// Date Year - pub year: i32, - /// Date Month - pub month: i32, - /// Date Day - pub day: i32, - /// The calendar value. - pub calendar: Option, -} - -/// The `IsoTime` node consists of non-validated time fields. -#[derive(Default, Debug, Clone, Copy)] -pub struct IsoTime { - /// An hour value between 0-23 - pub hour: u8, - /// A minute value between 0-59 - pub minute: u8, - /// A second value between 0-60 - pub second: u8, - /// A millisecond value between 0-999 - pub millisecond: u16, - /// A microsecond value between 0-999 - pub microsecond: u16, - /// A nanosecond value between 0-999 - pub nanosecond: u16, -} - -impl IsoTime { - #[must_use] - #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] - /// A utility initialization function to create `ISOTime` from the `TimeSpec` components. - pub fn from_components(hour: u8, minute: u8, second: u8, fraction: f64) -> Self { - // Note: Precision on nanoseconds drifts, so opting for round over floor or ceil for now. - // e.g. 0.329402834 becomes 329.402833.999 - let millisecond = fraction * 1000f64; - let micros = millisecond.rem_euclid(1f64) * 1000f64; - let nanos = micros.rem_euclid(1f64) * 1000f64; - - Self { - hour, - minute, - second, - millisecond: millisecond.floor() as u16, - microsecond: micros.floor() as u16, - nanosecond: nanos.round() as u16, - } - } -} - -/// The `IsoDateTime` node output by the ISO parser -#[derive(Default, Debug)] -pub struct IsoDateTime { - /// The `ISODate` record - pub date: IsoDate, - /// The `ISOTime` record - pub time: IsoTime, - /// The `TimeZone` value for this `ISODateTime` - pub tz: Option, -} - -/// `TimeZone` data -#[derive(Default, Debug, Clone)] -pub struct TimeZone { - /// TimeZoneIANAName - pub name: Option, - /// TimeZoneOffset - pub offset: Option, -} - -/// A full precision `UtcOffset` -#[derive(Debug, Clone, Copy)] -pub struct UTCOffset { - /// The `+`/`-` sign of this `UtcOffset` - pub sign: i8, - /// The hour value of the `UtcOffset` - pub hour: u8, - /// The minute value of the `UtcOffset`. - pub minute: u8, - /// The second value of the `UtcOffset`. - pub second: u8, - /// Any sub second components of the `UTCOffset` - pub fraction: f64, -} diff --git a/core/temporal/src/parser/tests.rs b/core/temporal/src/parser/tests.rs deleted file mode 100644 index 077dc74d43..0000000000 --- a/core/temporal/src/parser/tests.rs +++ /dev/null @@ -1,244 +0,0 @@ -use std::str::FromStr; - -use crate::{ - components::{DateTime, Duration, MonthDay, YearMonth}, - parser::{parse_date_time, Cursor, TemporalInstantString}, -}; - -#[test] -fn temporal_parser_basic() { - let basic = "20201108"; - let basic_separated = "2020-11-08"; - - let basic_result = basic.parse::>().unwrap(); - - let sep_result = basic_separated.parse::>().unwrap(); - - assert_eq!(basic_result.iso_year(), 2020); - assert_eq!(basic_result.iso_month(), 11); - assert_eq!(basic_result.iso_day(), 8); - assert_eq!(basic_result.iso_year(), sep_result.iso_year()); - assert_eq!(basic_result.iso_month(), sep_result.iso_month()); - assert_eq!(basic_result.iso_day(), sep_result.iso_day()); -} - -#[test] -#[allow(clippy::cast_possible_truncation)] -fn temporal_date_time_max() { - // Fractions not accurate, but for testing purposes. - let date_time = - "+002020-11-08T12:28:32.329402834[!America/Argentina/ComodRivadavia][!u-ca=iso8601]"; - - let result = date_time.parse::>().unwrap(); - - assert_eq!(result.hour(), 12); - assert_eq!(result.minute(), 28); - assert_eq!(result.second(), 32); - assert_eq!(result.millisecond(), 329); - assert_eq!(result.microsecond(), 402); - assert_eq!(result.nanosecond(), 834); -} - -#[test] -fn temporal_year_parsing() { - let long = "+002020-11-08"; - let bad_year = "-000000-11-08"; - - let result_good = long.parse::>().unwrap(); - assert_eq!(result_good.iso_year(), 2020); - - let err_result = bad_year.parse::>(); - assert!( - err_result.is_err(), - "Invalid extended year parsing: \"{bad_year}\" should fail to parse." - ); -} - -#[test] -fn temporal_annotated_date_time() { - let basic = "2020-11-08[America/Argentina/ComodRivadavia][u-ca=iso8601][foo=bar]"; - let omitted = "+0020201108[u-ca=iso8601][f-1a2b=a0sa-2l4s]"; - - let result = parse_date_time(basic).unwrap(); - - let tz = &result.tz.unwrap().name.unwrap(); - - assert_eq!(tz, "America/Argentina/ComodRivadavia"); - - assert_eq!(&result.calendar, &Some("iso8601".to_string())); - - let omit_result = parse_date_time(omitted).unwrap(); - - assert!(&omit_result.tz.is_none()); - - assert_eq!(&omit_result.calendar, &Some("iso8601".to_string())); -} - -#[test] -fn temporal_year_month() { - let possible_year_months = [ - "+002020-11", - "2020-11[u-ca=iso8601]", - "+00202011", - "202011[u-ca=iso8601]", - "+002020-11-07T12:28:32[!u-ca=iso8601]", - ]; - - for ym in possible_year_months { - let result = ym.parse::>().unwrap(); - - assert_eq!(result.year(), 2020); - assert_eq!(result.month(), 11); - } -} - -#[test] -fn temporal_month_day() { - let possible_month_day = [ - "11-07", - "1107[+04:00]", - "--11-07", - "--1107[+04:00]", - "+002020-11-07T12:28:32[!u-ca=iso8601]", - ]; - - for md in possible_month_day { - let result = md.parse::>().unwrap(); - - assert_eq!(result.month(), 11); - assert_eq!(result.day(), 7); - } -} - -#[test] -fn temporal_invalid_annotations() { - let invalid_annotations = [ - "2020-11-11[!u-ca=iso8601][u-ca=iso8601]", - "2020-11-11[u-ca=iso8601][!u-ca=iso8601]", - "2020-11-11[u-ca=iso8601][!rip=this-invalid-annotation]", - ]; - - for invalid in invalid_annotations { - let err_result = invalid.parse::>(); - assert!( - err_result.is_err(), - "Invalid ISO annotation parsing: \"{invalid}\" should fail parsing." - ); - } -} - -#[test] -fn temporal_valid_instant_strings() { - let instants = [ - "1970-01-01T00:00+00:00[!Africa/Abidjan]", - "1970-01-01T00:00+00:00[UTC]", - "1970-01-01T00:00Z[!Europe/Vienna]", - ]; - - for test in instants { - let result = TemporalInstantString::parse(&mut Cursor::new(test)); - assert!(result.is_ok()); - } -} - -#[test] -#[allow(clippy::cast_possible_truncation)] -#[allow(clippy::float_cmp)] -fn temporal_duration_parsing() { - let durations = [ - "p1y1m1dt1h1m1s", - "P1Y1M1W1DT1H1M1.1S", - "-P1Y1M1W1DT1H1M1.123456789S", - "-P1Y3wT0,5H", - ]; - - for dur in durations { - let ok_result = Duration::from_str(dur); - assert!( - ok_result.is_ok(), - "Failing to parse a valid ISO 8601 target: \"{dur}\" should pass." - ); - } - - let sub_second = durations[2].parse::().unwrap(); - - assert_eq!(sub_second.time().milliseconds(), -123.0); - assert_eq!(sub_second.time().microseconds(), -456.0); - assert_eq!(sub_second.time().nanoseconds(), -789.0); - - let test_result = durations[3].parse::().unwrap(); - - assert_eq!(test_result.date().years(), -1f64); - assert_eq!(test_result.date().weeks(), -3f64); - assert_eq!(test_result.time().minutes(), -30.0); -} - -#[test] -fn temporal_invalid_durations() { - let invalids = [ - "P1Y1M1W0,5D", - "P1Y1M1W1DT1H1M1.123456789123S", - "+PT", - "P1Y1M1W1DT1H0.5M0.5S", - ]; - - for test in invalids { - let err = test.parse::(); - assert!( - err.is_err(), - "Invalid ISO8601 Duration target: \"{test}\" should fail duration parsing." - ); - } -} - -#[test] -fn temporal_invalid_iso_datetime_strings() { - // NOTE: The below tests were initially pulled from test262's `argument-string-invalid` - const INVALID_DATETIME_STRINGS: [&str; 34] = [ - "", // 1 - "invalid iso8601", - "2020-01-00", - "2020-01-32", - "2020-02-30", - "2021-02-29", - "2020-00-01", - "2020-13-01", - "2020-01-01T", - "2020-01-01T25:00:00", - "2020-01-01T01:60:00", - "2020-01-01T01:60:61", - "2020-01-01junk", - "2020-01-01T00:00:00junk", - "2020-01-01T00:00:00+00:00junk", - "2020-01-01T00:00:00+00:00[UTC]junk", - "2020-01-01T00:00:00+00:00[UTC][u-ca=iso8601]junk", - "02020-01-01", - "2020-001-01", - "2020-01-001", - "2020-01-01T001", - "2020-01-01T01:001", - "2020-01-01T01:01:001", - "2020-W01-1", - "2020-001", - "+0002020-01-01", - // TODO: Add the non-existent calendar test back to the test cases. - // may be valid in other contexts, but insufficient information for PlainDate: - "2020-01", - "+002020-01", - "01-01", - "2020-W01", - "P1Y", - "-P12Y", - // valid, but outside the supported range: - "-999999-01-01", - "+999999-01-01", - ]; - - for invalid_target in INVALID_DATETIME_STRINGS { - let error_result = invalid_target.parse::>(); - assert!( - error_result.is_err(), - "Invalid ISO8601 `DateTime` target: \"{invalid_target}\" should fail parsing." - ); - } -} diff --git a/core/temporal/src/parser/time.rs b/core/temporal/src/parser/time.rs deleted file mode 100644 index d58fcbe459..0000000000 --- a/core/temporal/src/parser/time.rs +++ /dev/null @@ -1,134 +0,0 @@ -//! Parsing of ISO8601 Time Values - -use super::{ - grammar::{is_decimal_separator, is_time_separator}, - Cursor, -}; -use crate::{assert_syntax, TemporalError, TemporalResult}; - -/// Parsed Time info -#[derive(Debug, Default, Clone, Copy)] -pub(crate) struct TimeSpec { - /// An hour - pub(crate) hour: u8, - /// A minute value - pub(crate) minute: u8, - /// A second value. - pub(crate) second: u8, - /// A floating point number representing the sub-second values - pub(crate) fraction: f64, -} - -/// Parse `TimeSpec` -pub(crate) fn parse_time_spec(cursor: &mut Cursor) -> TemporalResult { - let hour = parse_hour(cursor)?; - - if !cursor.check_or(false, |ch| is_time_separator(ch) || ch.is_ascii_digit()) { - return Ok(TimeSpec { - hour, - minute: 0, - second: 0, - fraction: 0.0, - }); - } - - let separator_present = cursor.check_or(false, is_time_separator); - cursor.advance_if(separator_present); - - let minute = parse_minute_second(cursor, false)?; - - if !cursor.check_or(false, |ch| is_time_separator(ch) || ch.is_ascii_digit()) { - return Ok(TimeSpec { - hour, - minute, - second: 0, - fraction: 0.0, - }); - } else if cursor.check_or(false, is_time_separator) && !separator_present { - return Err(TemporalError::syntax().with_message("Invalid TimeSeparator")); - } - - cursor.advance_if(separator_present); - - let second = parse_minute_second(cursor, true)?; - - let fraction = if cursor.check_or(false, is_decimal_separator) { - parse_fraction(cursor)? - } else { - 0.0 - }; - - Ok(TimeSpec { - hour, - minute, - second, - fraction, - }) -} - -pub(crate) fn parse_hour(cursor: &mut Cursor) -> TemporalResult { - let start = cursor.pos(); - for _ in 0..2 { - let digit = cursor.abrupt_next()?; - assert_syntax!(digit.is_ascii_digit(), "Hour must be a digit."); - } - let hour_value = cursor - .slice(start, cursor.pos()) - .parse::() - .map_err(|e| TemporalError::syntax().with_message(e.to_string()))?; - if !(0..=23).contains(&hour_value) { - return Err(TemporalError::syntax().with_message("Hour must be in a range of 0-23")); - } - Ok(hour_value) -} - -// NOTE: `TimeSecond` is a 60 inclusive `MinuteSecond`. -/// Parse `MinuteSecond` -pub(crate) fn parse_minute_second(cursor: &mut Cursor, inclusive: bool) -> TemporalResult { - let start = cursor.pos(); - for _ in 0..2 { - let digit = cursor.abrupt_next()?; - assert_syntax!(digit.is_ascii_digit(), "MinuteSecond must be a digit."); - } - let min_sec_value = cursor - .slice(start, cursor.pos()) - .parse::() - .map_err(|e| TemporalError::syntax().with_message(e.to_string()))?; - - let valid_range = if inclusive { 0..=60 } else { 0..=59 }; - if !valid_range.contains(&min_sec_value) { - return Err(TemporalError::syntax().with_message("MinuteSecond must be in a range of 0-59")); - } - Ok(min_sec_value) -} - -/// Parse a `Fraction` value -/// -/// This is primarily used in ISO8601 to add percision past -/// a second. -pub(crate) fn parse_fraction(cursor: &mut Cursor) -> TemporalResult { - let mut fraction_components = Vec::default(); - - // Assert that the first char provided is a decimal separator. - assert_syntax!( - is_decimal_separator(cursor.abrupt_next()?), - "fraction must begin with a valid decimal separator." - ); - fraction_components.push('.'); - - while cursor.check_or(false, |ch| ch.is_ascii_digit()) { - fraction_components.push(cursor.abrupt_next()?); - } - - assert_syntax!( - fraction_components.len() <= 10, - "Fraction component cannot exceed 9 digits." - ); - - let fraction_value = fraction_components - .iter() - .collect::() - .parse::() - .map_err(|e| TemporalError::syntax().with_message(e.to_string()))?; - Ok(fraction_value) -} diff --git a/core/temporal/src/parser/time_zone.rs b/core/temporal/src/parser/time_zone.rs deleted file mode 100644 index 3b408e4150..0000000000 --- a/core/temporal/src/parser/time_zone.rs +++ /dev/null @@ -1,229 +0,0 @@ -//! ISO8601 parsing for Time Zone and Offset data. - -use super::{ - grammar::{ - is_a_key_char, is_a_key_leading_char, is_annotation_close, - is_annotation_key_value_separator, is_annotation_open, is_critical_flag, - is_decimal_separator, is_sign, is_time_separator, is_tz_char, is_tz_leading_char, - is_tz_name_separator, is_utc_designator, - }, - nodes::{TimeZone, UTCOffset}, - time::{parse_fraction, parse_hour, parse_minute_second}, - Cursor, -}; -use crate::{assert_syntax, TemporalError, TemporalResult}; - -/// A `TimeZoneAnnotation`. -#[derive(Debug, Clone)] -#[allow(unused)] -pub(crate) struct TimeZoneAnnotation { - /// Critical Flag for the annotation. - pub(crate) critical: bool, - /// TimeZone Data - pub(crate) tz: TimeZone, -} - -// ==== Time Zone Annotation Parsing ==== - -pub(crate) fn parse_ambiguous_tz_annotation( - cursor: &mut Cursor, -) -> TemporalResult> { - // Peek position + 1 to check for critical flag. - let mut current_peek = 1; - let critical = cursor - .peek_n(current_peek) - .map(is_critical_flag) - .ok_or_else(TemporalError::abrupt_end)?; - - // Advance cursor if critical flag present. - if critical { - current_peek += 1; - } - - let leading_char = cursor - .peek_n(current_peek) - .ok_or_else(TemporalError::abrupt_end)?; - - if is_tz_leading_char(leading_char) || is_sign(leading_char) { - // Ambigious start values when lowercase alpha that is shared between `TzLeadingChar` and `KeyLeadingChar`. - if is_a_key_leading_char(leading_char) { - let mut peek_pos = current_peek + 1; - while let Some(ch) = cursor.peek_n(peek_pos) { - if is_tz_name_separator(ch) || (is_tz_char(ch) && !is_a_key_char(ch)) { - let tz = parse_tz_annotation(cursor)?; - return Ok(Some(tz)); - } else if is_annotation_key_value_separator(ch) - || (is_a_key_char(ch) && !is_tz_char(ch)) - { - return Ok(None); - } else if is_annotation_close(ch) { - return Err(TemporalError::syntax().with_message("Invalid Annotation")); - } - - peek_pos += 1; - } - return Err(TemporalError::abrupt_end()); - } - let tz = parse_tz_annotation(cursor)?; - return Ok(Some(tz)); - } - - if is_a_key_leading_char(leading_char) { - return Ok(None); - }; - - Err(TemporalError::syntax().with_message("Unexpected character in ambiguous annotation.")) -} - -fn parse_tz_annotation(cursor: &mut Cursor) -> TemporalResult { - assert_syntax!( - is_annotation_open(cursor.abrupt_next()?), - "Invalid annotation opening character." - ); - - let critical = cursor.check_or(false, is_critical_flag); - cursor.advance_if(critical); - - let tz = parse_time_zone(cursor)?; - - assert_syntax!( - is_annotation_close(cursor.abrupt_next()?), - "Invalid annotation closing character." - ); - - Ok(TimeZoneAnnotation { critical, tz }) -} - -/// Parses the [`TimeZoneIdentifier`][tz] node. -/// -/// [tz]: https://tc39.es/proposal-temporal/#prod-TimeZoneIdentifier -pub(crate) fn parse_time_zone(cursor: &mut Cursor) -> TemporalResult { - let is_iana = cursor - .check(is_tz_leading_char) - .ok_or_else(TemporalError::abrupt_end)?; - let is_offset = cursor.check_or(false, is_sign); - - if is_iana { - return parse_tz_iana_name(cursor); - } else if is_offset { - let offset = parse_utc_offset_minute_precision(cursor)?; - return Ok(TimeZone { - name: None, - offset: Some(offset), - }); - } - - Err(TemporalError::syntax().with_message("Invalid leading character for a TimeZoneIdentifier")) -} - -/// Parse a `TimeZoneIANAName` Parse Node -fn parse_tz_iana_name(cursor: &mut Cursor) -> TemporalResult { - let tz_name_start = cursor.pos(); - while let Some(potential_value_char) = cursor.next() { - if cursor.check_or(false, is_annotation_close) { - // Return the valid TimeZoneIANAName - return Ok(TimeZone { - name: Some(cursor.slice(tz_name_start, cursor.pos())), - offset: None, - }); - } - - if is_tz_name_separator(potential_value_char) { - assert_syntax!( - cursor.peek_n(2).map_or(false, is_tz_char), - "Missing IANA name component after '/'" - ); - continue; - } - - assert_syntax!( - is_tz_char(potential_value_char), - "Invalid TimeZone IANA name character." - ); - } - - Err(TemporalError::abrupt_end()) -} - -// ==== Utc Offset Parsing ==== - -/// Parse a full precision `UtcOffset` -pub(crate) fn parse_date_time_utc(cursor: &mut Cursor) -> TemporalResult { - if cursor.check_or(false, is_utc_designator) { - cursor.advance(); - return Ok(TimeZone { - name: Some("UTC".to_owned()), - offset: None, - }); - } - - let separated = cursor.peek_n(3).map_or(false, is_time_separator); - - let mut utc_to_minute = parse_utc_offset_minute_precision(cursor)?; - - if cursor.check_or(false, is_time_separator) && !separated { - return Err(TemporalError::syntax().with_message("Invalid time separator in UTC offset.")); - } - cursor.advance_if(cursor.check_or(false, is_time_separator)); - - // Return early on None or AnnotationOpen. - if cursor.check_or(true, is_annotation_open) { - return Ok(TimeZone { - name: None, - offset: Some(utc_to_minute), - }); - } - - // If `UtcOffsetWithSubMinuteComponents`, continue parsing. - utc_to_minute.second = parse_minute_second(cursor, true)?; - - let sub_second = if cursor.check_or(false, is_decimal_separator) { - parse_fraction(cursor)? - } else { - 0.0 - }; - - utc_to_minute.fraction = sub_second; - - Ok(TimeZone { - name: None, - offset: Some(utc_to_minute), - }) -} - -/// Parse an `UtcOffsetMinutePrecision` node -pub(crate) fn parse_utc_offset_minute_precision(cursor: &mut Cursor) -> TemporalResult { - let sign = if cursor.check_or(false, is_sign) { - if cursor.expect_next() == '+' { - 1 - } else { - -1 - } - } else { - 1 - }; - let hour = parse_hour(cursor)?; - - // If at the end of the utc, then return. - if !cursor.check_or(false, |ch| ch.is_ascii_digit() || is_time_separator(ch)) { - return Ok(UTCOffset { - sign, - hour, - minute: 0, - second: 0, - fraction: 0.0, - }); - } - // Advance cursor beyond any TimeSeparator - cursor.advance_if(cursor.check_or(false, is_time_separator)); - - let minute = parse_minute_second(cursor, false)?; - - Ok(UTCOffset { - sign, - hour, - minute, - second: 0, - fraction: 0.0, - }) -} diff --git a/core/temporal/src/utils.rs b/core/temporal/src/utils.rs deleted file mode 100644 index cc370f42a9..0000000000 --- a/core/temporal/src/utils.rs +++ /dev/null @@ -1,341 +0,0 @@ -//! Utility date and time equations for Temporal - -use crate::{ - options::{TemporalRoundingMode, TemporalUnsignedRoundingMode}, - TemporalError, TemporalResult, MS_PER_DAY, -}; - -// NOTE: Review the below for optimizations and add ALOT of tests. - -/// Converts and validates an `Option` rounding increment value into a valid increment result. -pub(crate) fn to_rounding_increment(increment: Option) -> TemporalResult { - let inc = increment.unwrap_or(1.0); - - if !inc.is_finite() { - return Err(TemporalError::range().with_message("roundingIncrement must be finite.")); - } - - let integer = inc.trunc(); - - if !(1.0..=1_000_000_000f64).contains(&integer) { - return Err( - TemporalError::range().with_message("roundingIncrement is not within a valid range.") - ); - } - - Ok(integer) -} - -fn apply_unsigned_rounding_mode( - x: f64, - r1: f64, - r2: f64, - unsigned_rounding_mode: TemporalUnsignedRoundingMode, -) -> f64 { - // 1. If x is equal to r1, return r1. - if (x - r1).abs() == 0.0 { - return r1; - }; - // 2. Assert: r1 < x < r2. - assert!(r1 < x && x < r2); - // 3. Assert: unsignedRoundingMode is not undefined. - - // 4. If unsignedRoundingMode is zero, return r1. - if unsigned_rounding_mode == TemporalUnsignedRoundingMode::Zero { - return r1; - }; - // 5. If unsignedRoundingMode is infinity, return r2. - if unsigned_rounding_mode == TemporalUnsignedRoundingMode::Infinity { - return r2; - }; - - // 6. Let d1 be x – r1. - let d1 = x - r1; - // 7. Let d2 be r2 – x. - let d2 = r2 - x; - // 8. If d1 < d2, return r1. - if d1 < d2 { - return r1; - } - // 9. If d2 < d1, return r2. - if d2 < d1 { - return r2; - } - // 10. Assert: d1 is equal to d2. - assert!((d1 - d2).abs() == 0.0); - - // 11. If unsignedRoundingMode is half-zero, return r1. - if unsigned_rounding_mode == TemporalUnsignedRoundingMode::HalfZero { - return r1; - }; - // 12. If unsignedRoundingMode is half-infinity, return r2. - if unsigned_rounding_mode == TemporalUnsignedRoundingMode::HalfInfinity { - return r2; - }; - // 13. Assert: unsignedRoundingMode is half-even. - assert!(unsigned_rounding_mode == TemporalUnsignedRoundingMode::HalfEven); - // 14. Let cardinality be (r1 / (r2 – r1)) modulo 2. - let cardinality = (r1 / (r2 - r1)) % 2.0; - // 15. If cardinality is 0, return r1. - if cardinality == 0.0 { - return r1; - } - // 16. Return r2. - r2 -} - -/// 13.28 `RoundNumberToIncrement ( x, increment, roundingMode )` -pub(crate) fn round_number_to_increment( - x: f64, - increment: f64, - rounding_mode: TemporalRoundingMode, -) -> f64 { - // 1. Let quotient be x / increment. - let mut quotient = x / increment; - - // 2. If quotient < 0, then - let is_negative = if quotient < 0_f64 { - // a. Let isNegative be true. - // b. Set quotient to -quotient. - quotient = -quotient; - true - // 3. Else, - } else { - // a. Let isNegative be false. - false - }; - - // 4. Let unsignedRoundingMode be GetUnsignedRoundingMode(roundingMode, isNegative). - let unsigned_rounding_mode = rounding_mode.get_unsigned_round_mode(is_negative); - // 5. Let r1 be the largest integer such that r1 ≤ quotient. - let r1 = quotient.floor(); - // 6. Let r2 be the smallest integer such that r2 > quotient. - let r2 = quotient.ceil(); - // 7. Let rounded be ApplyUnsignedRoundingMode(quotient, r1, r2, unsignedRoundingMode). - let mut rounded = apply_unsigned_rounding_mode(quotient, r1, r2, unsigned_rounding_mode); - // 8. If isNegative is true, set rounded to -rounded. - if is_negative { - rounded = -rounded; - }; - // 9. Return rounded × increment. - rounded * increment -} - -/// Rounds provided number assuming that the increment is greater than 0. -pub(crate) fn round_number_to_increment_as_if_positive( - nanos: f64, - increment_nanos: f64, - rounding_mode: TemporalRoundingMode, -) -> f64 { - // 1. Let quotient be x / increment. - let quotient = nanos / increment_nanos; - // 2. Let unsignedRoundingMode be GetUnsignedRoundingMode(roundingMode, false). - let unsigned_rounding_mode = rounding_mode.get_unsigned_round_mode(false); - // 3. Let r1 be the largest integer such that r1 ≤ quotient. - let r1 = quotient.floor(); - // 4. Let r2 be the smallest integer such that r2 > quotient. - let r2 = quotient.ceil(); - // 5. Let rounded be ApplyUnsignedRoundingMode(quotient, r1, r2, unsignedRoundingMode). - let rounded = apply_unsigned_rounding_mode(quotient, r1, r2, unsigned_rounding_mode); - // 6. Return rounded × increment. - rounded * increment_nanos -} - -pub(crate) fn validate_temporal_rounding_increment( - increment: u32, - dividend: u64, - inclusive: bool, -) -> TemporalResult<()> { - let max = if inclusive { dividend } else { dividend - 1 }; - - if u64::from(increment) > max { - return Err(TemporalError::range().with_message("roundingIncrement exceeds maximum.")); - } - - if dividend.rem_euclid(u64::from(increment)) != 0 { - return Err( - TemporalError::range().with_message("dividend is not divisble by roundingIncrement.") - ); - } - - Ok(()) -} - -// ==== Begin Date Equations ==== - -pub(crate) const MS_PER_HOUR: f64 = 3_600_000f64; -pub(crate) const MS_PER_MINUTE: f64 = 60_000f64; - -/// `EpochDaysToEpochMS` -/// -/// Functionally the same as Date's abstract operation `MakeDate` -pub(crate) fn epoch_days_to_epoch_ms(day: i32, time: f64) -> f64 { - f64::from(day).mul_add(f64::from(MS_PER_DAY), time).floor() -} - -/// `EpochTimeToDayNumber` -/// -/// This equation is the equivalent to `ECMAScript`'s `Date(t)` -pub(crate) fn epoch_time_to_day_number(t: f64) -> i32 { - (t / f64::from(MS_PER_DAY)).floor() as i32 -} - -/// Mathematically determine the days in a year. -pub(crate) fn mathematical_days_in_year(y: i32) -> i32 { - if y % 4 != 0 { - 365 - } else if y % 4 == 0 && y % 100 != 0 { - 366 - } else if y % 100 == 0 && y % 400 != 0 { - 365 - } else { - // Assert that y is divisble by 400 to ensure we are returning the correct result. - assert_eq!(y % 400, 0); - 366 - } -} - -/// Returns the epoch day number for a given year. -pub(crate) fn epoch_day_number_for_year(y: f64) -> f64 { - 365.0f64.mul_add(y - 1970.0, ((y - 1969.0) / 4.0).floor()) - ((y - 1901.0) / 100.0).floor() - + ((y - 1601.0) / 400.0).floor() -} - -pub(crate) fn epoch_time_for_year(y: i32) -> f64 { - f64::from(MS_PER_DAY) * epoch_day_number_for_year(f64::from(y)) -} - -pub(crate) fn epoch_time_to_epoch_year(t: f64) -> i32 { - // roughly calculate the largest possible year given the time t, - // then check and refine the year. - let day_count = epoch_time_to_day_number(t); - let mut year = (day_count / 365) + 1970; - loop { - if epoch_time_for_year(year) <= t { - break; - } - year -= 1; - } - - year -} - -/// Returns either 1 (true) or 0 (false) -pub(crate) fn mathematical_in_leap_year(t: f64) -> i32 { - mathematical_days_in_year(epoch_time_to_epoch_year(t)) - 365 -} - -pub(crate) fn epoch_time_to_month_in_year(t: f64) -> u8 { - const DAYS: [i32; 11] = [30, 58, 89, 120, 150, 181, 212, 242, 272, 303, 333]; - const LEAP_DAYS: [i32; 11] = [30, 59, 90, 121, 151, 182, 213, 242, 272, 303, 334]; - - let in_leap_year = mathematical_in_leap_year(t) == 1; - let day = epoch_time_to_day_in_year(t); - - let result = if in_leap_year { - LEAP_DAYS.binary_search(&day) - } else { - DAYS.binary_search(&day) - }; - - match result { - Ok(i) | Err(i) => i as u8, - } -} - -pub(crate) fn epoch_time_for_month_given_year(m: i32, y: i32) -> f64 { - let leap_day = mathematical_days_in_year(y) - 365; - - let days = match m { - 0 => 1, - 1 => 31, - 2 => 59 + leap_day, - 3 => 90 + leap_day, - 4 => 121 + leap_day, - 5 => 151 + leap_day, - 6 => 182 + leap_day, - 7 => 213 + leap_day, - 8 => 243 + leap_day, - 9 => 273 + leap_day, - 10 => 304 + leap_day, - 11 => 334 + leap_day, - _ => unreachable!(), - }; - - f64::from(MS_PER_DAY) * f64::from(days) -} - -pub(crate) fn epoch_time_to_date(t: f64) -> u8 { - const OFFSETS: [i16; 12] = [ - 1, -30, -58, -89, -119, -150, -180, -211, -242, -272, -303, -333, - ]; - let day_in_year = epoch_time_to_day_in_year(t); - let in_leap_year = mathematical_in_leap_year(t); - let month = epoch_time_to_month_in_year(t); - - // Cast from i32 to usize should be safe as the return must be 0-11 - let mut date = day_in_year + i32::from(OFFSETS[month as usize]); - - if month >= 2 { - date -= in_leap_year; - } - - // This return of date should be < 31. - date as u8 -} - -pub(crate) fn epoch_time_to_day_in_year(t: f64) -> i32 { - epoch_time_to_day_number(t) - - (epoch_day_number_for_year(f64::from(epoch_time_to_epoch_year(t))) as i32) -} - -// EpochTimeTOWeekDay -> REMOVED - -// ==== End Date Equations ==== - -// ==== Begin Calendar Equations ==== - -// NOTE: below was the iso methods in temporal::calendar -> Need to be reassessed. - -/// 12.2.31 `ISODaysInMonth ( year, month )` -pub(crate) fn iso_days_in_month(year: i32, month: i32) -> i32 { - match month { - 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, - 4 | 6 | 9 | 11 => 30, - 2 => 28 + mathematical_in_leap_year(epoch_time_for_year(year)), - _ => unreachable!("ISODaysInMonth panicking is an implementation error."), - } -} - -// The below calendar abstract equations/utilities were removed for being unused. -// 12.2.32 `ToISOWeekOfYear ( year, month, day )` -// 12.2.33 `ISOMonthCode ( month )` -// 12.2.39 `ToISODayOfYear ( year, month, day )` -// 12.2.40 `ToISODayOfWeek ( year, month, day )` - -// ==== End Calendar Equations ==== - -// ==== Tests ===== - -// TODO(nekevss): Add way more to the below. - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn time_to_month() { - let oct_2023 = 1_696_459_917_000_f64; - let mar_1_2020 = 1_583_020_800_000_f64; - let feb_29_2020 = 1_582_934_400_000_f64; - let mar_1_2021 = 1_614_556_800_000_f64; - - assert_eq!(epoch_time_to_month_in_year(oct_2023), 9); - assert_eq!(epoch_time_to_month_in_year(mar_1_2020), 2); - assert_eq!(mathematical_in_leap_year(mar_1_2020), 1); - assert_eq!(epoch_time_to_month_in_year(feb_29_2020), 1); - assert_eq!(mathematical_in_leap_year(feb_29_2020), 1); - assert_eq!(epoch_time_to_month_in_year(mar_1_2021), 2); - assert_eq!(mathematical_in_leap_year(mar_1_2021), 0); - } -}