Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation of ZonedDateTime, TimeZone, and Instant #3495

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 26 additions & 54 deletions boa_engine/src/builtins/temporal/time_zone/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,19 @@ use crate::{
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
js_string,
object::{internal_methods::get_prototype_from_constructor, ObjectData, CONSTRUCTOR},
object::{internal_methods::get_prototype_from_constructor, CONSTRUCTOR},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use boa_profiler::Profiler;
use boa_temporal::tz::{TimeZoneSlot, TzProtocol};

/// The `Temporal.TimeZone` object.
#[derive(Debug, Clone)]
pub struct TimeZone {
pub(crate) initialized_temporal_time_zone: bool,
pub(crate) identifier: String,
pub(crate) offset_nanoseconds: Option<i64>,
slot: TimeZoneSlot,
}

impl BuiltInObject for TimeZone {
Expand Down Expand Up @@ -129,15 +128,18 @@ impl BuiltInConstructor for TimeZone {
}

impl TimeZone {
// NOTE: id, toJSON, toString currently share the exact same implementation -> Consolidate into one function and define multiple accesors?
pub(crate) fn get_id(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
pub(crate) fn get_id(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let o = this.as_object().map(JsObject::borrow).ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a Temporal.TimeZone")
})?;
let tz = o.as_time_zone().ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a Temporal.TimeZone")
})?;
Ok(JsString::from(tz.identifier.clone()).into())
Ok(JsString::from(tz.slot.id(context)).into())
}

pub(crate) fn get_offset_nanoseconds_for(
Expand All @@ -147,20 +149,15 @@ impl TimeZone {
) -> JsResult<JsValue> {
// 1. Let timeZone be the this value.
// 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimeZone]]).
let _tz = this
.as_object()
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a Temporal.TimeZone")
})?
.borrow()
.as_time_zone()
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a Temporal.TimeZone")
})?;
let o = this.as_object().map(JsObject::borrow).ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a Temporal.TimeZone")
})?;
let _tz = o.as_time_zone().ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a Temporal.TimeZone")
})?;

// 3. Set instant to ? ToTemporalInstant(instant).
let _i = args.get_or_undefined(0);
// TODO: to_temporal_instant is abstract operation for Temporal.Instant objects.
// let instant = to_temporal_instant(i)?;

// 4. If timeZone.[[OffsetNanoseconds]] is not undefined, return 𝔽(timeZone.[[OffsetNanoseconds]]).
// 5. Return 𝔽(GetNamedTimeZoneOffsetNanoseconds(timeZone.[[Identifier]], instant.[[Nanoseconds]])).
Expand Down Expand Up @@ -247,7 +244,11 @@ impl TimeZone {
.into())
}

