Browse Source

Temporal: `DateTime` and `PlainDateTime` functionality (#3628)

* Build out Temporal DateTime functionality

* Fix limit.js test and add datetime limit test

* clippy and fmt
pull/3636/head
Kevin 9 months ago committed by GitHub
parent
commit
39ba99972d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 9
      core/engine/src/builtins/temporal/calendar/object.rs
  2. 607
      core/engine/src/builtins/temporal/plain_date_time/mod.rs
  3. 8
      core/temporal/src/components/calendar.rs
  4. 14
      core/temporal/src/components/date.rs
  5. 274
      core/temporal/src/components/datetime.rs
  6. 4
      core/temporal/src/components/month_day.rs
  7. 4
      core/temporal/src/components/year_month.rs
  8. 12
      core/temporal/src/components/zoneddatetime.rs
  9. 104
      core/temporal/src/iso.rs
  10. 2
      core/temporal/src/lib.rs
  11. 31
      core/temporal/src/parser/tests.rs
  12. 4
      core/temporal/src/utils.rs

9
core/engine/src/builtins/temporal/calendar/object.rs

@ -4,7 +4,8 @@ use crate::{
builtins::{ builtins::{
iterable::IteratorHint, iterable::IteratorHint,
temporal::{ temporal::{
fields::object_to_temporal_fields, plain_date, plain_month_day, plain_year_month, fields::object_to_temporal_fields, plain_date, plain_date_time, plain_month_day,
plain_year_month,
}, },
Array, Array,
}, },
@ -951,8 +952,10 @@ pub(crate) fn date_like_to_object(
CalendarDateLike::Date(d) => plain_date::create_temporal_date(d.clone(), None, context) CalendarDateLike::Date(d) => plain_date::create_temporal_date(d.clone(), None, context)
.map_err(|e| TemporalError::general(e.to_string())) .map_err(|e| TemporalError::general(e.to_string()))
.map(Into::into), .map(Into::into),
CalendarDateLike::DateTime(_dt) => { CalendarDateLike::DateTime(dt) => {
todo!() plain_date_time::create_temporal_datetime(dt.clone(), None, context)
.map_err(|e| TemporalError::general(e.to_string()))
.map(Into::into)
} }
CalendarDateLike::MonthDay(md) => { CalendarDateLike::MonthDay(md) => {
plain_month_day::create_temporal_month_day(md.clone(), None, context) plain_month_day::create_temporal_month_day(md.clone(), None, context)

607
core/engine/src/builtins/temporal/plain_date_time/mod.rs

@ -2,12 +2,17 @@
#![allow(dead_code, unused_variables)] #![allow(dead_code, unused_variables)]
use crate::{ use crate::{
builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, builtins::{
temporal::{calendar, to_integer_with_truncation},
BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
js_string,
object::internal_methods::get_prototype_from_constructor,
property::Attribute, property::Attribute,
realm::Realm, realm::Realm,
string::common::StaticJsStrings, string::{common::StaticJsStrings, utf16},
Context, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
}; };
use boa_gc::{Finalize, Trace}; use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -41,12 +46,207 @@ impl IntrinsicObject for PlainDateTime {
fn init(realm: &Realm) { fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(std::any::type_name::<Self>(), "init"); let _timer = Profiler::global().start_event(std::any::type_name::<Self>(), "init");
let get_calendar_id = BuiltInBuilder::callable(realm, Self::get_calendar_id)
.name(js_string!("get calendarId"))
.build();
let get_year = BuiltInBuilder::callable(realm, Self::get_year)
.name(js_string!("get year"))
.build();
let get_month = BuiltInBuilder::callable(realm, Self::get_month)
.name(js_string!("get month"))
.build();
let get_month_code = BuiltInBuilder::callable(realm, Self::get_month_code)
.name(js_string!("get monthCode"))
.build();
let get_day = BuiltInBuilder::callable(realm, Self::get_day)
.name(js_string!("get day"))
.build();
let get_hour = BuiltInBuilder::callable(realm, Self::get_hour)
.name(js_string!("get hour"))
.build();
let get_minute = BuiltInBuilder::callable(realm, Self::get_minute)
.name(js_string!("get minute"))
.build();
let get_second = BuiltInBuilder::callable(realm, Self::get_second)
.name(js_string!("get second"))
.build();
let get_millisecond = BuiltInBuilder::callable(realm, Self::get_millisecond)
.name(js_string!("get millisecond"))
.build();
let get_microsecond = BuiltInBuilder::callable(realm, Self::get_microsecond)
.name(js_string!("get microsecond"))
.build();
let get_nanosecond = BuiltInBuilder::callable(realm, Self::get_nanosecond)
.name(js_string!("get nanosecond"))
.build();
let get_day_of_week = BuiltInBuilder::callable(realm, Self::get_day_of_week)
.name(js_string!("get dayOfWeek"))
.build();
let get_day_of_year = BuiltInBuilder::callable(realm, Self::get_day_of_year)
.name(js_string!("get dayOfYear"))
.build();
let get_week_of_year = BuiltInBuilder::callable(realm, Self::get_week_of_year)
.name(js_string!("get weekOfYear"))
.build();
let get_year_of_week = BuiltInBuilder::callable(realm, Self::get_year_of_week)
.name(js_string!("get yearOfWeek"))
.build();
let get_days_in_week = BuiltInBuilder::callable(realm, Self::get_days_in_week)
.name(js_string!("get daysInWeek"))
.build();
let get_days_in_month = BuiltInBuilder::callable(realm, Self::get_days_in_month)
.name(js_string!("get daysInMonth"))
.build();
let get_days_in_year = BuiltInBuilder::callable(realm, Self::get_days_in_year)
.name(js_string!("get daysInYear"))
.build();
let get_months_in_year = BuiltInBuilder::callable(realm, Self::get_months_in_year)
.name(js_string!("get monthsInYear"))
.build();
let get_in_leap_year = BuiltInBuilder::callable(realm, Self::get_in_leap_year)
.name(js_string!("get inLeapYear"))
.build();
BuiltInBuilder::from_standard_constructor::<Self>(realm) BuiltInBuilder::from_standard_constructor::<Self>(realm)
.static_property( .static_property(
JsSymbol::to_string_tag(), JsSymbol::to_string_tag(),
Self::NAME, Self::NAME,
Attribute::CONFIGURABLE, Attribute::CONFIGURABLE,
) )
.accessor(
utf16!("calendarId"),
Some(get_calendar_id),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("year"),
Some(get_year),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("month"),
Some(get_month),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("monthCode"),
Some(get_month_code),
None,
Attribute::CONFIGURABLE,
)
.accessor(utf16!("day"), Some(get_day), None, Attribute::CONFIGURABLE)
.accessor(
utf16!("hour"),
Some(get_hour),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("minute"),
Some(get_minute),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("second"),
Some(get_second),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("millisecond"),
Some(get_millisecond),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("microsecond"),
Some(get_microsecond),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("nanosecond"),
Some(get_nanosecond),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("dayOfWeek"),
Some(get_day_of_week),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("dayOfYear"),
Some(get_day_of_year),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("weekOfYear"),
Some(get_week_of_year),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("yearOfWeek"),
Some(get_year_of_week),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("daysInWeek"),
Some(get_days_in_week),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("daysInMonth"),
Some(get_days_in_month),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("daysInYear"),
Some(get_days_in_year),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("monthsInYear"),
Some(get_months_in_year),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("inLeapYear"),
Some(get_in_leap_year),
None,
Attribute::CONFIGURABLE,
)
.build(); .build();
} }
@ -66,94 +266,381 @@ impl BuiltInConstructor for PlainDateTime {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
Err(JsNativeError::range() // 1. If NewTarget is undefined, then
.with_message("Not yet implemented.") if new_target.is_undefined() {
.into()) // a. Throw a TypeError exception.
return Err(JsNativeError::typ()
.with_message("NewTarget cannot be undefined when contructing PlainDateTime.")
.into());
};
// 2. Set isoYear to ? ToIntegerWithTruncation(isoYear).
let iso_year = to_integer_with_truncation(args.get_or_undefined(0), context)?;
// 3. Set isoMonth to ? ToIntegerWithTruncation(isoMonth).
let iso_month = to_integer_with_truncation(args.get_or_undefined(1), context)?;
// 4. Set isoDay to ? ToIntegerWithTruncation(isoDay).
let iso_day = to_integer_with_truncation(args.get_or_undefined(2), context)?;
// 5. If hour is undefined, set hour to 0; else set hour to ? ToIntegerWithTruncation(hour).
let hour = args
.get(3)
.map_or(Ok(0), |v| to_integer_with_truncation(v, context))?;
// 6. If minute is undefined, set minute to 0; else set minute to ? ToIntegerWithTruncation(minute).
let minute = args
.get(4)
.map_or(Ok(0), |v| to_integer_with_truncation(v, context))?;
// 7. If second is undefined, set second to 0; else set second to ? ToIntegerWithTruncation(second).
let second = args
.get(5)
.map_or(Ok(0), |v| to_integer_with_truncation(v, context))?;
// 8. If millisecond is undefined, set millisecond to 0; else set millisecond to ? ToIntegerWithTruncation(millisecond).
let millisecond = args
.get(6)
.map_or(Ok(0), |v| to_integer_with_truncation(v, context))?;
// 9. If microsecond is undefined, set microsecond to 0; else set microsecond to ? ToIntegerWithTruncation(microsecond).
let microsecond = args
.get(7)
.map_or(Ok(0), |v| to_integer_with_truncation(v, context))?;
// 10. If nanosecond is undefined, set nanosecond to 0; else set nanosecond to ? ToIntegerWithTruncation(nanosecond).
let nanosecond = args
.get(8)
.map_or(Ok(0), |v| to_integer_with_truncation(v, context))?;
// 11. Let calendar be ? ToTemporalCalendarSlotValue(calendarLike, "iso8601").
let calendar_slot =
calendar::to_temporal_calendar_slot_value(args.get_or_undefined(9), context)?;
let dt = InnerDateTime::new(
iso_year,
iso_month,
iso_day,
hour,
minute,
second,
millisecond,
microsecond,
nanosecond,
calendar_slot,
)?;
// 12. Return ? CreateTemporalDateTime(isoYear, isoMonth, isoDay, hour, minute, second, millisecond, microsecond, nanosecond, calendar, NewTarget).
create_temporal_datetime(dt, Some(new_target), context).map(Into::into)
} }
} }
// ==== `PlainDateTime` Accessor Properties ==== // ==== `PlainDateTimeTime` accessor implmentations ====
impl PlainDateTime { impl PlainDateTime {
fn calendar_id(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> { /// 5.3.3 get `Temporal.PlainDateTime.prototype.calendarId`
Err(JsNativeError::error() fn get_calendar_id(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
.with_message("calendars not yet implemented.") let date = this
.into()) .as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
Ok(JsString::from(date.inner.calendar().identifier(context)?).into())
}
/// 5.3.4 get `Temporal.PlainDateTime.prototype.year`
fn get_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let date = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
Ok(date.inner.contextual_year(context)?.into())
}
/// 5.3.5 get `Temporal.PlainDateTime.prototype.month`
fn get_month(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let date = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
Ok(date.inner.contextual_month(context)?.into())
}
/// 5.3.6 get Temporal.PlainDateTime.prototype.monthCode
fn get_month_code(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let date = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
Ok(JsString::from(date.inner.contextual_month_code(context)?.as_str()).into())
}
/// 5.3.7 get `Temporal.PlainDateTime.prototype.day`
fn get_day(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let date = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
Ok(date.inner.contextual_day(context)?.into())
} }
fn year(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> { /// 5.3.8 get `Temporal.PlainDateTime.prototype.hour`
Err(JsNativeError::error() fn get_hour(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
.with_message("calendars not yet implemented.") // 1. Let dateTime be the this value.
.into()) // 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
// 3. Return 𝔽(dateTime.[[ISOHour]]).
Ok(time.inner.hour().into())
} }
fn month(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> { /// 5.3.9 get `Temporal.PlainDateTime.prototype.minute`
Err(JsNativeError::error() fn get_minute(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
.with_message("calendars not yet implemented.") // 1. Let dateTime be the this value.
.into()) // 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
// 3. Return 𝔽(dateTime.[[ISOMinute]]).
Ok(time.inner.minute().into())
} }
fn month_code(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> { /// 5.3.10 get `Temporal.PlainDateTime.prototype.second`
Err(JsNativeError::error() fn get_second(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
.with_message("calendars not yet implemented.") // 1. Let dateTime be the this value.
.into()) // 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
// 3. Return 𝔽(dateTime.[[ISOSecond]]).
Ok(time.inner.second().into())
} }
fn day(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> { /// 5.3.11 get `Temporal.PlainDateTime.prototype.millisecond`
Err(JsNativeError::error() fn get_millisecond(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
.with_message("calendars not yet implemented.") // 1. Let dateTime be the this value.
.into()) // 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
// 3. Return 𝔽(dateTime.[[ISOMillisecond]]).
Ok(time.inner.millisecond().into())
} }
fn hour(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> { /// 5.3.12 get `Temporal.PlainDateTime.prototype.microsecond`
Err(JsNativeError::error() fn get_microsecond(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
.with_message("calendars not yet implemented.") // 1. Let dateTime be the this value.
.into()) // 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
// 3. Return 𝔽(dateTime.[[ISOMicrosecond]]).
Ok(time.inner.microsecond().into())
} }
fn minute(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> { /// 5.3.13 get `Temporal.PlainDateTime.prototype.nanosecond`
Err(JsNativeError::error() fn get_nanosecond(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
.with_message("calendars not yet implemented.") // 1. Let dateTime be the this value.
.into()) // 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
// 3. Return 𝔽(dateTime.[[ISONanosecond]]).
Ok(time.inner.nanosecond().into())
} }
fn second(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> { /// 5.3.14 get `Temporal.PlainDateTime.prototype.dayOfWeek`
Err(JsNativeError::error() fn get_day_of_week(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
.with_message("calendars not yet implemented.") let date = this
.into()) .as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
Ok(date.inner.contextual_day_of_week(context)?.into())
} }
fn millisecond(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> { /// 5.3.15 get `Temporal.PlainDateTime.prototype.dayOfYear`
Err(JsNativeError::error() fn get_day_of_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
.with_message("calendars not yet implemented.") let date = this
.into()) .as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
Ok(date.inner.contextual_day_of_year(context)?.into())
} }
fn microsecond(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> { /// 5.3.16 get `Temporal.PlainDateTime.prototype.weekOfYear`
Err(JsNativeError::error() fn get_week_of_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
.with_message("calendars not yet implemented.") let date = this
.into()) .as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
Ok(date.inner.contextual_week_of_year(context)?.into())
} }
fn nanosecond(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> { /// 5.3.17 get `Temporal.PlainDateTime.prototype.yearOfWeek`
Err(JsNativeError::error() fn get_year_of_week(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
.with_message("calendars not yet implemented.") let date = this
.into()) .as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
Ok(date.inner.contextual_year_of_week(context)?.into())
} }
fn era(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> { /// 5.3.18 get `Temporal.PlainDateTime.prototype.daysInWeek`
Err(JsNativeError::error() fn get_days_in_week(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
.with_message("calendars not yet implemented.") let date = this
.into()) .as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
Ok(date.inner.contextual_days_in_week(context)?.into())
} }
fn era_year(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> { /// 5.3.19 get `Temporal.PlainDateTime.prototype.daysInMonth`
Err(JsNativeError::error() fn get_days_in_month(
.with_message("calendars not yet implemented.") this: &JsValue,
.into()) _: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let date = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
Ok(date.inner.contextual_days_in_month(context)?.into())
}
/// 5.3.20 get `Temporal.PlainDateTime.prototype.daysInYear`
fn get_days_in_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let date = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
Ok(date.inner.contextual_days_in_year(context)?.into())
}
/// 5.3.21 get `Temporal.PlainDateTime.prototype.monthsInYear`
fn get_months_in_year(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let date = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
Ok(date.inner.contextual_months_in_year(context)?.into())
}
/// 5.3.22 get `Temporal.PlainDateTime.prototype.inLeapYear`
fn get_in_leap_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let date = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;
Ok(date.inner.contextual_in_leap_year(context)?.into())
} }
} }
// ==== `PlainDateTime` Abstract Operations` ==== // ==== `PlainDateTime` Abstract Operations` ====
// See `IsoDateTimeRecord` pub(crate) fn create_temporal_datetime(
inner: InnerDateTime<JsCustomCalendar>,
new_target: Option<&JsValue>,
context: &mut Context,
) -> JsResult<JsObject> {
// NOTE(nekevss): The below validations should be upheld with the creation of `InnerDateTime`.
// 1. If IsValidISODate(isoYear, isoMonth, isoDay) is false, throw a RangeError exception.
// 2. If IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond) is false, throw a RangeError exception.
// 3. If ISODateTimeWithinLimits(isoYear, isoMonth, isoDay, hour, minute, second, millisecond, microsecond, nanosecond) is false, then
// a. Throw a RangeError exception.
// 4. If newTarget is not present, set newTarget to %Temporal.PlainDateTime%.
let new_target = if let Some(new_target) = new_target {
new_target.clone()
} else {
context
.realm()
.intrinsics()
.constructors()
.plain_date_time()
.constructor()
.into()
};
// 5. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainDateTime.prototype%", « [[InitializedTemporalDateTime]], [[ISOYear]], [[ISOMonth]], [[ISODay]], [[ISOHour]], [[ISOMinute]], [[ISOSecond]], [[ISOMillisecond]], [[ISOMicrosecond]], [[ISONanosecond]], [[Calendar]] »).
let prototype = get_prototype_from_constructor(
&new_target,
StandardConstructors::plain_date_time,
context,
)?;
// 6. Set object.[[ISOYear]] to isoYear.
// 7. Set object.[[ISOMonth]] to isoMonth.
// 8. Set object.[[ISODay]] to isoDay.
// 9. Set object.[[ISOHour]] to hour.
// 10. Set object.[[ISOMinute]] to minute.
// 11. Set object.[[ISOSecond]] to second.
// 12. Set object.[[ISOMillisecond]] to millisecond.
// 13. Set object.[[ISOMicrosecond]] to microsecond.
// 14. Set object.[[ISONanosecond]] to nanosecond.
// 15. Set object.[[Calendar]] to calendar.
let obj = JsObject::from_proto_and_data(prototype, PlainDateTime::new(inner));
// 16. Return object.
Ok(obj)
}

8
core/temporal/src/components/calendar.rs

@ -96,7 +96,7 @@ impl<C: CalendarProtocol> CalendarDateLike<C> {
pub fn as_iso_date(&self) -> IsoDate { pub fn as_iso_date(&self) -> IsoDate {
match self { match self {
CalendarDateLike::Date(d) => d.iso_date(), CalendarDateLike::Date(d) => d.iso_date(),
CalendarDateLike::DateTime(dt) => dt.iso_date(), CalendarDateLike::DateTime(dt) => *dt.iso_date(),
CalendarDateLike::MonthDay(md) => md.iso_date(), CalendarDateLike::MonthDay(md) => md.iso_date(),
CalendarDateLike::YearMonth(ym) => ym.iso_date(), CalendarDateLike::YearMonth(ym) => ym.iso_date(),
} }
@ -516,7 +516,7 @@ impl<C: CalendarProtocol> CalendarSlot<C> {
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<i32> { ) -> TemporalResult<i32> {
match self { match self {
CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(date_like.as_iso_date().year()), CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(date_like.as_iso_date().year),
CalendarSlot::Builtin(builtin) => { CalendarSlot::Builtin(builtin) => {
let calendar_date = builtin.date_from_iso(date_like.as_iso_date().as_icu4x()?); let calendar_date = builtin.date_from_iso(date_like.as_iso_date().as_icu4x()?);
Ok(builtin.year(&calendar_date).number) Ok(builtin.year(&calendar_date).number)
@ -532,7 +532,7 @@ impl<C: CalendarProtocol> CalendarSlot<C> {
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<u8> { ) -> TemporalResult<u8> {
match self { match self {
CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(date_like.as_iso_date().month()), CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(date_like.as_iso_date().month),
CalendarSlot::Builtin(_) => { CalendarSlot::Builtin(_) => {
Err(TemporalError::range().with_message("Not yet implemented.")) Err(TemporalError::range().with_message("Not yet implemented."))
} }
@ -564,7 +564,7 @@ impl<C: CalendarProtocol> CalendarSlot<C> {
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<u8> { ) -> TemporalResult<u8> {
match self { match self {
CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(date_like.as_iso_date().day()), CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(date_like.as_iso_date().day),
CalendarSlot::Builtin(_) => { CalendarSlot::Builtin(_) => {
Err(TemporalError::range().with_message("Not yet implemented.")) Err(TemporalError::range().with_message("Not yet implemented."))
} }

14
core/temporal/src/components/date.rs

@ -67,7 +67,7 @@ impl<C: CalendarProtocol> Date<C> {
/// Creates a `Date` from a `DateTime`. /// Creates a `Date` from a `DateTime`.
pub fn from_datetime(dt: &DateTime<C>) -> Self { pub fn from_datetime(dt: &DateTime<C>) -> Self {
Self { Self {
iso: dt.iso_date(), iso: *dt.iso_date(),
calendar: dt.calendar().clone(), calendar: dt.calendar().clone(),
} }
} }
@ -76,21 +76,21 @@ impl<C: CalendarProtocol> Date<C> {
#[must_use] #[must_use]
/// Returns this `Date`'s ISO year value. /// Returns this `Date`'s ISO year value.
pub const fn iso_year(&self) -> i32 { pub const fn iso_year(&self) -> i32 {
self.iso.year() self.iso.year
} }
#[inline] #[inline]
#[must_use] #[must_use]
/// Returns this `Date`'s ISO month value. /// Returns this `Date`'s ISO month value.
pub const fn iso_month(&self) -> u8 { pub const fn iso_month(&self) -> u8 {
self.iso.month() self.iso.month
} }
#[inline] #[inline]
#[must_use] #[must_use]
/// Returns this `Date`'s ISO day value. /// Returns this `Date`'s ISO day value.
pub const fn iso_day(&self) -> u8 { pub const fn iso_day(&self) -> u8 {
self.iso.day() self.iso.day
} }
#[inline] #[inline]
@ -373,9 +373,9 @@ impl<C: CalendarProtocol> Date<C> {
largest_unit: TemporalUnit, largest_unit: TemporalUnit,
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<Duration> { ) -> TemporalResult<Duration> {
if self.iso.year() == other.iso.year() if self.iso.year == other.iso.year
&& self.iso.month() == other.iso.month() && self.iso.month == other.iso.month
&& self.iso.day() == other.iso.day() && self.iso.day == other.iso.day
{ {
return Ok(Duration::default()); return Ok(Duration::default());
} }

274
core/temporal/src/components/datetime.rs

@ -1,7 +1,5 @@
//! This module implements `DateTime` any directly related algorithms. //! This module implements `DateTime` any directly related algorithms.
use std::str::FromStr;
use crate::{ use crate::{
components::{ components::{
calendar::{CalendarProtocol, CalendarSlot}, calendar::{CalendarProtocol, CalendarSlot},
@ -13,6 +11,9 @@ use crate::{
TemporalError, TemporalResult, TemporalError, TemporalResult,
}; };
use std::{any::Any, str::FromStr};
use tinystr::TinyAsciiStr;
/// The native Rust implementation of `Temporal.PlainDateTime` /// The native Rust implementation of `Temporal.PlainDateTime`
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct DateTime<C: CalendarProtocol> { pub struct DateTime<C: CalendarProtocol> {
@ -26,11 +27,8 @@ impl<C: CalendarProtocol> DateTime<C> {
/// Creates a new unchecked `DateTime`. /// Creates a new unchecked `DateTime`.
#[inline] #[inline]
#[must_use] #[must_use]
pub(crate) fn new_unchecked(date: IsoDate, time: IsoTime, calendar: CalendarSlot<C>) -> Self { pub(crate) fn new_unchecked(iso: IsoDateTime, calendar: CalendarSlot<C>) -> Self {
Self { Self { iso, calendar }
iso: IsoDateTime::new_unchecked(date, time),
calendar,
}
} }
#[inline] #[inline]
@ -50,6 +48,13 @@ impl<C: CalendarProtocol> DateTime<C> {
let iso = IsoDateTime::from_epoch_nanos(&instant.nanos, offset)?; let iso = IsoDateTime::from_epoch_nanos(&instant.nanos, offset)?;
Ok(Self { iso, calendar }) Ok(Self { iso, calendar })
} }
/// Returns the inner `IsoDate` value.
#[inline]
#[must_use]
pub(crate) fn iso_date(&self) -> &IsoDate {
self.iso.date()
}
} }
// ==== Public DateTime API ==== // ==== Public DateTime API ====
@ -80,7 +85,10 @@ impl<C: CalendarProtocol> DateTime<C> {
nanosecond, nanosecond,
ArithmeticOverflow::Reject, ArithmeticOverflow::Reject,
)?; )?;
Ok(Self::new_unchecked(iso_date, iso_time, calendar)) Ok(Self::new_unchecked(
IsoDateTime::new(iso_date, iso_time)?,
calendar,
))
} }
/// Validates whether ISO date slots are within iso limits at noon. /// Validates whether ISO date slots are within iso limits at noon.
@ -89,59 +97,66 @@ impl<C: CalendarProtocol> DateTime<C> {
Self::validate_iso(target.iso_date()) Self::validate_iso(target.iso_date())
} }
/// Returns the inner `IsoDate` value. /// Returns this `Date`'s ISO year value.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn iso_date(&self) -> IsoDate { pub const fn iso_year(&self) -> i32 {
self.iso.date() self.iso.date().year
}
/// Returns this `Date`'s ISO month value.
#[inline]
#[must_use]
pub const fn iso_month(&self) -> u8 {
self.iso.date().month
} }
/// Returns the inner `IsoTime` value. /// Returns this `Date`'s ISO day value.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn iso_time(&self) -> IsoTime { pub const fn iso_day(&self) -> u8 {
self.iso.time() self.iso.date().day
} }
/// Returns the hour value /// Returns the hour value
#[inline] #[inline]
#[must_use] #[must_use]
pub fn hours(&self) -> u8 { pub fn hour(&self) -> u8 {
self.iso.time().hour self.iso.time().hour
} }
/// Returns the minute value /// Returns the minute value
#[inline] #[inline]
#[must_use] #[must_use]
pub fn minutes(&self) -> u8 { pub fn minute(&self) -> u8 {
self.iso.time().minute self.iso.time().minute
} }
/// Returns the second value /// Returns the second value
#[inline] #[inline]
#[must_use] #[must_use]
pub fn seconds(&self) -> u8 { pub fn second(&self) -> u8 {
self.iso.time().second self.iso.time().second
} }
/// Returns the `millisecond` value /// Returns the `millisecond` value
#[inline] #[inline]
#[must_use] #[must_use]
pub fn milliseconds(&self) -> u16 { pub fn millisecond(&self) -> u16 {
self.iso.time().millisecond self.iso.time().millisecond
} }
/// Returns the `microsecond` value /// Returns the `microsecond` value
#[inline] #[inline]
#[must_use] #[must_use]
pub fn microseconds(&self) -> u16 { pub fn microsecond(&self) -> u16 {
self.iso.time().microsecond self.iso.time().microsecond
} }
/// Returns the `nanosecond` value /// Returns the `nanosecond` value
#[inline] #[inline]
#[must_use] #[must_use]
pub fn nanoseconds(&self) -> u16 { pub fn nanosecond(&self) -> u16 {
self.iso.time().nanosecond self.iso.time().nanosecond
} }
@ -153,6 +168,179 @@ impl<C: CalendarProtocol> DateTime<C> {
} }
} }
// ==== Calendar-derived public API ====
impl<C: CalendarProtocol> DateTime<C> {
/// Returns the calendar year value with provided context.
pub fn contextual_year(&self, context: &mut dyn Any) -> TemporalResult<i32> {
self.calendar.year(
&super::calendar::CalendarDateLike::DateTime(self.clone()),
context,
)
}
/// Returns the calendar year value.
pub fn year(&self) -> TemporalResult<i32> {
self.contextual_year(&mut ())
}
/// Returns the calendar month value with provided context.
pub fn contextual_month(&self, context: &mut dyn Any) -> TemporalResult<u8> {
self.calendar.month(
&super::calendar::CalendarDateLike::DateTime(self.clone()),
context,
)
}
/// Returns the calendar month value.
pub fn month(&self) -> TemporalResult<u8> {
self.contextual_month(&mut ())
}
/// Returns the calendar month code value with provided context.
pub fn contextual_month_code(&self, context: &mut dyn Any) -> TemporalResult<TinyAsciiStr<4>> {
self.calendar.month_code(
&super::calendar::CalendarDateLike::DateTime(self.clone()),
context,
)
}
/// Returns the calendar month code value.
pub fn month_code(&self) -> TemporalResult<TinyAsciiStr<4>> {
self.contextual_month_code(&mut ())
}
/// Returns the calendar day value with provided context.
pub fn contextual_day(&self, context: &mut dyn Any) -> TemporalResult<u8> {
self.calendar.day(
&super::calendar::CalendarDateLike::DateTime(self.clone()),
context,
)
}
/// Returns the calendar day value.
pub fn day(&self) -> TemporalResult<u8> {
self.contextual_day(&mut ())
}
/// Returns the calendar day of week value with provided context.
pub fn contextual_day_of_week(&self, context: &mut dyn Any) -> TemporalResult<u16> {
self.calendar.day_of_week(
&super::calendar::CalendarDateLike::DateTime(self.clone()),
context,
)
}
/// Returns the calendar day of week value.
pub fn day_of_week(&self) -> TemporalResult<u16> {
self.contextual_day_of_week(&mut ())
}
/// Returns the calendar day of year value with provided context.
pub fn contextual_day_of_year(&self, context: &mut dyn Any) -> TemporalResult<u16> {
self.calendar.day_of_week(
&super::calendar::CalendarDateLike::DateTime(self.clone()),
context,
)
}
/// Returns the calendar day of year value.
pub fn day_of_year(&self) -> TemporalResult<u16> {
self.contextual_day_of_week(&mut ())
}
/// Returns the calendar week of year value with provided context.
pub fn contextual_week_of_year(&self, context: &mut dyn Any) -> TemporalResult<u16> {
self.calendar.week_of_year(
&super::calendar::CalendarDateLike::DateTime(self.clone()),
context,
)
}
/// Returns the calendar week of year value.
pub fn week_of_year(&self) -> TemporalResult<u16> {
self.contextual_week_of_year(&mut ())
}
/// Returns the calendar year of week value with provided context.
pub fn contextual_year_of_week(&self, context: &mut dyn Any) -> TemporalResult<i32> {
self.calendar.year_of_week(
&super::calendar::CalendarDateLike::DateTime(self.clone()),
context,
)
}
/// Returns the calendar year of week value.
pub fn year_of_week(&self) -> TemporalResult<i32> {
self.contextual_year_of_week(&mut ())
}
/// Returns the calendar days in week value with provided context.
pub fn contextual_days_in_week(&self, context: &mut dyn Any) -> TemporalResult<u16> {
self.calendar.days_in_week(
&super::calendar::CalendarDateLike::DateTime(self.clone()),
context,
)
}
/// Returns the calendar days in week value.
pub fn days_in_week(&self) -> TemporalResult<u16> {
self.contextual_days_in_week(&mut ())
}
/// Returns the calendar days in month value with provided context.
pub fn contextual_days_in_month(&self, context: &mut dyn Any) -> TemporalResult<u16> {
self.calendar.days_in_month(
&super::calendar::CalendarDateLike::DateTime(self.clone()),
context,
)
}
/// Returns the calendar days in month value.
pub fn days_in_month(&self) -> TemporalResult<u16> {
self.contextual_days_in_month(&mut ())
}
/// Returns the calendar days in year value with provided context.
pub fn contextual_days_in_year(&self, context: &mut dyn Any) -> TemporalResult<u16> {
self.calendar.days_in_year(
&super::calendar::CalendarDateLike::DateTime(self.clone()),
context,
)
}
/// Returns the calendar days in year value.
pub fn days_in_year(&self) -> TemporalResult<u16> {
self.contextual_days_in_year(&mut ())
}
/// Returns the calendar months in year value with provided context.
pub fn contextual_months_in_year(&self, context: &mut dyn Any) -> TemporalResult<u16> {
self.calendar.months_in_year(
&super::calendar::CalendarDateLike::DateTime(self.clone()),
context,
)
}
/// Returns the calendar months in year value.
pub fn months_in_year(&self) -> TemporalResult<u16> {
self.contextual_months_in_year(&mut ())
}
/// Returns whether the date is in a leap year for the given calendar with provided context.
pub fn contextual_in_leap_year(&self, context: &mut dyn Any) -> TemporalResult<bool> {
self.calendar.in_leap_year(
&super::calendar::CalendarDateLike::DateTime(self.clone()),
context,
)
}
/// Returns returns whether the date in a leap year for the given calendar.
pub fn in_leap_year(&self) -> TemporalResult<bool> {
self.contextual_in_leap_year(&mut ())
}
}
// ==== Trait impls ==== // ==== Trait impls ====
impl<C: CalendarProtocol> FromStr for DateTime<C> { impl<C: CalendarProtocol> FromStr for DateTime<C> {
@ -182,9 +370,51 @@ impl<C: CalendarProtocol> FromStr for DateTime<C> {
)?; )?;
Ok(Self::new_unchecked( Ok(Self::new_unchecked(
date, IsoDateTime::new(date, time)?,
time,
CalendarSlot::from_str(&calendar)?, CalendarSlot::from_str(&calendar)?,
)) ))
} }
} }
#[cfg(test)]
mod tests {
use std::str::FromStr;
use crate::components::calendar::CalendarSlot;
use super::DateTime;
#[test]
#[allow(clippy::float_cmp)]
fn plain_date_time_limits() {
// This test is primarily to assert that the `expect` in the epoch methods is
// valid, i.e., a valid instant is within the range of an f64.
let negative_limit = DateTime::<()>::new(
-271_821,
4,
19,
0,
0,
0,
0,
0,
0,
CalendarSlot::from_str("iso8601").unwrap(),
);
let positive_limit = DateTime::<()>::new(
275_760,
9,
14,
0,
0,
0,
0,
0,
0,
CalendarSlot::from_str("iso8601").unwrap(),
);
assert!(negative_limit.is_err());
assert!(positive_limit.is_err());
}
}

4
core/temporal/src/components/month_day.rs

@ -42,14 +42,14 @@ impl<C: CalendarProtocol> MonthDay<C> {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn month(&self) -> u8 { pub fn month(&self) -> u8 {
self.iso.month() self.iso.month
} }
/// Returns the `day` value of `MonthDay`. /// Returns the `day` value of `MonthDay`.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn day(&self) -> u8 { pub fn day(&self) -> u8 {
self.iso.day() self.iso.day
} }
/// Returns a reference to `MonthDay`'s `CalendarSlot` /// Returns a reference to `MonthDay`'s `CalendarSlot`

4
core/temporal/src/components/year_month.rs

@ -44,14 +44,14 @@ impl<C: CalendarProtocol> YearMonth<C> {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn year(&self) -> i32 { pub fn year(&self) -> i32 {
self.iso.year() self.iso.year
} }
/// Returns the `month` value for this `YearMonth`. /// Returns the `month` value for this `YearMonth`.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn month(&self) -> u8 { pub fn month(&self) -> u8 {
self.iso.month() self.iso.month
} }
#[inline] #[inline]

12
core/temporal/src/components/zoneddatetime.rs

@ -162,7 +162,7 @@ impl<C: CalendarProtocol, Z: TzProtocol> ZonedDateTime<C, Z> {
let dt = self let dt = self
.tz .tz
.get_datetime_for(&self.instant, &self.calendar, context)?; .get_datetime_for(&self.instant, &self.calendar, context)?;
Ok(dt.hours()) Ok(dt.hour())
} }
/// Returns the `hour` value for this `ZonedDateTime`. /// Returns the `hour` value for this `ZonedDateTime`.
@ -175,7 +175,7 @@ impl<C: CalendarProtocol, Z: TzProtocol> ZonedDateTime<C, Z> {
let dt = self let dt = self
.tz .tz
.get_datetime_for(&self.instant, &self.calendar, context)?; .get_datetime_for(&self.instant, &self.calendar, context)?;
Ok(dt.minutes()) Ok(dt.minute())
} }
/// Returns the `minute` value for this `ZonedDateTime`. /// Returns the `minute` value for this `ZonedDateTime`.
@ -188,7 +188,7 @@ impl<C: CalendarProtocol, Z: TzProtocol> ZonedDateTime<C, Z> {
let dt = self let dt = self
.tz .tz
.get_datetime_for(&self.instant, &self.calendar, context)?; .get_datetime_for(&self.instant, &self.calendar, context)?;
Ok(dt.seconds()) Ok(dt.second())
} }
/// Returns the `second` value for this `ZonedDateTime`. /// Returns the `second` value for this `ZonedDateTime`.
@ -201,7 +201,7 @@ impl<C: CalendarProtocol, Z: TzProtocol> ZonedDateTime<C, Z> {
let dt = self let dt = self
.tz .tz
.get_datetime_for(&self.instant, &self.calendar, context)?; .get_datetime_for(&self.instant, &self.calendar, context)?;
Ok(dt.milliseconds()) Ok(dt.millisecond())
} }
/// Returns the `millisecond` value for this `ZonedDateTime`. /// Returns the `millisecond` value for this `ZonedDateTime`.
@ -214,7 +214,7 @@ impl<C: CalendarProtocol, Z: TzProtocol> ZonedDateTime<C, Z> {
let dt = self let dt = self
.tz .tz
.get_datetime_for(&self.instant, &self.calendar, context)?; .get_datetime_for(&self.instant, &self.calendar, context)?;
Ok(dt.milliseconds()) Ok(dt.millisecond())
} }
/// Returns the `microsecond` value for this `ZonedDateTime`. /// Returns the `microsecond` value for this `ZonedDateTime`.
@ -227,7 +227,7 @@ impl<C: CalendarProtocol, Z: TzProtocol> ZonedDateTime<C, Z> {
let dt = self let dt = self
.tz .tz
.get_datetime_for(&self.instant, &self.calendar, context)?; .get_datetime_for(&self.instant, &self.calendar, context)?;
Ok(dt.nanoseconds()) Ok(dt.nanosecond())
} }
/// Returns the `nanosecond` value for this `ZonedDateTime`. /// Returns the `nanosecond` value for this `ZonedDateTime`.

104
core/temporal/src/iso.rs

@ -35,6 +35,16 @@ impl IsoDateTime {
Self { date, time } Self { date, time }
} }
/// Creates a new validated `IsoDateTime` that is within valid limits.
pub(crate) fn new(date: IsoDate, time: IsoTime) -> TemporalResult<Self> {
if !iso_dt_within_valid_limits(date, &time) {
return Err(
TemporalError::range().with_message("IsoDateTime not within a valid range.")
);
}
Ok(Self::new_unchecked(date, time))
}
// NOTE: The below assumes that nanos is from an `Instant` and thus in a valid range. -> Needs validation. // NOTE: The below assumes that nanos is from an `Instant` and thus in a valid range. -> Needs validation.
/// Creates an `IsoDateTime` from a `BigInt` of epochNanoseconds. /// Creates an `IsoDateTime` from a `BigInt` of epochNanoseconds.
pub(crate) fn from_epoch_nanos(nanos: &BigInt, offset: f64) -> TemporalResult<Self> { pub(crate) fn from_epoch_nanos(nanos: &BigInt, offset: f64) -> TemporalResult<Self> {
@ -104,36 +114,15 @@ impl IsoDateTime {
/// Returns whether the `IsoDateTime` is within valid limits. /// Returns whether the `IsoDateTime` is within valid limits.
pub(crate) fn is_within_limits(&self) -> bool { pub(crate) fn is_within_limits(&self) -> bool {
let Some(ns) = self.to_utc_epoch_nanoseconds(0f64) else { iso_dt_within_valid_limits(self.date, &self.time)
return false;
};
let max = BigInt::from(crate::NS_MAX_INSTANT + i128::from(NS_PER_DAY));
let min = BigInt::from(crate::NS_MIN_INSTANT - i128::from(NS_PER_DAY));
min < ns && max > ns
}
/// Returns the UTC epoch nanoseconds for this `IsoDateTime`.
pub(crate) fn to_utc_epoch_nanoseconds(self, offset: f64) -> Option<BigInt> {
let day = self.date.to_epoch_days();
let time = self.time.to_epoch_ms();
let epoch_ms = utils::epoch_days_to_epoch_ms(day, time);
let epoch_nanos = epoch_ms.mul_add(
1_000_000f64,
f64::from(self.time.microsecond).mul_add(1_000f64, f64::from(self.time.nanosecond)),
);
BigInt::from_f64(epoch_nanos - offset)
} }
pub(crate) fn date(&self) -> IsoDate { pub(crate) const fn date(&self) -> &IsoDate {
self.date &self.date
} }
pub(crate) fn time(&self) -> IsoTime { pub(crate) const fn time(&self) -> &IsoTime {
self.time &self.time
} }
} }
@ -154,9 +143,9 @@ pub trait IsoDateSlots {
/// `Temporal.YearMonth` object, and the `Temporal.MonthDay` object. /// `Temporal.YearMonth` object, and the `Temporal.MonthDay` object.
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct IsoDate { pub struct IsoDate {
year: i32, pub(crate) year: i32,
month: u8, pub(crate) month: u8,
day: u8, pub(crate) day: u8,
} }
impl IsoDate { impl IsoDate {
@ -202,21 +191,6 @@ impl IsoDate {
) )
} }
/// Returns the year field
pub(crate) const fn year(self) -> i32 {
self.year
}
/// Returns the month field
pub(crate) const fn month(self) -> u8 {
self.month
}
/// Returns the day field
pub(crate) const fn day(self) -> u8 {
self.day
}
/// Functionally the same as Date's abstract operation `MakeDay` /// Functionally the same as Date's abstract operation `MakeDay`
/// ///
/// Equivalent to `IsoDateToEpochDays` /// Equivalent to `IsoDateToEpochDays`
@ -588,11 +562,43 @@ impl IsoTime {
/// ///
/// Functionally the same as Date's `MakeTime` /// Functionally the same as Date's `MakeTime`
pub(crate) fn to_epoch_ms(self) -> f64 { pub(crate) fn to_epoch_ms(self) -> f64 {
f64::from(self.hour).mul_add( ((f64::from(self.hour) * utils::MS_PER_HOUR
utils::MS_PER_HOUR, + f64::from(self.minute) * utils::MS_PER_MINUTE)
f64::from(self.minute) * utils::MS_PER_MINUTE, + f64::from(self.second) * 1000f64)
) + f64::from(self.second).mul_add(1000f64, f64::from(self.millisecond)) + f64::from(self.millisecond)
}
}
// ==== `IsoDateTime` specific utility functions ====
#[inline]
/// Utility function to determine if a `DateTime`'s components create a `DateTime` within valid limits
fn iso_dt_within_valid_limits(date: IsoDate, time: &IsoTime) -> bool {
if iso_date_to_epoch_days(date.year, (date.month).into(), date.day.into()).abs() > 100_000_001 {
return false;
} }
let Some(ns) = utc_epoch_nanos(date, time, 0.0) else {
return false;
};
let max = BigInt::from(crate::NS_MAX_INSTANT + i128::from(NS_PER_DAY));
let min = BigInt::from(crate::NS_MIN_INSTANT - i128::from(NS_PER_DAY));
min < ns && max > ns
}
#[inline]
/// Utility function to convert a `IsoDate` and `IsoTime` values into epoch nanoseconds
fn utc_epoch_nanos(date: IsoDate, time: &IsoTime, offset: f64) -> Option<BigInt> {
let ms = time.to_epoch_ms();
let epoch_ms = utils::epoch_days_to_epoch_ms(date.to_epoch_days(), ms);
let epoch_nanos = epoch_ms.mul_add(
1_000_000f64,
f64::from(time.microsecond).mul_add(1_000f64, f64::from(time.nanosecond)),
);
BigInt::from_f64(epoch_nanos - offset)
} }
// ==== `IsoDate` specific utiltiy functions ==== // ==== `IsoDate` specific utiltiy functions ====
@ -610,7 +616,7 @@ fn iso_date_to_epoch_days(year: i32, month: i32, day: i32) -> i32 {
let month_t = utils::epoch_time_for_month_given_year(resolved_month, resolved_year); let month_t = utils::epoch_time_for_month_given_year(resolved_month, resolved_year);
// 4. Return EpochTimeToDayNumber(t) + date - 1. // 4. Return EpochTimeToDayNumber(t) + date - 1.
utils::epoch_time_to_day_number(year_t + month_t) + day - 1 utils::epoch_time_to_day_number((year_t.abs() + month_t).copysign(year_t)) + day - 1
} }
#[inline] #[inline]

2
core/temporal/src/lib.rs

@ -61,7 +61,7 @@ pub type TemporalResult<T> = Result<T, TemporalError>;
// Relevant numeric constants // Relevant numeric constants
/// Nanoseconds per day constant: 8.64e+13 /// Nanoseconds per day constant: 8.64e+13
pub const NS_PER_DAY: i64 = 86_400_000_000_000; pub const NS_PER_DAY: i64 = MS_PER_DAY as i64 * 1_000_000;
/// Milliseconds per day constant: 8.64e+7 /// Milliseconds per day constant: 8.64e+7
pub const MS_PER_DAY: i32 = 24 * 60 * 60 * 1000; pub const MS_PER_DAY: i32 = 24 * 60 * 60 * 1000;
/// Max Instant nanosecond constant /// Max Instant nanosecond constant

31
core/temporal/src/parser/tests.rs

@ -14,15 +14,12 @@ fn temporal_parser_basic() {
let sep_result = basic_separated.parse::<DateTime<()>>().unwrap(); let sep_result = basic_separated.parse::<DateTime<()>>().unwrap();
assert_eq!(basic_result.iso_date().year(), 2020); assert_eq!(basic_result.iso_year(), 2020);
assert_eq!(basic_result.iso_date().month(), 11); assert_eq!(basic_result.iso_month(), 11);
assert_eq!(basic_result.iso_date().day(), 8); assert_eq!(basic_result.iso_day(), 8);
assert_eq!(basic_result.iso_date().year(), sep_result.iso_date().year()); assert_eq!(basic_result.iso_year(), sep_result.iso_year());
assert_eq!( assert_eq!(basic_result.iso_month(), sep_result.iso_month());
basic_result.iso_date().month(), assert_eq!(basic_result.iso_day(), sep_result.iso_day());
sep_result.iso_date().month()
);
assert_eq!(basic_result.iso_date().day(), sep_result.iso_date().day());
} }
#[test] #[test]
@ -34,14 +31,12 @@ fn temporal_date_time_max() {
let result = date_time.parse::<DateTime<()>>().unwrap(); let result = date_time.parse::<DateTime<()>>().unwrap();
let time_results = result.iso_time(); assert_eq!(result.hour(), 12);
assert_eq!(result.minute(), 28);
assert_eq!(time_results.hour, 12); assert_eq!(result.second(), 32);
assert_eq!(time_results.minute, 28); assert_eq!(result.millisecond(), 329);
assert_eq!(time_results.second, 32); assert_eq!(result.microsecond(), 402);
assert_eq!(time_results.millisecond, 329); assert_eq!(result.nanosecond(), 834);
assert_eq!(time_results.microsecond, 402);
assert_eq!(time_results.nanosecond, 834);
} }
#[test] #[test]
@ -50,7 +45,7 @@ fn temporal_year_parsing() {
let bad_year = "-000000-11-08"; let bad_year = "-000000-11-08";
let result_good = long.parse::<DateTime<()>>().unwrap(); let result_good = long.parse::<DateTime<()>>().unwrap();
assert_eq!(result_good.iso_date().year(), 2020); assert_eq!(result_good.iso_year(), 2020);
let err_result = bad_year.parse::<DateTime<()>>(); let err_result = bad_year.parse::<DateTime<()>>();
assert!( assert!(

4
core/temporal/src/utils.rs

@ -5,8 +5,6 @@ use crate::{
TemporalError, TemporalResult, MS_PER_DAY, TemporalError, TemporalResult, MS_PER_DAY,
}; };
use std::ops::Mul;
// NOTE: Review the below for optimizations and add ALOT of tests. // NOTE: Review the below for optimizations and add ALOT of tests.
/// Converts and validates an `Option<f64>` rounding increment value into a valid increment result. /// Converts and validates an `Option<f64>` rounding increment value into a valid increment result.
@ -264,7 +262,7 @@ pub(crate) fn epoch_time_for_month_given_year(m: i32, y: i32) -> f64 {
_ => unreachable!(), _ => unreachable!(),
}; };
f64::from(MS_PER_DAY).mul(f64::from(days)) f64::from(MS_PER_DAY) * f64::from(days)
} }
pub(crate) fn epoch_time_to_date(t: f64) -> u8 { pub(crate) fn epoch_time_to_date(t: f64) -> u8 {

Loading…
Cancel
Save