use super::*;
use crate::temporal_iso::TemporalKind;
use alloc::string::String;
pub(crate) const N_TEMPORAL_PLAINDATE: u16 = 1200;
pub(crate) const N_TEMPORAL_PLAINTIME: u16 = 1201;
pub(crate) const N_TEMPORAL_PLAINDATETIME: u16 = 1202;
pub(crate) const N_TEMPORAL_DURATION: u16 = 1203;
pub(crate) const N_TEMPORAL_INSTANT: u16 = 1204;
pub(crate) const N_TEMPORAL_PLAINYEARMONTH: u16 = 1205;
pub(crate) const N_TEMPORAL_PLAINMONTHDAY: u16 = 1206;
pub(crate) const N_TEMPORAL_ZONEDDATETIME: u16 = 1207;
pub(crate) const N_TEMPORAL_PROTO_FN: u16 = 1210;
pub(crate) const N_TEMPORAL_GETTER: u16 = 1211;
pub(crate) const N_TEMPORAL_STATIC_FN: u16 = 1212;
pub(crate) const N_TEMPORAL_NOW_FN: u16 = 1213;
const NOW_METHODS: [(&str, u32); 6] = [
("timeZoneId", 0),
("instant", 0),
("plainDateTimeISO", 0),
("zonedDateTimeISO", 0),
("plainDateISO", 0),
("plainTimeISO", 0),
];
fn statics_for(kind: TemporalKind) -> &'static [(&'static str, u32)] {
match kind {
TemporalKind::Instant => &[
("from", 1),
("fromEpochMilliseconds", 1),
("fromEpochNanoseconds", 1),
("compare", 2),
],
TemporalKind::PlainMonthDay => &[("from", 1)],
_ => &[("from", 1), ("compare", 2)],
}
}
fn ctor_len(kind: TemporalKind) -> u32 {
match kind {
TemporalKind::PlainDate | TemporalKind::PlainDateTime => 3,
TemporalKind::PlainTime | TemporalKind::Duration => 0,
TemporalKind::Instant => 1,
TemporalKind::PlainYearMonth
| TemporalKind::PlainMonthDay
| TemporalKind::ZonedDateTime => 2,
}
}
fn method_len(kind: TemporalKind, name: &str) -> u32 {
if name == "toPlainDate" {
return match kind {
TemporalKind::PlainMonthDay | TemporalKind::PlainYearMonth => 1,
_ => 0,
};
}
match name {
"with"
| "add"
| "subtract"
| "until"
| "since"
| "round"
| "total"
| "equals"
| "toZonedDateTime"
| "toZonedDateTimeISO"
| "withCalendar"
| "withTimeZone"
| "getTimeZoneTransition" => 1,
_ => 0,
}
}
pub(crate) const TEMPORAL_CTOR_IDS: [(TemporalKind, u16); 8] = [
(TemporalKind::PlainDate, N_TEMPORAL_PLAINDATE),
(TemporalKind::PlainTime, N_TEMPORAL_PLAINTIME),
(TemporalKind::PlainDateTime, N_TEMPORAL_PLAINDATETIME),
(TemporalKind::Duration, N_TEMPORAL_DURATION),
(TemporalKind::Instant, N_TEMPORAL_INSTANT),
(TemporalKind::PlainYearMonth, N_TEMPORAL_PLAINYEARMONTH),
(TemporalKind::PlainMonthDay, N_TEMPORAL_PLAINMONTHDAY),
(TemporalKind::ZonedDateTime, N_TEMPORAL_ZONEDDATETIME),
];
pub(crate) fn kind_for_ctor_id(id: u16) -> Option<TemporalKind> {
TEMPORAL_CTOR_IDS
.iter()
.find(|(_, cid)| *cid == id)
.map(|(k, _)| *k)
}
pub(crate) fn is_temporal_ctor_id(id: u16) -> bool {
(N_TEMPORAL_PLAINDATE..=N_TEMPORAL_ZONEDDATETIME).contains(&id)
}
fn tables_for(kind: TemporalKind) -> (&'static [&'static str], &'static [&'static str]) {
use crate::nbexec::{
temporal_duration as dur, temporal_instant as inst, temporal_plaindate as pd,
temporal_plaindatetime as pdt, temporal_plainmonthday as pmd, temporal_plaintime as pt,
temporal_plainyearmonth as pym, temporal_zoneddatetime as zdt,
};
match kind {
TemporalKind::PlainDate => (pd::METHODS, pd::GETTERS),
TemporalKind::PlainTime => (pt::METHODS, pt::GETTERS),
TemporalKind::PlainDateTime => (pdt::METHODS, pdt::GETTERS),
TemporalKind::Duration => (dur::METHODS, dur::GETTERS),
TemporalKind::Instant => (inst::METHODS, inst::GETTERS),
TemporalKind::PlainYearMonth => (pym::METHODS, pym::GETTERS),
TemporalKind::PlainMonthDay => (pmd::METHODS, pmd::GETTERS),
TemporalKind::ZonedDateTime => (zdt::METHODS, zdt::GETTERS),
}
}
impl<'a> Interp<'a> {
pub(crate) fn install_temporal(&mut self) {
let temporal_ns = self.realm.new_object();
if let Some(op) = self.object_prototype() {
self.realm.set_object_proto(temporal_ns, Some(op));
}
for (kind, ctor_id) in TEMPORAL_CTOR_IDS {
let name = kind.type_name();
let ctor = self.new_named_native(name, ctor_id);
self.install_fn_name_length(ctor, name, ctor_len(kind));
let (methods, getters) = tables_for(kind);
let op = self.object_prototype();
let proto = self.realm.new_object_with_proto(op);
for &(sname, slen) in statics_for(kind) {
let target = alloc::format!("{}\u{0}{sname}", kind as usize);
let name_h = self.realm.new_string(&target);
let f = self.realm.new_bound_native(N_TEMPORAL_STATIC_FN, name_h);
self.install_fn_name_length(f, sname, slen);
self.realm
.set_property(ctor, sname, NanBox::handle(f.to_raw()));
self.realm.mark_hidden(ctor, sname);
}
for &m in methods {
let name_h = self.realm.new_string(m);
let f = self.realm.new_bound_native(N_TEMPORAL_PROTO_FN, name_h);
self.install_fn_name_length(f, m, method_len(kind, m));
self.realm
.set_property(proto, m, NanBox::handle(f.to_raw()));
self.realm.mark_hidden(proto, m);
}
for &g in getters {
let getname = alloc::format!("get {g}");
let name_h = self.realm.new_string(g);
let f = self.realm.new_bound_native(N_TEMPORAL_GETTER, name_h);
self.install_fn_name_length(f, &getname, 0);
self.realm.define_accessor(
proto,
g,
NanBox::handle(f.to_raw()),
NanBox::undefined(),
);
self.realm.mark_hidden(proto, g);
}
self.install_to_string_tag(proto, &alloc::format!("Temporal.{name}"));
self.realm
.set_hidden_property(proto, "constructor", NanBox::handle(ctor.to_raw()));
self.realm
.set_property(ctor, "prototype", NanBox::handle(proto.to_raw()));
self.realm.mark_hidden(ctor, "prototype");
self.realm.set_readonly_property(ctor, "prototype");
self.realm.set_non_configurable_property(ctor, "prototype");
self.realm
.set_property(temporal_ns, name, NanBox::handle(ctor.to_raw()));
self.realm.mark_hidden(temporal_ns, name);
self.set_temporal_proto(kind, proto);
}
let now_ns = self.realm.new_object();
if let Some(op) = self.object_prototype() {
self.realm.set_object_proto(now_ns, Some(op));
}
for (nm, len) in NOW_METHODS {
let name_h = self.realm.new_string(nm);
let f = self.realm.new_bound_native(N_TEMPORAL_NOW_FN, name_h);
self.install_fn_name_length(f, nm, len);
self.realm
.set_property(now_ns, nm, NanBox::handle(f.to_raw()));
self.realm.mark_hidden(now_ns, nm);
}
self.install_to_string_tag(now_ns, "Temporal.Now");
self.realm
.set_property(temporal_ns, "Now", NanBox::handle(now_ns.to_raw()));
self.realm.mark_hidden(temporal_ns, "Now");
self.install_to_string_tag(temporal_ns, "Temporal");
self.current
.declare("Temporal", NanBox::handle(temporal_ns.to_raw()));
}
fn set_temporal_proto(&mut self, kind: TemporalKind, proto: Handle) {
let idx = kind as usize;
if self.temporal_protos.len() <= idx {
self.temporal_protos.resize(idx + 1, None);
}
self.temporal_protos[idx] = Some(proto);
}
#[allow(dead_code)] pub(crate) fn temporal_proto(&self, kind: TemporalKind) -> Option<Handle> {
self.temporal_protos.get(kind as usize).copied().flatten()
}
pub(crate) fn temporal_object_calendar(&self, v: NanBox) -> Option<String> {
let h = v.as_handle().map(Handle::from_raw)?;
let d = self.realm.temporal_at(h)?;
match d.kind {
TemporalKind::PlainDate
| TemporalKind::PlainDateTime
| TemporalKind::PlainMonthDay
| TemporalKind::PlainYearMonth
| TemporalKind::ZonedDateTime => Some(d.calendar.clone()),
_ => None,
}
}
#[allow(dead_code)] pub(crate) fn finish_temporal(
&mut self,
data: crate::temporal_iso::TemporalData,
new_target: NanBox,
callee: NanBox,
) -> Result<NanBox, ExecError> {
let kind = data.kind;
let h = self.realm.new_temporal(data);
let default = self.temporal_proto(kind);
if let Some(proto) = self.instance_proto_checked(new_target, callee, default)? {
self.realm.set_native_proto(h, proto);
}
Ok(NanBox::handle(h.to_raw()))
}
pub(crate) fn temporal_construct(
&mut self,
kind: TemporalKind,
args: &[NanBox],
new_target: NanBox,
callee: NanBox,
) -> Result<NanBox, ExecError> {
match kind {
TemporalKind::PlainDate => self.plaindate_construct(args, new_target, callee),
TemporalKind::PlainTime => self.plaintime_construct(args, new_target, callee),
TemporalKind::PlainDateTime => self.plaindatetime_construct(args, new_target, callee),
TemporalKind::Duration => self.duration_construct(args, new_target, callee),
TemporalKind::Instant => self.instant_construct(args, new_target, callee),
TemporalKind::PlainYearMonth => self.plainyearmonth_construct(args, new_target, callee),
TemporalKind::PlainMonthDay => self.plainmonthday_construct(args, new_target, callee),
TemporalKind::ZonedDateTime => self.zoneddatetime_construct(args, new_target, callee),
}
}
pub(crate) fn temporal_method(
&mut self,
this: NanBox,
data: &crate::temporal_iso::TemporalData,
method: &str,
args: &[NanBox],
) -> Result<NanBox, ExecError> {
#[cfg(feature = "intl")]
if method == "toLocaleString" && !matches!(data.kind, TemporalKind::Duration) {
return self.temporal_to_locale_string(this, args);
}
match data.kind {
TemporalKind::PlainDate => self.plaindate_method(this, data, method, args),
TemporalKind::PlainTime => self.plaintime_method(this, data, method, args),
TemporalKind::PlainDateTime => self.plaindatetime_method(this, data, method, args),
TemporalKind::Duration => self.duration_method(this, data, method, args),
TemporalKind::Instant => self.instant_method(this, data, method, args),
TemporalKind::PlainYearMonth => self.plainyearmonth_method(this, data, method, args),
TemporalKind::PlainMonthDay => self.plainmonthday_method(this, data, method, args),
TemporalKind::ZonedDateTime => self.zoneddatetime_method(this, data, method, args),
}
}
pub(crate) fn temporal_getter(
&mut self,
this: NanBox,
data: &crate::temporal_iso::TemporalData,
name: &str,
) -> Result<NanBox, ExecError> {
match data.kind {
TemporalKind::PlainDate => self.plaindate_getter(this, data, name),
TemporalKind::PlainTime => self.plaintime_getter(this, data, name),
TemporalKind::PlainDateTime => self.plaindatetime_getter(this, data, name),
TemporalKind::Duration => self.duration_getter(this, data, name),
TemporalKind::Instant => self.instant_getter(this, data, name),
TemporalKind::PlainYearMonth => self.plainyearmonth_getter(this, data, name),
TemporalKind::PlainMonthDay => self.plainmonthday_getter(this, data, name),
TemporalKind::ZonedDateTime => self.zoneddatetime_getter(this, data, name),
}
}
pub(crate) fn temporal_static(
&mut self,
kind: TemporalKind,
ctor: NanBox,
method: &str,
args: &[NanBox],
) -> Result<Option<NanBox>, ExecError> {
match kind {
TemporalKind::PlainDate => self.plaindate_static(ctor, method, args),
TemporalKind::PlainTime => self.plaintime_static(ctor, method, args),
TemporalKind::PlainDateTime => self.plaindatetime_static(ctor, method, args),
TemporalKind::Duration => self.duration_static(ctor, method, args),
TemporalKind::Instant => self.instant_static(ctor, method, args),
TemporalKind::PlainYearMonth => self.plainyearmonth_static(ctor, method, args),
TemporalKind::PlainMonthDay => self.plainmonthday_static(ctor, method, args),
TemporalKind::ZonedDateTime => self.zoneddatetime_static(ctor, method, args),
}
}
pub(crate) fn temporal_todo(&mut self, what: &str) -> ExecError {
self.type_error(&alloc::format!("Temporal: {what} is not yet implemented"))
}
pub(crate) fn build_temporal(&mut self, data: crate::temporal_iso::TemporalData) -> NanBox {
let kind = data.kind;
let h = self.realm.new_temporal(data);
if let Some(p) = self.temporal_proto(kind) {
self.realm.set_native_proto(h, p);
}
NanBox::handle(h.to_raw())
}
pub(crate) fn temporal_tz_arg(
&mut self,
arg: NanBox,
) -> Result<alloc::string::String, ExecError> {
if matches!(arg.unpack(), Unpacked::Undefined) {
return Ok(alloc::string::String::from("UTC"));
}
if self.is_object_value(arg) {
if let Some(h) = arg.as_handle().map(Handle::from_raw)
&& let Some(d) = self.realm.temporal_at(h)
&& d.kind == TemporalKind::ZonedDateTime
{
return Ok(d.tz.clone().unwrap_or_else(|| String::from("UTC")));
}
return Err(self.type_error("time zone must be a string or Temporal.ZonedDateTime"));
}
let Some(s) = arg
.as_handle()
.map(Handle::from_raw)
.and_then(|h| self.realm.string_value(h))
else {
return Err(self.type_error("time zone must be a string"));
};
self.tz_from_string(&s)
}
pub(crate) fn temporal_tz_offset_ns(&self, tz: &str, epoch_ns: i128) -> Option<i128> {
if tz == "UTC" || tz == "Etc/UTC" || tz == "Etc/GMT" {
return Some(0);
}
let b = tz.as_bytes();
if matches!(b.first(), Some(b'+' | b'-')) {
let neg = b[0] == b'-';
let digits: alloc::vec::Vec<u8> = tz[1..].bytes().filter(u8::is_ascii_digit).collect();
if digits.len() >= 2 {
let h: i128 = (digits[0] - b'0') as i128 * 10 + (digits[1] - b'0') as i128;
let m: i128 = if digits.len() >= 4 {
(digits[2] - b'0') as i128 * 10 + (digits[3] - b'0') as i128
} else {
0
};
let ns = (h * 3600 + m * 60) * crate::temporal_iso::NS_PER_SEC;
return Some(if neg { -ns } else { ns });
}
return None;
}
let unix = (epoch_ns / crate::temporal_iso::NS_PER_SEC) as i64;
timezone_data::load_insensitive(tz)
.ok()
.map(|z| i128::from(z.lookup(unix).offset) * crate::temporal_iso::NS_PER_SEC)
}
pub(crate) fn now_method(&mut self, name: &str, args: &[NanBox]) -> Result<NanBox, ExecError> {
use crate::temporal_iso::{
TemporalData, TemporalKind, balance_time_from_nanos, epoch_days_to_iso,
};
let ms = now_ms();
let epoch_ns = (ms as i128) * 1_000_000;
match name {
"timeZoneId" => {
let h = self.realm.new_string("UTC");
Ok(NanBox::handle(h.to_raw()))
}
"instant" => Ok(self.build_temporal(TemporalData {
kind: TemporalKind::Instant,
epoch_ns,
..Default::default()
})),
"zonedDateTimeISO" => {
let tz =
self.temporal_tz_arg(args.first().copied().unwrap_or(NanBox::undefined()))?;
Ok(self.build_temporal(TemporalData {
kind: TemporalKind::ZonedDateTime,
epoch_ns,
tz: Some(tz),
..Default::default()
}))
}
"plainDateTimeISO" | "plainDateISO" | "plainTimeISO" => {
let tz =
self.temporal_tz_arg(args.first().copied().unwrap_or(NanBox::undefined()))?;
let offset = self.temporal_tz_offset_ns(&tz, epoch_ns).unwrap_or(0);
let (day, time) = balance_time_from_nanos(epoch_ns + offset);
let date = epoch_days_to_iso(day);
let data = match name {
"plainDateISO" => TemporalData {
kind: TemporalKind::PlainDate,
date,
..Default::default()
},
"plainTimeISO" => TemporalData {
kind: TemporalKind::PlainTime,
time,
..Default::default()
},
_ => TemporalData {
kind: TemporalKind::PlainDateTime,
date,
time,
..Default::default()
},
};
Ok(self.build_temporal(data))
}
_ => Err(self.temporal_todo(&alloc::format!("Now.{name}"))),
}
}
}