pub(crate) fn to_string(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
pub(crate) fn to_string(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let timeZone be the this value.
// 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimeZone]]).
let o = this.as_object().ok_or_else(|| {
Expand All @@ -258,7 +259,7 @@ impl TimeZone {
JsNativeError::typ().with_message("this value must be a Temporal.TimeZone")
})?;
// 3. Return timeZone.[[Identifier]].
Ok(JsString::from(tz.identifier.clone()).into())
Ok(JsString::from(tz.slot.id(context)).into())
}
}

Expand Down Expand Up @@ -317,39 +318,10 @@ pub(super) fn create_temporal_time_zone(
let prototype =
get_prototype_from_constructor(&new_target, StandardConstructors::time_zone, context)?;

// 3. Let offsetNanosecondsResult be Completion(ParseTimeZoneOffsetString(identifier)).
let offset_nanoseconds_result = parse_timezone_offset_string(&identifier, context);

// 4. If offsetNanosecondsResult is an abrupt completion, then
let (identifier, offset_nanoseconds) = if let Ok(offset_nanoseconds) = offset_nanoseconds_result
{
// Switched conditions for more idiomatic rust code structuring
// 5. Else,
// a. Set object.[[Identifier]] to ! FormatTimeZoneOffsetString(offsetNanosecondsResult.[[Value]]).
// b. Set object.[[OffsetNanoseconds]] to offsetNanosecondsResult.[[Value]].
(
format_time_zone_offset_string(offset_nanoseconds),
Some(offset_nanoseconds),
)
} else {
// a. Assert: ! CanonicalizeTimeZoneName(identifier) is identifier.
assert_eq!(canonicalize_time_zone_name(&identifier), identifier);

// b. Set object.[[Identifier]] to identifier.
// c. Set object.[[OffsetNanoseconds]] to undefined.
(identifier, None)
};

// 6. Return object.
let object = JsObject::from_proto_and_data(
prototype,
ObjectData::time_zone(TimeZone {
initialized_temporal_time_zone: false,
identifier,
offset_nanoseconds,
}),
);
Ok(object.into())
// TODO: Migrate ISO8601 parsing to `boa_temporal`
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
}

/// Abstract operation `ParseTimeZoneOffsetString ( offsetString )`
Expand Down
8 changes: 4 additions & 4 deletions boa_engine/src/builtins/temporal/zoned_date_time/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ use crate::{
Context, JsBigInt, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use boa_profiler::Profiler;
use boa_temporal::duration::Duration as TemporalDuration;
use boa_temporal::{
duration::Duration as TemporalDuration, zoneddatetime::ZonedDateTime as InnerZdt,
};

/// The `Temporal.ZonedDateTime` object.
#[derive(Debug, Clone)]
pub struct ZonedDateTime {
nanoseconds: JsBigInt,
time_zone: JsObject,
calendar: JsObject,
inner: InnerZdt,
}

impl BuiltInObject for ZonedDateTime {
Expand Down
58 changes: 56 additions & 2 deletions boa_temporal/src/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use crate::{
calendar::CalendarSlot,
instant::Instant,
iso::{IsoDate, IsoDateSlots, IsoDateTime, IsoTime},
options::ArithmeticOverflow,
TemporalResult,
Expand Down Expand Up @@ -33,6 +34,17 @@ impl DateTime {
fn validate_iso(iso: IsoDate) -> bool {
IsoDateTime::new_unchecked(iso, IsoTime::noon()).is_within_limits()
}

/// Create a new `DateTime` from an `Instant`.
#[inline]
pub(crate) fn from_instant(
instant: &Instant,
offset: f64,
calendar: CalendarSlot,
) -> TemporalResult<Self> {
let iso = IsoDateTime::from_epoch_nanos(&instant.nanos, offset)?;
Ok(Self { iso, calendar })
}
}

// ==== Public DateTime API ====
Expand Down Expand Up @@ -76,14 +88,56 @@ impl DateTime {
#[inline]
#[must_use]
pub fn iso_date(&self) -> IsoDate {
self.iso.iso_date()
self.iso.date()
}

/// Returns the inner `IsoTime` value.
#[inline]
#[must_use]
pub fn iso_time(&self) -> IsoTime {
self.iso.iso_time()
self.iso.time()
}

/// Returns the hour value
#[inline]
#[must_use]
pub fn hours(&self) -> u8 {
self.iso.time().hour
}

/// Returns the minute value
#[inline]
#[must_use]
pub fn minutes(&self) -> u8 {
self.iso.time().minute
}

/// Returns the second value
#[inline]
#[must_use]
pub fn seconds(&self) -> u8 {
self.iso.time().second
}

/// Returns the `millisecond` value
#[inline]
#[must_use]
pub fn milliseconds(&self) -> u16 {
self.iso.time().millisecond
}

/// Returns the `microsecond` value
#[inline]
#[must_use]
pub fn microseconds(&self) -> u16 {
self.iso.time().microsecond
}

/// Returns the `nanosecond` value
#[inline]
#[must_use]
pub fn nanoseconds(&self) -> u16 {
self.iso.time().nanosecond
}

/// Returns the Calendar value.
Expand Down
68 changes: 68 additions & 0 deletions boa_temporal/src/instant.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//! An implementation of the Temporal Instant.

use crate::{TemporalError, TemporalResult};

use num_bigint::BigInt;
use num_traits::ToPrimitive;

/// A Temporal Instant
#[derive(Debug, Clone)]
pub struct Instant {
pub(crate) nanos: BigInt,
}

// ==== Public API ====

impl Instant {
/// Create a new validated `Instant`.
#[inline]
pub fn new(nanos: BigInt) -> TemporalResult<Self> {
if !is_valid_epoch_nanos(&nanos) {
return Err(TemporalError::range()
.with_message("Instant nanoseconds are not within a valid epoch range."));
}
Ok(Self { nanos })
}

/// Returns the `epochSeconds` value for this `Instant`.
#[must_use]
pub fn epoch_seconds(&self) -> f64 {
(&self.nanos / BigInt::from(1_000_000_000))
.to_f64()
.expect("A validated Instant should be within a valid f64")
.floor()
}

/// Returns the `epochMilliseconds` value for this `Instant`.
#[must_use]
pub fn epoch_milliseconds(&self) -> f64 {
(&self.nanos / BigInt::from(1_000_000))
.to_f64()
.expect("A validated Instant should be within a valid f64")
.floor()
}

/// Returns the `epochMicroseconds` value for this `Instant`.
#[must_use]
pub fn epoch_microseconds(&self) -> f64 {
(&self.nanos / BigInt::from(1_000))
.to_f64()
.expect("A validated Instant should be within a valid f64")
.floor()
}

/// Returns the `epochNanoseconds` value for this `Instant`.
#[must_use]
pub fn epoch_nanoseconds(&self) -> f64 {
self.nanos
.to_f64()
.expect("A validated Instant should be within a valid f64")
}
}

/// Utility for determining if the nanos are within a valid range.
#[inline]
#[must_use]
pub(crate) fn is_valid_epoch_nanos(nanos: &BigInt) -> bool {
nanos < &BigInt::from(crate::NS_MAX_INSTANT) && nanos > &BigInt::from(crate::NS_MIN_INSTANT)
}
Loading