diff --git a/boa_ast/src/temporal/mod.rs b/boa_ast/src/temporal/mod.rs index c2f4c84a21c..8ce643019ac 100644 --- a/boa_ast/src/temporal/mod.rs +++ b/boa_ast/src/temporal/mod.rs @@ -6,16 +6,33 @@ pub mod annotation; /// TBD... #[derive(Default, Debug)] -pub struct AnnotatedDateTime { +pub struct IsoParseRecord { /// Parsed Date Record - pub date_time: DateTimeRecord, + pub date: DateRecord, + /// Parsed Time + pub time: Option, + /// Parsed Offset + pub offset: Option, /// Parsed `TimeZoneAnnotation` pub tz_annotation: Option, /// Parsed Annotations pub annotations: Option>, } -#[derive(Default, Debug, Clone,Copy)] +impl IsoParseRecord { + /// Returns the a stored calendar value if it exists. + #[must_use] + pub fn calendar(&self) -> Option { + if let Some(annotations) = &self.annotations { + if let Some(cal) = annotations.get("u-ca") { + return Some(cal.1.clone()); + } + } + None + } +} + +#[derive(Default, Debug, Clone, Copy)] /// The record of a parsed date. pub struct DateRecord { /// Date Year @@ -27,7 +44,7 @@ pub struct DateRecord { } /// Parsed Time info -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Default, Clone, Copy)] #[allow(dead_code)] pub struct TimeSpec { /// An hour @@ -38,12 +55,12 @@ pub struct TimeSpec { pub second: f64, } -/// TimeZone UTC Offset info. +/// `TimeZone` UTC Offset info. #[derive(Debug, Clone, Copy)] pub struct DateTimeUtcOffset; #[derive(Debug, Default, Clone, Copy)] -/// Boop +/// A `DateTime` Parse Node that contains the date, time, and offset info. pub struct DateTimeRecord { /// Date pub date: DateRecord, @@ -53,7 +70,7 @@ pub struct DateTimeRecord { pub offset: Option, } -/// A TimeZoneAnnotation. +/// A `TimeZoneAnnotation`. #[derive(Debug, Clone)] #[allow(dead_code)] pub struct TimeZoneAnnotation { @@ -100,7 +117,7 @@ pub struct UtcOffset { pub second: f64, } -/// A KeyValueAnnotation Parse Node. +/// A `KeyValueAnnotation` Parse Node. #[derive(Debug, Clone)] #[allow(dead_code)] pub struct KeyValueAnnotation { @@ -110,4 +127,4 @@ pub struct KeyValueAnnotation { pub value: String, /// Whether the annotation was flagged as critical. pub critical: bool, -} \ No newline at end of file +} diff --git a/boa_engine/src/bigint.rs b/boa_engine/src/bigint.rs index 27f1e3af8c7..6b08bc7518d 100644 --- a/boa_engine/src/bigint.rs +++ b/boa_engine/src/bigint.rs @@ -229,7 +229,7 @@ impl JsBigInt { #[inline] #[must_use] pub fn add_n(values: &[Self]) -> Self { - let mut result = JsBigInt::zero(); + let mut result = Self::zero(); for big_int in values { result = Self::add(&result, big_int); } diff --git a/boa_engine/src/builtins/temporal/calendar/iso.rs b/boa_engine/src/builtins/temporal/calendar/iso.rs index f8f37b0ed6d..7836a067d5e 100644 --- a/boa_engine/src/builtins/temporal/calendar/iso.rs +++ b/boa_engine/src/builtins/temporal/calendar/iso.rs @@ -2,7 +2,11 @@ use std::env::temp_dir; /// Implementation of the "iso8601" calendar. use crate::{ - builtins::temporal::{self, plain_date::iso::IsoDateRecord, IsoYearMonthRecord}, + builtins::temporal::{ + self, create_temporal_date, create_temporal_duration, get_options_object, + get_temporal_unit, plain_date::iso::IsoDateRecord, to_temporal_date, to_temporal_overflow, + IsoYearMonthRecord, + }, js_string, string::utf16, Context, JsArgs, JsNativeError, JsResult, JsString, JsValue, @@ -15,7 +19,9 @@ use icu_calendar::{iso::Iso, Calendar}; pub(crate) struct IsoCalendar; impl BuiltinCalendar for IsoCalendar { - /// Temporal Proposal 15.8.2.1 `Temporal.prototype.dateFromFields( fields [, options])` - Supercedes 12.5.4 + /// Temporal 15.8.2.1 `Temporal.prototype.dateFromFields( fields [, options])` - Supercedes 12.5.4 + /// + /// This is a basic implementation for an iso8601 calendar's `dateFromFields` method. fn date_from_fields(&self, args: &[JsValue], context: &mut Context<'_>) -> JsResult { // 1. Let calendar be the this value. // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). @@ -27,35 +33,37 @@ impl BuiltinCalendar for IsoCalendar { })?; // 5. Set options to ? GetOptionsObject(options). - let options = temporal::get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1))?; // 6. Set fields to ? PrepareTemporalFields(fields, « "day", "month", "monthCode", "year" », « "year", "day" »). let mut fields = temporal::TemporalFields::from_js_object( fields_obj, - &vec![ + &[ js_string!("day"), js_string!("month"), js_string!("monthCode"), ], - Some(&vec![js_string!("year"), js_string!("day")]), + Some(&[js_string!("year"), js_string!("day")]), None, context, )?; // NOTE: Overflow will probably have to be a work around for now for "constrained". // 7. Let overflow be ? ToTemporalOverflow(options). - let overflow = temporal::to_temporal_overflow(&options, context)?; + let overflow = to_temporal_overflow(&options, context)?; fields.resolve_month()?; // 8. Let result be ? ISODateFromFields(fields, overflow). - let result = IsoDateRecord::from_temporal_fields(fields, &overflow)?; + let result = IsoDateRecord::from_temporal_fields(&fields, &overflow)?; // 9. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601"). - temporal::create_temporal_date(result, JsValue::from("iso8601"), None, context) + Ok(create_temporal_date(result, "iso8601".into(), None, context)?.into()) } - /// 12.5.5 Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] ) + /// 12.5.5 `Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )` + /// + /// This is a basic implementation for an iso8601 calendar's `yearMonthFromFields` method. fn year_month_from_fields( &self, args: &[JsValue], @@ -71,23 +79,23 @@ impl BuiltinCalendar for IsoCalendar { })?; // 5. Set options to ? GetOptionsObject(options). - let options = temporal::get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1))?; // 6. Set fields to ? PrepareTemporalFields(fields, « "month", "monthCode", "year" », « "year" »). let mut fields = temporal::TemporalFields::from_js_object( fields_obj, - &vec![ + &[ js_string!("year"), js_string!("month"), js_string!("monthCode"), ], - Some(&vec![js_string!("year")]), + Some(&[js_string!("year")]), None, context, )?; // 7. Let overflow be ? ToTemporalOverflow(options). - let overflow = temporal::to_temporal_overflow(&options, context)?; + let overflow = to_temporal_overflow(&options, context)?; // 8. Perform ? ISOResolveMonth(fields). fields.resolve_month()?; @@ -99,7 +107,9 @@ impl BuiltinCalendar for IsoCalendar { temporal::create_temporal_year_month(result, JsValue::from("iso8601"), None, context) } - /// TODO: Docs + /// 12.5.6 `Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )` + /// + /// This is a basic implementation for an iso8601 calendar's `monthDayFromFields` method. fn month_day_from_fields( &self, args: &[JsValue], @@ -115,59 +125,115 @@ impl BuiltinCalendar for IsoCalendar { })?; // 5. Set options to ? GetOptionsObject(options). - let options = temporal::get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1))?; // 6. Set fields to ? PrepareTemporalFields(fields, « "day", "month", "monthCode", "year" », « "day" »). let mut fields = temporal::TemporalFields::from_js_object( fields_obj, - &vec![ + &[ js_string!("day"), js_string!("month"), js_string!("monthCode"), js_string!("year"), ], - Some(&vec![js_string!("year")]), + Some(&[js_string!("year")]), None, context, )?; // 7. Let overflow be ? ToTemporalOverflow(options). - let overflow = temporal::to_temporal_overflow(&options, context)?; + let overflow = to_temporal_overflow(&options, context)?; // 8. Perform ? ISOResolveMonth(fields). fields.resolve_month()?; // 9. Let result be ? ISOMonthDayFromFields(fields, overflow). - let result = IsoDateRecord::month_day_from_temporal_fields(fields, &overflow)?; + let result = IsoDateRecord::month_day_from_temporal_fields(&fields, &overflow)?; // 10. Return ? CreateTemporalMonthDay(result.[[Month]], result.[[Day]], "iso8601", result.[[ReferenceISOYear]]). temporal::create_temporal_month_day(result, JsValue::from("iso8601"), None, context) } - /// 12.5.7 Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] ) + /// 12.5.7 `Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )` /// - /// Below implements the basic implementation for an iso8601 calendar's "dateAdd" method. + /// Below implements the basic implementation for an iso8601 calendar's `dateAdd` method. fn date_add(&self, args: &[JsValue], context: &mut Context<'_>) -> JsResult { // 1. Let calendar be the this value. // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). // 3. Assert: calendar.[[Identifier]] is "iso8601". + // 4. Set date to ? ToTemporalDate(date). + let date_like = args.get_or_undefined(0); + let date = to_temporal_date(date_like, None, context)?; + // 5. Set duration to ? ToTemporalDuration(duration). + let duration_like = args.get_or_undefined(1); + let mut duration = temporal::duration::to_temporal_duration(duration_like, context)?; + // 6. Set options to ? GetOptionsObject(options). + let options = args.get_or_undefined(2); + let options_obj = get_options_object(options)?; + // 7. Let overflow be ? ToTemporalOverflow(options). + let overflow = to_temporal_overflow(&options_obj, context)?; + // 8. Let balanceResult be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day"). + duration + .inner + .balance_time_duration(&JsString::from("day"), None)?; + // 9. Let result be ? AddISODate(date.[[ISOYear]], date.[[ISOMonth]], date.[[ISODay]], duration.[[Years]], duration.[[Months]], duration.[[Weeks]], balanceResult.[[Days]], overflow). + let result = date.inner.add_iso_date( + duration.inner.years() as i32, + duration.inner.months() as i32, + duration.inner.weeks() as i32, + duration.inner.days() as i32, + &overflow, + )?; + // 10. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601"). - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) + Ok(create_temporal_date(result, "iso8601".into(), None, context)?.into()) } - /// TODO: Docs + /// 12.5.8 `Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )` + /// + /// Below implements the basic implementation for an iso8601 calendar's `dateUntil` method. fn date_until(&self, args: &[JsValue], context: &mut Context<'_>) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) + // 1. Let calendar be the this value. + // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). + // 3. Assert: calendar.[[Identifier]] is "iso8601". + + // 4. Set one to ? ToTemporalDate(one). + let one = to_temporal_date(args.get_or_undefined(0), None, context)?; + // 5. Set two to ? ToTemporalDate(two). + let two = 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))?; + + let auto: JsValue = "auto".into(); + // 7. Let largestUnit be ? GetTemporalUnit(options, "largestUnit", date, "auto"). + let retrieved_unit = get_temporal_unit( + &options, + "largestUnit".into(), + &JsString::from("date"), + Some(&auto), + None, + context, + )? + .expect("Return must be a string."); + + // 8. If largestUnit is "auto", set largestUnit to "day". + let largest_unit = match retrieved_unit.to_std_string_escaped().as_str() { + "auto" => JsString::from("day"), + _ => retrieved_unit, + }; + + // 9. Let result be DifferenceISODate(one.[[ISOYear]], one.[[ISOMonth]], one.[[ISODay]], two.[[ISOYear]], two.[[ISOMonth]], two.[[ISODay]], largestUnit). + let result = one.inner.diff_iso_date(&two.inner, &largest_unit)?; + + // 10. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], 0, 0, 0, 0, 0, 0). + Ok(create_temporal_duration(result, None, context)?.into()) } /// TODO: Docs diff --git a/boa_engine/src/builtins/temporal/calendar/mod.rs b/boa_engine/src/builtins/temporal/calendar/mod.rs index b2a4f454c7f..e0d1beaf80b 100644 --- a/boa_engine/src/builtins/temporal/calendar/mod.rs +++ b/boa_engine/src/builtins/temporal/calendar/mod.rs @@ -81,7 +81,7 @@ pub trait BuiltinCalendar { impl core::fmt::Debug for dyn BuiltinCalendar { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{}", "Builtin Calendar Protocol") + write!(f, "Builtin Calendar Protocol") } } @@ -99,7 +99,7 @@ fn available_calendars() -> FxHashMap<&'static str, Box> { } // Returns if an identifier is a builtin calendar. -fn is_builtin_calendar(identifier: String) -> bool { +pub(crate) fn is_builtin_calendar(identifier: &str) -> bool { let calendars = available_calendars(); calendars.contains_key(identifier.to_ascii_lowercase().as_str()) } @@ -183,7 +183,7 @@ impl BuiltInConstructor for Calendar { // 2. If id is not a String, throw a TypeError exception. if let Some(id) = identifier.as_string() { // 3. If IsBuiltinCalendar(id) is false, then - if !is_builtin_calendar(id.to_std_string_escaped()) { + if !is_builtin_calendar(&id.to_std_string_escaped()) { // a. Throw a RangeError exception. return Err(JsNativeError::range() .with_message("Calendar ID must be a valid builtin calendar.") @@ -214,7 +214,7 @@ impl Calendar { Ok(calendar.identifier.clone().into()) } - /// 15.8.2.1 Temporal.Calendar.prototype.dateFromFields ( fields [ , options ] ) - Supercedes 12.5.4 + /// 15.8.2.1 `Temporal.Calendar.prototype.dateFromFields ( fields [ , options ] )` - Supercedes 12.5.4 fn date_from_fields( this: &JsValue, args: &[JsValue], @@ -237,7 +237,7 @@ impl Calendar { this_protocol.date_from_fields(args, context) } - /// 15.8.2.2 Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] ) - Supercedes 12.5.5 + /// 15.8.2.2 `Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )` - Supercedes 12.5.5 fn year_month_from_fields( this: &JsValue, args: &[JsValue], @@ -260,7 +260,7 @@ impl Calendar { this_protocol.year_month_from_fields(args, context) } - /// 15.8.2.3 Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] ) - Supercedes 12.5.6 + /// 15.8.2.3 `Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )` - Supercedes 12.5.6 fn month_day_from_fields( this: &JsValue, args: &[JsValue], @@ -283,7 +283,7 @@ impl Calendar { this_protocol.month_day_from_fields(args, context) } - /// 15.8.2.4 Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] ) - supercedes 12.5.7 + /// 15.8.2.4 `Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )` - supercedes 12.5.7 fn date_add(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult { let o = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("this value of Calendar must be an object.") @@ -302,7 +302,7 @@ impl Calendar { this_protocol.date_add(args, context) } - ///15.8.2.5 Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] ) - Supercedes 12.5.8 + ///15.8.2.5 `Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )` - Supercedes 12.5.8 fn date_until( this: &JsValue, args: &[JsValue], @@ -325,7 +325,7 @@ impl Calendar { this_protocol.date_until(args, context) } - /// 15.8.2.6 Temporal.Calendar.prototype.era ( temporalDateLike ) + /// 15.8.2.6 `Temporal.Calendar.prototype.era ( temporalDateLike )` fn era(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult { let o = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("this value of Calendar must be an object.") @@ -344,7 +344,7 @@ impl Calendar { this_protocol.era(args, context) } - /// 15.8.2.7 Temporal.Calendar.prototype.eraYear ( temporalDateLike ) + /// 15.8.2.7 `Temporal.Calendar.prototype.eraYear ( temporalDateLike )` fn era_year(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult { let o = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("this value of Calendar must be an object.") @@ -687,13 +687,13 @@ pub(crate) fn create_temporal_calendar( context: &mut Context<'_>, ) -> JsResult { // 1. Assert: IsBuiltinCalendar(identifier) is true. - assert!(is_builtin_calendar(identifier.to_std_string_escaped())); + assert!(is_builtin_calendar(&identifier.to_std_string_escaped())); let calendar = Calendar { identifier: identifier.clone(), }; // 2. If newTarget is not provided, set newTarget to %Temporal.Calendar%. - let new_target = new_target.unwrap_or( + let new_target = new_target.unwrap_or_else(|| context .realm() .intrinsics() @@ -714,7 +714,7 @@ pub(crate) fn create_temporal_calendar( Ok(obj.into()) } -/// 12.2.21 GetTemporalCalendarSlotValueWithISODefault ( item ) +/// 12.2.21 `GetTemporalCalendarSlotValueWithISODefault ( item )` pub(crate) fn get_temporal_calendar_slot_value_with_default( item: &JsObject, context: &mut Context<'_>, @@ -774,7 +774,7 @@ fn to_temporal_calendar_slot_value( if calendar_like.is_undefined() { if let Some(default) = default { // a. Assert: IsBuiltinCalendar(default) is true. - if is_builtin_calendar(default.to_std_string_escaped()) { + if is_builtin_calendar(&default.to_std_string_escaped()) { // b. Return default. return Ok(default.into()); } @@ -838,7 +838,7 @@ fn to_temporal_calendar_slot_value( // 4. Let identifier be ? ParseTemporalCalendarString(temporalCalendarLike). // 5. If IsBuiltinCalendar(identifier) is false, throw a RangeError exception. // 6. Return the ASCII-lowercase of identifier. - return Ok("iso8601".into()); + Ok("iso8601".into()) } // ---------------------------- AbstractCalendar Methods ---------------------------- @@ -874,7 +874,7 @@ fn call_method_on_abstract_calendar( method.call(&this_calendar.into(), args, context) } -/// 12.2.2 CalendarFields ( calendar, fieldNames ) +/// 12.2.2 `CalendarFields ( calendar, fieldNames )` /// /// Returns either a normal completion containing a List of Strings, or a throw completion. pub(crate) fn calendar_fields( @@ -901,7 +901,7 @@ pub(crate) fn calendar_fields( super::iterator_to_list_of_types(&mut iterator_record, &[crate::value::Type::String], context) } -/// 12.2.3 CalendarMergeFields ( calendar, fields, additionalFields ) +/// 12.2.3 `CalendarMergeFields ( calendar, fields, additionalFields )` /// /// Returns either a normal completion containing an Object, or a throw completion. pub(crate) fn calendar_merge_fields( @@ -998,7 +998,7 @@ pub(crate) fn calendar_date_until( .expect("Value is confirmed to be a duration."); let record = dur.inner; drop(obj); - return Ok(record); + Ok(record) } _ => Err(JsNativeError::typ() .with_message("Calendar dateUntil must return a Duration") @@ -1006,7 +1006,7 @@ pub(crate) fn calendar_date_until( } } -/// 12.2.6 CalendarYear ( calendar, dateLike ) +/// 12.2.6 `CalendarYear ( calendar, dateLike )` /// /// Returns either a normal completion containing an integer, or an abrupt completion. pub(crate) fn calendar_year( @@ -1026,13 +1026,10 @@ pub(crate) fn calendar_year( )?; // 3. If Type(result) is not Number, throw a TypeError exception. - let number = match result.as_number() { - Some(n) => n, - None => { - return Err(JsNativeError::typ() - .with_message("CalendarYear result must be a number.") - .into()) - } + let Some(number) = result.as_number() else { + return Err(JsNativeError::typ() + .with_message("CalendarYear result must be a number.") + .into()) }; // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. @@ -1046,7 +1043,7 @@ pub(crate) fn calendar_year( Ok(number) } -/// 12.2.7 CalendarMonth ( calendar, dateLike ) +/// 12.2.7 `CalendarMonth ( calendar, dateLike )` pub(crate) fn calendar_month( calendar: &JsValue, datelike: &JsValue, @@ -1064,13 +1061,10 @@ pub(crate) fn calendar_month( )?; // 3. If Type(result) is not Number, throw a TypeError exception. - let number = match result.as_number() { - Some(n) => n, - None => { - return Err(JsNativeError::typ() - .with_message("CalendarYear result must be a number.") - .into()) - } + let Some(number) = result.as_number() else { + return Err(JsNativeError::typ() + .with_message("CalendarYear result must be a number.") + .into()) }; // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. @@ -1091,7 +1085,7 @@ pub(crate) fn calendar_month( Ok(number) } -/// 12.2.8 CalendarMonthCode ( calendar, dateLike ) +/// 12.2.8 `CalendarMonthCode ( calendar, dateLike )` pub(crate) fn calendar_month_code( calendar: &JsValue, datelike: &JsValue, @@ -1118,7 +1112,7 @@ pub(crate) fn calendar_month_code( } } -/// 12.2.9 CalendarDay ( calendar, dateLike ) +/// 12.2.9 `CalendarDay ( calendar, dateLike )` pub(crate) fn calendar_day( calendar: &JsValue, datelike: &JsValue, @@ -1136,13 +1130,10 @@ pub(crate) fn calendar_day( )?; // 3. If Type(result) is not Number, throw a TypeError exception. - let number = match result.as_number() { - Some(n) => n, - None => { - return Err(JsNativeError::typ() - .with_message("CalendarYear result must be a number.") - .into()) - } + let Some(number) = result.as_number() else { + return Err(JsNativeError::typ() + .with_message("CalendarYear result must be a number.") + .into()) }; // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. @@ -1163,7 +1154,7 @@ pub(crate) fn calendar_day( Ok(number) } -/// 12.2.10 CalendarDayOfWeek ( calendar, dateLike ) +/// 12.2.10 `CalendarDayOfWeek ( calendar, dateLike )` pub(crate) fn calendar_day_of_week( calendar: &JsValue, datelike: &JsValue, @@ -1181,13 +1172,10 @@ pub(crate) fn calendar_day_of_week( )?; // 3. If Type(result) is not Number, throw a TypeError exception. - let number = match result.as_number() { - Some(n) => n, - None => { - return Err(JsNativeError::typ() - .with_message("CalendarDayOfWeek result must be a number.") - .into()) - } + let Some(number) = result.as_number() else { + return Err(JsNativeError::typ() + .with_message("CalendarDayOfWeek result must be a number.") + .into()) }; // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. @@ -1208,7 +1196,7 @@ pub(crate) fn calendar_day_of_week( Ok(number) } -/// 12.2.11 CalendarDayOfYear ( calendar, dateLike ) +/// 12.2.11 `CalendarDayOfYear ( calendar, dateLike )` pub(crate) fn calendar_day_of_year( calendar: &JsValue, datelike: &JsValue, @@ -1226,13 +1214,10 @@ pub(crate) fn calendar_day_of_year( )?; // 3. If Type(result) is not Number, throw a TypeError exception. - let number = match result.as_number() { - Some(n) => n, - None => { - return Err(JsNativeError::typ() - .with_message("CalendarDayOfYear result must be a number.") - .into()) - } + let Some(number) = result.as_number() else { + return Err(JsNativeError::typ() + .with_message("CalendarDayOfYear result must be a number.") + .into()) }; // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. @@ -1253,7 +1238,7 @@ pub(crate) fn calendar_day_of_year( Ok(number) } -/// 12.2.12 CalendarWeekOfYear ( calendar, dateLike ) +/// 12.2.12 `CalendarWeekOfYear ( calendar, dateLike )` pub(crate) fn calendar_week_of_year( calendar: &JsValue, datelike: &JsValue, @@ -1271,13 +1256,10 @@ pub(crate) fn calendar_week_of_year( )?; // 3. If Type(result) is not Number, throw a TypeError exception. - let number = match result.as_number() { - Some(n) => n, - None => { - return Err(JsNativeError::typ() - .with_message("CalendarWeekOfYear result must be a number.") - .into()) - } + let Some(number) = result.as_number() else { + return Err(JsNativeError::typ() + .with_message("CalendarWeekOfYear result must be a number.") + .into()) }; // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. @@ -1298,7 +1280,7 @@ pub(crate) fn calendar_week_of_year( Ok(number) } -/// 12.2.13 CalendarYearOfWeek ( calendar, dateLike ) +/// 12.2.13 `CalendarYearOfWeek ( calendar, dateLike )` pub(crate) fn calendar_year_of_week( calendar: &JsValue, datelike: &JsValue, @@ -1316,13 +1298,10 @@ pub(crate) fn calendar_year_of_week( )?; // 3. If Type(result) is not Number, throw a TypeError exception. - let number = match result.as_number() { - Some(n) => n, - None => { - return Err(JsNativeError::typ() - .with_message("CalendarYearOfWeek result must be a number.") - .into()) - } + let Some(number) = result.as_number() else { + return Err(JsNativeError::typ() + .with_message("CalendarYearOfWeek result must be a number.") + .into()) }; // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. @@ -1336,7 +1315,7 @@ pub(crate) fn calendar_year_of_week( Ok(number) } -/// 12.2.14 CalendarDaysInWeek ( calendar, dateLike ) +/// 12.2.14 `CalendarDaysInWeek ( calendar, dateLike )` pub(crate) fn calendar_days_in_week( calendar: &JsValue, datelike: &JsValue, @@ -1354,13 +1333,10 @@ pub(crate) fn calendar_days_in_week( )?; // 3. If Type(result) is not Number, throw a TypeError exception. - let number = match result.as_number() { - Some(n) => n, - None => { - return Err(JsNativeError::typ() - .with_message("CalendarDaysInWeek result must be a number.") - .into()) - } + let Some(number) = result.as_number() else { + return Err(JsNativeError::typ() + .with_message("CalendarDaysInWeek result must be a number.") + .into()) }; // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. @@ -1381,7 +1357,7 @@ pub(crate) fn calendar_days_in_week( Ok(number) } -/// 12.2.15 CalendarDaysInMonth ( calendar, dateLike ) +/// 12.2.15 `CalendarDaysInMonth ( calendar, dateLike )` pub(crate) fn calendar_days_in_month( calendar: &JsValue, datelike: &JsValue, @@ -1399,13 +1375,10 @@ pub(crate) fn calendar_days_in_month( )?; // 3. If Type(result) is not Number, throw a TypeError exception. - let number = match result.as_number() { - Some(n) => n, - None => { - return Err(JsNativeError::typ() - .with_message("CalendarDaysInMonth result must be a number.") - .into()) - } + let Some(number) = result.as_number() else { + return Err(JsNativeError::typ() + .with_message("CalendarDaysInMonth result must be a number.") + .into()) }; // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. @@ -1426,7 +1399,7 @@ pub(crate) fn calendar_days_in_month( Ok(number) } -/// 12.2.16 CalendarDaysInYear ( calendar, dateLike ) +/// 12.2.16 `CalendarDaysInYear ( calendar, dateLike )` pub(crate) fn calendar_days_in_year( calendar: &JsValue, datelike: &JsValue, @@ -1444,13 +1417,10 @@ pub(crate) fn calendar_days_in_year( )?; // 3. If Type(result) is not Number, throw a TypeError exception. - let number = match result.as_number() { - Some(n) => n, - None => { - return Err(JsNativeError::typ() - .with_message("CalendarDaysInYear result must be a number.") - .into()) - } + let Some(number) = result.as_number() else { + return Err(JsNativeError::typ() + .with_message("CalendarDaysInYear result must be a number.") + .into()) }; // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. @@ -1471,7 +1441,7 @@ pub(crate) fn calendar_days_in_year( Ok(number) } -/// 12.2.17 CalendarMonthsInYear ( calendar, dateLike ) +/// 12.2.17 `CalendarMonthsInYear ( calendar, dateLike )` pub(crate) fn calendar_months_in_year( calendar: &JsValue, datelike: &JsValue, @@ -1489,13 +1459,10 @@ pub(crate) fn calendar_months_in_year( )?; // 3. If Type(result) is not Number, throw a TypeError exception. - let number = match result.as_number() { - Some(n) => n, - None => { - return Err(JsNativeError::typ() - .with_message("CalendarMonthsInYear result must be a number.") - .into()) - } + let Some(number) = result.as_number() else { + return Err(JsNativeError::typ() + .with_message("CalendarMonthsInYear result must be a number.") + .into()) }; // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. @@ -1516,7 +1483,7 @@ pub(crate) fn calendar_months_in_year( Ok(number) } -/// 12.2.18 CalendarInLeapYear ( calendar, dateLike ) +/// 12.2.18 `CalendarInLeapYear ( calendar, dateLike )` pub(crate) fn calendar_in_lear_year( calendar: &JsValue, datelike: &JsValue, @@ -1543,7 +1510,7 @@ pub(crate) fn calendar_in_lear_year( } } -/// 12.2.24 CalendarDateFromFields ( calendar, fields [ , options [ , dateFromFields ] ] ) +/// 12.2.24 `CalendarDateFromFields ( calendar, fields [ , options [ , dateFromFields ] ] )` pub(crate) fn calendar_date_from_fields( calendar: &JsValue, fields: &JsObject, diff --git a/boa_engine/src/builtins/temporal/calendar/utils.rs b/boa_engine/src/builtins/temporal/calendar/utils.rs index 11c3b34c0ea..9ab0bb8c73d 100644 --- a/boa_engine/src/builtins/temporal/calendar/utils.rs +++ b/boa_engine/src/builtins/temporal/calendar/utils.rs @@ -17,7 +17,7 @@ pub(crate) fn iso_days_in_month(year: i32, month: i32) -> i32 { } } -/// 12.2.32 ToISOWeekOfYear ( year, month, day ) +/// 12.2.32 `ToISOWeekOfYear ( year, month, day )` fn to_iso_week_of_year(year: i32, month: i32, day: i32) -> (i32, i32) { // Function constants // 2. Let wednesday be 3. @@ -53,7 +53,7 @@ fn to_iso_week_of_year(year: i32, month: i32, day: i32) -> (i32, i32) { (week, year) } -/// 12.2.33 ISOMonthCode ( month ) +/// 12.2.33 `ISOMonthCode ( month )` fn iso_month_code(month: i32) -> JsString { // TODO: optimize if month < 10 { @@ -63,7 +63,7 @@ fn iso_month_code(month: i32) -> JsString { } } -// 12.2.34 ISOResolveMonth ( fields ) +// 12.2.34 `ISOResolveMonth ( fields )` // Note: currently implemented on TemporalFields -> implement in this mod? // 12.2.35 ISODateFromFields ( fields, overflow ) @@ -78,7 +78,7 @@ fn iso_month_code(month: i32) -> JsString { // 12.2.38 IsoFieldKeysToIgnore // TODO: determine usefulness. -/// 12.2.39 ToISODayOfYear ( year, month, day ) +/// 12.2.39 `ToISODayOfYear ( year, month, day )` fn to_iso_day_of_year(year: i32, month: i32, day: i32) -> i32 { // TODO: update fn parameter to take IsoDateRecord. let iso = IsoDateRecord::new(year, month - 1, day); @@ -86,7 +86,7 @@ fn to_iso_day_of_year(year: i32, month: i32, day: i32) -> i32 { date_equations::epoch_time_to_day_in_year(temporal::epoch_days_to_epoch_ms(epoch_days, 0)) + 1 } -/// 12.2.40 ToISODayOfWeek ( year, month, day ) +/// 12.2.40 `ToISODayOfWeek ( year, month, day )` fn to_iso_day_of_week(year: i32, month: i32, day: i32) -> i32 { let iso = IsoDateRecord::new(year, month - 1, day); let epoch_days = iso.as_epoch_days(); diff --git a/boa_engine/src/builtins/temporal/date_equations.rs b/boa_engine/src/builtins/temporal/date_equations.rs index baae478f849..f9a0fee2bdb 100644 --- a/boa_engine/src/builtins/temporal/date_equations.rs +++ b/boa_engine/src/builtins/temporal/date_equations.rs @@ -22,12 +22,12 @@ pub(crate) fn mathematical_days_in_year(y: i32) -> i32 { } } -pub(crate) fn epoch_day_number_for_year(y: i32) -> i32 { +pub(crate) const fn epoch_day_number_for_year(y: i32) -> i32 { 365 * (y - 1970) + ((y - 1970) / 4) - ((y - 1901) / 100) + ((y - 1601) / 400) } pub(crate) fn epoch_time_for_year(y: i32) -> f64 { - super::NS_PER_DAY as f64 * epoch_day_number_for_year(y) as f64 + super::NS_PER_DAY as f64 * f64::from(epoch_day_number_for_year(y)) } pub(crate) fn epoch_time_to_epoch_year(t: f64) -> i32 { diff --git a/boa_engine/src/builtins/temporal/duration/mod.rs b/boa_engine/src/builtins/temporal/duration/mod.rs index 65161b2a76c..bba0f468041 100644 --- a/boa_engine/src/builtins/temporal/duration/mod.rs +++ b/boa_engine/src/builtins/temporal/duration/mod.rs @@ -266,7 +266,7 @@ impl Duration { DateTimeValues::Millisecond => Ok(JsValue::Rational(duration.inner.milliseconds())), DateTimeValues::Microsecond => Ok(JsValue::Rational(duration.inner.microseconds())), DateTimeValues::Nanosecond => Ok(JsValue::Rational(duration.inner.nanoseconds())), - _ => unreachable!( + DateTimeValues::MonthCode => unreachable!( "Any other DateTimeValue fields on Duration would be an implementation error." ), } @@ -424,60 +424,40 @@ impl Duration { // a. Let nanoseconds be duration.[[Nanoseconds]]. // 24. Return ? CreateTemporalDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - todo!() + Err(JsNativeError::range() + .with_message("not yet implemented.") + .into()) } /// 7.3.16 `Temporal.Duration.prototype.negated ( )` pub(crate) fn negated(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { // 1. Let duration be the this value. // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let o = o.borrow(); - let _duration = o.as_duration().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; - // 3. Return ! CreateNegatedTemporalDuration(duration). - todo!() + Err(JsNativeError::range() + .with_message("not yet implemented.") + .into()) } /// 7.3.17 `Temporal.Duration.prototype.abs ( )` pub(crate) fn abs(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { // 1. Let duration be the this value. // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let o = o.borrow(); - let _duration = o.as_duration().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; - // 3. Return ! CreateTemporalDuration(abs(duration.[[Years]]), abs(duration.[[Months]]), // abs(duration.[[Weeks]]), abs(duration.[[Days]]), abs(duration.[[Hours]]), abs(duration.[[Minutes]]), // abs(duration.[[Seconds]]), abs(duration.[[Milliseconds]]), abs(duration.[[Microseconds]]), abs(duration.[[Nanoseconds]])). - todo!() + Err(JsNativeError::range() + .with_message("not yet implemented.") + .into()) } /// 7.3.18 `Temporal.Duration.prototype.add ( other [ , options ] )` pub(crate) fn add(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - // 1. Let duration be the this value. - // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let o = o.borrow(); - let _duration = o.as_duration().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; - - // 3. Return ? AddDurationToOrSubtractDurationFromDuration(add, duration, other, options). - - todo!() + Err(JsNativeError::range() + .with_message("not yet implemented.") + .into()) } /// 7.3.19 `Temporal.Duration.prototype.subtract ( other [ , options ] )` @@ -486,19 +466,9 @@ impl Duration { _: &[JsValue], _: &mut Context<'_>, ) -> JsResult { - // 1. Let duration be the this value. - // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let o = o.borrow(); - let _duration = o.as_duration().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; - - // 3. Return ? AddDurationToOrSubtractDurationFromDuration(subtract, duration, other, options). - - todo!() + Err(JsNativeError::range() + .with_message("not yet implemented.") + .into()) } /// 7.3.20 `Temporal.Duration.prototype.round ( roundTo )` @@ -587,16 +557,13 @@ impl Duration { )?; // 14. If smallestUnit is undefined, then - let smallest_unit = if smallest_unit.is_undefined() { + let smallest_unit = if let Some(unit) = smallest_unit { + unit + } else { // a. Set smallestUnitPresent to false. smallest_unit_present = false; // b. Set smallestUnit to "nanosecond". JsString::from("nanosecond") - } else { - smallest_unit - .as_string() - .expect("smallestUnit must be a string if it is not undefined.") - .clone() }; // 15. Let defaultLargestUnit be ! DefaultTemporalLargestUnit(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]]). @@ -608,19 +575,18 @@ impl Duration { let auto = JsString::from("auto"); // 17. If largestUnit is undefined, then let largest_unit = match largest_unit { - JsValue::Undefined => { + None => { // a. Set largestUnitPresent to false. largest_unit_present = false; // b. Set largestUnit to defaultLargestUnit. default_largest_unit } // 18. Else if largestUnit is "auto", then - JsValue::String(s) if s == auto => { + Some(s) if s == auto => { // a. Set largestUnit to defaultLargestUnit. default_largest_unit } - JsValue::String(s) => s, - _ => unreachable!("largestUnit must be a string or undefined."), + Some(s) => s, }; // 19. If smallestUnitPresent is false and largestUnitPresent is false, then @@ -651,11 +617,13 @@ impl Duration { } let mut unbalance_duration = DurationRecord::from_date_duration(&duration.inner); - // 23. Let unbalanceResult be ? UnbalanceDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], largestUnit, relativeTo). + + // 23. Let unbalanceResult be ? UnbalanceDateDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], largestUnit, relativeTo). unbalance_duration.unbalance_duration_relative(&largest_unit, &relative_to, context)?; let mut roundable_duration = DurationRecord::from_date_and_time_duration(&unbalance_duration, &duration.inner); + // 24. Let roundResult 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, relativeTo)).[[DurationRecord]]. @@ -667,12 +635,29 @@ impl Duration { context, )?; - // 25. Let adjustResult be ? AdjustRoundedDurationDays(roundResult.[[Years]], roundResult.[[Months]], roundResult.[[Weeks]], roundResult.[[Days]], roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode, relativeTo). - // 26. Let balanceResult be ? BalanceDuration(adjustResult.[[Days]], adjustResult.[[Hours]], adjustResult.[[Minutes]], adjustResult.[[Seconds]], adjustResult.[[Milliseconds]], adjustResult.[[Microseconds]], adjustResult.[[Nanoseconds]], largestUnit, relativeTo). - // 27. Let result be ? BalanceDurationRelative(adjustResult.[[Years]], adjustResult.[[Months]], adjustResult.[[Weeks]], balanceResult.[[Days]], largestUnit, relativeTo). - // 28. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], balanceResult.[[Hours]], balanceResult.[[Minutes]], balanceResult.[[Seconds]], balanceResult.[[Milliseconds]], balanceResult.[[Microseconds]], balanceResult.[[Nanoseconds]]). + // 25. Let roundResult be roundRecord.[[DurationRecord]]. + // 26. If relativeTo is not undefined and relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then + match relative_to { + JsValue::Object(o) if o.is_zoned_date_time() => { + // TODO: AdjustRoundedDurationDays requires 6.5.5 AddZonedDateTime. + // 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, relativeTo). + // b. Let balanceResult be ? BalanceTimeDurationRelative(roundResult.[[Days]], roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]], largestUnit, relativeTo). + return Err(JsNativeError::range() + .with_message("not yet implemented.") + .into()); + } + // 27. Else, + _ => { + // a. Let balanceResult be ? BalanceTimeDuration(roundResult.[[Days]], roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]], largestUnit). + roundable_duration.balance_time_duration(&largest_unit, None)?; + } + } + // 28. Let result be ? BalanceDateDurationRelative(roundResult.[[Years]], roundResult.[[Months]], roundResult.[[Weeks]], balanceResult.[[Days]], largestUnit, relativeTo). + // 29. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], balanceResult.[[Hours]], balanceResult.[[Minutes]], balanceResult.[[Seconds]], balanceResult.[[Milliseconds]], balanceResult.[[Microseconds]], balanceResult.[[Nanoseconds]]). - todo!() + Err(JsNativeError::range() + .with_message("not yet implemented.") + .into()) } /// 7.3.21 `Temporal.Duration.prototype.total ( totalOf )` @@ -731,9 +716,7 @@ impl Duration { None, context, )? - .as_string() - .expect("GetTemporalUnit must return a string if default is required.") - .clone(); + .expect("GetTemporalUnit must return a string if default is required."); let mut unbalance_duration = DurationRecord::default() .with_years(duration.inner.years()) @@ -754,7 +737,9 @@ impl Duration { .is_zoned_date_time() { // a. Set intermediate to ? MoveRelativeZonedDateTime(relativeTo, unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], 0). - todo!() + return Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()); } let mut balance_duration = DurationRecord::default() @@ -849,35 +834,26 @@ impl Duration { _: &[JsValue], _: &mut Context<'_>, ) -> JsResult { - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let o = o.borrow(); - let _duration = o.as_duration().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; - - todo!() + Err(JsNativeError::range() + .with_message("not yet implemented.") + .into()) } /// 7.3.23 Temporal.Duration.prototype.toJSON ( ) pub(crate) fn to_json(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let o = o.borrow(); - let _duration = o.as_duration().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; - - todo!() + Err(JsNativeError::range() + .with_message("not yet implemented.") + .into()) } } // -- Duration Abstract Operations -- /// 7.5.8 `ToTemporalDuration ( item )` -pub(crate) fn to_temporal_duration(item: &JsValue, context: &mut Context<'_>) -> JsResult { +pub(crate) fn to_temporal_duration( + item: &JsValue, + context: &mut Context<'_>, +) -> JsResult { // 1a. If Type(item) is Object if item.is_object() { // 1b. and item has an [[InitializedTemporalDuration]] internal slot, then @@ -886,14 +862,18 @@ pub(crate) fn to_temporal_duration(item: &JsValue, context: &mut Context<'_>) -> .expect("Value must be an object in this instance."); if o.is_duration() { // a. Return item. - return Ok(item.clone()); + let obj = o.borrow(); + let duration = obj.as_duration().expect("must be a duration."); + return Ok(Duration { + inner: duration.inner, + }); } } // 2. Let result be ? ToTemporalDurationRecord(item). let result = to_temporal_duration_record(item)?; // 3. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]). - Ok(create_temporal_duration(result, None, context)?.into()) + Ok(Duration { inner: result }) } /// 7.5.9 `ToTemporalDurationRecord ( temporalDurationLike )` @@ -901,7 +881,7 @@ pub(crate) fn to_temporal_duration_record( _temporal_duration_like: &JsValue, ) -> JsResult { Err(JsNativeError::range() - .with_message("Not yet implemented.") + .with_message("Duration Parsing is not yet complete.") .into()) } @@ -955,7 +935,9 @@ pub(crate) fn create_temporal_duration( Ok(prototype) } +// TODO: implement on `DurationRecord` // 7.5.17 `TotalDurationNanoseconds ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, offsetShift )` +#[allow(clippy::too_many_arguments)] fn total_duration_nanoseconds( days: f64, hours: f64, @@ -1008,10 +990,10 @@ fn days_until(earlier: &JsObject, later: &JsObject) -> i32 { fn move_relative_date( calendar: &JsValue, relative_to: &JsObject, - duration: JsObject, + duration: &JsObject, context: &mut Context<'_>, ) -> JsResult<(JsObject, f64)> { - let new_date = calendar::calendar_date_add(calendar, relative_to, &duration, None, context)?; + let new_date = calendar::calendar_date_add(calendar, relative_to, duration, None, context)?; let days = f64::from(days_until(relative_to, &new_date)); Ok((new_date, days)) } diff --git a/boa_engine/src/builtins/temporal/duration/record.rs b/boa_engine/src/builtins/temporal/duration/record.rs index dd32fb94f57..4d615a26aed 100644 --- a/boa_engine/src/builtins/temporal/duration/record.rs +++ b/boa_engine/src/builtins/temporal/duration/record.rs @@ -1,8 +1,9 @@ // use DurationRecord and Duration { inner: Duration } use crate::{ - builtins::temporal, Context, JsArgs, JsError, JsNativeError, JsNativeErrorKind, JsObject, - JsResult, JsString, JsSymbol, JsValue, + builtins::temporal::{self, create_temporal_date, to_temporal_date}, + Context, JsArgs, JsError, JsNativeError, JsNativeErrorKind, JsObject, JsResult, JsString, + JsSymbol, JsValue, }; use super::super::{calendar, to_integer_if_integral, zoned_date_time}; @@ -25,6 +26,7 @@ pub(crate) struct DurationRecord { // ==== Initialization methods for `DurationRecord` ==== impl DurationRecord { + #[allow(clippy::too_many_arguments)] pub(crate) const fn new( years: f64, months: f64, @@ -300,6 +302,10 @@ impl DurationRecord { // -- Abstract Operations implemented on `DurationRecord` impl DurationRecord { + fn as_object(&self, context: &mut Context<'_>) -> JsResult { + super::create_temporal_duration(*self, None, context) + } + /// Returns the values of the current duration record as a vec. fn values(&self) -> Vec { vec![ @@ -345,7 +351,7 @@ impl DurationRecord { #[inline] pub(crate) fn is_negative_overflow(&self) -> bool { - !self.is_positive_overflow() + self.years().is_infinite() && self.years().is_sign_negative() } /// 7.5.2 Date Duration Records @@ -443,8 +449,8 @@ impl DurationRecord { JsString::from("nanosecond") } - /// Abstract Operation 7.5.18 `BalanceDuration ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit [ , relativeTo ] )` - pub(crate) fn balance_duration( + /// Abstract Operation 7.5.18 `BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit [ , relativeTo ] )` + pub(crate) fn balance_time_duration( &mut self, largest_unit: &JsString, relative_to: Option<&JsValue>, @@ -484,9 +490,10 @@ impl DurationRecord { .expect("relative_to must be an object here.") .is_zoned_date_time() { + // TODO // a. Let endNs be ? AddZonedDateTime(relativeTo.[[Nanoseconds]], relativeTo.[[TimeZone]], relativeTo.[[Calendar]], 0, 0, 0, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). // b. Set nanoseconds to ℝ(endNs - relativeTo.[[Nanoseconds]]). - todo!() + self.set_nanoseconds(0_f64); // 3. Else, } else { // a. Set nanoseconds to ! TotalDurationNanoseconds(days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, 0). @@ -502,9 +509,9 @@ impl DurationRecord { )); } - match largest_unit.as_slice() { + match largest_unit.to_std_string_escaped().as_str() { // 4. If largestUnit is one of "year", "month", "week", or "day", then - temporal::YEAR | temporal::MONTH | temporal::WEEK | temporal::DAY => { + "year" | "month" | "week" | "day" => { // a. Let result be ? NanosecondsToDays(nanoseconds, relativeTo). let result = temporal::zoned_date_time::nanoseconds_to_days( self.nanoseconds(), @@ -512,7 +519,9 @@ impl DurationRecord { ); // b. Set days to result.[[Days]]. // c. Set nanoseconds to result.[[Nanoseconds]]. - todo!() + return Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()); } // 5. Else, // a. Set days to 0. @@ -629,7 +638,6 @@ impl DurationRecord { } // NOTE (nekevss): diviate from spec here as the current implementation with `DurationRecord` means that we create the record and than mutate values. - // // 16. Return ? CreateTimeDurationRecord(days, hours × sign, minutes × sign, seconds × sign, milliseconds × sign, microseconds × sign, nanoseconds × sign). self.set_hours(self.hours() * sign); self.set_minutes(self.minutes() * sign); @@ -663,7 +671,7 @@ impl DurationRecord { && self.days() == 0_f64; // 3. If largestUnit is "year" or allZero is true, then - if largest_unit.as_slice() == temporal::YEAR || all_zero { + if largest_unit.to_std_string_escaped().as_str() == "year" || all_zero { // a. Return ! CreateDateDurationRecord(years, months, weeks, days). return Ok(()); }; @@ -675,19 +683,19 @@ impl DurationRecord { // 6. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0). let one_year = super::create_temporal_duration( - DurationRecord::default().with_years(self.years()), + Self::default().with_years(self.years()), None, context, )?; // 7. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). let one_month = super::create_temporal_duration( - DurationRecord::default().with_months(self.months()), + Self::default().with_months(self.months()), None, context, )?; // 8. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0). let one_week = super::create_temporal_duration( - DurationRecord::default().with_weeks(self.weeks()), + Self::default().with_weeks(self.weeks()), None, context, )?; @@ -699,7 +707,7 @@ impl DurationRecord { None } else { // a. Set relativeTo to ? ToTemporalDate(relativeTo). - let relative_to = temporal::plain_date::to_temporal_date( + let relative_to = to_temporal_date( &relative_to .as_object() .expect("relative_to must be an object") @@ -707,28 +715,11 @@ impl DurationRecord { .into(), None, context, - ) - .and_then(|date| { - Ok(date - .as_object() - .ok_or::( - JsNativeError::typ() - .with_message("object did not return TemporalPlainDate.") - .into(), - )? - .clone()) - })?; + )?; // b. Let calendar be relativeTo.[[Calendar]]. - let obj = relative_to.borrow_mut(); - let date = obj.as_plain_date().ok_or::( - JsNativeError::typ() - .with_message("relativeTo was not a PlainDate.") - .into(), - )?; - let calendar = date.calendar.clone(); + let calendar = relative_to.calendar; - drop(obj); Some(calendar) }; @@ -791,7 +782,202 @@ impl DurationRecord { // 3. Set days to days + moveResult.[[Days]]. // 4. Set weeks to weeks - sign. // 14. Return ? CreateDateDurationRecord(years, months, weeks, days). - Ok(()) + Err(JsNativeError::range() + .with_message("not yet implemented.") + .into()) + } + + /// `BalanceDateDurationRelative` + pub(crate) fn balance_date_duration_relative( + &mut self, + largest_unit: &JsString, + relative_to: &JsValue, + context: &mut Context<'_>, + ) -> JsResult<()> { + // 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.years == 0.0 && self.months == 0.0 && self.weeks == 0.0 && self.days == 0.0; + + // 3. If largestUnit is not one of "year", "month", or "week", or allZero is true, then + match largest_unit.to_std_string_escaped().as_str() { + "year" | "month" | "week" if !all_zero => {} + _ => { + // a. Return ! CreateDateDurationRecord(years, months, weeks, days). + return Ok(()); + } + } + + // 4. If relativeTo is undefined, then + if relative_to.is_undefined() { + // a. Throw a RangeError exception. + return Err(JsNativeError::range() + .with_message("relativeTo cannot be undefined.") + .into()); + } + + // 5. Let sign be ! DurationSign(years, months, weeks, days, 0, 0, 0, 0, 0, 0). + // 6. Assert: sign ≠ 0. + let sign = self.duration_sign(); + + // 7. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0). + let one_year = Self::default().with_years(f64::from(sign)); + // 8. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). + let one_month = Self::default().with_months(f64::from(sign)); + // 9. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0). + let one_week = Self::default().with_weeks(f64::from(sign)); + + // 10. Set relativeTo to ? ToTemporalDate(relativeTo). + let date = to_temporal_date(relative_to, None, context)?; + + // 11. Let calendar be relativeTo.[[Calendar]]. + let calendar = &date.calendar.clone(); + + let relative_to = create_temporal_date(date.inner, date.calendar, None, context)?; + + match largest_unit.to_std_string_escaped().as_str() { + // 12. If largestUnit is "year", then + "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). + let move_result = super::move_relative_date( + calendar, + &relative_to, + &one_year.as_object(context)?, + context, + )?; + + // d. Let newRelativeTo be moveResult.[[RelativeTo]]. + let mut new_relative = move_result.0; + // e. Let oneYearDays be moveResult.[[Days]]. + let mut one_year_days = move_result.1; + + // f. Repeat, while abs(days) ≥ abs(oneYearDays), + while self.days.abs() >= one_year_days.abs() { + // i. Set days to days - oneYearDays. + self.set_days(self.days - one_year_days); + + // ii. Set years to years + sign. + self.set_years(self.years + f64::from(sign)); + + // iii. Set relativeTo to newRelativeTo. + let relative_to = new_relative; + // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd). + let move_result = super::move_relative_date( + calendar, + &relative_to, + &one_year.as_object(context)?, + context, + )?; + + // v. Set newRelativeTo to moveResult.[[RelativeTo]]. + new_relative = move_result.0; + // vi. Set oneYearDays to moveResult.[[Days]]. + one_year_days = move_result.1; + } + + // g. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). + let move_result = super::move_relative_date( + calendar, + &relative_to, + &one_month.as_object(context)?, + context, + )?; + + // h. Set newRelativeTo to moveResult.[[RelativeTo]]. + let mut new_relative = move_result.0; + // i. Let oneMonthDays be moveResult.[[Days]]. + let mut one_month_days = move_result.1; + + // j. Repeat, while abs(days) ≥ abs(oneMonthDays), + while self.days.abs() >= one_month_days.abs() { + // i. Set days to days - oneMonthDays. + self.set_days(self.days - one_month_days); + // ii. Set months to months + sign. + self.set_months(self.months + f64::from(sign)); + // iii. Set relativeTo to newRelativeTo. + + let relative_to = new_relative; + // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). + let move_result = super::move_relative_date( + calendar, + &relative_to, + &one_month.as_object(context)?, + context, + )?; + + // v. Set newRelativeTo to moveResult.[[RelativeTo]]. + new_relative = 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). + // 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). + // q. Let oneYearMonths be untilResult.[[Months]]. + // r. Repeat, while abs(months) ≥ abs(oneYearMonths), + // i. Set months to months - oneYearMonths. + // ii. Set years to years + sign. + // iii. Set relativeTo to newRelativeTo. + // iv. Set newRelativeTo to ? CalendarDateAdd(calendar, relativeTo, oneYear, undefined, dateAdd). + // v. Set untilOptions to OrdinaryObjectCreate(null). + // vi. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "month"). + // vii. Set untilResult to ? CalendarDateUntil(calendar, relativeTo, newRelativeTo, untilOptions, dateUntil). + // viii. Set oneYearMonths to untilResult.[[Months]]. + } + // 13. Else if largestUnit is "month", then + "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]]. + // f. Repeat, while abs(days) ≥ abs(oneMonthDays), + // i. Set days to days - oneMonthDays. + // ii. Set months to months + sign. + // iii. Set relativeTo to newRelativeTo. + // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). + // v. Set newRelativeTo to moveResult.[[RelativeTo]]. + // vi. Set oneMonthDays to moveResult.[[Days]]. + } + // 14. Else, + "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]]. + // g. Repeat, while abs(days) ≥ abs(oneWeekDays), + // i. Set days to days - oneWeekDays. + // ii. Set weeks to weeks + sign. + // iii. Set relativeTo to newRelativeTo. + // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd). + // v. Set newRelativeTo to moveResult.[[RelativeTo]]. + // vi. Set oneWeekDays to moveResult.[[Days]]. + todo!("week not implemented yet.") + } + _ => unreachable!(), + } + + // 15. Return ! CreateDateDurationRecord(years, months, weeks, days). + + Err(JsNativeError::range() + .with_message("not yet implemented.") + .into()) } /// Abstract Operation 7.5.26 `RoundDuration ( years, months, weeks, days, hours, minutes, @@ -826,25 +1012,21 @@ impl DurationRecord { .into()); } + // TODO: Handle `ZonedDateTime` // 3. Let zonedRelativeTo be undefined. - let mut zoned_relative_to = JsValue::undefined(); + let zoned_relative_to = JsValue::undefined(); // 4. If relativeTo is not undefined, then - let (calendar, relative_to) = if !relative_to.is_undefined() { + let (calendar, relative_to) = if relative_to.is_object() { let relative_to_obj = relative_to.as_object().expect( "relativeTo must be a Temporal.ZonedDateTime or Temporal.PlainDate object if defined.", ); // a. If relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then - let relative_to_obj = if relative_to_obj.is_zoned_date_time() { + if relative_to_obj.is_zoned_date_time() { // i. Set zonedRelativeTo to relativeTo. - zoned_relative_to = relative_to.clone(); - // TODO: ii. Set relativeTo to ? ToTemporalDate(relativeTo). - relative_to_obj.clone() - // b. Else, - } else { - // i. Assert: relativeTo has an [[InitializedTemporalDate]] internal slot. - relative_to_obj.clone() + return Err(JsNativeError::range().with_message("ZonedDateTime is not yet implemented.").into()) + // b. Else, }; let obj = relative_to_obj.borrow(); @@ -880,9 +1062,11 @@ impl DurationRecord { // b. Let intermediate be undefined. let intermediate = JsValue::undefined(); // c. If zonedRelativeTo is not undefined, then - if zoned_relative_to.is_undefined() { + if !zoned_relative_to.is_undefined() { // i. Let intermediate be ? MoveRelativeZonedDateTime(zonedRelativeTo, years, months, weeks, days). - todo!() + return Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()); } // d. Let result be ? NanosecondsToDays(nanoseconds, intermediate). let result = zoned_date_time::nanoseconds_to_days(nanoseconds, &intermediate)?; @@ -925,7 +1109,7 @@ impl DurationRecord { // a. Let yearsDuration be ! CreateTemporalDuration(years, 0, 0, 0, 0, 0, 0, 0, 0, 0). let years_duration = super::create_temporal_duration( - DurationRecord::default().with_years(self.years()), + Self::default().with_years(self.years()), None, context, )?; @@ -943,7 +1127,7 @@ impl DurationRecord { let years_later = calendar::calendar_date_add( &calendar_obj, - &relative_to, + relative_to, &years_duration, None, context, @@ -951,7 +1135,7 @@ impl DurationRecord { // e. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). let years_months_weeks = super::create_temporal_duration( - DurationRecord::default() + Self::default() .with_years(self.years()) .with_months(self.months()) .with_weeks(self.weeks()), @@ -962,7 +1146,7 @@ impl DurationRecord { // f. Let yearsMonthsWeeksLater be ? CalendarDateAdd(calendar, relativeTo, yearsMonthsWeeks, undefined, dateAdd). let years_months_weeks_later = calendar::calendar_date_add( &calendar_obj, - &relative_to, + relative_to, &years_months_weeks, None, context, @@ -980,7 +1164,7 @@ impl DurationRecord { // j. Let wholeDaysDuration be ? CreateTemporalDuration(0, 0, 0, truncate(days), 0, 0, 0, 0, 0, 0). let whole_days_duration = super::create_temporal_duration( - DurationRecord::default().with_days(self.days()), + Self::default().with_days(self.days()), None, context, )?; @@ -1018,7 +1202,7 @@ impl DurationRecord { // r. Let yearsDuration be ! CreateTemporalDuration(yearsPassed, 0, 0, 0, 0, 0, 0, 0, 0, 0). let years_duration = super::create_temporal_duration( - DurationRecord::default().with_years(years_passed), + Self::default().with_years(years_passed), None, context, )?; @@ -1043,14 +1227,14 @@ impl DurationRecord { // w. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0). let one_year = super::create_temporal_duration( - DurationRecord::default().with_years(f64::from(sign)), + Self::default().with_years(f64::from(sign)), None, context, )?; // x. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd). let move_result = - super::move_relative_date(&calendar_obj, &relative_to, one_year, context)?; + super::move_relative_date(&calendar_obj, &relative_to, &one_year, context)?; // y. Let oneYearDays be moveResult.[[Days]]. let one_year_days = move_result.1; @@ -1074,12 +1258,13 @@ impl DurationRecord { // 10. Else if unit is "month", then "month" => { let mut relative_to = - relative_to.expect("relative_to must exist if unit is a month"); + relative_to.expect("relative_to must exist if unit is a month") + .clone(); let calendar_obj = calendar.expect("calendar must exist at this point."); // a. Let yearsMonths be ! CreateTemporalDuration(years, months, 0, 0, 0, 0, 0, 0, 0, 0). let years_month = super::create_temporal_duration( - DurationRecord::default() + Self::default() .with_years(self.years()) .with_months(self.months()), None, @@ -1106,7 +1291,7 @@ impl DurationRecord { // e. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). let years_months_weeks = super::create_temporal_duration( - DurationRecord::default() + Self::default() .with_years(self.years()) .with_months(self.months()) .with_weeks(self.weeks()), @@ -1137,7 +1322,7 @@ impl DurationRecord { // k. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). let one_month = super::create_temporal_duration( - DurationRecord::default().with_months(sign), + Self::default().with_months(sign), None, context, )?; @@ -1146,7 +1331,7 @@ impl DurationRecord { let move_result = super::move_relative_date( &calendar_obj, &relative_to, - one_month.clone(), + &one_month, context, )?; @@ -1165,7 +1350,7 @@ impl DurationRecord { let move_result = super::move_relative_date( &calendar_obj, &relative_to, - one_month.clone(), + &one_month.clone(), context, )?; @@ -1192,13 +1377,14 @@ impl DurationRecord { // 11. Else if unit is "week", then "week" => { let mut relative_to = - relative_to.expect("relative_to must exist if unit is a month"); + relative_to.expect("relative_to must exist if unit is a month") + .clone(); let calendar_obj = calendar.expect("calendar must exist at this point."); // a. If days < 0, let sign be -1; else, let sign be 1. let sign = if self.days() < 0_f64 { -1_f64 } else { 1_f64 }; // b. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0). let one_week = super::create_temporal_duration( - DurationRecord::default().with_weeks(sign), + Self::default().with_weeks(sign), None, context, )?; @@ -1216,7 +1402,7 @@ impl DurationRecord { let move_result = super::move_relative_date( &calendar_obj, &relative_to, - one_week.clone(), + &one_week, context, )?; @@ -1235,7 +1421,7 @@ impl DurationRecord { let move_result = super::move_relative_date( &calendar_obj, &relative_to, - one_week.clone(), + &one_week.clone(), context, )?; // iv. Set relativeTo to moveResult.[[RelativeTo]]. @@ -1402,11 +1588,11 @@ impl DurationRecord { { let obj = rt.as_object().expect("This must be an object."); let obj = obj.borrow(); + // a. Return ! CreateDurationRecord(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). obj.as_zoned_date_time() .expect("Object must be a ZonedDateTime.") .clone() } - // a. Return ! CreateDurationRecord(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). _ => return Ok(()), }; @@ -1441,6 +1627,7 @@ impl DurationRecord { 1 }; + // TODO: 6.5.5 AddZonedDateTime // 6. Let dayStart be ? AddZonedDateTime(relativeTo.[[Nanoseconds]], relativeTo.[[TimeZone]], relativeTo.[[Calendar]], years, months, weeks, days, 0, 0, 0, 0, 0, 0). // 7. Let dayEnd be ? AddZonedDateTime(dayStart, relativeTo.[[TimeZone]], relativeTo.[[Calendar]], 0, 0, 0, direction, 0, 0, 0, 0, 0, 0). // 8. Let dayLengthNs be ℝ(dayEnd - dayStart). @@ -1452,6 +1639,8 @@ impl DurationRecord { // 13. Return ! CreateDurationRecord(adjustedDateDuration.[[Years]], adjustedDateDuration.[[Months]], adjustedDateDuration.[[Weeks]], // adjustedDateDuration.[[Days]], adjustedTimeDuration.[[Hours]], adjustedTimeDuration.[[Minutes]], adjustedTimeDuration.[[Seconds]], // adjustedTimeDuration.[[Milliseconds]], adjustedTimeDuration.[[Microseconds]], adjustedTimeDuration.[[Nanoseconds]]). - Ok(()) + Err(JsNativeError::range() + .with_message("not yet implemented.") + .into()) } } diff --git a/boa_engine/src/builtins/temporal/fields.rs b/boa_engine/src/builtins/temporal/fields.rs index a61dcc058b6..0763d55cdf4 100644 --- a/boa_engine/src/builtins/temporal/fields.rs +++ b/boa_engine/src/builtins/temporal/fields.rs @@ -12,20 +12,20 @@ use rustc_hash::FxHashSet; bitflags! { #[derive(PartialEq, Eq)] pub struct FieldMap: u16 { - const YEAR = 0b00000000_00000001; - const MONTH = 0b00000000_00000010; - const MONTH_CODE = 0b00000000_00000100; - const DAY = 0b00000000_00001000; - const HOUR = 0b00000000_00010000; - const MINUTE = 0b00000000_00100000; - const SECOND = 0b00000000_01000000; - const MILLISECOND = 0b00000000_10000000; - const MICROSECOND = 0b00000001_00000000; - const NANOSECOND = 0b00000010_00000000; - const OFFSET = 0b00000100_00000000; - const ERA = 0b00001000_00000000; - const ERA_YEAR = 0b00010000_00000000; - const TIME_ZONE = 0b00100000_00000000; + const YEAR = 0b0000_0000_0000_0001; + const MONTH = 0b0000_0000_0000_0010; + const MONTH_CODE = 0b0000_0000_0000_0100; + const DAY = 0b0000_0000_0000_1000; + const HOUR = 0b0000_0000_0001_0000; + const MINUTE = 0b0000_0000_0010_0000; + const SECOND = 0b0000_0000_0100_0000; + const MILLISECOND = 0b0000_0000_1000_0000; + const MICROSECOND = 0b0000_0001_0000_0000; + const NANOSECOND = 0b0000_0010_0000_0000; + const OFFSET = 0b0000_0100_0000_0000; + const ERA = 0b0000_1000_0000_0000; + const ERA_YEAR = 0b0001_0000_0000_0000; + const TIME_ZONE = 0b0010_0000_0000_0000; } } @@ -40,19 +40,19 @@ bitflags! { /// /// | 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 | +/// | "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" | | undefined | /// pub(crate) struct TemporalFields { @@ -96,15 +96,15 @@ impl Default for TemporalFields { } impl TemporalFields { - pub(crate) fn year(&self) -> Option { + pub(crate) const fn year(&self) -> Option { self.year } - pub(crate) fn month(&self) -> Option { + pub(crate) const fn month(&self) -> Option { self.month } - pub(crate) fn day(&self) -> Option { + pub(crate) const fn day(&self) -> Option { self.day } } @@ -117,21 +117,21 @@ impl TemporalFields { value: &JsValue, context: &mut Context<'_>, ) -> JsResult<()> { - match field.as_ref() { - super::YEAR => self.set_year(value, context)?, - super::MONTH => self.set_month(value, context)?, - super::MONTH_CODE => self.set_month_code(value, context)?, - super::DAY => self.set_day(value, context)?, - super::HOUR => self.set_hour(value, context)?, - super::MINUTE => self.set_minute(value, context)?, - super::SECOND => self.set_second(value, context)?, - super::MILLISECOND => self.set_milli(value, context)?, - super::MICROSECOND => self.set_micro(value, context)?, - super::NANOSECOND => self.set_nano(value, context)?, - super::OFFSET => self.set_nano(value, context)?, - super::ERA => self.set_era(value, context)?, - super::ERA_YEAR => self.set_era_year(value, context)?, - super::TZ => self.set_time_zone(value)?, + match field.to_std_string_escaped().as_str() { + "year" => self.set_year(value, context)?, + "month" => self.set_month(value, context)?, + "monthCode" => self.set_month_code(value, context)?, + "day" => self.set_day(value, context)?, + "hour" => self.set_hour(value, context)?, + "minute" => self.set_minute(value, context)?, + "second" => self.set_second(value, context)?, + "millisecond" => self.set_milli(value, context)?, + "microsecond" => self.set_micro(value, context)?, + "nanosecond" => self.set_nano(value, context)?, + "offset" => self.set_offset(value, context)?, + "era" => self.set_era(value, context)?, + "eraYear" => self.set_era_year(value, context)?, + "timeZone" => self.set_time_zone(value), _ => unreachable!(), } @@ -158,7 +158,7 @@ impl TemporalFields { fn set_month_code(&mut self, value: &JsValue, context: &mut Context<'_>) -> JsResult<()> { let mc = value.to_primitive(context, PreferredType::String)?; if let Some(string) = mc.as_string() { - self.month_code = Some(string.clone()) + self.month_code = Some(string.clone()); } else { return Err(JsNativeError::typ() .with_message("ToPrimativeAndRequireString must be of type String.") @@ -230,7 +230,7 @@ impl TemporalFields { fn set_offset(&mut self, value: &JsValue, context: &mut Context<'_>) -> JsResult<()> { let mc = value.to_primitive(context, PreferredType::String)?; if let Some(string) = mc.as_string() { - self.offset = Some(string.clone()) + self.offset = Some(string.clone()); } else { return Err(JsNativeError::typ() .with_message("ToPrimativeAndRequireString must be of type String.") @@ -245,7 +245,7 @@ impl TemporalFields { fn set_era(&mut self, value: &JsValue, context: &mut Context<'_>) -> JsResult<()> { let mc = value.to_primitive(context, PreferredType::String)?; if let Some(string) = mc.as_string() { - self.era = Some(string.clone()) + self.era = Some(string.clone()); } else { return Err(JsNativeError::typ() .with_message("ToPrimativeAndRequireString must be of type String.") @@ -265,17 +265,17 @@ impl TemporalFields { } #[inline] - fn set_time_zone(&mut self, value: &JsValue) -> JsResult<()> { - let tz = value.as_string().map(|s| s.clone()); + fn set_time_zone(&mut self, value: &JsValue) { + let tz = value.as_string().cloned(); self.time_zone = tz; self.bit_map.set(FieldMap::TIME_ZONE, true); - Ok(()) } } impl TemporalFields { // NOTE: required_fields should be None when it is set to Partial. - /// The equivalant function to PrepareTemporalFields + /// + /// The equivalant function to 13.46 `PrepareTemporalFields` pub(crate) fn from_js_object( fields: &JsObject, field_names: &[JsString], @@ -284,7 +284,7 @@ impl TemporalFields { context: &mut Context<'_>, ) -> JsResult { // 1. If duplicateBehaviour is not present, set duplicateBehaviour to throw. - let dup_option = dup_behaviour.unwrap_or(js_string!("throw")); + let dup_option = dup_behaviour.unwrap_or_else(|| js_string!("throw")); // 2. Let result be OrdinaryObjectCreate(null). let mut result = Self::default(); @@ -329,11 +329,11 @@ impl TemporalFields { // iii. Set value to ? ToPrimitive(value, string). // iv. If value is not a String, throw a TypeError exception. // 3. Perform ! CreateDataPropertyOrThrow(result, property, value). - result.set_field_value(field, &value, context)? + result.set_field_value(field, &value, context)?; // iii. Else if requiredFields is a List, then } else if let Some(list) = required_fields { // 1. If requiredFields contains property, then - if list.contains(&field) { + if list.contains(field) { // a. Throw a TypeError exception. return Err(JsNativeError::typ() .with_message("A required temporal field was not provided.") @@ -367,6 +367,7 @@ impl TemporalFields { Ok(result) } + /// Convert a `TemporalFields` struct into a `JsObject`. pub(crate) fn as_object(&self, context: &mut Context<'_>) -> JsResult { let obj = JsObject::with_null_proto(); @@ -375,7 +376,7 @@ impl TemporalFields { FieldMap::YEAR => { obj.create_data_property_or_throw( "year", - self.year.map(JsValue::from).unwrap_or(JsValue::undefined()), + self.year.map_or(JsValue::undefined(), JsValue::from), context, )?; } @@ -383,8 +384,7 @@ impl TemporalFields { obj.create_data_property_or_throw( "month", self.month - .map(JsValue::from) - .unwrap_or(JsValue::undefined()), + .map_or(JsValue::undefined(), JsValue::from), context, )?; } @@ -393,8 +393,7 @@ impl TemporalFields { "monthCode", self.month_code .as_ref() - .map(|f| f.clone().into()) - .unwrap_or(JsValue::undefined()), + .map_or(JsValue::undefined(), |f| f.clone().into()), context, )?; } @@ -402,8 +401,7 @@ impl TemporalFields { obj.create_data_property( "day", self.day() - .map(JsValue::from) - .unwrap_or(JsValue::undefined()), + .map_or(JsValue::undefined(), JsValue::from), context, )?; } @@ -430,8 +428,7 @@ impl TemporalFields { "offset", self.offset .as_ref() - .map(|s| s.clone().into()) - .unwrap_or(JsValue::undefined()), + .map_or(JsValue::undefined(), |s| s.clone().into()), context, )?; } @@ -440,8 +437,7 @@ impl TemporalFields { "era", self.era .as_ref() - .map(|s| s.clone().into()) - .unwrap_or(JsValue::undefined()), + .map_or(JsValue::undefined(), |s| s.clone().into()), context, )?; } @@ -449,8 +445,7 @@ impl TemporalFields { obj.create_data_property_or_throw( "eraYear", self.era_year - .map(JsValue::from) - .unwrap_or(JsValue::undefined()), + .map_or(JsValue::undefined(), JsValue::from), context, )?; } @@ -459,8 +454,7 @@ impl TemporalFields { "timeZone", self.time_zone .as_ref() - .map(|s| s.clone().into()) - .unwrap_or(JsValue::undefined()), + .map_or(JsValue::undefined(), |s| s.clone().into()), context, )?; } @@ -468,25 +462,10 @@ impl TemporalFields { } } - if self.bit_map.contains(FieldMap::YEAR) { - let value = self - .year() - .map(JsValue::from) - .unwrap_or(JsValue::undefined()); - obj.create_data_property_or_throw(PropertyKey::from("year"), value, context)?; - } - - if self.bit_map.contains(FieldMap::MONTH) { - let value = self - .month() - .map(JsValue::from) - .unwrap_or(JsValue::undefined()); - obj.create_data_property_or_throw(PropertyKey::from("month"), value, context)?; - } - Ok(obj) } + /// Resolve the month and monthCode on this `TemporalFields`. pub(crate) fn resolve_month(&mut self) -> JsResult<()> { if self.month_code.is_none() { if self.month.is_some() { diff --git a/boa_engine/src/builtins/temporal/instant/mod.rs b/boa_engine/src/builtins/temporal/instant/mod.rs index 4662081caad..6fbfd62bc24 100644 --- a/boa_engine/src/builtins/temporal/instant/mod.rs +++ b/boa_engine/src/builtins/temporal/instant/mod.rs @@ -16,10 +16,7 @@ use boa_profiler::Profiler; use num_bigint::ToBigInt; use num_traits::ToPrimitive; -use super::{ - duration, ns_max_instant, ns_min_instant, HOUR, MICROSECOND, MICRO_PER_DAY, MILLISECOND, - MILLI_PER_DAY, MINUTE, NANOSECOND, NS_PER_DAY, SECOND, -}; +use super::{duration, ns_max_instant, ns_min_instant, MICRO_PER_DAY, MILLI_PER_DAY, NS_PER_DAY}; const NANOSECONDS_PER_SECOND: i64 = 10_000_000_000; const NANOSECONDS_PER_MINUTE: i64 = 600_000_000_000; @@ -377,7 +374,6 @@ impl Instant { )?; let smallest_unit = smallest_unit - .as_string() .expect("GetTemporalUnit cannot return Undefined when default is required."); let maximum = match smallest_unit.to_std_string_escaped().as_str() { // 10. If smallestUnit is "hour", then @@ -410,7 +406,7 @@ impl Instant { let rounded_ns = round_temporal_instant( &instant.nanoseconds, rounding_increment, - smallest_unit, + &smallest_unit, &rounding_mode, )?; @@ -523,7 +519,10 @@ fn create_temporal_instant( /// 8.5.3 `ToTemporalInstant ( item )` #[inline] fn to_temporal_instant(_: &JsValue) -> JsResult { - todo!() + // TODO: Need to implement parsing. + Err(JsNativeError::error() + .with_message("Instant parsing is not yet implemented.") + .into()) } /// 8.5.6 `AddInstant ( epochNanoseconds, hours, minutes, seconds, milliseconds, microseconds, nanoseconds )` @@ -607,10 +606,10 @@ fn diff_instant( // 7. Assert: roundResult.[[Days]] is 0. assert_eq!(roundable_duration.days() as i32, 0); - // 8. Return ! BalanceDuration(0, roundResult.[[Hours]], roundResult.[[Minutes]], + // 8. Return ! BalanceTimeDuration(0, roundResult.[[Hours]], roundResult.[[Minutes]], // roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], // roundResult.[[Nanoseconds]], largestUnit). - roundable_duration.balance_duration(largest_unit, None)?; + roundable_duration.balance_time_duration(largest_unit, None)?; Ok(roundable_duration) } @@ -623,34 +622,34 @@ fn round_temporal_instant( unit: &JsString, rounding_mode: &JsString, ) -> JsResult { - let increment_ns = match unit.as_slice() { + let increment_ns = match unit.to_std_string_escaped().as_str() { // 1. If unit is "hour", then - HOUR => { + "hour" => { // a. Let incrementNs be increment × 3.6 × 10^12. increment as i64 * NANOSECONDS_PER_HOUR } // 2. Else if unit is "minute", then - MINUTE => { + "minute" => { // a. Let incrementNs be increment × 6 × 10^10. increment as i64 * NANOSECONDS_PER_MINUTE } // 3. Else if unit is "second", then - SECOND => { + "second" => { // a. Let incrementNs be increment × 10^9. increment as i64 * NANOSECONDS_PER_SECOND } // 4. Else if unit is "millisecond", then - MILLISECOND => { + "millisecond" => { // a. Let incrementNs be increment × 10^6. increment as i64 * 1_000_000 } // 5. Else if unit is "microsecond", then - MICROSECOND => { + "microsecond" => { // a. Let incrementNs be increment × 10^3. increment as i64 * 1000 } // 6. Else, - NANOSECOND => { + "nanosecond" => { // NOTE: We shouldn't have to assert here as `unreachable` asserts instead. // a. Assert: unit is "nanosecond". // b. Let incrementNs be increment. @@ -727,13 +726,22 @@ fn add_or_subtract_duration_from_instant( // 2. Let duration be ? ToTemporalDurationRecord(temporalDurationLike). let duration = super::to_temporal_duration_record(temporal_duration_like)?; // 3. If duration.[[Days]] is not 0, throw a RangeError exception. - if duration.days() != 0_f64 {} + if duration.days() != 0_f64 { + return Err(JsNativeError::range().with_message("DurationDays cannot be 0").into()) + } // 4. If duration.[[Months]] is not 0, throw a RangeError exception. - if duration.months() != 0_f64 {} + if duration.months() != 0_f64 { + return Err(JsNativeError::range().with_message("DurationMonths cannot be 0").into()) + } // 5. If duration.[[Weeks]] is not 0, throw a RangeError exception. - if duration.weeks() != 0_f64 {} + if duration.weeks() != 0_f64 { + return Err(JsNativeError::range().with_message("DurationWeeks cannot be 0").into()) + + } // 6. If duration.[[Years]] is not 0, throw a RangeError exception. - if duration.years() != 0_f64 {} + if duration.years() != 0_f64 { + return Err(JsNativeError::range().with_message("DurationYears cannot be 0").into()) + } // 7. Let ns be ? AddInstant(instant.[[Nanoseconds]], sign × duration.[[Hours]], // sign × duration.[[Minutes]], sign × duration.[[Seconds]], sign × duration.[[Milliseconds]], // sign × duration.[[Microseconds]], sign × duration.[[Nanoseconds]]). diff --git a/boa_engine/src/builtins/temporal/mod.rs b/boa_engine/src/builtins/temporal/mod.rs index 60704efc7b0..fd1a7de5cbe 100644 --- a/boa_engine/src/builtins/temporal/mod.rs +++ b/boa_engine/src/builtins/temporal/mod.rs @@ -19,6 +19,8 @@ mod plain_year_month; mod time_zone; mod zoned_date_time; +use std::ops::Mul; + pub(crate) use fields::TemporalFields; use self::date_equations::mathematical_days_in_year; @@ -56,6 +58,7 @@ pub(crate) fn ns_min_instant() -> JsBigInt { } // Relavant datetime utf16 constants. +/* pub(crate) const YEAR: &[u16] = utf16!("year"); pub(crate) const MONTH: &[u16] = utf16!("month"); pub(crate) const MONTH_CODE: &[u16] = utf16!("monthCode"); @@ -71,6 +74,7 @@ pub(crate) const OFFSET: &[u16] = utf16!("offset"); pub(crate) const ERA: &[u16] = utf16!("era"); pub(crate) const ERA_YEAR: &[u16] = utf16!("eraYear"); pub(crate) const TZ: &[u16] = utf16!("timeZone"); +*/ // An enum representing common fields across `Temporal` objects. pub(crate) enum DateTimeValues { @@ -107,16 +111,16 @@ pub struct TemporalUnits { impl Default for TemporalUnits { fn default() -> Self { Self { - year: (YEAR, utf16!("years")), - month: (MONTH, utf16!("months")), - week: (WEEK, utf16!("weeks")), - day: (DAY, utf16!("days")), - hour: (HOUR, utf16!("hours")), - minute: (MINUTE, utf16!("minutes")), - second: (SECOND, utf16!("seconds")), - millisecond: (MILLISECOND, utf16!("milliseconds")), - microsecond: (MICROSECOND, utf16!("microseconds")), - nanosecond: (NANOSECOND, utf16!("nanoseconds")), + year: (utf16!("year"), utf16!("years")), + month: (utf16!("month"), utf16!("months")), + week: (utf16!("week"), utf16!("weeks")), + day: (utf16!("day"), utf16!("days")), + hour: (utf16!("hour"), utf16!("hours")), + minute: (utf16!("minute"), utf16!("minutes")), + second: (utf16!("second"), utf16!("seconds")), + millisecond: (utf16!("millisecond"), utf16!("milliseconds")), + microsecond: (utf16!("microsecond"), utf16!("microseconds")), + nanosecond: (utf16!("nanosecond"), utf16!("nanoseconds")), } } } @@ -151,6 +155,7 @@ impl TemporalUnits { output } + /// Return a vector of all stored singular and plural `TemporalUnits`. fn all(&self) -> Vec<(&'static [u16], &'static [u16])> { vec![ self.year, @@ -239,7 +244,7 @@ fn to_zero_padded_decimal_string(n: u64, min_length: usize) -> String { format!("{n:0min_length$}") } -// TODO: 13.1 IteratorToListOfType +// TODO: 13.1 `IteratorToListOfType` pub(crate) fn iterator_to_list_of_types( iterator: &mut IteratorRecord, element_types: &[Type], @@ -276,9 +281,9 @@ pub(crate) fn iterator_to_list_of_types( /// 13.2 `ISODateToEpochDays ( year, month, date )` // Note: implemented on IsoDateRecord. -// TODO: 13.3 EpochDaysToEpochMs +// TODO: 13.3 `EpochDaysToEpochMs` pub(crate) fn epoch_days_to_epoch_ms(day: i32, time: i32) -> f64 { - f64::from(day) * (MILLI_PER_DAY as f64) + f64::from(time) + f64::from(day).mul_add(MILLI_PER_DAY as f64, f64::from(time)) } // TODO: 13.4 Date Equations -> See ./date_equations.rs @@ -392,7 +397,7 @@ pub(crate) fn get_option( Ok(value) } -/// 13.7 `ToTemporalOverflow (options) +/// 13.7 `ToTemporalOverflow (options)` pub(crate) fn to_temporal_overflow( options: &JsObject, context: &mut Context<'_>, @@ -543,7 +548,7 @@ pub(crate) fn get_temporal_unit( default: Option<&JsValue>, // Must be required (none), undefined, or JsString. extra_values: Option>, // Vec context: &mut Context<'_>, -) -> JsResult { +) -> JsResult> { // 1. Let singularNames be a new empty List. let temporal_units = TemporalUnits::default(); // 2. For each row of Table 13, except the header row, in table order, do @@ -611,8 +616,8 @@ pub(crate) fn get_temporal_unit( // a. Set value to the value in the Singular column of the corresponding row. // 12. Return value. match value { - JsValue::String(lookup_value) => Ok(temporal_units.plural_lookup(&lookup_value).into()), - JsValue::Undefined => Ok(JsValue::undefined()), + JsValue::String(lookup_value) => Ok(Some(temporal_units.plural_lookup(&lookup_value))), + JsValue::Undefined => Ok(None), // TODO: verify that this is correct to specification, i.e. is it possible for default value to exist and value to be undefined? _ => unreachable!("The value returned from getTemporalUnit must be a string or undefined"), } @@ -679,7 +684,9 @@ pub(crate) fn to_relative_temporal_object( // a. Let offsetNs be 0. // 11. Let epochNanoseconds be ? InterpretISODateTimeOffset(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], offsetBehaviour, offsetNs, timeZone, "compatible", "reject", matchBehaviour). // 12. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar). - todo!() + Err(JsNativeError::range() + .with_message("not yet implemented.") + .into()) } /// 13.22 `LargerOfTwoTemporalUnits ( u1, u2 )` @@ -864,7 +871,7 @@ pub(crate) fn round_to_increment_as_if_positive( Ok(JsBigInt::mul(&rounded, &JsBigInt::from(increment))) } -/// 13.43 ToPositiveIntegerWithTruncation ( argument ) +/// 13.43 `ToPositiveIntegerWithTruncation ( argument )` #[inline] pub(crate) fn to_positive_integer_with_trunc( value: &JsValue, @@ -915,7 +922,7 @@ pub(crate) fn to_integer_if_integral(arg: &JsValue, context: &mut Context<'_>) - arg.to_i32(context) } -// 13.46 PrepareTemporalFields ( fields, fieldNames, requiredFields [ , duplicateBehaviour ] ) +// 13.46 `PrepareTemporalFields ( fields, fieldNames, requiredFields [ , duplicateBehaviour ] )` // See fields.rs // IMPLEMENTATION NOTE: op -> true == until | false == since @@ -940,9 +947,7 @@ pub(crate) fn get_diff_settings( None, context, )? - .as_string() - .expect("GetTemporalUnit cannot return undefined as the default value is not Undefined.") - .clone(); + .expect("GetTemporalUnit cannot return undefined as the default value is not Undefined."); // 3. If disallowedUnits contains largestUnit, throw a RangeError exception. if disallowed_units.contains(&largest_unit) { @@ -970,10 +975,7 @@ pub(crate) fn get_diff_settings( Some(&fallback_smallest_unit.clone().into()), None, context, - )? - .as_string() - .expect("smallestUnit must be a string as default value is not undefined.") - .clone(); + )?.expect("smallestUnit must be a string as default value is not undefined."); // 8. If disallowedUnits contains smallestUnit, throw a RangeError exception. if disallowed_units.contains(&smallest_unit) { diff --git a/boa_engine/src/builtins/temporal/now.rs b/boa_engine/src/builtins/temporal/now.rs index 69f6107b056..c7fb3abeda4 100644 --- a/boa_engine/src/builtins/temporal/now.rs +++ b/boa_engine/src/builtins/temporal/now.rs @@ -94,37 +94,51 @@ impl Now { /// `Temporal.Now.instant()` fn instant(_: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - todo!() + Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()) } /// `Temporal.Now.plainDateTime()` fn plain_date_time(_: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - todo!() + Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()) } /// `Temporal.Now.plainDateTimeISO` fn plain_date_time_iso(_: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - todo!() + Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()) } /// `Temporal.Now.zonedDateTime` fn zoned_date_time(_: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - todo!() + Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()) } /// `Temporal.Now.zonedDateTimeISO` fn zoned_date_time_iso(_: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - todo!() + Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()) } /// `Temporal.Now.plainDate()` fn plain_date(_: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - todo!() + Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()) } /// `Temporal.Now.plainDateISO` fn plain_date_iso(_: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - todo!() + Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()) } } diff --git a/boa_engine/src/builtins/temporal/plain_date/iso.rs b/boa_engine/src/builtins/temporal/plain_date/iso.rs index 7744cf22fdd..0bafadc3aaf 100644 --- a/boa_engine/src/builtins/temporal/plain_date/iso.rs +++ b/boa_engine/src/builtins/temporal/plain_date/iso.rs @@ -24,12 +24,12 @@ impl IsoDateRecord { } impl IsoDateRecord { - /// 3.5.2 CreateISODateRecord - pub(crate) fn new(year: i32, month: i32, day: i32) -> Self { + /// 3.5.2 `CreateISODateRecord` + pub(crate) const fn new(year: i32, month: i32, day: i32) -> Self { Self { year, month, day } } - /// 3.5.6 RegulateISODate + /// 3.5.6 `RegulateISODate` pub(crate) fn from_unregulated( year: i32, month: i32, @@ -56,11 +56,11 @@ impl IsoDateRecord { } } - /// 12.2.35 ISODateFromFields ( fields, overflow ) + /// 12.2.35 `ISODateFromFields ( fields, overflow )` /// /// Note: fields.month must be resolved prior to using `from_temporal_fields` pub(crate) fn from_temporal_fields( - fields: TemporalFields, + fields: &TemporalFields, overflow: &JsString, ) -> JsResult { Self::from_unregulated( @@ -72,7 +72,7 @@ impl IsoDateRecord { } pub(crate) fn month_day_from_temporal_fields( - fields: TemporalFields, + fields: &TemporalFields, overflow: &JsString, ) -> JsResult { match fields.year() { @@ -91,23 +91,72 @@ impl IsoDateRecord { } } - /// 3.5.5 DifferenceISODate + /// 3.5.5 `DifferenceISODate` pub(crate) fn diff_iso_date( &self, - o: Self, + o: &Self, largest_unit: &JsString, - ) -> temporal::duration::DurationRecord { - todo!() + ) -> JsResult { + debug_assert!(self.is_valid()); + // 1. Assert: IsValidISODate(y1, m1, d1) is true. + // 2. Assert: IsValidISODate(y2, m2, d2) is true. + // 3. If largestUnit is "year" or "month", then + // a. Let sign be -(! CompareISODate(y1, m1, d1, y2, m2, d2)). + // b. If sign is 0, return ! CreateDateDurationRecord(0, 0, 0, 0). + // c. Let start be the Record { [[Year]]: y1, [[Month]]: m1, [[Day]]: d1 }. + // d. Let end be the Record { [[Year]]: y2, [[Month]]: m2, [[Day]]: d2 }. + // e. Let years be end.[[Year]] - start.[[Year]]. + // f. Let mid be ! AddISODate(y1, m1, d1, years, 0, 0, 0, "constrain"). + // g. Let midSign be -(! CompareISODate(mid.[[Year]], mid.[[Month]], mid.[[Day]], y2, m2, d2)). + // h. If midSign is 0, then + // i. If largestUnit is "year", return ! CreateDateDurationRecord(years, 0, 0, 0). + // ii. Return ! CreateDateDurationRecord(0, years × 12, 0, 0). + // i. Let months be end.[[Month]] - start.[[Month]]. + // j. If midSign is not equal to sign, then + // i. Set years to years - sign. + // ii. Set months to months + sign × 12. + // k. Set mid to ! AddISODate(y1, m1, d1, years, months, 0, 0, "constrain"). + // l. Set midSign to -(! CompareISODate(mid.[[Year]], mid.[[Month]], mid.[[Day]], y2, m2, d2)). + // m. If midSign is 0, then + // i. If largestUnit is "year", return ! CreateDateDurationRecord(years, months, 0, 0). + // ii. Return ! CreateDateDurationRecord(0, months + years × 12, 0, 0). + // n. If midSign is not equal to sign, then + // i. Set months to months - sign. + // ii. Set mid to ! AddISODate(y1, m1, d1, years, months, 0, 0, "constrain"). + // o. If mid.[[Month]] = end.[[Month]], then + // i. Assert: mid.[[Year]] = end.[[Year]]. + // ii. Let days be end.[[Day]] - mid.[[Day]]. + // p. Else, + // i. If sign < 0, let days be -mid.[[Day]] - (ISODaysInMonth(end.[[Year]], end.[[Month]]) - end.[[Day]]). + // q. Else, let days be end.[[Day]] + (ISODaysInMonth(mid.[[Year]], mid.[[Month]]) - mid.[[Day]]). + // r. If largestUnit is "month", then + // i. Set months to months + years × 12. + // ii. Set years to 0. + // s. Return ! CreateDateDurationRecord(years, months, 0, days). + // 4. Else, + // a. Assert: largestUnit is "day" or "week". + // b. Let epochDays1 be ISODateToEpochDays(y1, m1 - 1, d1). + // c. Let epochDays2 be ISODateToEpochDays(y2, m2 - 1, d2). + // d. Let days be epochDays2 - epochDays1. + // e. Let weeks be 0. + // f. If largestUnit is "week", then + // i. Set weeks to truncate(days / 7). + // ii. Set days to remainder(days, 7). + // g. Return ! CreateDateDurationRecord(0, 0, weeks, days). + + Err(JsNativeError::range() + .with_message("not yet implemented.") + .into()) } - /// 3.5.7 IsValidISODate + /// 3.5.7 `IsValidISODate` pub(crate) fn is_valid(&self) -> bool { if self.month < 1 || self.month > 12 { return false; } let days_in_month = - temporal::calendar::utils::iso_days_in_month(self.year, i32::from(self.month)); + temporal::calendar::utils::iso_days_in_month(self.year, self.month); if self.day < 1 || self.day > days_in_month { return false; @@ -115,7 +164,7 @@ impl IsoDateRecord { true } - /// 13.2 IsoDateToEpochDays + /// 13.2 `IsoDateToEpochDays` pub(crate) fn as_epoch_days(&self) -> i32 { // 1. Let resolvedYear be year + floor(month / 12). let resolved_year = self.year + (f64::from(self.month) / 12_f64).floor() as i32; @@ -134,7 +183,7 @@ impl IsoDateRecord { } // NOTE: Implementing as mut self so balance is applied to self, but TBD. - /// 3.5.8 BalanceIsoDate + /// 3.5.8 `BalanceIsoDate` pub(crate) fn balance(&mut self) { let epoch_days = self.as_epoch_days(); let ms = temporal::epoch_days_to_epoch_ms(epoch_days, 0); @@ -144,4 +193,43 @@ impl IsoDateRecord { self.month = temporal::date_equations::epoch_time_to_month_in_year(ms); self.day = temporal::date_equations::epoch_time_to_date(ms); } + + /// 3.5.11 `AddISODate ( year, month, day, years, months, weeks, days, overflow )` + pub(crate) fn add_iso_date( + &self, + years: i32, + months: i32, + weeks: i32, + days: i32, + overflow: &JsString, + ) -> JsResult { + // 1. Assert: year, month, day, years, months, weeks, and days are integers. + // 2. Assert: overflow is either "constrain" or "reject". + let mut intermediate = temporal::plain_year_month::IsoYearMonthRecord::new( + self.year + years, + self.month + months, + 0, + ); + + // 3. Let intermediate be ! BalanceISOYearMonth(year + years, month + months). + intermediate.balance(); + + // 4. Let intermediate be ? RegulateISODate(intermediate.[[Year]], intermediate.[[Month]], day, overflow). + let mut new_date = Self::from_unregulated( + intermediate.year(), + intermediate.month(), + self.day, + overflow, + )?; + + // 5. Set days to days + 7 × weeks. + // 6. Let d be intermediate.[[Day]] + days. + let additional_days = days + (weeks * 7); + new_date.day += additional_days; + + // 7. Return BalanceISODate(intermediate.[[Year]], intermediate.[[Month]], d). + new_date.balance(); + + Ok(new_date) + } } diff --git a/boa_engine/src/builtins/temporal/plain_date/mod.rs b/boa_engine/src/builtins/temporal/plain_date/mod.rs index 9a15b381776..1000e462269 100644 --- a/boa_engine/src/builtins/temporal/plain_date/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_date/mod.rs @@ -7,13 +7,15 @@ use crate::{ property::Attribute, realm::Realm, string::utf16, - Context, JsArgs, JsNativeError, JsObject, JsResult, JsSymbol, JsValue, + Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; +use boa_parser::temporal::{IsoCursor, TemporalDateTimeString}; use boa_profiler::Profiler; +use icu_datetime::provider::calendar; use self::iso::IsoDateRecord; -use super::{get_options_object, to_temporal_overflow}; +use super::{get_options_object, to_temporal_overflow, plain_date_time}; pub(crate) mod iso; @@ -207,7 +209,7 @@ impl BuiltInConstructor for PlainDate { let iso = IsoDateRecord::new(iso_year, iso_month, iso_day); - create_temporal_date(iso, calendar_like.clone(), Some(new_target), context) + Ok(create_temporal_date(iso, calendar_like.clone(), Some(new_target), context)?.into()) } } @@ -306,13 +308,6 @@ impl PlainDate { _: &[JsValue], _: &mut Context<'_>, ) -> JsResult { - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let o = o.borrow(); - let _plain_date = o.as_plain_date().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; Err(JsNativeError::error() .with_message("not yet implemented.") @@ -320,13 +315,6 @@ impl PlainDate { } fn to_plain_month_day(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let o = o.borrow(); - let _plain_date = o.as_plain_date().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; Err(JsNativeError::error() .with_message("not yet implemented.") @@ -334,13 +322,6 @@ impl PlainDate { } fn get_iso_fields(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let o = o.borrow(); - let _plain_date = o.as_plain_date().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; Err(JsNativeError::error() .with_message("not yet implemented.") @@ -348,13 +329,6 @@ impl PlainDate { } fn get_calendar(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let o = o.borrow(); - let _plain_date = o.as_plain_date().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; Err(JsNativeError::error() .with_message("not yet implemented.") @@ -362,13 +336,6 @@ impl PlainDate { } fn add(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let o = o.borrow(); - let _plain_date = o.as_plain_date().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; Err(JsNativeError::error() .with_message("not yet implemented.") @@ -376,13 +343,6 @@ impl PlainDate { } fn subtract(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let o = o.borrow(); - let _plain_date = o.as_plain_date().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; Err(JsNativeError::error() .with_message("not yet implemented.") @@ -390,13 +350,6 @@ impl PlainDate { } fn with(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let o = o.borrow(); - let _plain_date = o.as_plain_date().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; Err(JsNativeError::error() .with_message("not yet implemented.") @@ -404,13 +357,6 @@ impl PlainDate { } fn with_calendar(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let o = o.borrow(); - let _plain_date = o.as_plain_date().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; Err(JsNativeError::error() .with_message("not yet implemented.") @@ -418,13 +364,6 @@ impl PlainDate { } fn until(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let o = o.borrow(); - let _plain_date = o.as_plain_date().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; Err(JsNativeError::error() .with_message("not yet implemented.") @@ -432,13 +371,6 @@ impl PlainDate { } fn since(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let o = o.borrow(); - let _plain_date = o.as_plain_date().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; Err(JsNativeError::error() .with_message("not yet implemented.") @@ -446,13 +378,6 @@ impl PlainDate { } fn equals(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let o = o.borrow(); - let _plain_date = o.as_plain_date().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; Err(JsNativeError::error() .with_message("not yet implemented.") @@ -462,13 +387,16 @@ impl PlainDate { // -- `PlainDate` Abstract Operations -- +// 3.5.2 `CreateIsoDateRecord` +// Implemented on `IsoDateRecord` + /// 3.5.3 `CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] )` pub(crate) fn create_temporal_date( iso_date: IsoDateRecord, calendar: JsValue, new_target: Option<&JsValue>, context: &mut Context<'_>, -) -> JsResult { +) -> JsResult { // 1. If IsValidISODate(isoYear, isoMonth, isoDay) is false, throw a RangeError exception. if !iso_date.is_valid() { return Err(JsNativeError::range() @@ -476,18 +404,10 @@ pub(crate) fn create_temporal_date( .into()); }; + let iso_date_time = plain_date_time::iso::IsoDateTimeRecord::default().with_date(iso_date.year(), iso_date.month(), iso_date.day()).with_time(12, 0, 0, 0, 0, 0); + // 2. If ISODateTimeWithinLimits(isoYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception. - if super::plain_date_time::iso_datetime_within_limits( - iso_date.year(), - iso_date.month(), - iso_date.day(), - 12, - 0, - 0, - 0, - 0, - 0, - ) { + if iso_date_time.is_valid() { return Err(JsNativeError::range() .with_message("Date is not within ISO date time limits.") .into()); @@ -522,14 +442,17 @@ pub(crate) fn create_temporal_date( drop(obj); // 9. Return object. - Ok(new_date.into()) + Ok(new_date) } +/// 3.5.4 `ToTemporalDate ( item [ , options ] )` +/// +/// Converts an ambiguous `JsValue` into a `PlainDate` pub(crate) fn to_temporal_date( item: &JsValue, options: Option, context: &mut Context<'_>, -) -> JsResult { +) -> JsResult { // 1. If options is not present, set options to undefined. let options = options.unwrap_or(JsValue::undefined()); @@ -540,9 +463,14 @@ pub(crate) fn to_temporal_date( // 4. If Type(item) is Object, then if let Some(object) = item.as_object() { // a. If item has an [[InitializedTemporalDate]] internal slot, then - if object.is_date() { + if object.is_plain_date() { // i. Return item. - return Ok(object.clone().into()); + let obj = object.borrow(); + let date = obj.as_plain_date().expect("obj must be a PlainDate."); + return Ok(PlainDate { + inner: date.inner, + calendar: date.calendar.clone(), + }); // b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then } else if object.is_zoned_date_time() { return Err(JsNativeError::range() @@ -569,33 +497,71 @@ pub(crate) fn to_temporal_date( drop(obj); // ii. Return ! CreateTemporalDate(item.[[ISOYear]], item.[[ISOMonth]], item.[[ISODay]], item.[[Calendar]]). - return create_temporal_date(iso, calendar, None, context); + return Ok(PlainDate { + inner: iso, + calendar, + }); } // d. Let calendar be ? GetTemporalCalendarSlotValueWithISODefault(item). // e. Let fieldNames be ? CalendarFields(calendar, « "day", "month", "monthCode", "year" »). // f. Let fields be ? PrepareTemporalFields(item, fieldNames, «»). // g. Return ? CalendarDateFromFields(calendar, fields, options). + return Err(JsNativeError::error() + .with_message("CalendarDateFields not yet implemented.") + .into()); } // 5. If item is not a String, throw a TypeError exception. match item { JsValue::String(s) => { // 6. Let result be ? ParseTemporalDateString(item). + let result = TemporalDateTimeString::parse( + false, + &mut IsoCursor::new(&s.to_std_string_escaped()), + )?; + // 7. Assert: IsValidISODate(result.[[Year]], result.[[Month]], result.[[Day]]) is true. // 8. Let calendar be result.[[Calendar]]. // 9. If calendar is undefined, set calendar to "iso8601". + let identifier = result.calendar().unwrap_or_else(|| "iso8601".to_string()); + // 10. If IsBuiltinCalendar(calendar) is false, throw a RangeError exception. + if !super::calendar::is_builtin_calendar(&identifier) { + return Err(JsNativeError::range() + .with_message("not a valid calendar identifier.") + .into()); + } + // 11. Set calendar to the ASCII-lowercase of calendar. + let calendar = identifier.to_ascii_lowercase(); + // 12. Perform ? ToTemporalOverflow(options). - // 13. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar). + let _result = to_temporal_overflow(&options_obj, context)?; - Err(JsNativeError::range() - .with_message("Not yet implemented.") - .into()) + // 13. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar). + Ok(PlainDate { + inner: IsoDateRecord::new(result.date.year, result.date.month, result.date.day), + calendar: calendar.into(), + }) } _ => Err(JsNativeError::typ() .with_message("ToTemporalDate item must be an object or string.") .into()), } } + +// 3.5.5. DifferenceIsoDate +// Implemented on IsoDateRecord. + +// 3.5.6 RegulateIsoDate +// Implemented on IsoDateRecord. + +// 3.5.7 IsValidIsoDate +// Implemented on IsoDateRecord. + +// 3.5.8 BalanceIsoDate +// Implemented on IsoDateRecord. + +// 3.5.11 AddISODate ( year, month, day, years, months, weeks, days, overflow ) +// Implemented on IsoDateRecord diff --git a/boa_engine/src/builtins/temporal/plain_date_time/iso.rs b/boa_engine/src/builtins/temporal/plain_date_time/iso.rs index ad033fc426d..1bbfeed0d7f 100644 --- a/boa_engine/src/builtins/temporal/plain_date_time/iso.rs +++ b/boa_engine/src/builtins/temporal/plain_date_time/iso.rs @@ -18,7 +18,7 @@ pub(crate) struct IsoDateTimeRecord { } impl IsoDateTimeRecord { - pub(crate) fn iso_date(&self) -> IsoDateRecord { + pub(crate) const fn iso_date(&self) -> IsoDateRecord { self.iso_date } } @@ -26,13 +26,13 @@ impl IsoDateTimeRecord { // ==== `IsoDateTimeRecord` methods ==== impl IsoDateTimeRecord { - pub(crate) fn with_date(mut self, year: i32, month: i32, day: i32) -> Self { + pub(crate) const fn with_date(mut self, year: i32, month: i32, day: i32) -> Self { let iso_date = IsoDateRecord::new(year, month, day); self.iso_date = iso_date; self } - pub(crate) fn with_time( + pub(crate) const fn with_time( mut self, hour: i32, minute: i32, @@ -50,19 +50,19 @@ impl IsoDateTimeRecord { self } + /// 5.5.1 `ISODateTimeWithinLimits ( year, month, day, hour, minute, second, millisecond, microsecond, nanosecond )` pub(crate) fn is_valid(&self) -> bool { self.iso_date.is_valid(); let ns = self.get_utc_epoch_ns(None).to_f64(); - if ns <= temporal::ns_min_instant().to_f64() - (temporal::NS_PER_DAY as f64) { - return false; - } else if ns >= temporal::ns_max_instant().to_f64() + (temporal::NS_PER_DAY as f64) { + if ns <= temporal::ns_min_instant().to_f64() - (temporal::NS_PER_DAY as f64) + || ns >= temporal::ns_max_instant().to_f64() + (temporal::NS_PER_DAY as f64) { return false; } - return true; + true } - /// 14.8.1 GetUTCEpochNanoseconds + /// 14.8.1 `GetUTCEpochNanoseconds` pub(crate) fn get_utc_epoch_ns(&self, offset_ns: Option) -> JsBigInt { let day = utils::make_day( i64::from(self.iso_date.year()), @@ -82,13 +82,13 @@ impl IsoDateTimeRecord { let epoch_ns = match offset_ns { Some(offset) if offset != 0 => { - let ns = (i64::from(ms) * 1_000_000_i64) + let ns = (ms * 1_000_000_i64) + (i64::from(self.microsecond) * 1_000_i64) + i64::from(self.nanosecond); ns - offset } _ => { - (i64::from(ms) * 1_000_000_i64) + (ms * 1_000_000_i64) + (i64::from(self.microsecond) * 1_000_i64) + i64::from(self.nanosecond) } diff --git a/boa_engine/src/builtins/temporal/plain_date_time/mod.rs b/boa_engine/src/builtins/temporal/plain_date_time/mod.rs index d5b638dfae4..643a3c6d490 100644 --- a/boa_engine/src/builtins/temporal/plain_date_time/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_date_time/mod.rs @@ -53,7 +53,9 @@ impl BuiltInConstructor for PlainDateTime { args: &[JsValue], context: &mut Context<'_>, ) -> JsResult { - Err(JsNativeError::range().with_message("Not yet implemented.").into()) + Err(JsNativeError::range() + .with_message("Not yet implemented.") + .into()) } } @@ -141,59 +143,4 @@ impl PlainDateTime { // ==== `PlainDateTime` Abstract Operations` ==== -fn get_utc_epoch_nanos( - y: i64, - mo: i64, - d: i64, - h: i64, - m: i64, - sec: i64, - ms: i64, - mis: i64, - ns: i64, -) -> JsBigInt { - // NOTE(nekevss): specification calls to assert that the number is integral, - // the unwraps primarily function as that assertion, although admittedly clunkily. - let day = utils::make_day(y, mo, d).expect("must be valid number."); - let time = utils::make_time(h, m, sec, ms).expect("must be valid number."); - - let ms = utils::make_date(day, time).expect("must be valid number."); - - JsBigInt::from((ms * 1_000_000) + (mis * 1_000) + ns) -} - -// -- `PlainDateTime` Abstract Operations -- - -/// 5.5.1 `ISODateTimeWithinLimits ( year, month, day, hour, minute, second, millisecond, microsecond, nanosecond )` -pub(crate) fn iso_datetime_within_limits( - y: i32, - mo: i32, - d: i32, - h: i32, - m: i32, - sec: i32, - ms: i32, - mis: i32, - ns: i32, -) -> bool { - let iso = super::plain_date::iso::IsoDateRecord::new(y, mo, d); - assert!(iso.is_valid()); - - let ns = get_utc_epoch_nanos( - i64::from(y), - i64::from(mo), - i64::from(d), - i64::from(h), - i64::from(m), - i64::from(sec), - i64::from(ms), - i64::from(mis), - i64::from(ns), - ) - .to_f64(); - - let iso_min = super::ns_min_instant().to_f64() - super::NS_PER_DAY as f64; - let iso_max = super::ns_max_instant().to_f64() + super::NS_PER_DAY as f64; - - (iso_min..=iso_max).contains(&ns) -} +// See `IsoDateTimeRecord` diff --git a/boa_engine/src/builtins/temporal/plain_month_day/mod.rs b/boa_engine/src/builtins/temporal/plain_month_day/mod.rs index ec73ef48d6b..1987b7c015f 100644 --- a/boa_engine/src/builtins/temporal/plain_month_day/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_month_day/mod.rs @@ -52,7 +52,9 @@ impl BuiltInConstructor for PlainMonthDay { args: &[JsValue], context: &mut Context<'_>, ) -> JsResult { - Err(JsNativeError::range().with_message("Not yet implemented.").into()) + Err(JsNativeError::range() + .with_message("Not yet implemented.") + .into()) } } diff --git a/boa_engine/src/builtins/temporal/plain_time/mod.rs b/boa_engine/src/builtins/temporal/plain_time/mod.rs index 781b98e22e8..d7025b49289 100644 --- a/boa_engine/src/builtins/temporal/plain_time/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_time/mod.rs @@ -4,7 +4,7 @@ use crate::{ context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, property::Attribute, realm::Realm, - Context, JsObject, JsResult, JsSymbol, JsValue, JsNativeError, + Context, JsNativeError, JsObject, JsResult, JsSymbol, JsValue, }; use boa_profiler::Profiler; @@ -52,6 +52,8 @@ impl BuiltInConstructor for PlainTime { args: &[JsValue], context: &mut Context<'_>, ) -> JsResult { - Err(JsNativeError::range().with_message("Not yet implemented.").into()) + Err(JsNativeError::range() + .with_message("Not yet implemented.") + .into()) } } diff --git a/boa_engine/src/builtins/temporal/plain_year_month/mod.rs b/boa_engine/src/builtins/temporal/plain_year_month/mod.rs index 49acbc0f532..2a83de90163 100644 --- a/boa_engine/src/builtins/temporal/plain_year_month/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_year_month/mod.rs @@ -21,7 +21,17 @@ pub(crate) struct IsoYearMonthRecord { } impl IsoYearMonthRecord { - pub(crate) fn new(year: i32, month: i32, ref_day: i32) -> Self { + pub(crate) const fn year(&self) -> i32 { + self.year + } + + pub(crate) const fn month(&self) -> i32 { + self.month + } +} + +impl IsoYearMonthRecord { + pub(crate) const fn new(year: i32, month: i32, ref_day: i32) -> Self { Self { year, month, @@ -60,7 +70,7 @@ impl IsoYearMonthRecord { }) } "reject" => { - if month < 1 || month > 12 { + if !(1..=12).contains(&month) { return Err(JsNativeError::range() .with_message("month is not within the valid range.") .into()); @@ -76,38 +86,38 @@ impl IsoYearMonthRecord { } } - pub(crate) fn within_limits(&self) -> bool { - if self.year < -271821 || self.year > 275760 { + pub(crate) const fn within_limits(&self) -> bool { + if self.year < -271_821 || self.year > 275_760 { return false; } - if self.year == -271821 && self.month < 4 { + if self.year == -271_821 && self.month < 4 { return false; } - if self.year == 275760 && self.month > 9 { + if self.year == 275_760 && self.month > 9 { return true; } - return true; + true } fn is_valid_iso_date(&self) -> bool { - if self.month < 1 || self.month > 12 { + if !(1..=12).contains(&self.month) { return false; } let days_in_month = super::calendar::utils::iso_days_in_month(self.year, self.month); - if self.ref_day < 1 || self.ref_day > days_in_month { + if !(1..=days_in_month).contains(&self.ref_day) { return false; } true } - /// 9.5.4 BalanceISOYearMonth ( year, month ) + /// 9.5.4 `BalanceISOYearMonth ( year, month )` pub(crate) fn balance(&mut self) { - self.year = self.year + ((self.month - 1) / 12); + self.year += (self.month - 1) / 12; self.month = ((self.month - 1) % 12) + 1; } } @@ -354,7 +364,7 @@ impl PlainYearMonth { // ==== Abstract Operations ==== -// 9.5.2 RegulateISOYearMonth ( year, month, overflow ) +// 9.5.2 `RegulateISOYearMonth ( year, month, overflow )` pub(crate) fn regulate_iso_year_month( year: i32, month: i32, @@ -372,7 +382,7 @@ pub(crate) fn regulate_iso_year_month( "reject" => { // a. Assert: overflow is "reject". // b. If month < 1 or month > 12, throw a RangeError exception. - if month < 1 || month > 12 { + if !(1..=12).contains(&month) { return Err(JsNativeError::range() .with_message("month is not within the valid range.") .into()); @@ -386,7 +396,7 @@ pub(crate) fn regulate_iso_year_month( Ok((year, month)) } -// 9.5.5 CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ , newTarget ] ) +// 9.5.5 `CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ , newTarget ] )` pub(crate) fn create_temporal_year_month( year_month_record: IsoYearMonthRecord, calendar: JsValue, diff --git a/boa_engine/src/builtins/temporal/time_zone/mod.rs b/boa_engine/src/builtins/temporal/time_zone/mod.rs index 6dbcddb32ec..7c25bb92f37 100644 --- a/boa_engine/src/builtins/temporal/time_zone/mod.rs +++ b/boa_engine/src/builtins/temporal/time_zone/mod.rs @@ -1,6 +1,7 @@ #![allow(dead_code)] -use boa_ast::temporal::{UtcOffset, TzIdentifier}; +use boa_ast::temporal::{TzIdentifier, UtcOffset}; +use chrono::format::parse; use crate::{ builtins::{ @@ -148,10 +149,12 @@ impl TimeZone { let _i = args.get_or_undefined(0); // TODO: to_temporal_instant is abstract operation for Temporal.Instant objects. // let instant = to_temporal_instant(i)?; - todo!() // 4. If timeZone.[[OffsetNanoseconds]] is not undefined, return 𝔽(timeZone.[[OffsetNanoseconds]]). // 5. Return 𝔽(GetNamedTimeZoneOffsetNanoseconds(timeZone.[[Identifier]], instant.[[Nanoseconds]])). + Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()) } pub(crate) fn get_offset_string_for( @@ -175,7 +178,9 @@ impl TimeZone { let _i = args.get_or_undefined(0); // TODO: to_temporal_instant is abstract operation for Temporal.Instant objects. // let instant = to_temporal_instant(i)?; - todo!() + Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()) // 4. Return ? GetOffsetStringFor(timeZone, instant). } @@ -185,7 +190,9 @@ impl TimeZone { _: &[JsValue], _: &mut Context<'_>, ) -> JsResult { - todo!() + Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()) } pub(crate) fn get_instant_for( @@ -193,7 +200,9 @@ impl TimeZone { _: &[JsValue], _: &mut Context<'_>, ) -> JsResult { - todo!() + Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()) } pub(crate) fn get_possible_instants_for( @@ -201,7 +210,9 @@ impl TimeZone { _: &[JsValue], _: &mut Context<'_>, ) -> JsResult { - todo!() + Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()) } pub(crate) fn get_next_transition( @@ -209,7 +220,9 @@ impl TimeZone { _: &[JsValue], _: &mut Context<'_>, ) -> JsResult { - todo!() + Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()) } pub(crate) fn get_previous_transition( @@ -217,7 +230,9 @@ impl TimeZone { _: &[JsValue], _: &mut Context<'_>, ) -> JsResult { - todo!() + Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()) } pub(crate) fn to_string( @@ -280,7 +295,15 @@ pub(super) fn create_temporal_time_zone( context: &mut Context<'_>, ) -> JsResult { // 1. If newTarget is not present, set newTarget to %Temporal.TimeZone%. - let new_target = new_target.unwrap_or_else(|| todo!("%Temporal.TimeZone%")); + let new_target = new_target.unwrap_or_else(|| { + context + .realm() + .intrinsics() + .constructors() + .time_zone() + .prototype() + .into() + }); // 2. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.TimeZone.prototype%", « [[InitializedTemporalTimeZone]], [[Identifier]], [[OffsetNanoseconds]] »). let prototype = @@ -336,22 +359,24 @@ fn parse_timezone_offset_string(offset_string: &str, context: &mut Context<'_>) use boa_parser::temporal::{IsoCursor, TemporalTimeZoneString}; // 1. Let parseResult be ParseText(StringToCodePoints(offsetString), UTCOffset). - let parse_result = TemporalTimeZoneString::parse(&mut IsoCursor::new(offset_string.to_string()))?; + let parse_result = + TemporalTimeZoneString::parse(&mut IsoCursor::new(offset_string))?; // 2. Assert: parseResult is not a List of errors. // 3. Assert: parseResult contains a TemporalSign Parse Node. - let utc_offset = match parse_result { - TzIdentifier::UtcOffset(utc)=>utc, - _=> return Err(JsNativeError::typ().with_message("Offset string was not a valid offset").into()) + let TzIdentifier::UtcOffset(utc_offset) = parse_result else { + return Err(JsNativeError::typ() + .with_message("Offset string was not a valid offset") + .into()) }; // 4. Let parsedSign be the source text matched by the TemporalSign Parse Node contained within // parseResult. // 5. If parsedSign is the single code point U+002D (HYPHEN-MINUS) or U+2212 (MINUS SIGN), then let sign = utc_offset.sign; - // a. Let sign be -1. - // 6. Else, - // a. Let sign be 1. + // a. Let sign be -1. + // 6. Else, + // a. Let sign be 1. // 7. NOTE: Applications of StringToNumber below do not lose precision, since each of the parsed // values is guaranteed to be a sufficiently short string of decimal digits. @@ -379,7 +404,9 @@ fn parse_timezone_offset_string(offset_string: &str, context: &mut Context<'_>) // d. Let nanoseconds be ℝ(StringToNumber(nanosecondsString)). // 17. Return sign × (((hours × 60 + minutes) × 60 + seconds) × 10^9 + nanoseconds). - todo!() + Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()) } /// Abstract operation `FormatTimeZoneOffsetString ( offsetNanoseconds )` diff --git a/boa_engine/src/builtins/temporal/zoned_date_time/mod.rs b/boa_engine/src/builtins/temporal/zoned_date_time/mod.rs index 745b0c9f641..b31cfe12394 100644 --- a/boa_engine/src/builtins/temporal/zoned_date_time/mod.rs +++ b/boa_engine/src/builtins/temporal/zoned_date_time/mod.rs @@ -4,7 +4,7 @@ use crate::{ context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, property::Attribute, realm::Realm, - Context, JsBigInt, JsObject, JsResult, JsSymbol, JsValue, + Context, JsBigInt, JsNativeError, JsObject, JsResult, JsSymbol, JsValue, }; use boa_profiler::Profiler; @@ -49,7 +49,10 @@ impl BuiltInConstructor for ZonedDateTime { args: &[JsValue], context: &mut Context<'_>, ) -> JsResult { - todo!() + // TODO: Implement ZonedDateTime. + Err(JsNativeError::error() + .with_message("%ZonedDateTime% not yet implemented.") + .into()) } } @@ -75,7 +78,9 @@ pub(crate) fn add_zoned_date_time( // 9. Let intermediateDateTime be ? CreateTemporalDateTime(addedDate.[[ISOYear]], addedDate.[[ISOMonth]], addedDate.[[ISODay]], temporalDateTime.[[ISOHour]], temporalDateTime.[[ISOMinute]], temporalDateTime.[[ISOSecond]], temporalDateTime.[[ISOMillisecond]], temporalDateTime.[[ISOMicrosecond]], temporalDateTime.[[ISONanosecond]], calendar). // 10. Let intermediateInstant be ? GetInstantFor(timeZone, intermediateDateTime, "compatible"). // 11. Return ? AddInstant(intermediateInstant.[[Nanoseconds]], hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - todo!() + Err(JsNativeError::error() + .with_message("%ZonedDateTime% not yet implemented.") + .into()) } /// 6.5.7 `NanosecondsToDays ( nanoseconds, relativeTo )` @@ -121,5 +126,7 @@ pub(crate) fn nanoseconds_to_days( // 22. If nanoseconds > 0 and sign = -1, throw a RangeError exception. // 23. Assert: The inequality abs(nanoseconds) < abs(dayLengthNs) holds. // 24. Return the Record { [[Days]]: days, [[Nanoseconds]]: nanoseconds, [[DayLength]]: abs(dayLengthNs) }. - todo!() + Err(JsNativeError::error() + .with_message("%ZonedDateTime% not yet implemented.") + .into()) } diff --git a/boa_engine/src/context/intrinsics.rs b/boa_engine/src/context/intrinsics.rs index 5309d05eab1..7899582e2b1 100644 --- a/boa_engine/src/context/intrinsics.rs +++ b/boa_engine/src/context/intrinsics.rs @@ -849,6 +849,7 @@ impl StandardConstructors { /// /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.instant #[inline] + #[must_use] #[cfg(feature = "temporal")] pub const fn instant(&self) -> &StandardConstructor { &self.instant @@ -861,6 +862,7 @@ impl StandardConstructors { /// /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime #[inline] + #[must_use] #[cfg(feature = "temporal")] pub const fn plain_date_time(&self) -> &StandardConstructor { &self.plain_date_time @@ -873,6 +875,7 @@ impl StandardConstructors { /// /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaindate #[inline] + #[must_use] #[cfg(feature = "temporal")] pub const fn plain_date(&self) -> &StandardConstructor { &self.plain_date @@ -885,6 +888,7 @@ impl StandardConstructors { /// /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaintime #[inline] + #[must_use] #[cfg(feature = "temporal")] pub const fn plain_time(&self) -> &StandardConstructor { &self.plain_time @@ -897,6 +901,7 @@ impl StandardConstructors { /// /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth #[inline] + #[must_use] #[cfg(feature = "temporal")] pub const fn plain_year_month(&self) -> &StandardConstructor { &self.plain_year_month @@ -909,6 +914,7 @@ impl StandardConstructors { /// /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-plainmonthday-constructor #[inline] + #[must_use] #[cfg(feature = "temporal")] pub const fn plain_month_day(&self) -> &StandardConstructor { &self.plain_month_day @@ -921,6 +927,7 @@ impl StandardConstructors { /// /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal=timezone-constructor #[inline] + #[must_use] #[cfg(feature = "temporal")] pub const fn time_zone(&self) -> &StandardConstructor { &self.time_zone @@ -933,6 +940,7 @@ impl StandardConstructors { /// /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-duration-constructor #[inline] + #[must_use] #[cfg(feature = "temporal")] pub const fn duration(&self) -> &StandardConstructor { &self.duration @@ -945,6 +953,7 @@ impl StandardConstructors { /// /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-zoneddatetime-constructor #[inline] + #[must_use] #[cfg(feature = "temporal")] pub const fn zoned_date_time(&self) -> &StandardConstructor { &self.zoned_date_time @@ -957,6 +966,7 @@ impl StandardConstructors { /// /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-zoneddatetime-constructor #[inline] + #[must_use] #[cfg(feature = "temporal")] pub const fn calendar(&self) -> &StandardConstructor { &self.calendar @@ -1239,6 +1249,7 @@ impl IntrinsicObjects { /// /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-objects #[cfg(feature = "temporal")] + #[must_use] #[inline] pub fn temporal(&self) -> JsObject { self.temporal.clone() diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index 6f5e606df9d..5eb1952e76d 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -726,6 +726,7 @@ impl JsObject { /// /// Panics if the object is currently mutably borrowed. #[inline] + #[must_use] #[track_caller] #[cfg(feature = "temporal")] pub fn is_duration(&self) -> bool { @@ -738,6 +739,7 @@ impl JsObject { /// /// Panics if the object is currently mutably borrowed. #[inline] + #[must_use] #[track_caller] #[cfg(feature = "temporal")] pub fn is_time_zone(&self) -> bool { @@ -750,6 +752,7 @@ impl JsObject { /// /// Panics if the object is currently mutably borrowed. #[inline] + #[must_use] #[track_caller] #[cfg(feature = "temporal")] pub fn is_plain_date_time(&self) -> bool { @@ -762,6 +765,7 @@ impl JsObject { /// /// Panics if the object is currently mutably borrowed. #[inline] + #[must_use] #[track_caller] #[cfg(feature = "temporal")] pub fn is_plain_date(&self) -> bool { @@ -774,6 +778,7 @@ impl JsObject { /// /// Panics if the object is currently mutably borrowed. #[inline] + #[must_use] #[track_caller] #[cfg(feature = "temporal")] pub fn is_plain_year_month(&self) -> bool { @@ -786,6 +791,7 @@ impl JsObject { /// /// Panics if the object is currently mutably borrowed. #[inline] + #[must_use] #[track_caller] #[cfg(feature = "temporal")] pub fn is_plain_month_day(&self) -> bool { @@ -798,6 +804,7 @@ impl JsObject { /// /// Panics if the object is currently mutably borrowed. #[inline] + #[must_use] #[track_caller] #[cfg(feature = "temporal")] pub fn is_zoned_date_time(&self) -> bool { @@ -810,6 +817,7 @@ impl JsObject { /// /// Panics if the object is currently mutably borrowed. #[inline] + #[must_use] #[track_caller] #[cfg(feature = "temporal")] pub fn is_calendar(&self) -> bool { diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 016e61f6d41..584c711d38e 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -1963,6 +1963,7 @@ impl Object { /// Gets the `TimeZone` data if the object is a `Temporal.TimeZone`. #[inline] + #[must_use] #[cfg(feature = "temporal")] pub fn as_time_zone(&self) -> Option<&TimeZone> { match self.kind { @@ -1973,13 +1974,15 @@ impl Object { /// Checks if the object is a `TimeZone` object. #[inline] + #[must_use] #[cfg(feature = "temporal")] - pub fn is_time_zone(&self) -> bool { + pub const fn is_time_zone(&self) -> bool { matches!(self.kind, ObjectKind::TimeZone(_)) } /// Gets a mutable reference to `Instant` data if the object is a `Temporal.Instant`. #[inline] + #[must_use] #[cfg(feature = "temporal")] pub fn as_instant_mut(&mut self) -> Option<&mut Instant> { match &mut self.kind { @@ -1990,8 +1993,9 @@ impl Object { /// Gets the `Instant` data if the object is a `Temporal.Instant`. #[inline] + #[must_use] #[cfg(feature = "temporal")] - pub fn as_instant(&self) -> Option<&Instant> { + pub const fn as_instant(&self) -> Option<&Instant> { match &self.kind { ObjectKind::Instant(instant) => Some(instant), _ => None, @@ -2000,13 +2004,15 @@ impl Object { /// Checks if the object is a `Duration` object. #[inline] + #[must_use] #[cfg(feature = "temporal")] - pub fn is_duration(&self) -> bool { + pub const fn is_duration(&self) -> bool { matches!(self.kind, ObjectKind::Duration(_)) } /// Gets a mutable reference to `Duration` data if the object is a `Temporal.Duration`. #[inline] + #[must_use] #[cfg(feature = "temporal")] pub fn as_duration_mut(&mut self) -> Option<&mut Duration> { match &mut self.kind { @@ -2017,8 +2023,9 @@ impl Object { /// Gets the `Duration` data if the object is a `Temporal.Duration`. #[inline] + #[must_use] #[cfg(feature = "temporal")] - pub fn as_duration(&self) -> Option<&Duration> { + pub const fn as_duration(&self) -> Option<&Duration> { match &self.kind { ObjectKind::Duration(dur) => Some(dur), _ => None, @@ -2027,15 +2034,17 @@ impl Object { /// Checks if object is a `PlainDateTime` object. #[inline] + #[must_use] #[cfg(feature = "temporal")] - pub fn is_plain_date_time(&self) -> bool { + pub const fn is_plain_date_time(&self) -> bool { matches!(self.kind, ObjectKind::PlainDateTime(_)) } /// Gets a reference to `PlainDateTime` data if the object is a `Temporal.PlainDateTime`. #[inline] + #[must_use] #[cfg(feature = "temporal")] - pub fn as_plain_date_time(&self) -> Option<&PlainDateTime> { + pub const fn as_plain_date_time(&self) -> Option<&PlainDateTime> { match &self.kind { ObjectKind::PlainDateTime(date) => Some(date), _ => None, @@ -2044,13 +2053,15 @@ impl Object { /// Checks if object is a `PlainDate` object. #[inline] + #[must_use] #[cfg(feature = "temporal")] - pub fn is_plain_date(&self) -> bool { + pub const fn is_plain_date(&self) -> bool { matches!(self.kind, ObjectKind::PlainDate(_)) } /// Gets a mutable reference to `PlainDate` data if the object is a `Temporal.PlainDate`. #[inline] + #[must_use] #[cfg(feature = "temporal")] pub fn as_plain_date_mut(&mut self) -> Option<&mut PlainDate> { match &mut self.kind { @@ -2061,8 +2072,9 @@ impl Object { /// Gets the `PlainDate` data if the object is a `Temporal.PlainDate`. #[inline] + #[must_use] #[cfg(feature = "temporal")] - pub fn as_plain_date(&self) -> Option<&PlainDate> { + pub const fn as_plain_date(&self) -> Option<&PlainDate> { match &self.kind { ObjectKind::PlainDate(date) => Some(date), _ => None, @@ -2071,13 +2083,15 @@ impl Object { /// Checks if object is a `PlainYearMonth` object. #[inline] + #[must_use] #[cfg(feature = "temporal")] - pub fn is_plain_year_month(&self) -> bool { + pub const fn is_plain_year_month(&self) -> bool { matches!(self.kind, ObjectKind::PlainYearMonth(_)) } /// Gets a mutable reference to `PlainYearMonth` data if the object is a `Temporal.PlainYearMonth`. #[inline] + #[must_use] #[cfg(feature = "temporal")] pub fn as_plain_year_month_mut(&mut self) -> Option<&mut PlainYearMonth> { match &mut self.kind { @@ -2088,8 +2102,9 @@ impl Object { /// Gets the `PlainYearMonth` data if the object is a `Temporal.PlainYearMonth`. #[inline] + #[must_use] #[cfg(feature = "temporal")] - pub fn as_plain_year_month(&self) -> Option<&PlainYearMonth> { + pub const fn as_plain_year_month(&self) -> Option<&PlainYearMonth> { match &self.kind { ObjectKind::PlainYearMonth(ym) => Some(ym), _ => None, @@ -2098,15 +2113,17 @@ impl Object { /// Checks if object is a `PlainMonthDay` object. #[inline] + #[must_use] #[cfg(feature = "temporal")] - pub fn is_plain_month_day(&self) -> bool { + pub const fn is_plain_month_day(&self) -> bool { matches!(self.kind, ObjectKind::PlainMonthDay(_)) } /// Gets the `PlainMonthDay` data if the object is a `Temporal.PlainMonthDay`. #[inline] + #[must_use] #[cfg(feature = "temporal")] - pub fn as_plain_month_day(&self) -> Option<&PlainMonthDay> { + pub const fn as_plain_month_day(&self) -> Option<&PlainMonthDay> { match &self.kind { ObjectKind::PlainMonthDay(md) => Some(md), _ => None, @@ -2115,6 +2132,7 @@ impl Object { /// Gets a mutable reference to `PlainMonthDay` data if the object is a `Temporal.PlainMonthDay`. #[inline] + #[must_use] #[cfg(feature = "temporal")] pub fn as_plain_month_day_mut(&mut self) -> Option<&mut PlainMonthDay> { match &mut self.kind { @@ -2125,8 +2143,9 @@ impl Object { /// Gets the `PlainDate` data if the object is a `Temporal.PlainDate`. #[inline] + #[must_use] #[cfg(feature = "temporal")] - pub fn as_zoned_date_time(&self) -> Option<&ZonedDateTime> { + pub const fn as_zoned_date_time(&self) -> Option<&ZonedDateTime> { match &self.kind { ObjectKind::ZonedDateTime(zdt) => Some(zdt), _ => None, @@ -2135,20 +2154,23 @@ impl Object { /// Checks if the object is a `ZonedDateTime` object. #[inline] + #[must_use] #[cfg(feature = "temporal")] - pub fn is_zoned_date_time(&self) -> bool { + pub const fn is_zoned_date_time(&self) -> bool { matches!(self.kind, ObjectKind::ZonedDateTime(_)) } /// Checks if the object is a `Calendar` object. #[inline] + #[must_use] #[cfg(feature = "temporal")] - pub fn is_calendar(&self) -> bool { + pub const fn is_calendar(&self) -> bool { matches!(self.kind, ObjectKind::Calendar(_)) } /// Gets the `Calendar` data if the object is a `Temporal.Calendar`. #[inline] + #[must_use] #[cfg(feature = "temporal")] pub fn as_calendar(&self) -> Option<&Calendar> { match &self.kind { @@ -2333,6 +2355,7 @@ pub struct FunctionObjectBuilder { impl FunctionObjectBuilder { /// Create a new `FunctionBuilder` for creating a native function. #[inline] + #[must_use] pub fn new(realm: Realm, function: NativeFunction) -> Self { Self { realm, @@ -2432,6 +2455,7 @@ pub struct ObjectInitializer { impl ObjectInitializer { /// Create a new `ObjectBuilder`. #[inline] + #[must_use] pub fn new(realm: Realm) -> Self { let object = JsObject::with_object_proto(realm.intrinsics()); Self { realm, object } diff --git a/boa_parser/src/temporal/annotations.rs b/boa_parser/src/temporal/annotations.rs index 385025e5713..3fecc3a479e 100644 --- a/boa_parser/src/temporal/annotations.rs +++ b/boa_parser/src/temporal/annotations.rs @@ -1,43 +1,86 @@ /// Parsing for Temporal's `Annotations`. - use crate::{ error::{Error, ParseResult}, lexer::Error as LexError, - temporal::{IsoCursor, grammar::*} + temporal::{grammar::{is_a_key_char, is_a_key_leading_char, is_annotation_value_component}, time_zone, IsoCursor}, }; use boa_ast::{ - Position, - temporal::KeyValueAnnotation, + temporal::{KeyValueAnnotation, TimeZoneAnnotation}, + Position, Span, }; use rustc_hash::FxHashMap; +/// Strictly a Parsing Intermediary for the checking the common annotation backing. +pub(crate) struct AnnotationSet { + pub(crate) tz: Option, + pub(crate) annotations: Option>, +} + +/// Parse a `TimeZoneAnnotation` `Annotations` set +#[allow(clippy::cast_possible_truncation)] +pub(crate) fn parse_annotation_set( + zoned: bool, + cursor: &mut IsoCursor, +) -> ParseResult { + // Parse the first annotation. + let tz_annotation = time_zone::parse_ambiguous_tz_annotation(cursor)?; + + if tz_annotation.is_none() && zoned { + return Err(Error::unexpected( + "Annotation", + Span::new( + Position::new(1, (cursor.pos() + 1) as u32), + Position::new(1, (cursor.pos() + 2) as u32), + ), + "iso8601 ZonedDateTime requires a TimeZoneAnnotation.", + )); + } + + // Parse any `Annotations` + let annotations = cursor.check_or(false, |ch| ch == '['); + + if annotations { + let annotations = parse_annotations(cursor)?; + return Ok(AnnotationSet { + tz: tz_annotation, + annotations: Some(annotations), + }); + } + + Ok(AnnotationSet { + tz: tz_annotation, + annotations: None, + }) +} + /// Parse any number of `KeyValueAnnotation`s -pub(crate) fn parse_annotations(cursor: &mut IsoCursor) -> ParseResult> { +pub(crate) fn parse_annotations( + cursor: &mut IsoCursor, +) -> ParseResult> { let mut hash_map = FxHashMap::default(); while let Some(annotation_open) = cursor.peek() { - if *annotation_open == '[' { + if annotation_open == '[' { let kv = parse_kv_annotation(cursor)?; - if !hash_map.contains_key(&kv.key) { - hash_map.insert(kv.key, (kv.critical, kv.value)); + if let std::collections::hash_map::Entry::Vacant(e) = hash_map.entry(kv.key) { + e.insert((kv.critical, kv.value)); } } else { break; } } - return Ok(hash_map); + Ok(hash_map) } /// Parse an annotation with an `AnnotationKey`=`AnnotationValue` pair. +#[allow(clippy::cast_possible_truncation)] fn parse_kv_annotation(cursor: &mut IsoCursor) -> ParseResult { - assert!(*cursor.peek().unwrap() == '['); - // TODO: remove below if unneeded. - let _start = Position::new(1, (cursor.pos() + 1) as u32); + debug_assert!(cursor.check_or(false, |ch| ch == '[')); let potential_critical = cursor.next().ok_or_else(|| Error::AbruptEnd)?; - let (leading_char, critical) = if *potential_critical == '!' { + let (leading_char, critical) = if potential_critical == '!' { (cursor.next().ok_or_else(|| Error::AbruptEnd)?, true) } else { (potential_critical, false) @@ -47,38 +90,38 @@ fn parse_kv_annotation(cursor: &mut IsoCursor) -> ParseResult ParseResult { let key_start = cursor.pos(); while let Some(potential_key_char) = cursor.next() { // End of key. - if *potential_key_char == '=' { + if potential_key_char == '=' { // Return found key return Ok(cursor.slice(key_start, cursor.pos())); } @@ -87,7 +130,8 @@ fn parse_annotation_key(cursor: &mut IsoCursor) -> ParseResult { return Err(LexError::syntax( "Invalid AnnotationKey Character", Position::new(1, (cursor.pos() + 1) as u32), - ).into()); + ) + .into()); } } @@ -95,24 +139,25 @@ fn parse_annotation_key(cursor: &mut IsoCursor) -> ParseResult { } /// Parse an `AnnotationValue`. +#[allow(clippy::cast_possible_truncation)] fn parse_annotation_value(cursor: &mut IsoCursor) -> ParseResult { let value_start = cursor.pos(); while let Some(potential_value_char) = cursor.next() { - if *potential_value_char == ']' { + if potential_value_char == ']' { // Return the determined AnnotationValue. return Ok(cursor.slice(value_start, cursor.pos())); } - if *potential_value_char == '-' { + if potential_value_char == '-' { if !cursor .peek_n(1) - .map(|ch| is_annotation_value_component(ch)) - .unwrap_or(false) + .map_or(false, is_annotation_value_component) { return Err(LexError::syntax( "Missing AttributeValueComponent after '-'", Position::new(1, (cursor.pos() + 1) as u32), - ).into()); + ) + .into()); } cursor.advance(); continue; @@ -122,9 +167,10 @@ fn parse_annotation_value(cursor: &mut IsoCursor) -> ParseResult { return Err(LexError::syntax( "Invalid character in AnnotationValue", Position::new(1, (value_start + cursor.pos() + 1) as u32), - ).into()); + ) + .into()); } } Err(Error::AbruptEnd) -} \ No newline at end of file +} diff --git a/boa_parser/src/temporal/date_time.rs b/boa_parser/src/temporal/date_time.rs index fee11442696..d007bdacca8 100644 --- a/boa_parser/src/temporal/date_time.rs +++ b/boa_parser/src/temporal/date_time.rs @@ -3,40 +3,35 @@ use crate::{ error::{Error, ParseResult}, lexer::Error as LexError, - temporal::{ - IsoCursor, - grammar::*, - time_zone, - time, - annotations, - } + temporal::{annotations, grammar::{is_date_time_separator, is_sign, is_utc_designator}, time, time_zone, IsoCursor}, }; use boa_ast::{ + temporal::{DateRecord, DateTimeRecord, IsoParseRecord}, Position, Span, - temporal::{AnnotatedDateTime, DateRecord,DateTimeRecord} }; /// `AnnotatedDateTime` /// /// Defined in Temporal Proposal as follows: /// -/// AnnotatedDateTime[Zoned] : -/// [~Zoned] DateTime TimeZoneAnnotation(opt) Annotations(opt) -/// [+Zoned] DateTime TimeZoneAnnotation Annotations(opt) +/// `AnnotatedDateTime`[Zoned] : +/// [~Zoned] `DateTime` `TimeZoneAnnotation`(opt) `Annotations`(opt) +/// [+Zoned] `DateTime` `TimeZoneAnnotation` `Annotations`(opt) +#[allow(clippy::cast_possible_truncation)] pub(crate) fn parse_annotated_date_time( zoned: bool, cursor: &mut IsoCursor, -) -> ParseResult { +) -> ParseResult { let date_time = parse_date_time(cursor)?; // Peek Annotation presence // Throw error if annotation does not exist and zoned is true, else return. - let annotation_check = cursor.peek().map(|ch| *ch == '[').unwrap_or(false); + let annotation_check = cursor.check_or(false, |ch| ch == '['); if !annotation_check { if zoned { return Err(Error::expected( - ["TimeZoneAnnotation".into()], + ["TimeZoneAnnotation".into()], "No Annotation", Span::new( Position::new(1, (cursor.pos() + 1) as u32), @@ -45,40 +40,32 @@ pub(crate) fn parse_annotated_date_time( "iso8601 grammar", )); } - return Ok(AnnotatedDateTime { date_time, tz_annotation: None, annotations: None }); - } - - // Parse the first annotation. - let tz_annotation = time_zone::parse_ambiguous_tz_annotation(cursor)?; - - if tz_annotation.is_none() && zoned { - return Err(Error::unexpected( - "Annotation", - Span::new(Position::new(1, (cursor.pos() + 1) as u32), Position::new(1, (cursor.pos() + 2) as u32)), - "iso8601 ZonedDateTime requires a TimeZoneAnnotation.", - )); + return Ok(IsoParseRecord { + date: date_time.date, + time: date_time.time, + offset: date_time.offset, + tz_annotation: None, + annotations: None, + }); } - // Parse any `Annotations` - let annotations = cursor.peek().map(|ch| *ch == '[').unwrap_or(false); - - if annotations { - let annotations = annotations::parse_annotations(cursor)?; - return Ok(AnnotatedDateTime { date_time, tz_annotation, annotations: Some(annotations) }) - } + let annotation_set = annotations::parse_annotation_set(zoned, cursor)?; - Ok(AnnotatedDateTime { date_time, tz_annotation, annotations: None }) + Ok(IsoParseRecord { + date: date_time.date, + time: date_time.time, + offset: date_time.offset, + tz_annotation: annotation_set.tz, + annotations: annotation_set.annotations, + }) } +/// Parses a `DateTime` record. fn parse_date_time(cursor: &mut IsoCursor) -> ParseResult { let date = parse_date(cursor)?; // If there is no `DateTimeSeparator`, return date early. - if !cursor - .peek() - .map(|c| is_date_time_separator(c)) - .unwrap_or(false) - { + if !cursor.check_or(false, is_date_time_separator) { return Ok(DateTimeRecord { date, time: None, @@ -91,8 +78,7 @@ fn parse_date_time(cursor: &mut IsoCursor) -> ParseResult { let time = time::parse_time_spec(cursor)?; let offset = if cursor - .peek() - .map(|ch| is_sign(ch) || is_utc_designator(ch)) + .check(|ch| is_sign(ch) || is_utc_designator(ch)) .unwrap_or(false) { Some(time_zone::parse_date_time_utc(cursor)?) @@ -100,16 +86,19 @@ fn parse_date_time(cursor: &mut IsoCursor) -> ParseResult { None }; - Ok(DateTimeRecord { date, time: Some(time), offset }) + Ok(DateTimeRecord { + date, + time: Some(time), + offset, + }) } - -/// Parse `Date` +/// Parses `Date` record. +#[allow(clippy::cast_possible_truncation)] fn parse_date(cursor: &mut IsoCursor) -> ParseResult { let year = parse_date_year(cursor)?; let divided = cursor - .peek() - .map(|ch| *ch == '-') + .check(|ch| ch == '-') .ok_or_else(|| Error::AbruptEnd)?; if divided { @@ -118,12 +107,13 @@ fn parse_date(cursor: &mut IsoCursor) -> ParseResult { let month = parse_date_month(cursor)?; - if cursor.peek().map(|ch| *ch == '-').unwrap_or(false) { + if cursor.check_or(false, |ch| ch == '-') { if !divided { return Err(LexError::syntax( "Invalid date separator", Position::new(1, (cursor.pos() + 1) as u32), - ).into()); + ) + .into()); } cursor.advance(); } @@ -133,13 +123,115 @@ fn parse_date(cursor: &mut IsoCursor) -> ParseResult { Ok(DateRecord { year, month, day }) } +/// Determines if the string can be parsed as a `DateSpecYearMonth`. +pub(crate) fn peek_year_month(cursor: &mut IsoCursor) -> ParseResult { + let mut ym_peek = if is_sign(cursor.peek().ok_or_else(|| Error::AbruptEnd)?) { + 7 + } else { + 4 + }; + + if cursor + .peek_n(ym_peek) + .map(|ch| ch == '-') + .ok_or_else(|| Error::AbruptEnd)? + { + ym_peek += 1; + } + + ym_peek += 2; + + if cursor.peek_n(ym_peek).map_or(true, |ch| ch == '[') { + Ok(true) + } else { + Ok(false) + } +} + +/// Parses a `DateSpecYearMonth` +pub(crate) fn parse_year_month(cursor: &mut IsoCursor) -> ParseResult<(i32, i32)> { + let year = parse_date_year(cursor)?; + + if cursor.check_or(false, |ch| ch == '-') { + cursor.advance(); + } + + let month = parse_date_month(cursor)?; + + Ok((year, month)) +} + +/// Determines if the string can be parsed as a `DateSpecYearMonth`. +pub(crate) fn peek_month_day(cursor: &mut IsoCursor) -> ParseResult { + let mut md_peek = if cursor + .peek_n(1) + .map(|ch| ch == '-') + .ok_or_else(|| Error::AbruptEnd)? + { + 4 + } else { + 2 + }; + + if cursor + .peek_n(md_peek) + .map(|ch| ch == '-') + .ok_or_else(|| Error::AbruptEnd)? + { + md_peek += 1; + } + + md_peek += 2; + + if cursor.peek_n(md_peek).map_or(true, |ch| ch == '[') { + Ok(true) + } else { + Ok(false) + } +} + +/// Parses a `DateSpecMonthDay` +#[allow(clippy::cast_possible_truncation)] +pub(crate) fn parse_month_day(cursor: &mut IsoCursor) -> ParseResult<(i32, i32)> { + let dash_one = cursor + .check(|ch| ch == '-') + .ok_or_else(|| Error::AbruptEnd)?; + let dash_two = cursor + .peek_n(1) + .map(|ch| ch == '-') + .ok_or_else(|| Error::AbruptEnd)?; + + if dash_two && dash_one { + cursor.advance_n(2); + } else if dash_two && !dash_one { + return Err(LexError::syntax( + "MonthDay requires two dashes", + Position::new(1, cursor.pos() as u32), + ) + .into()); + } + + let month = parse_date_month(cursor)?; + if cursor.check_or(false, |ch| ch == '-') { + cursor.advance(); + } + + let day = parse_date_day(cursor)?; + + Ok((month, day)) +} + // ==== Unit Parsers ==== -// (referring to Year, month, day, hour, sec, etc...) +#[allow(clippy::cast_possible_truncation)] fn parse_date_year(cursor: &mut IsoCursor) -> ParseResult { if is_sign(cursor.peek().ok_or_else(|| Error::AbruptEnd)?) { let year_start = cursor.pos(); - let sign = if *cursor.peek().unwrap() == '+' { 1 } else { -1 }; + let sign = if cursor.check_or(false, |ch| ch == '+') { + 1 + } else { + -1 + }; cursor.advance(); @@ -155,7 +247,9 @@ fn parse_date_year(cursor: &mut IsoCursor) -> ParseResult { } let year_string = cursor.slice(year_start + 1, cursor.pos()); - let year_value = year_string.parse::().map_err(|e| Error::general(e.to_string(), Position::new(1, (year_start + 1) as u32)))?; + let year_value = year_string.parse::().map_err(|e| { + Error::general(e.to_string(), Position::new(1, (year_start + 1) as u32)) + })?; // 13.30.1 Static Semantics: Early Errors // @@ -178,17 +272,21 @@ fn parse_date_year(cursor: &mut IsoCursor) -> ParseResult { return Err(LexError::syntax( "DateYear must contain digit", Position::new(1, (cursor.pos() + 1) as u32), - ).into()); + ) + .into()); } cursor.advance(); } let year_string = cursor.slice(year_start, cursor.pos()); - let year_value = year_string.parse::().map_err(|e| Error::general(e.to_string(), Position::new(1, (cursor.pos() + 1) as u32)))?; + let year_value = year_string + .parse::() + .map_err(|e| Error::general(e.to_string(), Position::new(1, (cursor.pos() + 1) as u32)))?; - return Ok(year_value); + Ok(year_value) } +#[allow(clippy::cast_possible_truncation)] fn parse_date_month(cursor: &mut IsoCursor) -> ParseResult { let month_value = cursor .slice(cursor.pos(), cursor.pos() + 2) @@ -198,12 +296,14 @@ fn parse_date_month(cursor: &mut IsoCursor) -> ParseResult { return Err(LexError::syntax( "DateMonth must be in a range of 1-12", Position::new(1, (cursor.pos() + 1) as u32), - ).into()); + ) + .into()); } cursor.advance_n(2); Ok(month_value) } +#[allow(clippy::cast_possible_truncation)] fn parse_date_day(cursor: &mut IsoCursor) -> ParseResult { let day_value = cursor .slice(cursor.pos(), cursor.pos() + 2) @@ -213,7 +313,8 @@ fn parse_date_day(cursor: &mut IsoCursor) -> ParseResult { return Err(LexError::syntax( "DateDay must be in a range of 1-31", Position::new(1, (cursor.pos() + 1) as u32), - ).into()); + ) + .into()); } cursor.advance_n(2); Ok(day_value) diff --git a/boa_parser/src/temporal/grammar.rs b/boa_parser/src/temporal/grammar.rs index 68413e0bc76..425a7c6d7d1 100644 --- a/boa_parser/src/temporal/grammar.rs +++ b/boa_parser/src/temporal/grammar.rs @@ -2,54 +2,54 @@ /// Checks if char is a `AKeyLeadingChar`. #[inline] -pub(crate) fn is_a_key_leading_char(ch: &char) -> bool { - ch.is_ascii_lowercase() || *ch == '_' +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) fn is_a_key_char(ch: &char) -> bool { - is_a_key_leading_char(ch) || ch.is_ascii_digit() || *ch == '-' +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) fn is_annotation_value_component(ch: &char) -> bool { +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) fn is_tz_leading_char(ch: &char) -> bool { - ch.is_ascii_alphabetic() || *ch == '_' || *ch == '.' +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) fn is_tz_char(ch: &char) -> bool { - is_tz_leading_char(ch) || ch.is_ascii_digit() || *ch == '-' || *ch == '+' +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 an ascii sign. -pub(crate) fn is_ascii_sign(ch: &char) -> bool { - *ch == '+' || *ch == '-' +pub(crate) const fn is_ascii_sign(ch: char) -> bool { + ch == '+' || ch == '-' } /// Checks if char is an ascii sign or U+2212 -pub(crate) fn is_sign(ch: &char) -> bool { - is_ascii_sign(ch) || *ch == '\u{2212}' +pub(crate) const fn is_sign(ch: char) -> bool { + is_ascii_sign(ch) || ch == '\u{2212}' } /// Checks if char is a `DateTimeSeparator`. -pub(crate) fn is_date_time_separator(ch: &char) -> bool { - *ch == 'T' || *ch == 't' || *ch == '\u{0020}' +pub(crate) const fn is_date_time_separator(ch: char) -> bool { + ch == 'T' || ch == 't' || ch == '\u{0020}' } /// Checks if char is a `UtcDesignator`. -pub(crate) fn is_utc_designator(ch: &char) -> bool { - *ch == 'Z' || *ch == 'z' +pub(crate) const fn is_utc_designator(ch: char) -> bool { + ch == 'Z' || ch == 'z' } /// Checks if char is a `DecimalSeparator`. -pub(crate) fn is_decimal_separator(ch: &char) -> bool { - *ch == '.' || *ch == ',' -} \ No newline at end of file +pub(crate) const fn is_decimal_separator(ch: char) -> bool { + ch == '.' || ch == ',' +} diff --git a/boa_parser/src/temporal/mod.rs b/boa_parser/src/temporal/mod.rs index 4ef6bfa3217..d577025ee8e 100644 --- a/boa_parser/src/temporal/mod.rs +++ b/boa_parser/src/temporal/mod.rs @@ -1,16 +1,14 @@ //! Implementation of ISO8601 grammar lexing/parsing -#[allow(unused_variables)] - use crate::error::ParseResult; +mod annotations; +mod date_time; +mod grammar; mod tests; mod time; mod time_zone; -mod grammar; -mod date_time; -mod annotations; -use boa_ast::temporal::{AnnotatedDateTime, TzIdentifier}; +use boa_ast::temporal::{DateRecord, IsoParseRecord, TzIdentifier}; // TODO: optimize where possible. // @@ -24,8 +22,13 @@ use boa_ast::temporal::{AnnotatedDateTime, TzIdentifier}; pub struct TemporalDateTimeString; impl TemporalDateTimeString { - /// Parses a targeted `DateTimeString`. - pub fn parse(zoned: bool, cursor: &mut IsoCursor) -> ParseResult { + /// Parses a targeted `DateTimeString` + /// + /// # Errors + /// + /// The parse will error if the provided target is not valid + /// ISO8601 grammar.. + pub fn parse(zoned: bool, cursor: &mut IsoCursor) -> ParseResult { date_time::parse_annotated_date_time(zoned, cursor) } } @@ -35,12 +38,95 @@ impl TemporalDateTimeString { pub struct TemporalTimeZoneString; impl TemporalTimeZoneString { - /// Parses a targeted `TimeZoneString`. + /// Parses a targeted `TimeZoneString` + /// + /// # Errors + /// + /// The parse will error if the provided target is not valid + /// ISO8601 grammar.. pub fn parse(cursor: &mut IsoCursor) -> ParseResult { time_zone::parse_tz_identifier(cursor) } } +/// Parse a `TemporalYearMonthString` +#[derive(Debug, Clone, Copy)] +pub struct TemporalYearMonthString; + +impl TemporalYearMonthString { + /// Parses a targeted `YearMonthString`. + /// + /// # Errors + /// + /// The parse will error if the provided target is not valid + /// ISO8601 grammar. + pub fn parse(cursor: &mut IsoCursor) -> ParseResult { + if date_time::peek_year_month(cursor)? { + let ym = date_time::parse_year_month(cursor)?; + + let (tz_annotation, annotations) = if cursor.check_or(false, |ch| ch == '[') { + let set = annotations::parse_annotation_set(false, cursor)?; + (set.tz, set.annotations) + } else { + (None, None) + }; + + return Ok(IsoParseRecord { + date: DateRecord { + year: ym.0, + month: ym.1, + day: 0, + }, + time: None, + offset: None, + tz_annotation, + annotations, + }); + } + + date_time::parse_annotated_date_time(false, cursor) + } +} + +/// Parse a `TemporalMonthDayString` +#[derive(Debug, Clone, Copy)] +pub struct TemporalMonthDayString; + +impl TemporalMonthDayString { + /// Parses a targeted `MonthDayString`. + /// + /// # Errors + /// + /// The parse will error if the provided target is not valid + /// ISO8601 grammar. + pub fn parse(cursor: &mut IsoCursor) -> ParseResult { + if date_time::peek_month_day(cursor)? { + let md = date_time::parse_month_day(cursor)?; + + let (tz_annotation, annotations) = if cursor.check_or(false, |ch| ch == '[') { + let set = annotations::parse_annotation_set(false, cursor)?; + (set.tz, set.annotations) + } else { + (None, None) + }; + + return Ok(IsoParseRecord { + date: DateRecord { + year: 0, + month: md.0, + day: md.1, + }, + time: None, + offset: None, + tz_annotation, + annotations, + }); + } + + date_time::parse_annotated_date_time(false, cursor) + } +} + // ==== Mini cursor implementation for ISO8601 targets ==== /// `IsoCursor` is a small cursor implementation for parsing ISO8601 grammar. @@ -52,7 +138,8 @@ pub struct IsoCursor { impl IsoCursor { /// Create a new cursor from a source `String` value. - pub fn new(source: String) -> Self { + #[must_use] + pub fn new(source: &str) -> Self { Self { pos: 0, source: source.chars().collect(), @@ -61,7 +148,7 @@ impl IsoCursor { /// Returns a string value from a slice of the cursor. fn slice(&self, start: usize, end: usize) -> String { - self.source[start..end].into_iter().collect() + self.source[start..end].iter().collect() } /// Get current position @@ -70,19 +157,34 @@ impl IsoCursor { } /// Peek the value at the current position. - fn peek(&self) -> Option<&char> { - self.source.get(self.pos) + fn peek(&self) -> Option { + if self.pos < self.source.len() { Some(self.source[self.pos]) } else { None } } /// Peek the value at n len from current. - fn peek_n(&self, n: usize) -> Option<&char> { - self.source.get(self.pos + n) + fn peek_n(&self, n: usize) -> Option { + if self.pos + n < self.source.len() { Some(self.source[self.pos]) } else { None } } + /// Returns boolean if current position passes check. + fn check(&self, f: F) -> Option + where + F: FnOnce(char) -> bool, + { + self.peek().map(f) + } + + /// Returns boolean if current position passes check or default if None. + fn check_or(&self, default: bool, f: F) -> bool + where + F: FnOnce(char) -> bool, + { + self.peek().map_or(default, f) + } /// Advances the cursor's position and returns the new character. - fn next(&mut self) -> Option<&char> { + fn next(&mut self) -> Option { self.advance(); - self.source.get(self.pos) + self.peek() } /// Advances the cursor's position by 1. diff --git a/boa_parser/src/temporal/tests.rs b/boa_parser/src/temporal/tests.rs index 83cea1c6c12..f7611f15880 100644 --- a/boa_parser/src/temporal/tests.rs +++ b/boa_parser/src/temporal/tests.rs @@ -1,20 +1,22 @@ - #[test] fn temporal_parser_basic() { use super::{IsoCursor, TemporalDateTimeString}; let basic = "20201108"; let basic_separated = "2020-11-08"; - let basic_result = TemporalDateTimeString::parse(false, &mut IsoCursor::new(basic.to_string())).unwrap(); + let basic_result = + TemporalDateTimeString::parse(false, &mut IsoCursor::new(basic)).unwrap(); - let sep_result = TemporalDateTimeString::parse(false, &mut IsoCursor::new(basic_separated.to_string())).unwrap(); + let sep_result = + TemporalDateTimeString::parse(false, &mut IsoCursor::new(basic_separated)) + .unwrap(); - assert_eq!(basic_result.date_time.date.year, 2020); - assert_eq!(basic_result.date_time.date.month, 11); - assert_eq!(basic_result.date_time.date.day, 8); - assert_eq!(basic_result.date_time.date.year, sep_result.date_time.date.year); - assert_eq!(basic_result.date_time.date.month, sep_result.date_time.date.month); - assert_eq!(basic_result.date_time.date.day, sep_result.date_time.date.day); + assert_eq!(basic_result.date.year, 2020); + assert_eq!(basic_result.date.month, 11); + assert_eq!(basic_result.date.day, 8); + assert_eq!(basic_result.date.year, sep_result.date.year); + assert_eq!(basic_result.date.month, sep_result.date.month); + assert_eq!(basic_result.date.day, sep_result.date.day); } #[test] @@ -23,15 +25,16 @@ fn temporal_date_time_max() { // Fractions not accurate, but for testing purposes. let date_time = "+002020-11-08T12:28:32.329402834-03:00:00.123456789[!America/Argentina/ComodRivadavia][!u-ca=iso8601]"; - let result = TemporalDateTimeString::parse(false, &mut IsoCursor::new(date_time.to_string())).unwrap(); + let result = + TemporalDateTimeString::parse(false, &mut IsoCursor::new(date_time)).unwrap(); - let time_results = &result.date_time.time.unwrap(); + let time_results = &result.time.unwrap(); assert_eq!(time_results.hour, 12); assert_eq!(time_results.minute, 28); assert_eq!(time_results.second, 32.329402834); - let offset_results = &result.date_time.offset.unwrap(); + let offset_results = &result.offset.unwrap(); assert_eq!(offset_results.sign, -1); assert_eq!(offset_results.hour, 3); @@ -43,14 +46,19 @@ fn temporal_date_time_max() { assert!(tz.critical); match &tz.tz { - boa_ast::temporal::TzIdentifier::TzIANAName(id) => assert_eq!(id, "America/Argentina/ComodRivadavia"), - _=> unreachable!(), + boa_ast::temporal::TzIdentifier::TzIANAName(id) => { + assert_eq!(id, "America/Argentina/ComodRivadavia") + } + _ => unreachable!(), } let annotations = &result.annotations.unwrap(); assert!(annotations.contains_key("u-ca")); - assert_eq!(annotations.get("u-ca"), Some(&(true, "iso8601".to_string()))); + assert_eq!( + annotations.get("u-ca"), + Some(&(true, "iso8601".to_string())) + ); } #[test] @@ -59,10 +67,12 @@ fn temporal_year_parsing() { let long = "+002020-11-08"; let bad_year = "-000000-11-08"; - let result_good = TemporalDateTimeString::parse(false, &mut IsoCursor::new(long.to_string())).unwrap(); - assert_eq!(result_good.date_time.date.year, 2020); + let result_good = + TemporalDateTimeString::parse(false, &mut IsoCursor::new(long)).unwrap(); + assert_eq!(result_good.date.year, 2020); - let err_result = TemporalDateTimeString::parse(false, &mut IsoCursor::new(bad_year.to_string())); + let err_result = + TemporalDateTimeString::parse(false, &mut IsoCursor::new(bad_year)); assert!(err_result.is_err()); } @@ -72,27 +82,76 @@ 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 = TemporalDateTimeString::parse(false, &mut IsoCursor::new(basic.to_string())).unwrap(); + let result = + TemporalDateTimeString::parse(false, &mut IsoCursor::new(basic)).unwrap(); if let Some(tz) = &result.tz_annotation { match &tz.tz { - boa_ast::temporal::TzIdentifier::TzIANAName(id) => assert_eq!(id, "America/Argentina/ComodRivadavia"), - _=> unreachable!(), + boa_ast::temporal::TzIdentifier::TzIANAName(id) => { + assert_eq!(id, "America/Argentina/ComodRivadavia") + } + _ => unreachable!(), } } if let Some(annotations) = &result.annotations { assert!(annotations.contains_key("u-ca")); - assert_eq!(annotations.get("u-ca"), Some(&(false, "iso8601".to_string()))) + assert_eq!( + annotations.get("u-ca"), + Some(&(false, "iso8601".to_string())) + ) } - let omit_result = TemporalDateTimeString::parse(false, &mut IsoCursor::new(omitted.to_string())).unwrap(); + let omit_result = + TemporalDateTimeString::parse(false, &mut IsoCursor::new(omitted)).unwrap(); assert!(&omit_result.tz_annotation.is_none()); if let Some(annotations) = &omit_result.annotations { assert!(annotations.contains_key("u-ca")); - assert_eq!(annotations.get("u-ca"), Some(&(false, "iso8601".to_string()))) + assert_eq!( + annotations.get("u-ca"), + Some(&(false, "iso8601".to_string())) + ) + } +} + +#[test] +fn temporal_year_month() { + use super::{IsoCursor, TemporalYearMonthString}; + use boa_ast::temporal::TzIdentifier; + + let possible_year_months = &[ + "+002020-11", + "2020-11[+04:00]", + "+00202011", + "202011[+04:00]", + ]; + + for ym in possible_year_months { + let result = TemporalYearMonthString::parse(&mut IsoCursor::new(ym)).unwrap(); + + assert_eq!(result.date.year, 2020); + assert_eq!(result.date.month, 11); + + if let Some(annotation) = &result.tz_annotation { + match &annotation.tz { + TzIdentifier::UtcOffset(utc) => assert_eq!(utc.hour, 4), + _ => unreachable!(), + } + } } +} + +#[test] +fn temporal_month_day() { + use super::{IsoCursor, TemporalMonthDayString}; + let possible_month_day = &["11-07", "1107[+04:00]", "--11-07", "--1107[+04:00]"]; + + for md in possible_month_day { + let result = TemporalMonthDayString::parse(&mut IsoCursor::new(md)).unwrap(); + assert_eq!(result.date.month, 11); + assert_eq!(result.date.day, 7); + } } diff --git a/boa_parser/src/temporal/time.rs b/boa_parser/src/temporal/time.rs index 7bc7efcc5ef..25b2758bdcb 100644 --- a/boa_parser/src/temporal/time.rs +++ b/boa_parser/src/temporal/time.rs @@ -1,54 +1,75 @@ //! Parsing of ISO8601 Time Values +use super::{grammar::is_decimal_separator, IsoCursor}; use crate::{ error::{Error, ParseResult}, lexer::Error as LexError, }; -use super::{IsoCursor, grammar::*}; -use boa_ast::{ - Position, - temporal::TimeSpec -}; +use boa_ast::{temporal::TimeSpec, Position}; /// Parse `TimeSpec` +#[allow(clippy::cast_possible_truncation)] pub(crate) fn parse_time_spec(cursor: &mut IsoCursor) -> ParseResult { let hour = parse_hour(cursor)?; let mut separator = false; - if cursor.peek().map(|ch| *ch == ':' || ch.is_ascii_digit()).unwrap_or(false) { - if cursor.peek().map(|ch| *ch == ':').unwrap_or(false) { + if cursor + .check_or(false, |ch| ch == ':' || ch.is_ascii_digit()) + { + if cursor.check_or(false, |ch| ch == ':') { separator = true; cursor.advance(); } } else { - return Ok(TimeSpec{ hour, minute: 0, second: 0.0 }) + return Ok(TimeSpec { + hour, + minute: 0, + second: 0.0, + }); } let minute = parse_minute_second(cursor, false)?; - if cursor.peek().map(|ch| *ch == ':' || ch.is_ascii_digit()).unwrap_or(false) { - let is_time_separator = cursor.peek().map(|ch| *ch == ':').unwrap_or(false); + if cursor + .check_or(false, |ch| ch == ':' || ch.is_ascii_digit()) + { + let is_time_separator = cursor.check_or(false, |ch| ch == ':'); if separator && is_time_separator { cursor.advance(); } else if is_time_separator { - return Err(LexError::syntax("Invalid TimeSeparator", Position::new(1, cursor.pos() as u32)).into()); + return Err(LexError::syntax( + "Invalid TimeSeparator", + Position::new(1, cursor.pos() as u32), + ) + .into()); } } else { - return Ok(TimeSpec{ hour, minute, second: 0.0 }) + return Ok(TimeSpec { + hour, + minute, + second: 0.0, + }); } let second = parse_minute_second(cursor, true)?; - let double = if cursor.peek().map(|ch| is_decimal_separator(ch)).unwrap_or(false) { + let double = if cursor + .check_or(false,is_decimal_separator) + { f64::from(second) + parse_fraction(cursor)? } else { f64::from(second) }; - Ok(TimeSpec { hour, minute, second: double }) + Ok(TimeSpec { + hour, + minute, + second: double, + }) } +#[allow(clippy::cast_possible_truncation)] pub(crate) fn parse_hour(cursor: &mut IsoCursor) -> ParseResult { let hour_value = cursor .slice(cursor.pos(), cursor.pos() + 2) @@ -58,7 +79,8 @@ pub(crate) fn parse_hour(cursor: &mut IsoCursor) -> ParseResult { return Err(LexError::syntax( "Hour must be in a range of 0-23", Position::new(1, (cursor.pos() + 1) as u32), - ).into()); + ) + .into()); } cursor.advance_n(2); Ok(hour_value) @@ -66,6 +88,7 @@ pub(crate) fn parse_hour(cursor: &mut IsoCursor) -> ParseResult { // NOTE: `TimeSecond` is a 60 inclusive `MinuteSecond`. /// Parse `MinuteSecond` +#[allow(clippy::cast_possible_truncation)] pub(crate) fn parse_minute_second(cursor: &mut IsoCursor, inclusive: bool) -> ParseResult { let min_sec_value = cursor .slice(cursor.pos(), cursor.pos() + 2) @@ -76,7 +99,8 @@ pub(crate) fn parse_minute_second(cursor: &mut IsoCursor, inclusive: bool) -> Pa return Err(LexError::syntax( "MinuteSecond must be in a range of 0-59", Position::new(1, (cursor.pos() + 1) as u32), - ).into()); + ) + .into()); } cursor.advance_n(2); Ok(min_sec_value) @@ -86,6 +110,7 @@ pub(crate) fn parse_minute_second(cursor: &mut IsoCursor, inclusive: bool) -> Pa /// /// This is primarily used in ISO8601 to add percision past /// a second. +#[allow(clippy::cast_possible_truncation)] pub(crate) fn parse_fraction(cursor: &mut IsoCursor) -> ParseResult { let fraction_start = cursor.pos(); cursor.advance(); @@ -96,10 +121,12 @@ pub(crate) fn parse_fraction(cursor: &mut IsoCursor) -> ParseResult { let frac = cursor .slice(fraction_start, cursor.pos()) .parse::() - .map_err(|e| Error::general(e.to_string(), Position::new(1, (cursor.pos() - 1) as u32)))?; - return Ok(frac) + .map_err(|e| { + Error::general(e.to_string(), Position::new(1, (cursor.pos() - 1) as u32)) + })?; + return Ok(frac); } } - return Err(Error::AbruptEnd) + Err(Error::AbruptEnd) } diff --git a/boa_parser/src/temporal/time_zone.rs b/boa_parser/src/temporal/time_zone.rs index aac801eb6b6..9af2a86a10c 100644 --- a/boa_parser/src/temporal/time_zone.rs +++ b/boa_parser/src/temporal/time_zone.rs @@ -1,25 +1,31 @@ //! ISO8601 parsing for Time Zone and Offset data. +use super::{ + grammar::{is_a_key_char, is_a_key_leading_char, is_decimal_separator, is_sign, is_tz_char, is_tz_leading_char, is_utc_designator}, + time::{parse_fraction, parse_hour, parse_minute_second}, + IsoCursor, +}; use crate::{ error::{Error, ParseResult}, lexer::Error as LexError, }; -use super::{ - IsoCursor, - time::{parse_minute_second, parse_fraction, parse_hour}, - grammar::*, -}; -use boa_ast::{Position, temporal::{TimeZoneAnnotation, UtcOffset, TzIdentifier}}; +use boa_ast::{ + temporal::{TimeZoneAnnotation, TzIdentifier, UtcOffset}, + Position, +}; // ==== Time Zone Annotation Parsing ==== -pub(crate) fn parse_ambiguous_tz_annotation(cursor: &mut IsoCursor) -> ParseResult> { +#[allow(clippy::cast_possible_truncation)] +pub(crate) fn parse_ambiguous_tz_annotation( + cursor: &mut IsoCursor, +) -> ParseResult> { // Peek position + 1 to check for critical flag. let mut current_peek = 1; let critical = cursor .peek_n(current_peek) - .map(|ch| *ch == '!') + .map(|ch| ch == '!') .ok_or_else(|| Error::AbruptEnd)?; // Advance cursor if critical flag present. @@ -31,46 +37,46 @@ pub(crate) fn parse_ambiguous_tz_annotation(cursor: &mut IsoCursor) -> ParseResu .peek_n(current_peek) .ok_or_else(|| Error::AbruptEnd)?; - match is_tz_leading_char(leading_char) || is_sign(leading_char) { + if is_tz_leading_char(leading_char) || is_sign(leading_char) { // Ambigious start values when lowercase alpha that is shared between `TzLeadingChar` and `KeyLeadingChar`. - true if is_a_key_leading_char(leading_char) => { + 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 *ch == '/' || (is_tz_char(ch) && !is_a_key_char(ch)) { + if ch == '/' || (is_tz_char(ch) && !is_a_key_char(ch)) { let tz = parse_tz_annotation(cursor)?; return Ok(Some(tz)); - } else if *ch == '=' || (is_a_key_char(ch) && !is_tz_char(ch)) { + } else if ch == '=' || (is_a_key_char(ch) && !is_tz_char(ch)) { return Ok(None); - } else if *ch == ']' { + } else if ch == ']' { return Err(LexError::syntax( "Invalid Annotation", Position::new(1, (peek_pos + 1) as u32), - ).into()); + ) + .into()); } peek_pos += 1; } - Err(Error::AbruptEnd) - } - true => { - let tz = parse_tz_annotation(cursor)?; - Ok(Some(tz)) + return Err(Error::AbruptEnd) } - false if is_a_key_leading_char(leading_char) => { - Ok(None) - } - _ => Err(Error::lex(LexError::syntax( - "Unexpected character in ambiguous annotation.", - Position::new(1, (cursor.pos() + 1) as u32), - ))), + let tz = parse_tz_annotation(cursor)?; + return Ok(Some(tz)) } + + if is_a_key_leading_char(leading_char) { return Ok(None) }; + + Err(Error::lex(LexError::syntax( + "Unexpected character in ambiguous annotation.", + Position::new(1, (cursor.pos() + 1) as u32), + ))) } +#[allow(clippy::cast_possible_truncation)] fn parse_tz_annotation(cursor: &mut IsoCursor) -> ParseResult { - assert!(*cursor.peek().unwrap() == '['); + assert!(cursor.peek().expect("annotation start") == '['); let potential_critical = cursor.next().ok_or_else(|| Error::AbruptEnd)?; - let critical = *potential_critical == '!'; + let critical = potential_critical == '!'; if critical { cursor.advance(); @@ -78,8 +84,12 @@ fn parse_tz_annotation(cursor: &mut IsoCursor) -> ParseResult ParseResult ParseResult { - let is_iana = cursor.peek().map(|ch| is_tz_leading_char(ch)).ok_or_else(|| Error::AbruptEnd)?; - let is_offset = cursor.peek().map(|ch| is_sign(ch)).unwrap_or(false); + let is_iana = cursor + .check(is_tz_leading_char) + .ok_or_else(|| Error::AbruptEnd)?; + let is_offset = cursor.check_or(false, is_sign); if is_iana { let iana_name = parse_tz_iana_name(cursor)?; return Ok(TzIdentifier::TzIANAName(iana_name)); } else if is_offset { let offset = parse_utc_offset_minute_precision(cursor)?; - return Ok(TzIdentifier::UtcOffset(offset)) + return Ok(TzIdentifier::UtcOffset(offset)); } - Err(LexError::syntax("Invalid leading character for a TimeZoneIdentifier", Position::new(1, (cursor.pos() + 1) as u32)).into()) + Err(LexError::syntax( + "Invalid leading character for a TimeZoneIdentifier", + Position::new(1, (cursor.pos() + 1) as u32), + ) + .into()) } /// Parse a `TimeZoneIANAName` Parse Node +#[allow(clippy::cast_possible_truncation)] fn parse_tz_iana_name(cursor: &mut IsoCursor) -> ParseResult { let tz_name_start = cursor.pos(); while let Some(potential_value_char) = cursor.next() { - if *potential_value_char == '/' { - if !cursor - .peek_n(1) - .map(|ch| is_tz_char(ch)) - .unwrap_or(false) - { + if potential_value_char == '/' { + if !cursor.peek_n(1).map_or(false, is_tz_char) { return Err(LexError::syntax( "Missing TimeZoneIANANameComponent after '/'", Position::new(1, (cursor.pos() + 2) as u32), - ).into()); + ) + .into()); } continue; } @@ -124,35 +139,45 @@ fn parse_tz_iana_name(cursor: &mut IsoCursor) -> ParseResult { // Return the valid TimeZoneIANAName return Ok(cursor.slice(tz_name_start, cursor.pos())); } - } - return Err(Error::AbruptEnd); + Err(Error::AbruptEnd) } // ==== Utc Offset Parsing ==== /// Parse a full precision `UtcOffset` +#[allow(clippy::cast_possible_truncation)] pub(crate) fn parse_date_time_utc(cursor: &mut IsoCursor) -> ParseResult { - if cursor.peek().map(|ch| is_utc_designator(ch)).unwrap_or(false) { + if cursor.check_or(false, is_utc_designator) { cursor.advance(); - return Ok(UtcOffset { utc: true, sign: 1, hour: 0, minute: 0, second: 0.0 }) + return Ok(UtcOffset { + utc: true, + sign: 1, + hour: 0, + minute: 0, + second: 0.0, + }); } - let separated = cursor.peek_n(3).map(|ch| *ch == ':').unwrap_or(false); + let separated = cursor.peek_n(3).map_or(false, |ch| ch == ':'); let mut utc_to_minute = parse_utc_offset_minute_precision(cursor)?; - if cursor.peek().map(|ch| *ch == ':').unwrap_or(false) { + if cursor.check_or(false, |ch| ch == ':') { if !separated { - return Err(LexError::syntax("Unexpected TimeSeparator", Position::new(1, cursor.pos() as u32)).into()) + return Err(LexError::syntax( + "Unexpected TimeSeparator", + Position::new(1, cursor.pos() as u32), + ) + .into()); } cursor.advance(); } let sec = parse_minute_second(cursor, true)?; - let double = if cursor.peek().map(|ch| is_decimal_separator(ch)).unwrap_or(false) { + let double = if cursor.check_or(false, is_decimal_separator) { f64::from(sec) + parse_fraction(cursor)? } else { f64::from(sec) @@ -164,20 +189,43 @@ pub(crate) fn parse_date_time_utc(cursor: &mut IsoCursor) -> ParseResult ParseResult { - let sign = if let Some(ch) = cursor.next() { if *ch == '+' { 1_i8 } else { -1_i8 }} else {return Err(Error::AbruptEnd)}; + let sign = if let Some(ch) = cursor.next() { + if ch == '+' { + 1_i8 + } else { + -1_i8 + } + } else { + return Err(Error::AbruptEnd); + }; let hour = parse_hour(cursor)?; // If at the end of the utc, then return. - if cursor.peek().map(|ch| !(ch.is_ascii_digit() || *ch == ':')).ok_or_else(|| Error::AbruptEnd)? { - return Ok(UtcOffset { utc: false, sign, hour, minute: 0, second: 0.0 }) + if cursor + .check(|ch| !(ch.is_ascii_digit() || ch == ':')) + .ok_or_else(|| Error::AbruptEnd)? + { + return Ok(UtcOffset { + utc: false, + sign, + hour, + minute: 0, + second: 0.0, + }); } // Advance cursor beyond any TimeSeparator - if cursor.peek().map(|ch| *ch == ':').unwrap_or(false) { + if cursor.check_or(false, |ch| ch == ':') { cursor.advance(); } let minute = parse_minute_second(cursor, false)?; - return Ok(UtcOffset { utc: false, sign, hour, minute, second: 0.0 }) -} \ No newline at end of file + Ok(UtcOffset { + utc: false, + sign, + hour, + minute, + second: 0.0, + }) +}