1use crate::runtime::host::{abi_arg, abi_result};
5use std::collections::BTreeMap;
6
7use jiff::{
8 RoundMode, Span, SpanRelativeTo, SpanRound, Timestamp, TimestampDifference, Unit, Zoned,
9 ZonedDifference, civil, tz::TimeZone,
10};
11use wasmtime::{
12 Caller, FieldType, Func, FuncType, Global, GlobalType, HeapType, Linker, Mutability, RefType,
13 Rooted, StorageType, Store, StructRef, StructRefPre, StructType, Val, ValType,
14};
15
16use crate::runtime::StoreData;
17use crate::runtime::fuel::host_func_async;
18use crate::runtime::host::{read_string_arg, register_host_fn, write_submilli_string_struct};
19use crate::runtime::intrinsic_types::IntrinsicTypes;
20use crate::runtime::prelude::undefined::is_undefined;
21use crate::{
22 MangledName, NamespaceSymbol, ObjectField, Package, PackageDeclaration, Param,
23 Span as DiagSpan, Type, ValueKind, ValueSymbol,
24};
25
26pub const TEMPORAL_MODULE_NAME: &str = "submilli:temporal";
27const I64_BOUND_EXCLUSIVE: f64 = 9_223_372_036_854_775_808.0;
28const PLAIN_MONTH_DAY_MARKER: i64 = 0x4d4f_4e54_4844_4159;
29
30#[derive(Clone)]
31pub(crate) struct TemporalAbi {
32 pub(crate) instant: StructType,
33 pub(crate) duration: StructType,
34 pub(crate) zoned_date_time: StructType,
35 pub(crate) plain_date: StructType,
36 pub(crate) plain_time: StructType,
37 pub(crate) plain_date_time: StructType,
38 pub(crate) plain_year_month: StructType,
39 pub(crate) plain_month_day: StructType,
40 pub(crate) instant_vtable: Global,
41 pub(crate) duration_vtable: Global,
42 pub(crate) zoned_date_time_vtable: Global,
43 pub(crate) plain_date_vtable: Global,
44 pub(crate) plain_time_vtable: Global,
45 pub(crate) plain_date_time_vtable: Global,
46 pub(crate) plain_year_month_vtable: Global,
47 pub(crate) plain_month_day_vtable: Global,
48}
49
50pub(crate) fn install_abi(
51 linker: &mut Linker<StoreData>,
52 store: &mut Store<StoreData>,
53 intr: &IntrinsicTypes,
54 module: &str,
55) -> wasmtime::Result<TemporalAbi> {
56 use crate::runtime::gc_singleton::singleton_struct;
57 use wasmtime::Finality;
58
59 let imm = Mutability::Const;
60 let vtable_field = FieldType::new(
61 imm,
62 StorageType::ValType(ValType::Ref(RefType::new(
63 false,
64 intr.vtable.clone().into(),
65 ))),
66 );
67 let i32_field = FieldType::new(imm, StorageType::ValType(ValType::I32));
68 let i64_field = FieldType::new(imm, StorageType::ValType(ValType::I64));
69
70 let plain_date = singleton_struct(
71 store.engine(),
72 Finality::NonFinal,
73 Some(intr.object.clone()),
74 vec![
75 vtable_field.clone(),
76 i32_field.clone(),
77 i32_field.clone(),
78 i32_field.clone(),
79 ],
80 )?;
81 let plain_time = singleton_struct(
82 store.engine(),
83 Finality::NonFinal,
84 Some(intr.object.clone()),
85 vec![
86 vtable_field.clone(),
87 i32_field.clone(),
88 i32_field.clone(),
89 i32_field.clone(),
90 i32_field.clone(),
91 ],
92 )?;
93 let plain_date_time = singleton_struct(
94 store.engine(),
95 Finality::NonFinal,
96 Some(intr.object.clone()),
97 vec![
98 vtable_field.clone(),
99 i32_field.clone(),
100 i32_field.clone(),
101 i32_field.clone(),
102 i32_field.clone(),
103 i32_field.clone(),
104 i32_field.clone(),
105 i32_field.clone(),
106 ],
107 )?;
108 let plain_year_month = singleton_struct(
109 store.engine(),
110 Finality::NonFinal,
111 Some(intr.object.clone()),
112 vec![vtable_field.clone(), i32_field.clone(), i32_field.clone()],
113 )?;
114 let plain_month_day = singleton_struct(
115 store.engine(),
116 Finality::NonFinal,
117 Some(intr.object.clone()),
118 vec![vtable_field, i32_field.clone(), i32_field, i64_field],
119 )?;
120
121 let instant_slots = plain_vtable_slots(
122 store,
123 intr,
124 intr.temporal_instant.clone(),
125 instant_to_string_host,
126 instant_equals_host,
127 )?;
128 let instant_vtable = define_plain_vtable(
129 linker,
130 store,
131 intr,
132 module,
133 "temporal_instant_host_vtable",
134 instant_slots,
135 )?;
136 let duration_slots = plain_vtable_slots(
137 store,
138 intr,
139 intr.temporal_duration.clone(),
140 duration_to_string_host,
141 duration_equals_host,
142 )?;
143 let duration_vtable = define_plain_vtable(
144 linker,
145 store,
146 intr,
147 module,
148 "temporal_duration_host_vtable",
149 duration_slots,
150 )?;
151 let zdt_slots = plain_vtable_slots(
152 store,
153 intr,
154 intr.temporal_zdt.clone(),
155 zoned_date_time_to_string_host,
156 zoned_date_time_equals_host,
157 )?;
158 let zoned_date_time_vtable = define_plain_vtable(
159 linker,
160 store,
161 intr,
162 module,
163 "temporal_zoned_date_time_host_vtable",
164 zdt_slots,
165 )?;
166 let plain_date_slots = plain_vtable_slots(
167 store,
168 intr,
169 plain_date.clone(),
170 plain_to_string_date,
171 plain_equals_date,
172 )?;
173 let plain_date_vtable = define_plain_vtable(
174 linker,
175 store,
176 intr,
177 module,
178 "temporal_plain_date_vtable",
179 plain_date_slots,
180 )?;
181 let plain_time_slots = plain_vtable_slots(
182 store,
183 intr,
184 plain_time.clone(),
185 plain_to_string_time,
186 plain_equals_time,
187 )?;
188 let plain_time_vtable = define_plain_vtable(
189 linker,
190 store,
191 intr,
192 module,
193 "temporal_plain_time_vtable",
194 plain_time_slots,
195 )?;
196 let plain_date_time_slots = plain_vtable_slots(
197 store,
198 intr,
199 plain_date_time.clone(),
200 plain_to_string_date_time,
201 plain_equals_date_time,
202 )?;
203 let plain_date_time_vtable = define_plain_vtable(
204 linker,
205 store,
206 intr,
207 module,
208 "temporal_plain_date_time_vtable",
209 plain_date_time_slots,
210 )?;
211 let plain_year_month_slots = plain_vtable_slots(
212 store,
213 intr,
214 plain_year_month.clone(),
215 plain_to_string_year_month,
216 plain_equals_year_month,
217 )?;
218 let plain_year_month_vtable = define_plain_vtable(
219 linker,
220 store,
221 intr,
222 module,
223 "temporal_plain_year_month_vtable",
224 plain_year_month_slots,
225 )?;
226 let plain_month_day_slots = plain_vtable_slots(
227 store,
228 intr,
229 plain_month_day.clone(),
230 plain_to_string_month_day,
231 plain_equals_month_day,
232 )?;
233 let plain_month_day_vtable = define_plain_vtable(
234 linker,
235 store,
236 intr,
237 module,
238 "temporal_plain_month_day_vtable",
239 plain_month_day_slots,
240 )?;
241
242 Ok(TemporalAbi {
243 instant: intr.temporal_instant.clone(),
244 duration: intr.temporal_duration.clone(),
245 zoned_date_time: intr.temporal_zdt.clone(),
246 plain_date,
247 plain_time,
248 plain_date_time,
249 plain_year_month,
250 plain_month_day,
251 instant_vtable,
252 duration_vtable,
253 zoned_date_time_vtable,
254 plain_date_vtable,
255 plain_time_vtable,
256 plain_date_time_vtable,
257 plain_year_month_vtable,
258 plain_month_day_vtable,
259 })
260}
261
262fn define_plain_vtable(
263 linker: &mut Linker<StoreData>,
264 store: &mut Store<StoreData>,
265 intr: &IntrinsicTypes,
266 module: &str,
267 name: &str,
268 slots: [Func; 4],
269) -> wasmtime::Result<Global> {
270 let pre = StructRefPre::new(&mut *store, intr.vtable.clone());
271 let vtable = StructRef::new(
272 &mut *store,
273 &pre,
274 &[
275 Val::FuncRef(Some(slots[0])),
276 Val::FuncRef(Some(slots[1])),
277 Val::FuncRef(Some(slots[2])),
278 Val::FuncRef(Some(slots[3])),
279 ],
280 )?;
281 let gty = GlobalType::new(
282 ValType::Ref(RefType::new(
283 false,
284 HeapType::ConcreteStruct(intr.vtable.clone()),
285 )),
286 Mutability::Const,
287 );
288 let global = Global::new(&mut *store, gty, Val::AnyRef(Some(vtable.to_anyref())))?;
289 let field = crate::mangle::prelude(name);
290 linker.define(&mut *store, module, field.as_str(), global)?;
291 Ok(global)
292}
293
294pub(super) type PlainStringer =
295 fn(&mut Caller<'_, StoreData>, Rooted<StructRef>) -> wasmtime::Result<String>;
296pub(super) type PlainEquals =
297 fn(&mut Caller<'_, StoreData>, Rooted<StructRef>, Rooted<StructRef>) -> wasmtime::Result<bool>;
298
299fn plain_vtable_slots(
300 store: &mut Store<StoreData>,
301 intr: &IntrinsicTypes,
302 ty: StructType,
303 stringer: PlainStringer,
304 equals_op: PlainEquals,
305) -> wasmtime::Result<[Func; 4]> {
306 let to_string_ty = intr.to_string_fn.clone();
307 let to_json_ty = intr.to_json_fn.clone();
308 let equals_ty = intr.equals_fn.clone();
309 let hash_ty = intr.hash_fn.clone();
310
311 let st_ty = ty.clone();
312 let to_string = host_func_async(
313 &mut *store,
314 to_string_ty,
315 move |mut caller, params, results| {
316 let st_ty = st_ty.clone();
317 Box::new(async move {
318 let st = cast_struct(
319 &mut caller,
320 abi_arg(params, 0)?,
321 &st_ty,
322 "Temporal.Plain.toString",
323 )?;
324 let out = stringer(&mut caller, st)?;
325 let s = write_submilli_string_struct(&mut caller, &out)?;
326 *abi_result(results, 0)? = Val::AnyRef(Some(s.to_anyref()));
327 Ok(())
328 })
329 },
330 );
331
332 let st_ty = ty.clone();
333 let to_json = host_func_async(
334 &mut *store,
335 to_json_ty,
336 move |mut caller, params, results| {
337 let st_ty = st_ty.clone();
338 Box::new(async move {
339 let st = cast_struct(
340 &mut caller,
341 abi_arg(params, 0)?,
342 &st_ty,
343 "Temporal.Plain.toJSON",
344 )?;
345 let out = stringer(&mut caller, st)?;
346 let json = serde_json::to_string(&out).map_err(|_| {
347 wasmtime::Error::msg(
348 "Temporal.Plain.toJSON: could not encode the ISO value as JSON; use a value within the supported date and time range",
349 )
350 })?;
351 let s = write_submilli_string_struct(&mut caller, &json)?;
352 *abi_result(results, 0)? = Val::AnyRef(Some(s.to_anyref()));
353 Ok(())
354 })
355 },
356 );
357
358 let st_ty = ty.clone();
359 let equals = host_func_async(
360 &mut *store,
361 equals_ty,
362 move |mut caller, params, results| {
363 let st_ty = st_ty.clone();
364 Box::new(async move {
365 let a = cast_struct(
366 &mut caller,
367 abi_arg(params, 0)?,
368 &st_ty,
369 "Temporal.Plain.equals",
370 )?;
371 let Some(b) = try_cast_struct(&mut caller, abi_arg(params, 1)?, &st_ty)? else {
372 *abi_result(results, 0)? = Val::I32(0);
373 return Ok(());
374 };
375 let (Val::AnyRef(Some(a_vtable)), Val::AnyRef(Some(b_vtable))) =
378 (a.field(&mut caller, 0)?, b.field(&mut caller, 0)?)
379 else {
380 return Err(crate::runtime::host::fatal_host_error(
381 "Temporal.equals: invalid vtable",
382 ));
383 };
384 *abi_result(results, 0)? = Val::I32(
385 (Rooted::ref_eq(&caller, &a_vtable, &b_vtable)?
386 && equals_op(&mut caller, a, b)?) as i32,
387 );
388 Ok(())
389 })
390 },
391 );
392
393 let hash = host_func_async(&mut *store, hash_ty, move |mut caller, params, results| {
394 let ty = ty.clone();
395 Box::new(async move {
396 let value = cast_struct(&mut caller, abi_arg(params, 0)?, &ty, "Temporal.hash")?;
397 let fields = ty.fields().count();
398 crate::runtime::fuel::charge(
399 &mut caller,
400 crate::runtime::fuel::ELEM,
401 fields.saturating_sub(1) as u64,
402 )?;
403 let mut hash = 0_u32;
404 for field in 1..fields {
405 let bits = match value.field(&mut caller, field)? {
406 Val::I32(n) => n as u64,
407 Val::I64(n) => n as u64,
408 Val::AnyRef(_) => continue,
411 Val::ExternRef(_) if fields == 5 && field == 4 => continue,
413 _ => {
414 return Err(crate::runtime::host::fatal_host_error(
415 "Temporal: invalid hash field",
416 ));
417 }
418 };
419 hash = hash.rotate_left(5) ^ super::super::vtable::mix_hash_bits(bits);
420 }
421 *abi_result(results, 0)? = Val::I32(hash as i32);
422 Ok(())
423 })
424 });
425
426 Ok([to_string, to_json, equals, hash])
427}
428
429pub(super) fn as_struct_val(
430 caller: &mut Caller<'_, StoreData>,
431 val: &Val,
432 label: &str,
433) -> wasmtime::Result<Rooted<StructRef>> {
434 match val {
435 Val::AnyRef(Some(any)) => any
436 .as_struct(&mut *caller)?
437 .ok_or_else(|| wasmtime::Error::msg(format!("{label}: expected object"))),
438 other => Err(wasmtime::Error::msg(format!(
439 "{label}: expected object, got {other:?}"
440 ))),
441 }
442}
443
444fn cast_struct(
445 caller: &mut Caller<'_, StoreData>,
446 val: &Val,
447 _ty: &StructType,
448 label: &str,
449) -> wasmtime::Result<Rooted<StructRef>> {
450 as_struct_val(caller, val, label)
451}
452
453fn try_cast_struct(
454 caller: &mut Caller<'_, StoreData>,
455 val: &Val,
456 ty: &StructType,
457) -> wasmtime::Result<Option<Rooted<StructRef>>> {
458 let Val::AnyRef(Some(any)) = val else {
459 return Ok(None);
460 };
461 let Some(value) = any.as_struct(&mut *caller)? else {
462 return Ok(None);
463 };
464 Ok(value.matches_ty(&*caller, ty)?.then_some(value))
465}
466
467pub(super) fn st_i32(
468 caller: &mut Caller<'_, StoreData>,
469 st: Rooted<StructRef>,
470 field: u32,
471 label: &str,
472) -> wasmtime::Result<i32> {
473 match st.field(&mut *caller, field as usize)? {
474 Val::I32(v) => Ok(v),
475 other => Err(wasmtime::Error::msg(format!(
476 "{label}: field {field} is {other:?}"
477 ))),
478 }
479}
480
481pub(super) fn plain_to_string_date(
482 caller: &mut Caller<'_, StoreData>,
483 st: Rooted<StructRef>,
484) -> wasmtime::Result<String> {
485 let y = st_i32(caller, st, 1, "PlainDate")?;
486 let m = st_i32(caller, st, 2, "PlainDate")?;
487 let d = st_i32(caller, st, 3, "PlainDate")?;
488 let date = y_m_d_date(
489 i64::from(y),
490 i64::from(m),
491 i64::from(d),
492 "PlainDate.toString",
493 )?;
494 Ok(super::plain_date::to_string(date))
495}
496
497pub(super) fn plain_to_string_time(
498 caller: &mut Caller<'_, StoreData>,
499 st: Rooted<StructRef>,
500) -> wasmtime::Result<String> {
501 let h = st_i32(caller, st, 1, "PlainTime")?;
502 let m = st_i32(caller, st, 2, "PlainTime")?;
503 let s = st_i32(caller, st, 3, "PlainTime")?;
504 let ns = st_i32(caller, st, 4, "PlainTime")?;
505 let t = h_m_s_ns_time(
506 i64::from(h),
507 i64::from(m),
508 i64::from(s),
509 i64::from(ns),
510 "PlainTime.toString",
511 )?;
512 Ok(super::plain_time::to_string(t))
513}
514
515pub(super) fn plain_to_string_date_time(
516 caller: &mut Caller<'_, StoreData>,
517 st: Rooted<StructRef>,
518) -> wasmtime::Result<String> {
519 let date = y_m_d_date(
520 i64::from(st_i32(caller, st, 1, "PlainDateTime")?),
521 i64::from(st_i32(caller, st, 2, "PlainDateTime")?),
522 i64::from(st_i32(caller, st, 3, "PlainDateTime")?),
523 "PlainDateTime.toString",
524 )?;
525 let time = h_m_s_ns_time(
526 i64::from(st_i32(caller, st, 4, "PlainDateTime")?),
527 i64::from(st_i32(caller, st, 5, "PlainDateTime")?),
528 i64::from(st_i32(caller, st, 6, "PlainDateTime")?),
529 i64::from(st_i32(caller, st, 7, "PlainDateTime")?),
530 "PlainDateTime.toString",
531 )?;
532 Ok(super::plain_date_time::to_string(date.to_datetime(time)))
533}
534
535pub(super) fn plain_to_string_year_month(
536 caller: &mut Caller<'_, StoreData>,
537 st: Rooted<StructRef>,
538) -> wasmtime::Result<String> {
539 Ok(super::plain_year_month::to_string(
540 st_i32(caller, st, 1, "PlainYearMonth")?,
541 st_i32(caller, st, 2, "PlainYearMonth")?,
542 ))
543}
544
545pub(super) fn plain_to_string_month_day(
546 caller: &mut Caller<'_, StoreData>,
547 st: Rooted<StructRef>,
548) -> wasmtime::Result<String> {
549 Ok(super::plain_month_day::to_string(
550 st_i32(caller, st, 1, "PlainMonthDay")?,
551 st_i32(caller, st, 2, "PlainMonthDay")?,
552 ))
553}
554
555fn fields_equal(
556 caller: &mut Caller<'_, StoreData>,
557 a: Rooted<StructRef>,
558 b: Rooted<StructRef>,
559 fields: &[u32],
560 label: &str,
561) -> wasmtime::Result<bool> {
562 for &field in fields {
563 if st_i32(caller, a, field, label)? != st_i32(caller, b, field, label)? {
564 return Ok(false);
565 }
566 }
567 Ok(true)
568}
569
570pub(super) fn plain_equals_date(
571 caller: &mut Caller<'_, StoreData>,
572 a: Rooted<StructRef>,
573 b: Rooted<StructRef>,
574) -> wasmtime::Result<bool> {
575 Ok(super::plain_date::equals(
576 plain_date_from_struct(caller, a, "PlainDate.equals")?,
577 plain_date_from_struct(caller, b, "PlainDate.equals")?,
578 ))
579}
580
581pub(super) fn plain_equals_time(
582 caller: &mut Caller<'_, StoreData>,
583 a: Rooted<StructRef>,
584 b: Rooted<StructRef>,
585) -> wasmtime::Result<bool> {
586 Ok(super::plain_time::equals(
587 plain_time_from_struct(caller, a, 0, "PlainTime.equals")?,
588 plain_time_from_struct(caller, b, 0, "PlainTime.equals")?,
589 ))
590}
591
592pub(super) fn plain_equals_date_time(
593 caller: &mut Caller<'_, StoreData>,
594 a: Rooted<StructRef>,
595 b: Rooted<StructRef>,
596) -> wasmtime::Result<bool> {
597 let a = plain_date_from_struct(caller, a, "PlainDateTime.equals")?.to_datetime(
598 plain_time_from_struct(caller, a, 3, "PlainDateTime.equals")?,
599 );
600 let b = plain_date_from_struct(caller, b, "PlainDateTime.equals")?.to_datetime(
601 plain_time_from_struct(caller, b, 3, "PlainDateTime.equals")?,
602 );
603 Ok(super::plain_date_time::equals(a, b))
604}
605
606pub(super) fn plain_equals_year_month(
607 caller: &mut Caller<'_, StoreData>,
608 a: Rooted<StructRef>,
609 b: Rooted<StructRef>,
610) -> wasmtime::Result<bool> {
611 Ok(super::plain_year_month::equals(
612 (
613 st_i32(caller, a, 1, "PlainYearMonth.equals")?,
614 st_i32(caller, a, 2, "PlainYearMonth.equals")?,
615 ),
616 (
617 st_i32(caller, b, 1, "PlainYearMonth.equals")?,
618 st_i32(caller, b, 2, "PlainYearMonth.equals")?,
619 ),
620 ))
621}
622
623pub(super) fn plain_equals_month_day(
624 caller: &mut Caller<'_, StoreData>,
625 a: Rooted<StructRef>,
626 b: Rooted<StructRef>,
627) -> wasmtime::Result<bool> {
628 Ok(super::plain_month_day::equals(
629 (
630 st_i32(caller, a, 1, "PlainMonthDay.equals")?,
631 st_i32(caller, a, 2, "PlainMonthDay.equals")?,
632 ),
633 (
634 st_i32(caller, b, 1, "PlainMonthDay.equals")?,
635 st_i32(caller, b, 2, "PlainMonthDay.equals")?,
636 ),
637 ))
638}
639
640fn instant_to_string_host(
641 caller: &mut Caller<'_, StoreData>,
642 st: Rooted<StructRef>,
643) -> wasmtime::Result<String> {
644 Ok(instant_from_struct(caller, st, "Instant.toString")?.to_string())
645}
646
647pub(super) fn instant_equals_host(
648 caller: &mut Caller<'_, StoreData>,
649 a: Rooted<StructRef>,
650 b: Rooted<StructRef>,
651) -> wasmtime::Result<bool> {
652 Ok(
653 st_i64(caller, a, 1, "Instant.equals")? == st_i64(caller, b, 1, "Instant.equals")?
654 && st_i32(caller, a, 2, "Instant.equals")? == st_i32(caller, b, 2, "Instant.equals")?,
655 )
656}
657
658fn duration_to_string_host(
659 caller: &mut Caller<'_, StoreData>,
660 st: Rooted<StructRef>,
661) -> wasmtime::Result<String> {
662 let span = span_from_duration_struct(caller, st, "Duration.toString")?;
663 duration_to_string_with_options(&span, None, None, None)
664}
665
666fn duration_equals_host(
667 caller: &mut Caller<'_, StoreData>,
668 a: Rooted<StructRef>,
669 b: Rooted<StructRef>,
670) -> wasmtime::Result<bool> {
671 fields_equal(
672 caller,
673 a,
674 b,
675 &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
676 "Duration.equals",
677 )
678}
679
680fn zoned_date_time_to_string_host(
681 caller: &mut Caller<'_, StoreData>,
682 st: Rooted<StructRef>,
683) -> wasmtime::Result<String> {
684 let z = zoned_date_time_from_struct(caller, st, "ZonedDateTime.toString")?;
685 Ok(z.to_string())
686}
687
688fn zoned_date_time_equals_host(
689 caller: &mut Caller<'_, StoreData>,
690 a: Rooted<StructRef>,
691 b: Rooted<StructRef>,
692) -> wasmtime::Result<bool> {
693 let a_secs = st_i64(caller, a, 1, "ZonedDateTime.equals")?;
694 let a_nanos = st_i32(caller, a, 2, "ZonedDateTime.equals")?;
695 let a_tz = st_string(caller, a, 3, "ZonedDateTime.equals")?;
696 let b_secs = st_i64(caller, b, 1, "ZonedDateTime.equals")?;
697 let b_nanos = st_i32(caller, b, 2, "ZonedDateTime.equals")?;
698 let b_tz = st_string(caller, b, 3, "ZonedDateTime.equals")?;
699 Ok(a_secs == b_secs
700 && a_nanos == b_nanos
701 && super::zoned_date_time::time_zone_ids_equal(caller, &a_tz, &b_tz)?)
702}
703
704pub(super) fn reg_plain_date_to_year_month(
705 linker: &mut Linker<StoreData>,
706 iface: &'static str,
707 ty: FuncType,
708) -> wasmtime::Result<()> {
709 register_host_fn(
710 linker,
711 crate::runtime::prelude::MODULE_NAME,
712 prelude_key(iface, "toPlainYearMonth"),
713 ty,
714 true,
715 move |caller, params, results| {
716 let st = as_struct_val(caller, abi_arg(params, 0)?, "toPlainYearMonth")?;
717 let y = st_i32(caller, st, 1, "toPlainYearMonth")?;
718 let m = st_i32(caller, st, 2, "toPlainYearMonth")?;
719 *abi_result(results, 0)? = make_plain_year_month(caller, y, m)?;
720 Ok(())
721 },
722 )
723}
724
725pub(super) fn reg_plain_date_to_month_day(
726 linker: &mut Linker<StoreData>,
727 iface: &'static str,
728 ty: FuncType,
729) -> wasmtime::Result<()> {
730 register_host_fn(
731 linker,
732 crate::runtime::prelude::MODULE_NAME,
733 prelude_key(iface, "toPlainMonthDay"),
734 ty,
735 true,
736 move |caller, params, results| {
737 let st = as_struct_val(caller, abi_arg(params, 0)?, "toPlainMonthDay")?;
738 let m = st_i32(caller, st, 2, "toPlainMonthDay")?;
739 let d = st_i32(caller, st, 3, "toPlainMonthDay")?;
740 *abi_result(results, 0)? = make_plain_month_day(caller, m, d)?;
741 Ok(())
742 },
743 )
744}
745
746pub(super) fn prelude_key(iface: &str, method: &str) -> MangledName {
747 crate::mangle::extend(
748 &crate::mangle::extend(&crate::mangle::prelude("Temporal"), iface),
749 method,
750 )
751}
752
753pub(super) fn reg_plain_from(
754 linker: &mut Linker<StoreData>,
755 engine: &wasmtime::Engine,
756 string: &ValType,
757 obj: &ValType,
758 ctor_iface: &str,
759 label: &'static str,
760 op: fn(&str, &mut Caller<'_, StoreData>) -> wasmtime::Result<Val>,
761) -> wasmtime::Result<()> {
762 register_host_fn(
763 linker,
764 crate::runtime::prelude::MODULE_NAME,
765 prelude_key(ctor_iface, "from"),
766 FuncType::new(engine, [string.clone()], [obj.clone()]),
767 true,
768 move |caller, params, results| {
769 let s = read_string_arg(caller, abi_arg(params, 0)?, label)?;
770 *abi_result(results, 0)? = op(&s, caller)?;
771 Ok(())
772 },
773 )
774}
775
776pub(super) fn reg_plain_string(
777 linker: &mut Linker<StoreData>,
778 engine: &wasmtime::Engine,
779 obj: &ValType,
780 string: &ValType,
781 iface: &str,
782 method: &str,
783 op: PlainStringer,
784) -> wasmtime::Result<()> {
785 let label = format!("{iface}.{method}");
786 register_host_fn(
787 linker,
788 crate::runtime::prelude::MODULE_NAME,
789 prelude_key(iface, method),
790 FuncType::new(engine, [obj.clone()], [string.clone()]),
791 true,
792 move |caller, params, results| {
793 let st = as_struct_val(caller, abi_arg(params, 0)?, &label)?;
794 let out = op(caller, st)?;
795 let s = write_submilli_string_struct(caller, &out)?;
796 *abi_result(results, 0)? = Val::AnyRef(Some(s.to_anyref()));
797 Ok(())
798 },
799 )
800}
801
802pub(super) fn reg_plain_equals(
803 linker: &mut Linker<StoreData>,
804 engine: &wasmtime::Engine,
805 obj: &ValType,
806 iface: &str,
807 op: PlainEquals,
808) -> wasmtime::Result<()> {
809 let label = format!("{iface}.equals");
810 register_host_fn(
811 linker,
812 crate::runtime::prelude::MODULE_NAME,
813 prelude_key(iface, "equals"),
814 FuncType::new(engine, [obj.clone(), obj.clone()], [ValType::I32]),
815 true,
816 move |caller, params, results| {
817 let a = as_struct_val(caller, abi_arg(params, 0)?, &label)?;
818 let b = as_struct_val(caller, abi_arg(params, 1)?, &label)?;
819 *abi_result(results, 0)? = Val::I32(op(caller, a, b)? as i32);
820 Ok(())
821 },
822 )
823}
824
825pub(super) fn reg_plain_compare(
826 linker: &mut Linker<StoreData>,
827 engine: &wasmtime::Engine,
828 obj: &ValType,
829 ctor_iface: &str,
830 fields: &'static [u32],
831) -> wasmtime::Result<()> {
832 let label = format!("{ctor_iface}.compare");
833 register_host_fn(
834 linker,
835 crate::runtime::prelude::MODULE_NAME,
836 prelude_key(ctor_iface, "compare"),
837 FuncType::new(engine, [obj.clone(), obj.clone()], [ValType::F64]),
838 true,
839 move |caller, params, results| {
840 let a = as_struct_val(caller, abi_arg(params, 0)?, &label)?;
841 let b = as_struct_val(caller, abi_arg(params, 1)?, &label)?;
842 let mut ord = 0;
843 for &field in fields {
844 let av = st_i32(caller, a, field, &label)?;
845 let bv = st_i32(caller, b, field, &label)?;
846 if av != bv {
847 ord = if av < bv { -1 } else { 1 };
848 break;
849 }
850 }
851 *abi_result(results, 0)? = Val::F64((ord as f64).to_bits());
852 Ok(())
853 },
854 )
855}
856
857pub(super) fn reg_plain_i32_getter(
858 linker: &mut Linker<StoreData>,
859 engine: &wasmtime::Engine,
860 obj: &ValType,
861 iface: &str,
862 name: &str,
863 field: u32,
864) -> wasmtime::Result<()> {
865 let label = format!("{iface}.{name}");
866 register_host_fn(
867 linker,
868 crate::runtime::prelude::MODULE_NAME,
869 prelude_key(iface, name),
870 FuncType::new(engine, [obj.clone()], [ValType::F64]),
871 true,
872 move |caller, params, results| {
873 let st = as_struct_val(caller, abi_arg(params, 0)?, &label)?;
874 *abi_result(results, 0)? =
875 Val::F64((st_i32(caller, st, field, &label)? as f64).to_bits());
876 Ok(())
877 },
878 )
879}
880
881pub(super) fn reg_plain_time_getters(
882 linker: &mut Linker<StoreData>,
883 engine: &wasmtime::Engine,
884 obj: &ValType,
885 iface: &str,
886) -> wasmtime::Result<()> {
887 let ns_field = if iface == "PlainTime" { 4 } else { 7 };
888 for (name, div) in [
889 ("millisecond", 1_000_000),
890 ("microsecond", 1_000),
891 ("nanosecond", 1),
892 ] {
893 let label = format!("{iface}.{name}");
894 register_host_fn(
895 linker,
896 crate::runtime::prelude::MODULE_NAME,
897 prelude_key(iface, name),
898 FuncType::new(engine, [obj.clone()], [ValType::F64]),
899 true,
900 move |caller, params, results| {
901 let st = as_struct_val(caller, abi_arg(params, 0)?, &label)?;
902 let ns = st_i32(caller, st, ns_field, &label)?;
903 let out = if div == 1 { ns } else { (ns / div) % 1000 };
904 *abi_result(results, 0)? = Val::F64((out as f64).to_bits());
905 Ok(())
906 },
907 )?;
908 }
909 Ok(())
910}
911
912pub(super) fn reg_plain_date_derived_getters(
913 linker: &mut Linker<StoreData>,
914 engine: &wasmtime::Engine,
915 obj: &ValType,
916 iface: &str,
917 year_month_only: i32,
918) -> wasmtime::Result<()> {
919 let specs = [
920 ("dayOfWeek", 0),
921 ("monthCode", 0),
922 ("dayOfYear", 1),
923 ("weekOfYear", 2),
924 ("yearOfWeek", 3),
925 ("daysInWeek", 4),
926 ("daysInMonth", 5),
927 ("daysInYear", 6),
928 ("monthsInYear", 7),
929 ("inLeapYear", 8),
930 ];
931 for (name, selector) in specs {
932 if year_month_only == 1
933 && matches!(
934 name,
935 "dayOfWeek" | "dayOfYear" | "weekOfYear" | "yearOfWeek" | "daysInWeek"
936 )
937 {
938 continue;
939 }
940 if name == "monthCode" {
941 continue;
942 }
943 let label = format!("{iface}.{name}");
944 let ret = if name == "inLeapYear" {
945 ValType::I32
946 } else {
947 ValType::F64
948 };
949 register_host_fn(
950 linker,
951 crate::runtime::prelude::MODULE_NAME,
952 prelude_key(iface, name),
953 FuncType::new(engine, [obj.clone()], [ret]),
954 true,
955 move |caller, params, results| {
956 let st = as_struct_val(caller, abi_arg(params, 0)?, &label)?;
957 let y = i64::from(st_i32(caller, st, 1, &label)?);
958 let m = i64::from(st_i32(caller, st, 2, &label)?);
959 let d = if year_month_only == 1 {
960 1
961 } else {
962 i64::from(st_i32(caller, st, 3, &label)?)
963 };
964 let date = y_m_d_date(y, m, d, "Temporal.Plain.derivedField")?;
965 let val = date_derived_field(date, selector);
966 if selector == 8 {
967 *abi_result(results, 0)? = Val::I32(val);
968 } else {
969 *abi_result(results, 0)? = Val::F64((val as f64).to_bits());
970 }
971 Ok(())
972 },
973 )?;
974 }
975 reg_plain_month_code_getter(
976 linker,
977 engine,
978 obj,
979 &ValType::Ref(RefType::new(
980 false,
981 HeapType::ConcreteStruct(
982 crate::runtime::intrinsic_types::build_intrinsic_types(engine)?.string,
983 ),
984 )),
985 iface,
986 2,
987 )
988}
989
990pub(super) fn reg_plain_month_code_getter(
991 linker: &mut Linker<StoreData>,
992 engine: &wasmtime::Engine,
993 obj: &ValType,
994 string: &ValType,
995 iface: &str,
996 month_field: u32,
997) -> wasmtime::Result<()> {
998 let label = format!("{iface}.monthCode");
999 register_host_fn(
1000 linker,
1001 crate::runtime::prelude::MODULE_NAME,
1002 prelude_key(iface, "monthCode"),
1003 FuncType::new(engine, [obj.clone()], [string.clone()]),
1004 true,
1005 move |caller, params, results| {
1006 let st = as_struct_val(caller, abi_arg(params, 0)?, &label)?;
1007 let month = st_i32(caller, st, month_field, &label)?;
1008 let s = write_submilli_string_struct(caller, &format!("M{month:02}"))?;
1009 *abi_result(results, 0)? = Val::AnyRef(Some(s.to_anyref()));
1010 Ok(())
1011 },
1012 )
1013}
1014
1015fn temporal_abi<'a>(caller: &'a Caller<'_, StoreData>) -> wasmtime::Result<&'a TemporalAbi> {
1016 caller
1017 .data()
1018 .host_abi
1019 .as_ref()
1020 .map(|abi| &abi.temporal)
1021 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))
1022}
1023
1024fn make_plain_struct(
1025 caller: &mut Caller<'_, StoreData>,
1026 ty: StructType,
1027 vtable: Global,
1028 fields: &[i32],
1029) -> wasmtime::Result<Val> {
1030 let mut vals = Vec::with_capacity(fields.len() + 1);
1031 vals.push(vtable.get(&mut *caller));
1032 vals.extend(fields.iter().copied().map(Val::I32));
1033 let pre = StructRefPre::new(&mut *caller, ty);
1034 let st = StructRef::new(&mut *caller, &pre, &vals)?;
1035 Ok(Val::AnyRef(Some(st.to_anyref())))
1036}
1037
1038fn make_temporal_struct(
1039 caller: &mut Caller<'_, StoreData>,
1040 ty: StructType,
1041 vtable: Global,
1042 fields: &[Val],
1043) -> wasmtime::Result<Val> {
1044 let mut vals = Vec::with_capacity(fields.len() + 1);
1045 vals.push(vtable.get(&mut *caller));
1046 vals.extend_from_slice(fields);
1047 let pre = StructRefPre::new(&mut *caller, ty);
1048 let st = StructRef::new(&mut *caller, &pre, &vals)?;
1049 Ok(Val::AnyRef(Some(st.to_anyref())))
1050}
1051
1052pub(super) fn make_duration(
1053 caller: &mut Caller<'_, StoreData>,
1054 span: &Span,
1055) -> wasmtime::Result<Val> {
1056 let (ty, vtable) = {
1057 let abi = temporal_abi(caller)?;
1058 (abi.duration.clone(), abi.duration_vtable)
1059 };
1060 let mut fields = vec![Val::I32(0); 10];
1061 write_span(&mut fields, span)?;
1062 make_temporal_struct(caller, ty, vtable, &fields)
1063}
1064
1065pub(super) fn make_instant(
1066 caller: &mut Caller<'_, StoreData>,
1067 ts: Timestamp,
1068) -> wasmtime::Result<Val> {
1069 let (ty, vtable) = {
1070 let abi = temporal_abi(caller)?;
1071 (abi.instant.clone(), abi.instant_vtable)
1072 };
1073 make_temporal_struct(
1074 caller,
1075 ty,
1076 vtable,
1077 &[Val::I64(ts.as_second()), Val::I32(ts.subsec_nanosecond())],
1078 )
1079}
1080
1081pub(super) fn instant_from_struct(
1082 caller: &mut Caller<'_, StoreData>,
1083 st: Rooted<StructRef>,
1084 label: &'static str,
1085) -> wasmtime::Result<Timestamp> {
1086 Timestamp::new(st_i64(caller, st, 1, label)?, st_i32(caller, st, 2, label)?)
1087 .map_err(temporal_err(label))
1088}
1089
1090pub(super) fn instant_from_val(
1091 caller: &mut Caller<'_, StoreData>,
1092 val: &Val,
1093 label: &'static str,
1094) -> wasmtime::Result<Timestamp> {
1095 let st = as_struct_val(caller, val, label)?;
1096 instant_from_struct(caller, st, label)
1097}
1098
1099pub(super) fn epoch_milliseconds(ts: Timestamp) -> f64 {
1100 ts.as_nanosecond().div_euclid(1_000_000) as f64
1101}
1102
1103pub(super) fn make_zoned_date_time(
1104 caller: &mut Caller<'_, StoreData>,
1105 zoned: &Zoned,
1106 tz_id: &str,
1107) -> wasmtime::Result<Val> {
1108 let (ty, vtable) = {
1109 let abi = temporal_abi(caller)?;
1110 (abi.zoned_date_time.clone(), abi.zoned_date_time_vtable)
1111 };
1112 let bytes = (std::mem::size_of::<CachedZoned>() + 128) as u64;
1113 caller.data().tenant_limits.charge_host_bytes(bytes)?;
1114 let payload = CachedZoned {
1115 value: zoned.clone(),
1116 bytes,
1117 counter: caller.data().tenant_limits.host_attached_counter(),
1118 };
1119 let cached = wasmtime::ExternRef::new(&mut *caller, payload)?;
1120 let ts = zoned.timestamp();
1121 let tz = write_submilli_string_struct(caller, tz_id)?;
1122 make_temporal_struct(
1123 caller,
1124 ty,
1125 vtable,
1126 &[
1127 Val::I64(ts.as_second()),
1128 Val::I32(ts.subsec_nanosecond()),
1129 Val::AnyRef(Some(tz.to_anyref())),
1130 Val::ExternRef(Some(cached)),
1131 ],
1132 )
1133}
1134
1135pub(super) fn make_plain_date(
1136 caller: &mut Caller<'_, StoreData>,
1137 y: i32,
1138 m: i32,
1139 d: i32,
1140) -> wasmtime::Result<Val> {
1141 let (ty, vtable) = {
1142 let abi = temporal_abi(caller)?;
1143 (abi.plain_date.clone(), abi.plain_date_vtable)
1144 };
1145 make_plain_struct(caller, ty, vtable, &[y, m, d])
1146}
1147
1148pub(super) fn make_plain_time(
1149 caller: &mut Caller<'_, StoreData>,
1150 h: i32,
1151 m: i32,
1152 s: i32,
1153 ns: i32,
1154) -> wasmtime::Result<Val> {
1155 let (ty, vtable) = {
1156 let abi = temporal_abi(caller)?;
1157 (abi.plain_time.clone(), abi.plain_time_vtable)
1158 };
1159 make_plain_struct(caller, ty, vtable, &[h, m, s, ns])
1160}
1161
1162#[allow(clippy::too_many_arguments)]
1163pub(super) fn make_plain_date_time(
1164 caller: &mut Caller<'_, StoreData>,
1165 y: i32,
1166 m: i32,
1167 d: i32,
1168 h: i32,
1169 mi: i32,
1170 s: i32,
1171 ns: i32,
1172) -> wasmtime::Result<Val> {
1173 let (ty, vtable) = {
1174 let abi = temporal_abi(caller)?;
1175 (abi.plain_date_time.clone(), abi.plain_date_time_vtable)
1176 };
1177 make_plain_struct(caller, ty, vtable, &[y, m, d, h, mi, s, ns])
1178}
1179
1180pub(super) fn make_plain_year_month(
1181 caller: &mut Caller<'_, StoreData>,
1182 y: i32,
1183 m: i32,
1184) -> wasmtime::Result<Val> {
1185 let (ty, vtable) = {
1186 let abi = temporal_abi(caller)?;
1187 (abi.plain_year_month.clone(), abi.plain_year_month_vtable)
1188 };
1189 make_plain_struct(caller, ty, vtable, &[y, m])
1190}
1191
1192pub(super) fn make_plain_month_day(
1193 caller: &mut Caller<'_, StoreData>,
1194 m: i32,
1195 d: i32,
1196) -> wasmtime::Result<Val> {
1197 let (ty, vtable) = {
1198 let abi = temporal_abi(caller)?;
1199 (abi.plain_month_day.clone(), abi.plain_month_day_vtable)
1200 };
1201 make_temporal_struct(
1202 caller,
1203 ty,
1204 vtable,
1205 &[Val::I32(m), Val::I32(d), Val::I64(PLAIN_MONTH_DAY_MARKER)],
1206 )
1207}
1208
1209pub(super) fn plain_date_from_struct(
1210 caller: &mut Caller<'_, StoreData>,
1211 st: Rooted<StructRef>,
1212 label: &str,
1213) -> wasmtime::Result<civil::Date> {
1214 y_m_d_date(
1215 i64::from(st_i32(caller, st, 1, label)?),
1216 i64::from(st_i32(caller, st, 2, label)?),
1217 i64::from(st_i32(caller, st, 3, label)?),
1218 "PlainDate",
1219 )
1220}
1221
1222pub(super) fn plain_time_from_struct(
1223 caller: &mut Caller<'_, StoreData>,
1224 st: Rooted<StructRef>,
1225 field_offset: u32,
1226 label: &str,
1227) -> wasmtime::Result<civil::Time> {
1228 h_m_s_ns_time(
1229 i64::from(st_i32(caller, st, field_offset + 1, label)?),
1230 i64::from(st_i32(caller, st, field_offset + 2, label)?),
1231 i64::from(st_i32(caller, st, field_offset + 3, label)?),
1232 i64::from(st_i32(caller, st, field_offset + 4, label)?),
1233 label,
1234 )
1235}
1236
1237pub(super) fn st_i32_or(
1238 caller: &mut Caller<'_, StoreData>,
1239 st: Rooted<StructRef>,
1240 field: u32,
1241) -> i32 {
1242 match st.field(caller, field as usize) {
1243 Ok(Val::I32(v)) => v,
1244 _ => 0,
1245 }
1246}
1247
1248fn st_i64(
1249 caller: &mut Caller<'_, StoreData>,
1250 st: Rooted<StructRef>,
1251 field: u32,
1252 label: &str,
1253) -> wasmtime::Result<i64> {
1254 match st.field(caller, field as usize)? {
1255 Val::I64(v) => Ok(v),
1256 other => wasmtime::bail!("Temporal.{label}: expected i64 field {field}, got {other:?}"),
1257 }
1258}
1259
1260fn st_string(
1261 caller: &mut Caller<'_, StoreData>,
1262 st: Rooted<StructRef>,
1263 field: u32,
1264 label: &str,
1265) -> wasmtime::Result<String> {
1266 let val = st.field(&mut *caller, field as usize)?;
1267 read_string_arg(caller, &val, label)
1268}
1269
1270pub(super) fn span_from_duration_struct(
1271 caller: &mut Caller<'_, StoreData>,
1272 st: Rooted<StructRef>,
1273 label: &str,
1274) -> wasmtime::Result<Span> {
1275 let mut fields = Vec::with_capacity(10);
1276 for i in 1..=10 {
1277 fields.push(st.field(&mut *caller, i)?);
1278 }
1279 let fields = read_span_fields(&fields)?;
1280 super::duration::from_fields(fields, label).map_err(crate::runtime::host::range_error)
1281}
1282
1283struct CachedZoned {
1284 value: Zoned,
1285 counter: std::sync::Arc<std::sync::atomic::AtomicU64>,
1286 bytes: u64,
1287}
1288impl Drop for CachedZoned {
1289 fn drop(&mut self) {
1290 self.counter
1291 .fetch_sub(self.bytes, std::sync::atomic::Ordering::Relaxed);
1292 }
1293}
1294
1295pub(super) fn zoned_date_time_from_struct(
1296 caller: &mut Caller<'_, StoreData>,
1297 st: Rooted<StructRef>,
1298 _label: &'static str,
1299) -> wasmtime::Result<Zoned> {
1300 let Val::ExternRef(Some(cached)) = st.field(&mut *caller, 4)? else {
1301 return Err(crate::runtime::host::fatal_host_error(
1302 "Temporal.ZonedDateTime: missing resolved value",
1303 ));
1304 };
1305 let payload = cached
1306 .data(&*caller)?
1307 .and_then(|data| data.downcast_ref::<CachedZoned>())
1308 .ok_or_else(|| {
1309 crate::runtime::host::fatal_host_error("Temporal.ZonedDateTime: invalid resolved value")
1310 })?;
1311 Ok(payload.value.clone())
1312}
1313
1314pub(super) fn zoned_date_time_from_val(
1315 caller: &mut Caller<'_, StoreData>,
1316 val: &Val,
1317 label: &'static str,
1318) -> wasmtime::Result<Zoned> {
1319 let ty = temporal_abi(caller)?.zoned_date_time.clone();
1320 let st = cast_struct(caller, val, &ty, label)?;
1321 zoned_date_time_from_struct(caller, st, label)
1322}
1323
1324pub(super) fn zoned_date_time_timestamp_from_val(
1325 caller: &mut Caller<'_, StoreData>,
1326 val: &Val,
1327 label: &'static str,
1328) -> wasmtime::Result<Timestamp> {
1329 let ty = temporal_abi(caller)?.zoned_date_time.clone();
1330 let st = cast_struct(caller, val, &ty, label)?;
1331 instant_from_struct(caller, st, label)
1332}
1333
1334pub(super) fn zoned_date_time_identity_from_val(
1335 caller: &mut Caller<'_, StoreData>,
1336 val: &Val,
1337 label: &'static str,
1338) -> wasmtime::Result<(Timestamp, String)> {
1339 let ty = temporal_abi(caller)?.zoned_date_time.clone();
1340 let st = cast_struct(caller, val, &ty, label)?;
1341 let timestamp = instant_from_struct(caller, st, label)?;
1342 let time_zone_id = st_string(caller, st, 3, label)?;
1343 Ok((timestamp, time_zone_id))
1344}
1345
1346pub(super) fn zoned_date_time_time_zone_id_from_val(
1347 caller: &mut Caller<'_, StoreData>,
1348 val: &Val,
1349 label: &'static str,
1350) -> wasmtime::Result<String> {
1351 let ty = temporal_abi(caller)?.zoned_date_time.clone();
1352 let st = cast_struct(caller, val, &ty, label)?;
1353 st_string(caller, st, 3, label)
1354}
1355
1356pub(super) fn zoned_date_time_parts_from_val(
1357 caller: &mut Caller<'_, StoreData>,
1358 val: &Val,
1359 label: &'static str,
1360) -> wasmtime::Result<(Zoned, String)> {
1361 let ty = temporal_abi(caller)?.zoned_date_time.clone();
1362 let st = cast_struct(caller, val, &ty, label)?;
1363 let secs = st_i64(caller, st, 1, label)?;
1364 let nanos = st_i32(caller, st, 2, label)?;
1365 let tz_id = st_string(caller, st, 3, label)?;
1366 let zoned = make_zoned(caller, secs, nanos, &tz_id, label)?;
1367 Ok((zoned, tz_id))
1368}
1369
1370pub(super) fn read_duration_like(
1371 caller: &mut Caller<'_, StoreData>,
1372 val: &Val,
1373 intr: &IntrinsicTypes,
1374 label: &str,
1375) -> wasmtime::Result<Span> {
1376 if let Val::AnyRef(Some(any)) = val
1377 && let Some(st) = any.as_struct(&mut *caller)?
1378 && st.matches_ty(&*caller, &intr.temporal_duration)?
1379 {
1380 return span_from_duration_struct(caller, st, label);
1381 }
1382
1383 let mut fields = [0; 10];
1384 for (index, name) in DURATION_FIELD_NAMES.iter().enumerate() {
1385 fields[index] = object_duration_field(caller, val, name, index, label)?.unwrap_or(0);
1386 }
1387 super::duration::from_fields(fields, label).map_err(crate::runtime::host::range_error)
1388}
1389
1390pub(super) fn duration_field_from_val(
1391 caller: &mut Caller<'_, StoreData>,
1392 val: &Val,
1393 index: usize,
1394 label: &'static str,
1395) -> wasmtime::Result<i64> {
1396 let ty = temporal_abi(caller)?.duration.clone();
1397 let st = cast_struct(caller, val, &ty, label)?;
1398 duration_struct_field(caller, st, index, label)
1399}
1400
1401pub(super) fn duration_sign_from_val(
1402 caller: &mut Caller<'_, StoreData>,
1403 val: &Val,
1404 label: &'static str,
1405) -> wasmtime::Result<i32> {
1406 let ty = temporal_abi(caller)?.duration.clone();
1407 let st = cast_struct(caller, val, &ty, label)?;
1408 for index in 0..DURATION_FIELD_NAMES.len() {
1409 let value = duration_struct_field(caller, st, index, label)?;
1410 if value != 0 {
1411 return Ok(value.signum() as i32);
1412 }
1413 }
1414 Ok(0)
1415}
1416
1417fn duration_struct_field(
1418 caller: &mut Caller<'_, StoreData>,
1419 st: Rooted<StructRef>,
1420 index: usize,
1421 label: &str,
1422) -> wasmtime::Result<i64> {
1423 let field = u32::try_from(index + 1)
1424 .map_err(|_| wasmtime::Error::msg("Temporal.Duration: invalid field index"))?;
1425 if index < 4 {
1426 st_i32(caller, st, field, label).map(i64::from)
1427 } else {
1428 st_i64(caller, st, field, label)
1429 }
1430}
1431
1432pub(super) const DURATION_FIELD_NAMES: [&str; 10] = [
1433 "years",
1434 "months",
1435 "weeks",
1436 "days",
1437 "hours",
1438 "minutes",
1439 "seconds",
1440 "milliseconds",
1441 "microseconds",
1442 "nanoseconds",
1443];
1444
1445fn object_field(
1446 caller: &mut Caller<'_, StoreData>,
1447 obj: &Val,
1448 name: &str,
1449) -> wasmtime::Result<Option<Val>> {
1450 let Some(value) = crate::runtime::prelude::collection::object_field(caller, obj, name)? else {
1451 return Ok(None);
1452 };
1453 if is_undefined(caller, &value)? {
1454 return Ok(None);
1455 }
1456 Ok(Some(value))
1457}
1458
1459pub(super) fn object_time_bag(
1460 caller: &mut Caller<'_, StoreData>,
1461 obj: &Val,
1462 label: &'static str,
1463) -> wasmtime::Result<[Option<i64>; 6]> {
1464 Ok([
1465 object_i64_field(caller, obj, "hour", label)?,
1466 object_i64_field(caller, obj, "minute", label)?,
1467 object_i64_field(caller, obj, "second", label)?,
1468 object_i64_field(caller, obj, "millisecond", label)?,
1469 object_i64_field(caller, obj, "microsecond", label)?,
1470 object_i64_field(caller, obj, "nanosecond", label)?,
1471 ])
1472}
1473
1474pub(super) fn object_i64_field(
1475 caller: &mut Caller<'_, StoreData>,
1476 obj: &Val,
1477 name: &str,
1478 label: &str,
1479) -> wasmtime::Result<Option<i64>> {
1480 let Some(f) = object_integer_f64_field(caller, obj, name, label)? else {
1481 return Ok(None);
1482 };
1483 if f <= -I64_BOUND_EXCLUSIVE || f >= I64_BOUND_EXCLUSIVE {
1484 return Err(crate::runtime::host::range_error(format!(
1485 "Temporal.{label}{{{name}}}: value {f} is outside the supported integer range; use a smaller absolute value"
1486 )));
1487 }
1488 Ok(Some(f as i64))
1489}
1490
1491pub(super) fn object_duration_field(
1492 caller: &mut Caller<'_, StoreData>,
1493 obj: &Val,
1494 name: &str,
1495 index: usize,
1496 label: &str,
1497) -> wasmtime::Result<Option<i64>> {
1498 let Some(value) = object_integer_f64_field(caller, obj, name, label)? else {
1499 return Ok(None);
1500 };
1501 super::duration::field_from_f64(value, index, label)
1502 .map(Some)
1503 .map_err(crate::runtime::host::range_error)
1504}
1505
1506fn object_integer_f64_field(
1507 caller: &mut Caller<'_, StoreData>,
1508 obj: &Val,
1509 name: &str,
1510 label: &str,
1511) -> wasmtime::Result<Option<f64>> {
1512 let Some(value) = object_f64_field(caller, obj, name, label)? else {
1513 return Ok(None);
1514 };
1515 if !value.is_finite() {
1516 return Err(crate::runtime::host::range_error(format!(
1517 "Temporal.{label}{{{name}}}: non-finite value"
1518 )));
1519 }
1520 if value.fract() != 0.0 {
1521 return Err(crate::runtime::host::range_error(format!(
1522 "Temporal.{label}{{{name}}}: non-integer value {value}"
1523 )));
1524 }
1525 Ok(Some(value))
1526}
1527
1528pub(super) fn object_string_field(
1529 caller: &mut Caller<'_, StoreData>,
1530 obj: &Val,
1531 name: &str,
1532 label: &str,
1533) -> wasmtime::Result<Option<String>> {
1534 let Some(v) = object_field(caller, obj, name)? else {
1535 return Ok(None);
1536 };
1537 read_string_arg(caller, &v, label).map(Some)
1538}
1539
1540pub(super) fn object_diff_options(
1541 caller: &mut Caller<'_, StoreData>,
1542 obj: &Val,
1543 label: &'static str,
1544) -> wasmtime::Result<DiffOptions> {
1545 let smallest = object_string_field(caller, obj, "smallestUnit", label)?
1546 .map(|unit| unit_from_str(&unit, label))
1547 .transpose()?;
1548 let largest = object_string_field(caller, obj, "largestUnit", label)?
1549 .map(|unit| unit_from_str(&unit, label))
1550 .transpose()?;
1551 let mode = object_string_field(caller, obj, "roundingMode", label)?
1552 .map(|mode| round_mode_from_str(&mode, label))
1553 .transpose()?;
1554 let increment = object_i64_field(caller, obj, "roundingIncrement", label)?;
1555 Ok(DiffOptions {
1556 smallest,
1557 largest,
1558 mode,
1559 increment,
1560 })
1561}
1562
1563fn object_f64_field(
1564 caller: &mut Caller<'_, StoreData>,
1565 obj: &Val,
1566 name: &str,
1567 label: &str,
1568) -> wasmtime::Result<Option<f64>> {
1569 let Some(v) = object_field(caller, obj, name)? else {
1570 return Ok(None);
1571 };
1572 let boxed_number_type = caller
1573 .data()
1574 .host_abi
1575 .as_ref()
1576 .map(|abi| abi.boxed_number_type.clone())
1577 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
1578 let Val::AnyRef(Some(any)) = v else {
1579 wasmtime::bail!("Temporal.{label}{{{name}}}: expected boxed number");
1580 };
1581 let Some(st) = any.as_struct(&mut *caller)? else {
1582 wasmtime::bail!("Temporal.{label}{{{name}}}: expected boxed number");
1583 };
1584 if !st.matches_ty(&*caller, &boxed_number_type)? {
1585 wasmtime::bail!("Temporal.{label}{{{name}}}: expected boxed number");
1586 }
1587 match st.field(&mut *caller, 1)? {
1588 Val::F64(bits) => Ok(Some(f64::from_bits(bits))),
1589 other => wasmtime::bail!("Temporal.{label}{{{name}}}: expected f64, got {other:?}"),
1590 }
1591}
1592
1593pub fn temporal_module_package_declaration() -> PackageDeclaration {
1594 let mut defs = PackageDeclaration::with_package(TEMPORAL_MODULE_NAME);
1595 let temporal_prefix = crate::mangle::host(TEMPORAL_MODULE_NAME, "Temporal");
1596
1597 let temporal = NamespaceSymbol {
1598 name: "Temporal".to_string(),
1599 mangled_prefix: temporal_prefix.clone(),
1600 declaration_span: DiagSpan::at(crate::FileId::TEMPORAL),
1601 values: BTreeMap::new(),
1602 types: BTreeMap::new(),
1603 namespaces: BTreeMap::new(),
1604 doc: None,
1605 };
1606
1607 let _ = &temporal_prefix;
1610
1611 defs.namespaces.insert("Temporal".to_string(), temporal);
1612 defs
1613}
1614
1615pub(super) fn temporal_type(name: &str) -> Type {
1616 Type::interface_ref(
1617 Package::prelude(),
1618 format!("Temporal.{name}"),
1619 crate::mangle::extend(&crate::mangle::prelude("Temporal"), name),
1620 Vec::new(),
1621 )
1622}
1623
1624pub(super) fn object_shape_type(optional_numbers: &[&str]) -> Type {
1625 let fields = optional_numbers
1626 .iter()
1627 .map(|name| (name.to_string(), ObjectField::optional(Type::Number)))
1628 .collect();
1629 Type::Object {
1630 index: None,
1631 fields,
1632 }
1633}
1634
1635pub(super) fn declare_direct_fn(
1636 defs: &mut PackageDeclaration,
1637 name: &str,
1638 mangled_name: MangledName,
1639 params: Vec<Param>,
1640 ret: Type,
1641) {
1642 defs.values.insert(
1643 mangled_name.as_str().to_string(),
1644 ValueSymbol {
1645 name: name.to_string(),
1646 mangled_name,
1647 declaration_span: DiagSpan::at(crate::FileId::TEMPORAL),
1648 kind: ValueKind::Function {
1649 generics: Vec::new(),
1650 params,
1651 ret,
1652 type_predicate: None,
1653 doc: None,
1654 },
1655 },
1656 );
1657}
1658
1659pub(super) fn clamp_month(m: i64) -> i64 {
1660 m.clamp(1, 12)
1661}
1662
1663pub(super) fn days_in_month(year: i64, month: i64, label: &'static str) -> wasmtime::Result<i64> {
1664 let d = y_m_d_date(year, month, 1, label)?;
1665 Ok(i64::from(d.days_in_month()))
1666}
1667
1668fn date_derived_field(d: civil::Date, selector: i32) -> i32 {
1669 match selector {
1670 0 => i32::from(d.weekday().to_monday_one_offset()),
1671 1 => i32::from(d.day_of_year()),
1672 2 => i32::from(d.iso_week_date().week()),
1673 3 => i32::from(d.iso_week_date().year()),
1674 4 => 7,
1675 5 => i32::from(d.days_in_month()),
1676 6 => i32::from(d.days_in_year()),
1677 7 => 12,
1678 8 => i32::from(d.in_leap_year()),
1679 _ => 0,
1680 }
1681}
1682
1683pub(super) fn y_m_d_date(
1684 year: i64,
1685 month: i64,
1686 day: i64,
1687 label: &'static str,
1688) -> wasmtime::Result<civil::Date> {
1689 let year = checked_field(year, -9_999, 9_999, label, "year")?;
1690 let month = checked_field(month, 1, 12, label, "month")?;
1691 let day = checked_field(day, 1, 31, label, "day")?;
1692 civil::Date::new(year, month, day).map_err(|_| {
1693 crate::runtime::host::range_error(format!(
1694 "Temporal.{label}: the resulting date is outside the representable range"
1695 ))
1696 })
1697}
1698
1699fn checked_field<T>(
1700 value: i64,
1701 minimum: i64,
1702 maximum: i64,
1703 label: &str,
1704 field: &str,
1705) -> wasmtime::Result<T>
1706where
1707 T: TryFrom<i64>,
1708{
1709 let range_error = || {
1710 crate::runtime::host::range_error(format!(
1711 "Temporal.{label}{{{field}}}: value {value} is outside the supported range {minimum}..={maximum}; use a {field} within that range"
1712 ))
1713 };
1714 if !(minimum..=maximum).contains(&value) {
1715 return Err(range_error());
1716 }
1717 T::try_from(value).map_err(|_| range_error())
1718}
1719
1720fn h_m_s_ns_time(
1721 hour: i64,
1722 minute: i64,
1723 second: i64,
1724 nanosecond: i64,
1725 label: &str,
1726) -> wasmtime::Result<civil::Time> {
1727 let hour = checked_field(hour, 0, 23, label, "hour")?;
1728 let minute = checked_field(minute, 0, 59, label, "minute")?;
1729 let second = checked_field(second, 0, 59, label, "second")?;
1730 let nanosecond = checked_field(nanosecond, 0, 999_999_999, label, "nanosecond")?;
1731 civil::Time::new(hour, minute, second, nanosecond).map_err(|_| {
1732 crate::runtime::host::range_error(format!(
1733 "Temporal.{label}: the resulting time is outside the representable range"
1734 ))
1735 })
1736}
1737
1738pub(super) fn merge_date(
1740 recv: (i32, i32, i32),
1741 bag_year: Option<i64>,
1742 bag_month: Option<i64>,
1743 bag_day: Option<i64>,
1744 label: &'static str,
1745) -> wasmtime::Result<(i64, i64, i64)> {
1746 let year = bag_year.unwrap_or_else(|| i64::from(recv.0));
1747 let month = clamp_month(bag_month.unwrap_or_else(|| i64::from(recv.1)));
1748 let dim = days_in_month(year, month, label)?;
1749 let day = bag_day.unwrap_or_else(|| i64::from(recv.2)).clamp(1, dim);
1750 Ok((year, month, day))
1751}
1752
1753pub(super) fn merge_time(
1757 recv: (i32, i32, i32, i32),
1758 bag: [Option<i64>; 6],
1759 label: &'static str,
1760) -> wasmtime::Result<civil::Time> {
1761 let hour = bag[0].unwrap_or_else(|| i64::from(recv.0)).clamp(0, 23);
1762 let minute = bag[1].unwrap_or_else(|| i64::from(recv.1)).clamp(0, 59);
1763 let second = bag[2].unwrap_or_else(|| i64::from(recv.2)).clamp(0, 59);
1764 let sub = i64::from(recv.3);
1765 let ms: i64 = checked_field(
1766 bag[3].unwrap_or(sub / 1_000_000).clamp(0, 999),
1767 0,
1768 999,
1769 label,
1770 "millisecond",
1771 )?;
1772 let us: i64 = checked_field(
1773 bag[4].unwrap_or((sub / 1_000) % 1_000).clamp(0, 999),
1774 0,
1775 999,
1776 label,
1777 "microsecond",
1778 )?;
1779 let ns: i64 = checked_field(
1780 bag[5].unwrap_or(sub % 1_000).clamp(0, 999),
1781 0,
1782 999,
1783 label,
1784 "nanosecond",
1785 )?;
1786 let subsec = ms * 1_000_000 + us * 1_000 + ns;
1787 h_m_s_ns_time(hour, minute, second, subsec, label)
1788}
1789
1790pub(super) fn year_month_reference(
1794 y: i32,
1795 m: i32,
1796 negative: bool,
1797 label: &'static str,
1798) -> wasmtime::Result<civil::Date> {
1799 let first = y_m_d_date(i64::from(y), i64::from(m), 1, label)?;
1800 Ok(if negative {
1801 first.last_of_month()
1802 } else {
1803 first
1804 })
1805}
1806
1807pub(super) struct DiffOptions {
1813 smallest: Option<Unit>,
1814 largest: Option<Unit>,
1815 mode: Option<RoundMode>,
1816 increment: Option<i64>,
1817}
1818
1819impl DiffOptions {
1820 fn is_default(&self) -> bool {
1821 self.smallest.is_none()
1822 && self.largest.is_none()
1823 && self.mode.is_none()
1824 && self.increment.is_none()
1825 }
1826
1827 pub(super) fn timestamp(&self, to: Timestamp) -> TimestampDifference {
1828 let mut d = TimestampDifference::new(to);
1829 if let Some(u) = self.smallest {
1830 d = d.smallest(u);
1831 }
1832 if let Some(u) = self.largest {
1833 d = d.largest(u);
1834 }
1835 if let Some(m) = self.mode {
1836 d = d.mode(m);
1837 }
1838 if let Some(i) = self.increment {
1839 d = d.increment(i);
1840 }
1841 d
1842 }
1843
1844 pub(super) fn zoned<'a>(&self, to: &'a Zoned) -> ZonedDifference<'a> {
1845 let mut d = ZonedDifference::new(to);
1846 if let Some(u) = self.smallest {
1847 d = d.smallest(u);
1848 }
1849 if let Some(u) = self.largest {
1850 d = d.largest(u);
1851 }
1852 if let Some(m) = self.mode {
1853 d = d.mode(m);
1854 }
1855 if let Some(i) = self.increment {
1856 d = d.increment(i);
1857 }
1858 d
1859 }
1860
1861 pub(super) fn description(&self) -> String {
1862 round_options_description(self.smallest, self.largest, self.mode, self.increment)
1863 }
1864
1865 pub(super) fn date(&self, to: civil::Date) -> civil::DateDifference {
1866 let mut d = civil::DateDifference::new(to);
1867 if let Some(u) = self.smallest {
1868 d = d.smallest(u);
1869 }
1870 if let Some(u) = self.largest {
1871 d = d.largest(u);
1872 }
1873 if let Some(m) = self.mode {
1874 d = d.mode(m);
1875 }
1876 if let Some(i) = self.increment {
1877 d = d.increment(i);
1878 }
1879 d
1880 }
1881
1882 pub(super) fn time(&self, to: civil::Time) -> civil::TimeDifference {
1883 let mut d = civil::TimeDifference::new(to);
1884 if let Some(u) = self.smallest {
1885 d = d.smallest(u);
1886 }
1887 if let Some(u) = self.largest {
1888 d = d.largest(u);
1889 }
1890 if let Some(m) = self.mode {
1891 d = d.mode(m);
1892 }
1893 if let Some(i) = self.increment {
1894 d = d.increment(i);
1895 }
1896 d
1897 }
1898
1899 pub(super) fn datetime(&self, to: civil::DateTime) -> civil::DateTimeDifference {
1900 let mut d = civil::DateTimeDifference::new(to);
1901 if let Some(u) = self.smallest {
1902 d = d.smallest(u);
1903 }
1904 if let Some(u) = self.largest {
1905 d = d.largest(u);
1906 }
1907 if let Some(m) = self.mode {
1908 d = d.mode(m);
1909 }
1910 if let Some(i) = self.increment {
1911 d = d.increment(i);
1912 }
1913 d
1914 }
1915}
1916
1917pub(super) fn year_month_span(
1921 ay: i32,
1922 am: i32,
1923 by: i32,
1924 bm: i32,
1925 o: &DiffOptions,
1926 label: &'static str,
1927) -> wasmtime::Result<Span> {
1928 if o.is_default() {
1929 return year_month_diff(ay, am, by, bm);
1930 }
1931 for unit in [o.smallest, o.largest].into_iter().flatten() {
1932 if !matches!(unit, Unit::Year | Unit::Month) {
1933 return Err(crate::runtime::host::range_error(format!(
1934 "{label}: largestUnit/smallestUnit must be \"years\" or \"months\" for a PlainYearMonth"
1935 )));
1936 }
1937 }
1938 let a = y_m_d_date(i64::from(ay), i64::from(am), 1, label)?;
1939 let b = y_m_d_date(i64::from(by), i64::from(bm), 1, label)?;
1940 let mut d = civil::DateDifference::new(b).largest(o.largest.unwrap_or(Unit::Year));
1941 if let Some(u) = o.smallest {
1942 d = d.smallest(u);
1943 }
1944 if let Some(m) = o.mode {
1945 d = d.mode(m);
1946 }
1947 if let Some(i) = o.increment {
1948 d = d.increment(i);
1949 }
1950 a.until(d).map_err(temporal_err(label))
1951}
1952
1953fn year_month_diff(ay: i32, am: i32, by: i32, bm: i32) -> wasmtime::Result<Span> {
1954 let months = (by - ay) * 12 + (bm - am);
1955 Span::new()
1956 .try_years(months / 12)
1957 .and_then(|s| s.try_months(months % 12))
1958 .map_err(temporal_err("PlainYearMonth.until"))
1959}
1960
1961pub(super) fn temporal_error(label: &str, detail: impl std::fmt::Display) -> String {
1967 format!("Temporal.{label}: {detail}")
1968}
1969
1970pub(super) fn temporal_err(label: &'static str) -> impl Fn(jiff::Error) -> wasmtime::Error {
1971 move |_e| {
1972 crate::runtime::host::range_error(temporal_error(
1973 label,
1974 "a date or time value is out of the representable range",
1975 ))
1976 }
1977}
1978
1979fn read_span_fields(slice: &[Val]) -> wasmtime::Result<[i64; 10]> {
1980 Ok([
1981 i64::from(read_i32(&slice[0], "Span.years")?),
1982 i64::from(read_i32(&slice[1], "Span.months")?),
1983 i64::from(read_i32(&slice[2], "Span.weeks")?),
1984 i64::from(read_i32(&slice[3], "Span.days")?),
1985 read_i64(&slice[4], "Span.hours")?,
1986 read_i64(&slice[5], "Span.minutes")?,
1987 read_i64(&slice[6], "Span.seconds")?,
1988 read_i64(&slice[7], "Span.milliseconds")?,
1989 read_i64(&slice[8], "Span.microseconds")?,
1990 read_i64(&slice[9], "Span.nanoseconds")?,
1991 ])
1992}
1993
1994#[allow(clippy::useless_conversion)]
1999fn write_span(results: &mut [Val], span: &Span) -> wasmtime::Result<()> {
2000 *abi_result(results, 0)? = Val::I32(i32::from(span.get_years()));
2001 *abi_result(results, 1)? = Val::I32(i32::from(span.get_months()));
2002 *abi_result(results, 2)? = Val::I32(span.get_weeks());
2003 *abi_result(results, 3)? = Val::I32(span.get_days());
2004 *abi_result(results, 4)? = Val::I64(i64::from(span.get_hours()));
2005 *abi_result(results, 5)? = Val::I64(span.get_minutes());
2006 *abi_result(results, 6)? = Val::I64(span.get_seconds());
2007 *abi_result(results, 7)? = Val::I64(span.get_milliseconds());
2008 *abi_result(results, 8)? = Val::I64(span.get_microseconds());
2009 *abi_result(results, 9)? = Val::I64(span.get_nanoseconds());
2010 Ok(())
2011}
2012
2013fn read_i32(v: &Val, name: &str) -> wasmtime::Result<i32> {
2014 match v {
2015 Val::I32(x) => Ok(*x),
2016 other => wasmtime::bail!("{name}: expected i32, got {other:?}"),
2017 }
2018}
2019
2020fn read_i64(v: &Val, name: &str) -> wasmtime::Result<i64> {
2021 match v {
2022 Val::I64(x) => Ok(*x),
2023 other => wasmtime::bail!("{name}: expected i64, got {other:?}"),
2024 }
2025}
2026
2027fn make_zoned(
2028 caller: &mut Caller<'_, StoreData>,
2029 secs: i64,
2030 nanos: i32,
2031 tz_id: &str,
2032 label: &'static str,
2033) -> wasmtime::Result<Zoned> {
2034 let (tz, _) = super::zoned_date_time::resolve_time_zone(caller, tz_id, label)?;
2035 let ts = Timestamp::new(secs, nanos).map_err(|_| {
2036 crate::runtime::host::range_error(format!(
2037 "Temporal.{label}: the instant is outside the representable range"
2038 ))
2039 })?;
2040 Ok(ts.to_zoned(tz))
2041}
2042
2043pub(super) enum Anchor {
2045 Date(civil::Date),
2046 Zoned(Zoned),
2047}
2048
2049pub(super) fn object_relative_to(
2050 caller: &mut Caller<'_, StoreData>,
2051 options: &Val,
2052 label: &'static str,
2053) -> wasmtime::Result<Option<Anchor>> {
2054 let Some(value) = object_field(caller, options, "relativeTo")? else {
2055 return Ok(None);
2056 };
2057 let Val::AnyRef(Some(any)) = value else {
2058 wasmtime::bail!("Temporal.{label}: relativeTo must be a PlainDate or ZonedDateTime");
2059 };
2060 let Some(st) = any.as_struct(&mut *caller)? else {
2061 wasmtime::bail!("Temporal.{label}: relativeTo must be a PlainDate or ZonedDateTime");
2062 };
2063 let (plain_date, zoned_date_time) = {
2064 let abi = temporal_abi(caller)?;
2065 (abi.plain_date.clone(), abi.zoned_date_time.clone())
2066 };
2067 if st.matches_ty(&*caller, &plain_date)? {
2068 return plain_date_from_struct(caller, st, label)
2069 .map(Anchor::Date)
2070 .map(Some);
2071 }
2072 if st.matches_ty(&*caller, &zoned_date_time)? {
2073 return zoned_date_time_from_struct(caller, st, label)
2074 .map(Anchor::Zoned)
2075 .map(Some);
2076 }
2077 wasmtime::bail!("Temporal.{label}: relativeTo must be a PlainDate or ZonedDateTime")
2078}
2079
2080pub(super) fn read_f64(v: &Val, name: &str) -> wasmtime::Result<f64> {
2081 match v {
2082 Val::F64(bits) => Ok(f64::from_bits(*bits)),
2083 other => wasmtime::bail!("{name}: expected f64, got {other:?}"),
2084 }
2085}
2086
2087pub(super) fn unit_from_str(unit: &str, label: &str) -> wasmtime::Result<Unit> {
2089 Ok(match unit {
2090 "nanosecond" | "nanoseconds" => Unit::Nanosecond,
2091 "microsecond" | "microseconds" => Unit::Microsecond,
2092 "millisecond" | "milliseconds" => Unit::Millisecond,
2093 "second" | "seconds" => Unit::Second,
2094 "minute" | "minutes" => Unit::Minute,
2095 "hour" | "hours" => Unit::Hour,
2096 "day" | "days" => Unit::Day,
2097 "week" | "weeks" => Unit::Week,
2098 "month" | "months" => Unit::Month,
2099 "year" | "years" => Unit::Year,
2100 other => {
2101 return Err(crate::runtime::host::range_error(format!(
2102 "Temporal.{label}: unknown unit {other:?}; use year, month, week, day, hour, minute, second, millisecond, microsecond, or nanosecond"
2103 )));
2104 }
2105 })
2106}
2107
2108pub(super) fn round_mode_from_str(mode: &str, label: &str) -> wasmtime::Result<RoundMode> {
2109 Ok(match mode {
2110 "ceil" => RoundMode::Ceil,
2111 "floor" => RoundMode::Floor,
2112 "expand" => RoundMode::Expand,
2113 "trunc" => RoundMode::Trunc,
2114 "halfCeil" => RoundMode::HalfCeil,
2115 "halfFloor" => RoundMode::HalfFloor,
2116 "halfExpand" => RoundMode::HalfExpand,
2117 "halfTrunc" => RoundMode::HalfTrunc,
2118 "halfEven" => RoundMode::HalfEven,
2119 other => {
2120 return Err(crate::runtime::host::range_error(format!(
2121 "Temporal.{label}: unknown roundingMode {other:?}; use ceil, floor, expand, trunc, halfCeil, halfFloor, halfExpand, halfTrunc, or halfEven"
2122 )));
2123 }
2124 })
2125}
2126
2127fn temporal_option(value: Option<&str>) -> String {
2128 value.map_or_else(|| "omitted".to_string(), |value| format!("{value:?}"))
2129}
2130
2131pub(super) fn string_option_description(value: Option<&str>) -> String {
2132 temporal_option(value)
2133}
2134
2135pub(super) fn i64_option_description(value: Option<i64>) -> String {
2136 value.map_or_else(|| "omitted".to_string(), |value| value.to_string())
2137}
2138
2139pub(super) fn temporal_unit_name(unit: Unit) -> &'static str {
2140 match unit {
2141 Unit::Year => "year",
2142 Unit::Month => "month",
2143 Unit::Week => "week",
2144 Unit::Day => "day",
2145 Unit::Hour => "hour",
2146 Unit::Minute => "minute",
2147 Unit::Second => "second",
2148 Unit::Millisecond => "millisecond",
2149 Unit::Microsecond => "microsecond",
2150 Unit::Nanosecond => "nanosecond",
2151 }
2152}
2153
2154fn temporal_round_mode_name(mode: RoundMode) -> &'static str {
2155 match mode {
2156 RoundMode::Ceil => "ceil",
2157 RoundMode::Floor => "floor",
2158 RoundMode::Expand => "expand",
2159 RoundMode::Trunc => "trunc",
2160 RoundMode::HalfCeil => "halfCeil",
2161 RoundMode::HalfFloor => "halfFloor",
2162 RoundMode::HalfExpand => "halfExpand",
2163 RoundMode::HalfTrunc => "halfTrunc",
2164 RoundMode::HalfEven => "halfEven",
2165 _ => "unsupported",
2166 }
2167}
2168
2169pub(super) fn round_options_description(
2170 smallest: Option<Unit>,
2171 largest: Option<Unit>,
2172 mode: Option<RoundMode>,
2173 increment: Option<i64>,
2174) -> String {
2175 let smallest = temporal_option(smallest.map(temporal_unit_name));
2176 let largest = temporal_option(largest.map(temporal_unit_name));
2177 let mode = temporal_option(mode.map(temporal_round_mode_name));
2178 let increment = i64_option_description(increment);
2179 format!(
2180 "smallestUnit={smallest}, largestUnit={largest}, roundingMode={mode}, roundingIncrement={increment}"
2181 )
2182}
2183
2184pub(super) fn duration_to_string_with_options(
2185 span: &Span,
2186 smallest: Option<&str>,
2187 mode: Option<&str>,
2188 fractional_digits: Option<u8>,
2189) -> wasmtime::Result<String> {
2190 let rounded = round_span_for_to_string(span, smallest, mode, fractional_digits)?;
2191 if let Some(digits) = fractional_digits {
2192 return format_duration_with_fractional_digits(&rounded, digits);
2193 }
2194 Ok(rounded.to_string())
2195}
2196
2197fn round_span_for_to_string(
2198 span: &Span,
2199 smallest: Option<&str>,
2200 mode: Option<&str>,
2201 fractional_digits: Option<u8>,
2202) -> wasmtime::Result<Span> {
2203 if smallest.is_none() && mode.is_none() && fractional_digits.is_none() {
2204 return Ok(*span);
2205 }
2206
2207 let mut round = SpanRound::new();
2208 let smallest_unit = smallest
2209 .map(|unit| unit_from_str(unit, "Duration.toString"))
2210 .transpose()?;
2211 let fractional_rounding = fractional_digits
2212 .map(rounding_for_fractional_second_digits)
2213 .transpose()?;
2214 let fractional_unit = fractional_rounding.map(|rounding| rounding.0);
2215 let effective_unit = match (smallest_unit, fractional_unit) {
2216 (Some(unit), Some(fractional)) => Some(more_specific_unit(unit, fractional)),
2217 (Some(unit), None) => Some(unit),
2218 (None, Some(unit)) => Some(unit),
2219 (None, None) => None,
2220 };
2221 if let Some(unit) = effective_unit {
2222 round = round.smallest(unit);
2223 }
2224 if let Some(mode) = mode {
2225 round = round.mode(round_mode_from_str(mode, "Duration.toString")?);
2226 }
2227 if let Some(digits) = fractional_digits {
2228 let (unit, increment) = rounding_for_fractional_second_digits(digits)?;
2229 if increment > 1 && Some(unit) == effective_unit {
2230 round = round.increment(increment);
2231 }
2232 }
2233 span.round(round).map_err(temporal_err("Duration.toString"))
2234}
2235
2236fn rounding_for_fractional_second_digits(digits: u8) -> wasmtime::Result<(Unit, i64)> {
2237 Ok(match digits {
2238 0 => (Unit::Second, 1),
2239 1 => (Unit::Millisecond, 100),
2240 2 => (Unit::Millisecond, 10),
2241 3 => (Unit::Millisecond, 1),
2242 4 => (Unit::Microsecond, 100),
2243 5 => (Unit::Microsecond, 10),
2244 6 => (Unit::Microsecond, 1),
2245 7 => (Unit::Nanosecond, 100),
2246 8 => (Unit::Nanosecond, 10),
2247 9 => (Unit::Nanosecond, 1),
2248 _ => {
2249 return Err(crate::runtime::host::invariant_trap(
2250 "Temporal: fractionalSecondDigits outside 0..=9",
2251 ));
2252 }
2253 })
2254}
2255
2256fn fractional_second_increment(digits: u8) -> wasmtime::Result<i64> {
2257 let exponent = 9u8.checked_sub(digits).ok_or_else(|| {
2258 crate::runtime::host::invariant_trap("Temporal: invalid fractional digits")
2259 })?;
2260 Ok(10_i64.pow(u32::from(exponent)))
2261}
2262
2263fn more_specific_unit(a: Unit, b: Unit) -> Unit {
2264 if unit_specificity(a) <= unit_specificity(b) {
2265 a
2266 } else {
2267 b
2268 }
2269}
2270
2271fn unit_specificity(unit: Unit) -> u8 {
2272 match unit {
2273 Unit::Nanosecond => 0,
2274 Unit::Microsecond => 1,
2275 Unit::Millisecond => 2,
2276 Unit::Second => 3,
2277 Unit::Minute => 4,
2278 Unit::Hour => 5,
2279 Unit::Day => 6,
2280 Unit::Week => 7,
2281 Unit::Month => 8,
2282 Unit::Year => 9,
2283 }
2284}
2285
2286fn format_duration_with_fractional_digits(span: &Span, digits: u8) -> wasmtime::Result<String> {
2287 let divisor = u128::try_from(fractional_second_increment(digits)?)
2288 .map_err(|_| crate::runtime::host::invariant_trap("Temporal: invalid fraction divisor"))?;
2289 let negative = span_fields(span).into_iter().any(|value| value < 0);
2290 let years = span.get_years().unsigned_abs();
2291 let months = span.get_months().unsigned_abs();
2292 let weeks = span.get_weeks().unsigned_abs();
2293 let days = span.get_days().unsigned_abs();
2294 let hours = span.get_hours().unsigned_abs();
2295 let minutes = span.get_minutes().unsigned_abs();
2296 let seconds = span.get_seconds().unsigned_abs();
2297 let milliseconds = span.get_milliseconds().unsigned_abs();
2298 let microseconds = span.get_microseconds().unsigned_abs();
2299 let nanoseconds = span.get_nanoseconds().unsigned_abs();
2300 let has_date = years != 0 || months != 0 || weeks != 0 || days != 0;
2301 let has_time = hours != 0
2302 || minutes != 0
2303 || seconds != 0
2304 || milliseconds != 0
2305 || microseconds != 0
2306 || nanoseconds != 0;
2307
2308 let mut out = String::new();
2309 if negative {
2310 out.push('-');
2311 }
2312 out.push('P');
2313 if years != 0 {
2314 out.push_str(&format!("{years}Y"));
2315 }
2316 if months != 0 {
2317 out.push_str(&format!("{months}M"));
2318 }
2319 if weeks != 0 {
2320 out.push_str(&format!("{weeks}W"));
2321 }
2322 if days != 0 {
2323 out.push_str(&format!("{days}D"));
2324 }
2325 if has_time || !has_date {
2326 out.push('T');
2327 if hours != 0 {
2328 out.push_str(&format!("{hours}H"));
2329 }
2330 if minutes != 0 {
2331 out.push_str(&format!("{minutes}M"));
2332 }
2333 let subsecond_nanos = u128::from(milliseconds) * 1_000_000
2334 + u128::from(microseconds) * 1_000
2335 + u128::from(nanoseconds);
2336 let include_seconds = seconds != 0 || subsecond_nanos != 0 || digits > 0 || !has_time;
2337 if include_seconds {
2338 out.push_str(&seconds.to_string());
2339 if digits > 0 {
2340 let fraction = subsecond_nanos / divisor;
2341 out.push('.');
2342 out.push_str(&format!("{fraction:0width$}", width = usize::from(digits)));
2343 }
2344 out.push('S');
2345 }
2346 }
2347 Ok(out)
2348}
2349
2350pub(super) fn span_fields(span: &Span) -> [i64; 10] {
2351 [
2352 i64::from(span.get_years()),
2353 i64::from(span.get_months()),
2354 i64::from(span.get_weeks()),
2355 i64::from(span.get_days()),
2356 i64::from(span.get_hours()),
2357 span.get_minutes(),
2358 span.get_seconds(),
2359 span.get_milliseconds(),
2360 span.get_microseconds(),
2361 span.get_nanoseconds(),
2362 ]
2363}
2364
2365pub(super) fn span_total(span: &Span, unit: &str, anchor: Option<Anchor>) -> wasmtime::Result<f64> {
2366 let unit_enum = unit_from_str(unit, "Duration.total")?;
2367 if anchor.is_none()
2368 && (super::duration::has_calendar_units(span)
2369 || super::duration::is_calendar_unit(unit_enum))
2370 {
2371 return Err(crate::runtime::host::range_error(
2372 super::duration::calendar_anchor_error("Duration.total", "compute this total"),
2373 ));
2374 }
2375 if span_fields(span).into_iter().all(|value| value == 0) {
2376 return Ok(0.0);
2377 }
2378 let total = match anchor {
2379 Some(Anchor::Zoned(z)) => span.total((unit_enum, &z)),
2380 Some(Anchor::Date(d)) => span.total((unit_enum, SpanRelativeTo::from(d))),
2381 None => {
2382 let sentinel = civil::date(2000, 1, 1)
2385 .at(0, 0, 0, 0)
2386 .to_zoned(TimeZone::UTC)
2387 .map_err(|_| {
2388 wasmtime::Error::msg(
2389 "Temporal.Duration.total: internal error computing the total",
2390 )
2391 })?;
2392 span.total((unit_enum, &sentinel))
2393 }
2394 };
2395 total.map_err(|_| {
2396 crate::runtime::host::range_error(
2397 "Temporal.Duration.total: cannot compute the total — the duration may be out of range",
2398 )
2399 })
2400}
2401
2402#[allow(clippy::too_many_lines)]
2405pub(crate) fn declare_types(defs: &mut crate::PackageDeclaration) {
2406 use crate::runtime::prelude::declaration::doc;
2407 use crate::runtime::prelude::declaration::{insert_temporal_plain_type, temporal_ref};
2408 use crate::{
2409 Dispatch, FileId, MethodSig, Param, PropertySig, Span, Type, TypeKind, TypeSymbol,
2410 ValueKind, ValueSymbol,
2411 };
2412 use std::collections::BTreeMap;
2413 let temporal_prefix = crate::mangle::prelude("Temporal");
2414 let temporal_instant_ref = || temporal_ref("Instant");
2415 let temporal_duration_ref = || temporal_ref("Duration");
2416 let temporal_zdt_ref = || temporal_ref("ZonedDateTime");
2417 let temporal_duration_fields_ref = || temporal_ref("DurationFields");
2418 let temporal_duration_like_ref = || {
2425 Type::union(vec![
2426 temporal_duration_ref(),
2427 temporal_duration_fields_ref(),
2428 ])
2429 };
2430 let temporal_to_json_sig = || MethodSig {
2431 optional: false,
2432 generics: Vec::new(),
2433 params: Vec::new(),
2434 ret: Type::String,
2435 predicate: None,
2436 doc: doc("/** Returns the ISO string form used by JSON.stringify. */"),
2437 };
2438 let temporal_plain_date_ref = || temporal_ref("PlainDate");
2439 let temporal_relative_to_ref =
2441 || Type::union(vec![temporal_plain_date_ref(), temporal_zdt_ref()]);
2442 let temporal_round_options_ref = || temporal_ref("DurationRoundOptions");
2443 let temporal_instant_round_options_ref = || temporal_ref("InstantRoundOptions");
2444 let temporal_zdt_round_options_ref = || temporal_ref("ZonedDateTimeRoundOptions");
2445 let temporal_since_until_options_ref = || temporal_ref("SinceUntilOptions");
2446 let temporal_total_options_ref = || temporal_ref("DurationTotalOptions");
2447 let temporal_compare_options_ref = || temporal_ref("DurationCompareOptions");
2448 let mut temporal = crate::NamespaceSymbol {
2449 name: "Temporal".to_string(),
2450 mangled_prefix: temporal_prefix.clone(),
2451 declaration_span: Span::at(crate::FileId::PRELUDE),
2452 values: BTreeMap::new(),
2453 types: BTreeMap::new(),
2454 namespaces: BTreeMap::new(),
2455 doc: doc(
2456 "/** Date/time primitives — subset of the JS Temporal proposal. Instant (timestamp), Duration (span), ZonedDateTime (instant + IANA tz), plus a non-deterministic `Now` sub-namespace. */",
2457 ),
2458 };
2459
2460 temporal.types.insert(
2462 "Instant".to_string(),
2463 TypeSymbol {
2464 name: "Temporal.Instant".to_string(),
2465 mangled_name: crate::mangle::extend(&temporal_prefix, "Instant"),
2466 declaration_span: Span::at(crate::FileId::PRELUDE),
2467 kind: TypeKind::Interface { index: None,
2468 generics: Vec::new(),
2469 methods: BTreeMap::from([
2470 (
2471 "add".to_string(),
2472 MethodSig {
2473 optional: false,
2474 generics: Vec::new(),
2475 params: vec![Param::new("d", temporal_duration_like_ref())],
2476 ret: temporal_instant_ref(),
2477 predicate: None,
2478 doc: doc("/** Returns a new Instant `d` later. Only time-unit components contribute. `d` may be a `Temporal.Duration` or a DurationLike bag (`{ hours: 1, minutes: 30 }`). */"),
2479 },
2480 ),
2481 (
2482 "subtract".to_string(),
2483 MethodSig {
2484 optional: false,
2485 generics: Vec::new(),
2486 params: vec![Param::new("d", temporal_duration_like_ref())],
2487 ret: temporal_instant_ref(),
2488 predicate: None,
2489 doc: doc("/** Returns a new Instant `d` earlier. `d` may be a `Temporal.Duration` or a DurationLike bag (`{ hours: 1, minutes: 30 }`). */"),
2490 },
2491 ),
2492 (
2493 "until".to_string(),
2494 MethodSig {
2495 optional: false,
2496 generics: Vec::new(),
2497 params: vec![
2498 Param::new("other", temporal_instant_ref()),
2499 Param::with_default(
2500 "options",
2501 temporal_since_until_options_ref(),
2502 crate::DefaultValue::Undefined,
2503 ),
2504 ],
2505 ret: temporal_duration_ref(),
2506 predicate: None,
2507 doc: doc("/** Returns the Duration from `this` to `other` (positive if `other` is later). `options` controls unit balancing and rounding. */"),
2508 },
2509 ),
2510 (
2511 "since".to_string(),
2512 MethodSig {
2513 optional: false,
2514 generics: Vec::new(),
2515 params: vec![
2516 Param::new("other", temporal_instant_ref()),
2517 Param::with_default(
2518 "options",
2519 temporal_since_until_options_ref(),
2520 crate::DefaultValue::Undefined,
2521 ),
2522 ],
2523 ret: temporal_duration_ref(),
2524 predicate: None,
2525 doc: doc("/** Returns the Duration from `other` to `this` (positive if `this` is later). `options` controls unit balancing and rounding. */"),
2526 },
2527 ),
2528 (
2529 "round".to_string(),
2530 MethodSig {
2531 optional: false,
2532 generics: Vec::new(),
2533 params: vec![Param::new(
2534 "roundTo",
2535 Type::union(vec![
2536 Type::String,
2537 temporal_instant_round_options_ref(),
2538 ]),
2539 )],
2540 ret: temporal_instant_ref(),
2541 predicate: None,
2542 doc: doc(
2543 "/** Rounds this Instant to the requested time unit and increment. */",
2544 ),
2545 },
2546 ),
2547 (
2548 "toZonedDateTimeISO".to_string(),
2549 MethodSig {
2550 optional: false,
2551 generics: Vec::new(),
2552 params: vec![Param::new("tz", Type::String)],
2553 ret: temporal_zdt_ref(),
2554 predicate: None,
2555 doc: doc("/** Project this Instant onto the IANA time zone `tz`. Throws on unknown zone id. */"),
2556 },
2557 ),
2558 (
2559 "toString".to_string(),
2560 MethodSig {
2561 optional: false,
2562 generics: Vec::new(),
2563 params: Vec::new(),
2564 ret: Type::String,
2565 predicate: None,
2566 doc: doc(
2567 "/** Returns the ISO 8601 representation (e.g. `\"2024-03-09T15:30:45.123Z\"`). */",
2568 ),
2569 },
2570 ),
2571 ("toJSON".to_string(), temporal_to_json_sig()),
2572 (
2573 "equals".to_string(),
2574 MethodSig {
2575 optional: false,
2576 generics: Vec::new(),
2577 params: vec![Param::new("other", temporal_instant_ref())],
2578 ret: Type::Boolean,
2579 predicate: None,
2580 doc: doc("/** `true` if `this` and `other` refer to the same instant. */"),
2581 },
2582 ),
2583 ]),
2584 properties: BTreeMap::from([
2585 (
2586 "epochMilliseconds".to_string(),
2587 PropertySig {
2588 ty: Type::Number,
2589 readonly: true,
2590 intrinsic: false,
2591 optional: false,
2592 doc: doc("/** Milliseconds since the Unix epoch (1970-01-01T00:00:00Z). */"),
2593 },
2594 ),
2595 (
2596 "epochNanoseconds".to_string(),
2597 PropertySig {
2598 ty: Type::BigInt,
2599 readonly: true,
2600 intrinsic: false,
2601 optional: false,
2602 doc: doc("/** Nanoseconds since the Unix epoch as a `bigint` (full precision). */"),
2603 },
2604 ),
2605 ]),
2606 dispatch: Dispatch::Direct,
2607 doc: doc(
2608 "/** A point in time, to nanosecond precision. Independent of any calendar or time zone — see `ZonedDateTime` for wall-clock fields. */",
2609 ),
2610 },
2611 },
2612 );
2613 temporal.types.insert(
2614 "InstantConstructor".to_string(),
2615 TypeSymbol {
2616 name: "Temporal.InstantConstructor".to_string(),
2617 mangled_name: crate::mangle::extend(&temporal_prefix, "InstantConstructor"),
2618 declaration_span: Span::at(crate::FileId::PRELUDE),
2619 kind: TypeKind::Interface { index: None,
2620 generics: Vec::new(),
2621 methods: BTreeMap::from([
2622 (
2623 "from".to_string(),
2624 MethodSig {
2625 optional: false,
2626 generics: Vec::new(),
2627 params: vec![Param::new("iso", Type::String)],
2628 ret: temporal_instant_ref(),
2629 predicate: None,
2630 doc: doc(
2631 "/** Parse an ISO 8601 instant string (e.g. `\"2024-03-09T15:30:45.123Z\"`). Throws on malformed input. */",
2632 ),
2633 },
2634 ),
2635 (
2636 "fromEpochMilliseconds".to_string(),
2637 MethodSig {
2638 optional: false,
2639 generics: Vec::new(),
2640 params: vec![Param::new("ms", Type::Number)],
2641 ret: temporal_instant_ref(),
2642 predicate: None,
2643 doc: doc("/** Construct an Instant from milliseconds since the Unix epoch. */"),
2644 },
2645 ),
2646 (
2647 "fromEpochNanoseconds".to_string(),
2648 MethodSig {
2649 optional: false,
2650 generics: Vec::new(),
2651 params: vec![Param::new("ns", Type::BigInt)],
2652 ret: temporal_instant_ref(),
2653 predicate: None,
2654 doc: doc(
2655 "/** Construct an Instant from nanoseconds since the Unix epoch (full precision). */",
2656 ),
2657 },
2658 ),
2659 (
2660 "compare".to_string(),
2661 MethodSig {
2662 optional: false,
2663 generics: Vec::new(),
2664 params: vec![
2665 Param::new("a", temporal_instant_ref()),
2666 Param::new("b", temporal_instant_ref()),
2667 ],
2668 ret: Type::Number,
2669 predicate: None,
2670 doc: doc(
2671 "/** Orders two Instants: -1, 0, or 1. Usable as an Array#sort comparator. */",
2672 ),
2673 },
2674 ),
2675 ]),
2676 properties: BTreeMap::new(),
2677 dispatch: Dispatch::Static,
2678 doc: doc(
2679 "/** Constructor object for `Temporal.Instant`. Accessed via the global `Temporal.Instant` binding. */",
2680 ),
2681 },
2682 },
2683 );
2684 temporal.values.insert(
2685 "Instant".to_string(),
2686 ValueSymbol {
2687 name: "Instant".to_string(),
2688 mangled_name: crate::mangle::extend(&temporal_prefix, "Instant"),
2689 declaration_span: Span::at(crate::FileId::PRELUDE),
2690 kind: ValueKind::Const {
2691 ty: temporal_ref("InstantConstructor"),
2692 doc: doc("/** The `Temporal.Instant` constructor. */"),
2693 },
2694 },
2695 );
2696
2697 let dur_field_prop = |doc_text: &'static str| PropertySig {
2698 ty: Type::Number,
2699 readonly: true,
2700 intrinsic: false,
2701 optional: true,
2702 doc: doc(doc_text),
2703 };
2704 let dur_unit_prop = |doc_text: &'static str| PropertySig {
2705 ty: Type::Number,
2706 readonly: true,
2707 intrinsic: false,
2708 optional: false,
2709 doc: doc(doc_text),
2710 };
2711 temporal.types.insert(
2712 "DurationFields".to_string(),
2713 TypeSymbol {
2714 name: "Temporal.DurationFields".to_string(),
2715 mangled_name: crate::mangle::extend(&temporal_prefix, "DurationFields"),
2716 declaration_span: Span::at(crate::FileId::PRELUDE),
2717 kind: TypeKind::Interface { index: None,
2718 generics: Vec::new(),
2719 methods: BTreeMap::new(),
2720 properties: BTreeMap::from([
2721 ("years".to_string(), dur_field_prop("/** Calendar years. */")),
2722 ("months".to_string(), dur_field_prop("/** Calendar months. */")),
2723 ("weeks".to_string(), dur_field_prop("/** Calendar weeks (7 days each). */")),
2724 ("days".to_string(), dur_field_prop("/** Calendar days. */")),
2725 ("hours".to_string(), dur_field_prop("/** Hours. */")),
2726 ("minutes".to_string(), dur_field_prop("/** Minutes. */")),
2727 ("seconds".to_string(), dur_field_prop("/** Seconds. */")),
2728 ("milliseconds".to_string(), dur_field_prop("/** Milliseconds. */")),
2729 ("microseconds".to_string(), dur_field_prop("/** Microseconds. */")),
2730 ("nanoseconds".to_string(), dur_field_prop("/** Nanoseconds. */")),
2731 ]),
2732 dispatch: Dispatch::VTable,
2733 doc: doc(
2734 "/** Options bag for `new Temporal.Duration({...})`. Every field is optional and defaults to 0. */",
2735 ),
2736 },
2737 },
2738 );
2739 {
2743 let mut insert_fields = |local: &str, names: &[&str]| {
2744 temporal.types.insert(
2745 local.to_string(),
2746 TypeSymbol {
2747 name: format!("Temporal.{local}"),
2748 mangled_name: crate::mangle::extend(&temporal_prefix, local),
2749 declaration_span: Span::at(crate::FileId::PRELUDE),
2750 kind: TypeKind::Interface { index: None,
2751 generics: Vec::new(),
2752 methods: BTreeMap::new(),
2753 properties: names
2754 .iter()
2755 .map(|n| ((*n).to_string(), dur_field_prop("/** Optional field; omitted fields keep the receiver's value. */")))
2756 .collect(),
2757 dispatch: Dispatch::VTable,
2758 doc: doc(
2759 "/** Partial fields bag for `with(...)`. Every field is optional. */",
2760 ),
2761 },
2762 },
2763 );
2764 };
2765 insert_fields("PlainDateFields", &["year", "month", "day"]);
2766 insert_fields(
2767 "PlainTimeFields",
2768 &[
2769 "hour",
2770 "minute",
2771 "second",
2772 "millisecond",
2773 "microsecond",
2774 "nanosecond",
2775 ],
2776 );
2777 insert_fields(
2778 "PlainDateTimeFields",
2779 &[
2780 "year",
2781 "month",
2782 "day",
2783 "hour",
2784 "minute",
2785 "second",
2786 "millisecond",
2787 "microsecond",
2788 "nanosecond",
2789 ],
2790 );
2791 insert_fields("PlainYearMonthFields", &["year", "month"]);
2792 insert_fields("PlainMonthDayFields", &["month", "day"]);
2793 insert_fields(
2794 "ZonedDateTimeFields",
2795 &[
2796 "year",
2797 "month",
2798 "day",
2799 "hour",
2800 "minute",
2801 "second",
2802 "millisecond",
2803 "microsecond",
2804 "nanosecond",
2805 ],
2806 );
2807 let mut insert_required = |local: &str, field: &str, field_doc: &'static str| {
2809 temporal.types.insert(
2810 local.to_string(),
2811 TypeSymbol {
2812 name: format!("Temporal.{local}"),
2813 mangled_name: crate::mangle::extend(&temporal_prefix, local),
2814 declaration_span: Span::at(crate::FileId::PRELUDE),
2815 kind: TypeKind::Interface {
2816 index: None,
2817 generics: Vec::new(),
2818 methods: BTreeMap::new(),
2819 properties: BTreeMap::from([(
2820 field.to_string(),
2821 PropertySig {
2822 ty: Type::Number,
2823 readonly: true,
2824 intrinsic: false,
2825 optional: false,
2826 doc: doc(field_doc),
2827 },
2828 )]),
2829 dispatch: Dispatch::VTable,
2830 doc: doc("/** The field that completes this value into a `PlainDate`. */"),
2831 },
2832 },
2833 );
2834 };
2835 insert_required(
2836 "PlainYearMonthToDateFields",
2837 "day",
2838 "/** Day of the month (1–31); constrained to the month length. */",
2839 );
2840 insert_required(
2841 "PlainMonthDayToDateFields",
2842 "year",
2843 "/** Calendar year; a Feb-29 month-day constrains to Feb-28 in a non-leap year. */",
2844 );
2845 temporal.types.insert(
2847 "PlainDateToZonedOptions".to_string(),
2848 TypeSymbol {
2849 name: "Temporal.PlainDateToZonedOptions".to_string(),
2850 mangled_name: crate::mangle::extend(&temporal_prefix, "PlainDateToZonedOptions"),
2851 declaration_span: Span::at(crate::FileId::PRELUDE),
2852 kind: TypeKind::Interface {
2853 index: None,
2854 generics: Vec::new(),
2855 methods: BTreeMap::new(),
2856 properties: BTreeMap::from([
2857 (
2858 "timeZone".to_string(),
2859 PropertySig {
2860 ty: Type::String,
2861 readonly: true,
2862 intrinsic: false,
2863 optional: false,
2864 doc: doc("/** IANA time-zone id (e.g. `\"America/New_York\"`). */"),
2865 },
2866 ),
2867 (
2868 "plainTime".to_string(),
2869 PropertySig {
2870 ty: temporal_ref("PlainTime"),
2871 readonly: true,
2872 intrinsic: false,
2873 optional: true,
2874 doc: doc(
2875 "/** Wall-clock time; defaults to midnight when omitted. */",
2876 ),
2877 },
2878 ),
2879 ]),
2880 dispatch: Dispatch::VTable,
2881 doc: doc("/** Options for `PlainDate.toZonedDateTime`. */"),
2882 },
2883 },
2884 );
2885 }
2886 let opt_string_prop = |doc_text: &'static str| PropertySig {
2887 ty: Type::String,
2888 readonly: true,
2889 intrinsic: false,
2890 optional: true,
2891 doc: doc(doc_text),
2892 };
2893 let relative_to_prop = |optional: bool, doc_text: &'static str| PropertySig {
2894 ty: temporal_relative_to_ref(),
2895 readonly: true,
2896 intrinsic: false,
2897 optional,
2898 doc: doc(doc_text),
2899 };
2900 let opt_interface = |name: &str,
2901 properties: BTreeMap<String, PropertySig>,
2902 doc_text: &'static str| TypeSymbol {
2903 name: format!("Temporal.{name}"),
2904 mangled_name: crate::mangle::extend(&temporal_prefix, name),
2905 declaration_span: Span::at(FileId::PRELUDE),
2906 kind: TypeKind::Interface {
2907 index: None,
2908 generics: Vec::new(),
2909 methods: BTreeMap::new(),
2910 properties,
2911 dispatch: Dispatch::VTable,
2912 doc: doc(doc_text),
2913 },
2914 };
2915 temporal.types.insert(
2916 "InstantRoundOptions".to_string(),
2917 opt_interface(
2918 "InstantRoundOptions",
2919 BTreeMap::from([
2920 (
2921 "smallestUnit".to_string(),
2922 PropertySig {
2923 ty: Type::String,
2924 readonly: true,
2925 intrinsic: false,
2926 optional: false,
2927 doc: doc(
2928 "/** Smallest time unit to retain, from hour through nanosecond. */",
2929 ),
2930 },
2931 ),
2932 (
2933 "roundingMode".to_string(),
2934 opt_string_prop("/** Rounding mode; defaults to `\"halfExpand\"`. */"),
2935 ),
2936 (
2937 "roundingIncrement".to_string(),
2938 dur_field_prop("/** Positive increment of `smallestUnit`; defaults to 1. */"),
2939 ),
2940 ]),
2941 "/** Options for `Temporal.Instant.round(...)`. */",
2942 ),
2943 );
2944 temporal.types.insert(
2945 "DurationRoundOptions".to_string(),
2946 opt_interface(
2947 "DurationRoundOptions",
2948 BTreeMap::from([
2949 ("smallestUnit".to_string(), opt_string_prop("/** Smallest unit to keep (e.g. `\"second\"`). */")),
2950 ("largestUnit".to_string(), opt_string_prop("/** Largest unit to balance into (e.g. `\"hour\"`). */")),
2951 ("roundingMode".to_string(), opt_string_prop("/** `\"halfExpand\"` (default), `\"ceil\"`, `\"floor\"`, `\"trunc\"`, `\"halfEven\"`, … */")),
2952 ("roundingIncrement".to_string(), dur_field_prop("/** Round to a multiple of this increment (default 1). */")),
2953 ("relativeTo".to_string(), relative_to_prop(true, "/** Anchor (PlainDate or ZonedDateTime) — required to round calendar units. */")),
2954 ]),
2955 "/** Options for `Temporal.Duration.round(...)`. */",
2956 ),
2957 );
2958 temporal.types.insert(
2959 "ZonedDateTimeRoundOptions".to_string(),
2960 opt_interface(
2961 "ZonedDateTimeRoundOptions",
2962 BTreeMap::from([
2963 (
2964 "smallestUnit".to_string(),
2965 PropertySig {
2966 ty: Type::String,
2967 readonly: true,
2968 intrinsic: false,
2969 optional: false,
2970 doc: doc(
2971 "/** Smallest wall-clock unit to retain, from day through nanosecond. */",
2972 ),
2973 },
2974 ),
2975 (
2976 "roundingMode".to_string(),
2977 opt_string_prop("/** Rounding mode; defaults to `\"halfExpand\"`. */"),
2978 ),
2979 (
2980 "roundingIncrement".to_string(),
2981 dur_field_prop("/** Positive increment of `smallestUnit`; defaults to 1. */"),
2982 ),
2983 ]),
2984 "/** Options for `Temporal.ZonedDateTime.round(...)`. */",
2985 ),
2986 );
2987 temporal.types.insert(
2988 "SinceUntilOptions".to_string(),
2989 opt_interface(
2990 "SinceUntilOptions",
2991 BTreeMap::from([
2992 ("largestUnit".to_string(), opt_string_prop("/** Largest unit to balance into (e.g. `\"hours\"`, `\"months\"`). */")),
2993 ("smallestUnit".to_string(), opt_string_prop("/** Smallest unit to keep; the result is rounded to it. */")),
2994 ("roundingMode".to_string(), opt_string_prop("/** `\"halfExpand\"` (default), `\"ceil\"`, `\"floor\"`, `\"trunc\"`, `\"halfEven\"`, … */")),
2995 ("roundingIncrement".to_string(), dur_field_prop("/** Round to a multiple of this increment of `smallestUnit` (default 1). */")),
2996 ]),
2997 "/** Options for `until`/`since` on every Temporal type. The receiver anchors calendar units — no `relativeTo` needed. */",
2998 ),
2999 );
3000 temporal.types.insert(
3001 "DurationTotalOptions".to_string(),
3002 opt_interface(
3003 "DurationTotalOptions",
3004 BTreeMap::from([
3005 (
3006 "unit".to_string(),
3007 PropertySig {
3008 ty: Type::String,
3009 readonly: true,
3010 intrinsic: false,
3011 optional: false,
3012 doc: doc("/** The unit to total into (e.g. `\"hours\"`, `\"days\"`). */"),
3013 },
3014 ),
3015 (
3016 "relativeTo".to_string(),
3017 relative_to_prop(
3018 true,
3019 "/** Anchor (PlainDate or ZonedDateTime) — required for calendar units. */",
3020 ),
3021 ),
3022 ]),
3023 "/** Options for `Temporal.Duration.prototype.total(...)`. */",
3024 ),
3025 );
3026 temporal.types.insert(
3027 "DurationToStringOptions".to_string(),
3028 opt_interface(
3029 "DurationToStringOptions",
3030 BTreeMap::from([
3031 (
3032 "smallestUnit".to_string(),
3033 opt_string_prop("/** Smallest unit to keep while formatting. */"),
3034 ),
3035 (
3036 "roundingMode".to_string(),
3037 opt_string_prop(
3038 "/** `\"trunc\"` (default), `\"ceil\"`, `\"floor\"`, `\"halfExpand\"`, … */",
3039 ),
3040 ),
3041 (
3042 "fractionalSecondDigits".to_string(),
3043 dur_field_prop(
3044 "/** Exact fractional second digits to print, from 0 through 9. */",
3045 ),
3046 ),
3047 ]),
3048 "/** Options for `Temporal.Duration.prototype.toString(...)`. */",
3049 ),
3050 );
3051 temporal.types.insert(
3052 "DurationCompareOptions".to_string(),
3053 opt_interface(
3054 "DurationCompareOptions",
3055 BTreeMap::from([(
3056 "relativeTo".to_string(),
3057 relative_to_prop(true, "/** Anchor (PlainDate or ZonedDateTime) — required to compare calendar durations. */"),
3058 )]),
3059 "/** Options for `Temporal.Duration.compare(...)`. */",
3060 ),
3061 );
3062 temporal.types.insert(
3063 "Duration".to_string(),
3064 TypeSymbol {
3065 name: "Temporal.Duration".to_string(),
3066 mangled_name: crate::mangle::extend(&temporal_prefix, "Duration"),
3067 declaration_span: Span::at(crate::FileId::PRELUDE),
3068 kind: TypeKind::Interface { index: None,
3069 generics: Vec::new(),
3070 methods: BTreeMap::from([
3071 (
3072 "add".to_string(),
3073 MethodSig {
3074 optional: false,
3075 generics: Vec::new(),
3076 params: vec![Param::new("d", temporal_duration_like_ref())],
3077 ret: temporal_duration_ref(),
3078 predicate: None,
3079 doc: doc("/** Returns `this + d` as a new Duration. `d` may be a `Temporal.Duration` or a DurationLike bag (`{ hours: 1, minutes: 30 }`). */"),
3080 },
3081 ),
3082 (
3083 "subtract".to_string(),
3084 MethodSig {
3085 optional: false,
3086 generics: Vec::new(),
3087 params: vec![Param::new("d", temporal_duration_like_ref())],
3088 ret: temporal_duration_ref(),
3089 predicate: None,
3090 doc: doc("/** Returns `this - d` as a new Duration. `d` may be a `Temporal.Duration` or a DurationLike bag (`{ hours: 1, minutes: 30 }`). */"),
3091 },
3092 ),
3093 (
3094 "negated".to_string(),
3095 MethodSig {
3096 optional: false,
3097 generics: Vec::new(),
3098 params: Vec::new(),
3099 ret: temporal_duration_ref(),
3100 predicate: None,
3101 doc: doc("/** Returns a Duration with every field's sign flipped. */"),
3102 },
3103 ),
3104 (
3105 "total".to_string(),
3106 MethodSig {
3107 optional: false,
3108 generics: Vec::new(),
3109 params: vec![Param::new(
3110 "totalOf",
3111 Type::union(vec![Type::String, temporal_total_options_ref()]),
3112 )],
3113 ret: Type::Number,
3114 predicate: None,
3115 doc: doc(
3116 "/** Total length of this Duration in a unit — `total(\"hours\")` or `total({ unit, relativeTo })`. Calendar units (`\"weeks\"`/`\"months\"`/`\"years\"`) require a `relativeTo` anchor (a PlainDate or ZonedDateTime). */",
3117 ),
3118 },
3119 ),
3120 (
3121 "round".to_string(),
3122 MethodSig {
3123 optional: false,
3124 generics: Vec::new(),
3125 params: vec![Param::new(
3126 "roundTo",
3127 Type::union(vec![
3128 Type::String,
3129 temporal_round_options_ref(),
3130 ]),
3131 )],
3132 ret: temporal_duration_ref(),
3133 predicate: None,
3134 doc: doc(
3135 "/** Rounds this Duration — `round(\"hour\")` (smallestUnit shorthand) or `round({ smallestUnit, largestUnit, roundingMode, roundingIncrement, relativeTo })`. Calendar units need a `relativeTo` anchor. */",
3136 ),
3137 },
3138 ),
3139 (
3140 "toString".to_string(),
3141 MethodSig {
3142 optional: false,
3143 generics: Vec::new(),
3144 params: vec![Param::with_default(
3145 "options",
3146 temporal_ref("DurationToStringOptions"),
3147 crate::DefaultValue::Undefined,
3148 )],
3149 ret: Type::String,
3150 predicate: None,
3151 doc: doc("/** Returns the ISO 8601 duration form (e.g. `\"PT1H30M\"`), optionally rounded/formatted. */"),
3152 },
3153 ),
3154 ("toJSON".to_string(), temporal_to_json_sig()),
3155 (
3156 "with".to_string(),
3157 MethodSig {
3158 optional: false,
3159 generics: Vec::new(),
3160 params: vec![Param::new("fields", temporal_duration_like_ref())],
3161 ret: temporal_duration_ref(),
3162 predicate: None,
3163 doc: doc(
3164 "/** Returns a copy of this Duration with the given slots replaced (e.g. `d.with({ hours: 5 })`); unlisted slots are kept. */",
3165 ),
3166 },
3167 ),
3168 (
3169 "abs".to_string(),
3170 MethodSig {
3171 optional: false,
3172 generics: Vec::new(),
3173 params: Vec::new(),
3174 ret: temporal_duration_ref(),
3175 predicate: None,
3176 doc: doc(
3177 "/** Returns a Duration with every slot's absolute value (a non-negative Duration). */",
3178 ),
3179 },
3180 ),
3181 ]),
3182 properties: BTreeMap::from([
3183 ("years".to_string(), dur_unit_prop("/** Calendar-years slot. */")),
3184 ("months".to_string(), dur_unit_prop("/** Calendar-months slot. */")),
3185 ("weeks".to_string(), dur_unit_prop("/** Calendar-weeks slot. */")),
3186 ("days".to_string(), dur_unit_prop("/** Calendar-days slot. */")),
3187 ("hours".to_string(), dur_unit_prop("/** Hours slot. */")),
3188 ("minutes".to_string(), dur_unit_prop("/** Minutes slot. */")),
3189 ("seconds".to_string(), dur_unit_prop("/** Seconds slot. */")),
3190 ("milliseconds".to_string(), dur_unit_prop("/** Milliseconds slot. */")),
3191 ("microseconds".to_string(), dur_unit_prop("/** Microseconds slot. */")),
3192 ("nanoseconds".to_string(), dur_unit_prop("/** Nanoseconds slot. */")),
3193 (
3194 "sign".to_string(),
3195 dur_unit_prop(
3196 "/** `-1`, `0`, or `1`: the Duration's overall direction (every slot shares this sign). */",
3197 ),
3198 ),
3199 (
3200 "blank".to_string(),
3201 PropertySig {
3202 ty: Type::Boolean,
3203 readonly: true,
3204 intrinsic: false,
3205 optional: false,
3206 doc: doc(
3207 "/** `true` when every slot is `0` (the Duration is empty / `sign === 0`). */",
3208 ),
3209 },
3210 ),
3211 ]),
3212 dispatch: Dispatch::Direct,
3213 doc: doc(
3214 "/** A span of time, expressed as separate calendar (`years`/`months`/`weeks`/`days`) and time (`hours`/…/`nanoseconds`) unit slots. Field reads return the literal slot value — use `total(unit)` for the computed total. */",
3215 ),
3216 },
3217 },
3218 );
3219 temporal.types.insert(
3220 "DurationConstructor".to_string(),
3221 TypeSymbol {
3222 name: "Temporal.DurationConstructor".to_string(),
3223 mangled_name: crate::mangle::extend(&temporal_prefix, "DurationConstructor"),
3224 declaration_span: Span::at(crate::FileId::PRELUDE),
3225 kind: TypeKind::Interface { index: None,
3226 generics: Vec::new(),
3227 methods: BTreeMap::from([
3228 (
3229 "new".to_string(),
3230 MethodSig {
3231 optional: false,
3232 generics: Vec::new(),
3233 params: vec![Param::new("fields", temporal_duration_fields_ref())],
3234 ret: temporal_duration_ref(),
3235 predicate: None,
3236 doc: doc(
3237 "/** Construct a Duration from a fields bag (e.g. `{ hours: 1, minutes: 30 }`). Every field defaults to 0. */",
3238 ),
3239 },
3240 ),
3241 (
3242 "from".to_string(),
3243 MethodSig {
3244 optional: false,
3245 generics: Vec::new(),
3246 params: vec![Param::new(
3247 "item",
3248 Type::union(vec![
3249 Type::String,
3250 temporal_duration_ref(),
3251 temporal_duration_fields_ref(),
3252 ]),
3253 )],
3254 ret: temporal_duration_ref(),
3255 predicate: None,
3256 doc: doc(
3257 "/** Build a Duration from an ISO 8601 string (e.g. `\"PT1H30M\"`), an existing Duration, or a fields bag (`{ hours: 1 }`). */",
3258 ),
3259 },
3260 ),
3261 (
3262 "compare".to_string(),
3263 MethodSig {
3264 optional: false,
3265 generics: Vec::new(),
3266 params: vec![
3267 Param::new("a", temporal_duration_like_ref()),
3268 Param::new("b", temporal_duration_like_ref()),
3269 Param::with_default(
3270 "options",
3271 temporal_compare_options_ref(),
3272 crate::DefaultValue::Undefined,
3273 ),
3274 ],
3275 ret: Type::Number,
3276 predicate: None,
3277 doc: doc(
3278 "/** Compares two Durations: `-1`, `0`, or `1`. Calendar durations need `options.relativeTo` (a PlainDate or ZonedDateTime). */",
3279 ),
3280 },
3281 ),
3282 ]),
3283 properties: BTreeMap::new(),
3284 dispatch: Dispatch::Static,
3285 doc: doc(
3286 "/** Constructor object for `Temporal.Duration`. Accessed via the global `Temporal.Duration` binding. */",
3287 ),
3288 },
3289 },
3290 );
3291 temporal.values.insert(
3292 "Duration".to_string(),
3293 ValueSymbol {
3294 name: "Duration".to_string(),
3295 mangled_name: crate::mangle::extend(&temporal_prefix, "Duration"),
3296 declaration_span: Span::at(crate::FileId::PRELUDE),
3297 kind: ValueKind::Const {
3298 ty: temporal_ref("DurationConstructor"),
3299 doc: doc("/** The `Temporal.Duration` constructor. */"),
3300 },
3301 },
3302 );
3303
3304 let zdt_field_prop = |doc_text: &'static str| PropertySig {
3305 ty: Type::Number,
3306 readonly: true,
3307 intrinsic: false,
3308 optional: false,
3309 doc: doc(doc_text),
3310 };
3311 temporal.types.insert(
3312 "ZonedDateTime".to_string(),
3313 TypeSymbol {
3314 name: "Temporal.ZonedDateTime".to_string(),
3315 mangled_name: crate::mangle::extend(&temporal_prefix, "ZonedDateTime"),
3316 declaration_span: Span::at(crate::FileId::PRELUDE),
3317 kind: TypeKind::Interface { index: None,
3318 generics: Vec::new(),
3319 methods: BTreeMap::from([
3320 (
3321 "add".to_string(),
3322 MethodSig {
3323 optional: false,
3324 generics: Vec::new(),
3325 params: vec![Param::new("d", temporal_duration_like_ref())],
3326 ret: temporal_zdt_ref(),
3327 predicate: None,
3328 doc: doc(
3329 "/** Returns a new ZonedDateTime `d` later. Calendar-aware (DST transitions respected). `d` may be a `Temporal.Duration` or a DurationLike bag (`{ hours: 1, minutes: 30 }`). */",
3330 ),
3331 },
3332 ),
3333 (
3334 "subtract".to_string(),
3335 MethodSig {
3336 optional: false,
3337 generics: Vec::new(),
3338 params: vec![Param::new("d", temporal_duration_like_ref())],
3339 ret: temporal_zdt_ref(),
3340 predicate: None,
3341 doc: doc("/** Returns a new ZonedDateTime `d` earlier. `d` may be a `Temporal.Duration` or a DurationLike bag (`{ hours: 1, minutes: 30 }`). */"),
3342 },
3343 ),
3344 (
3345 "until".to_string(),
3346 MethodSig {
3347 optional: false,
3348 generics: Vec::new(),
3349 params: vec![
3350 Param::new("other", temporal_zdt_ref()),
3351 Param::with_default(
3352 "options",
3353 temporal_since_until_options_ref(),
3354 crate::DefaultValue::Undefined,
3355 ),
3356 ],
3357 ret: temporal_duration_ref(),
3358 predicate: None,
3359 doc: doc("/** Returns the calendar-aware Duration from `this` to `other`. `options` controls unit balancing and rounding. */"),
3360 },
3361 ),
3362 (
3363 "since".to_string(),
3364 MethodSig {
3365 optional: false,
3366 generics: Vec::new(),
3367 params: vec![
3368 Param::new("other", temporal_zdt_ref()),
3369 Param::with_default(
3370 "options",
3371 temporal_since_until_options_ref(),
3372 crate::DefaultValue::Undefined,
3373 ),
3374 ],
3375 ret: temporal_duration_ref(),
3376 predicate: None,
3377 doc: doc("/** Returns the calendar-aware Duration from `other` to `this`. `options` controls unit balancing and rounding. */"),
3378 },
3379 ),
3380 (
3381 "withTimeZone".to_string(),
3382 MethodSig {
3383 optional: false,
3384 generics: Vec::new(),
3385 params: vec![Param::new("tz", Type::String)],
3386 ret: temporal_zdt_ref(),
3387 predicate: None,
3388 doc: doc(
3389 "/** Same instant, different IANA time zone. Wall-clock fields shift accordingly. Throws on unknown zone id. */",
3390 ),
3391 },
3392 ),
3393 (
3394 "with".to_string(),
3395 MethodSig {
3396 optional: false,
3397 generics: Vec::new(),
3398 params: vec![Param::new(
3399 "fields",
3400 temporal_ref("ZonedDateTimeFields"),
3401 )],
3402 ret: temporal_zdt_ref(),
3403 predicate: None,
3404 doc: doc(
3405 "/** Returns a copy with the listed wall-clock fields replaced (keeping the time zone); unlisted fields are kept. Out-of-range values are constrained. */",
3406 ),
3407 },
3408 ),
3409 (
3410 "round".to_string(),
3411 MethodSig {
3412 optional: false,
3413 generics: Vec::new(),
3414 params: vec![Param::new(
3415 "roundTo",
3416 Type::union(vec![
3417 Type::String,
3418 temporal_zdt_round_options_ref(),
3419 ]),
3420 )],
3421 ret: temporal_zdt_ref(),
3422 predicate: None,
3423 doc: doc(
3424 "/** Rounds this ZonedDateTime in its time zone, from day through nanosecond precision. */",
3425 ),
3426 },
3427 ),
3428 (
3429 "startOfDay".to_string(),
3430 MethodSig {
3431 optional: false,
3432 generics: Vec::new(),
3433 params: Vec::new(),
3434 ret: temporal_zdt_ref(),
3435 predicate: None,
3436 doc: doc(
3437 "/** Returns the first valid instant on this date in the current time zone. */",
3438 ),
3439 },
3440 ),
3441 (
3442 "toInstant".to_string(),
3443 MethodSig {
3444 optional: false,
3445 generics: Vec::new(),
3446 params: Vec::new(),
3447 ret: temporal_instant_ref(),
3448 predicate: None,
3449 doc: doc("/** Drops the time-zone, returns the underlying Instant. */"),
3450 },
3451 ),
3452 (
3453 "toPlainDate".to_string(),
3454 MethodSig {
3455 optional: false,
3456 generics: Vec::new(),
3457 params: Vec::new(),
3458 ret: temporal_plain_date_ref(),
3459 predicate: None,
3460 doc: doc(
3461 "/** The wall-clock calendar date in this time zone, as a tz-free `PlainDate`. */",
3462 ),
3463 },
3464 ),
3465 (
3466 "toPlainTime".to_string(),
3467 MethodSig {
3468 optional: false,
3469 generics: Vec::new(),
3470 params: Vec::new(),
3471 ret: temporal_ref("PlainTime"),
3472 predicate: None,
3473 doc: doc(
3474 "/** The wall-clock time in this time zone, as a tz-free `PlainTime` (with nanosecond precision). */",
3475 ),
3476 },
3477 ),
3478 (
3479 "toPlainDateTime".to_string(),
3480 MethodSig {
3481 optional: false,
3482 generics: Vec::new(),
3483 params: Vec::new(),
3484 ret: temporal_ref("PlainDateTime"),
3485 predicate: None,
3486 doc: doc(
3487 "/** The wall-clock date and time in this time zone, as a tz-free `PlainDateTime`. */",
3488 ),
3489 },
3490 ),
3491 (
3492 "toString".to_string(),
3493 MethodSig {
3494 optional: false,
3495 generics: Vec::new(),
3496 params: Vec::new(),
3497 ret: Type::String,
3498 predicate: None,
3499 doc: doc(
3500 "/** Returns the ISO 8601 form with bracketed tz (e.g. `\"2024-03-09T12:00:00+00:00[UTC]\"`). */",
3501 ),
3502 },
3503 ),
3504 ("toJSON".to_string(), temporal_to_json_sig()),
3505 (
3506 "equals".to_string(),
3507 MethodSig {
3508 optional: false,
3509 generics: Vec::new(),
3510 params: vec![Param::new("other", temporal_zdt_ref())],
3511 ret: Type::Boolean,
3512 predicate: None,
3513 doc: doc(
3514 "/** `true` if `this` and `other` have the same instant **and** the same time-zone id. */",
3515 ),
3516 },
3517 ),
3518 ]),
3519 properties: BTreeMap::from([
3520 (
3521 "timeZoneId".to_string(),
3522 PropertySig {
3523 ty: Type::String,
3524 readonly: true,
3525 intrinsic: false,
3526 optional: false,
3527 doc: doc("/** The IANA time-zone id (e.g. `\"America/New_York\"`). */"),
3528 },
3529 ),
3530 (
3531 "epochMilliseconds".to_string(),
3532 PropertySig {
3533 ty: Type::Number,
3534 readonly: true,
3535 intrinsic: false,
3536 optional: false,
3537 doc: doc("/** Milliseconds since the Unix epoch (timezone-independent). */"),
3538 },
3539 ),
3540 (
3541 "epochNanoseconds".to_string(),
3542 PropertySig {
3543 ty: Type::BigInt,
3544 readonly: true,
3545 intrinsic: false,
3546 optional: false,
3547 doc: doc("/** Nanoseconds since the Unix epoch. */"),
3548 },
3549 ),
3550 (
3551 "offset".to_string(),
3552 PropertySig {
3553 ty: Type::String,
3554 readonly: true,
3555 intrinsic: false,
3556 optional: false,
3557 doc: doc("/** UTC offset at this instant, such as `\"-05:00\"`. */"),
3558 },
3559 ),
3560 (
3561 "offsetNanoseconds".to_string(),
3562 zdt_field_prop("/** UTC offset at this instant in nanoseconds. */"),
3563 ),
3564 (
3565 "hoursInDay".to_string(),
3566 zdt_field_prop(
3567 "/** Elapsed hours in this local day, accounting for time-zone transitions. */",
3568 ),
3569 ),
3570 ("year".to_string(), zdt_field_prop("/** Calendar year in this time zone. */")),
3571 ("month".to_string(), zdt_field_prop("/** Calendar month (1–12). */")),
3572 ("day".to_string(), zdt_field_prop("/** Day of the month (1–31). */")),
3573 ("hour".to_string(), zdt_field_prop("/** Hour of the day (0–23). */")),
3574 ("minute".to_string(), zdt_field_prop("/** Minute of the hour (0–59). */")),
3575 ("second".to_string(), zdt_field_prop("/** Second of the minute (0–59 or 60 on a leap-second). */")),
3576 (
3577 "dayOfWeek".to_string(),
3578 zdt_field_prop("/** Day of the week, 1 (Monday) through 7 (Sunday). */"),
3579 ),
3580 (
3581 "monthCode".to_string(),
3582 PropertySig {
3583 ty: Type::String,
3584 readonly: true,
3585 intrinsic: false,
3586 optional: false,
3587 doc: doc("/** ISO month code, e.g. `\"M06\"`. */"),
3588 },
3589 ),
3590 ("dayOfYear".to_string(), zdt_field_prop("/** Day of the year (1–365 or 366). */")),
3591 ("weekOfYear".to_string(), zdt_field_prop("/** ISO week number. */")),
3592 ("yearOfWeek".to_string(), zdt_field_prop("/** ISO week-numbering year. */")),
3593 ("daysInWeek".to_string(), zdt_field_prop("/** Days in this ISO week. */")),
3594 ("daysInMonth".to_string(), zdt_field_prop("/** Days in this month. */")),
3595 ("daysInYear".to_string(), zdt_field_prop("/** Days in this year. */")),
3596 ("monthsInYear".to_string(), zdt_field_prop("/** Months in this year. */")),
3597 (
3598 "inLeapYear".to_string(),
3599 PropertySig {
3600 ty: Type::Boolean,
3601 readonly: true,
3602 intrinsic: false,
3603 optional: false,
3604 doc: doc("/** True when the ISO calendar year is a leap year. */"),
3605 },
3606 ),
3607 ("millisecond".to_string(), zdt_field_prop("/** Millisecond within the second (0–999). */")),
3608 ("microsecond".to_string(), zdt_field_prop("/** Microsecond within the millisecond (0–999). */")),
3609 ("nanosecond".to_string(), zdt_field_prop("/** Nanosecond within the microsecond (0–999). */")),
3610 ]),
3611 dispatch: Dispatch::Direct,
3612 doc: doc(
3613 "/** An Instant paired with an IANA time zone. Wall-clock fields (`year`/`month`/…/`dayOfWeek`) are computed in the tz. Arithmetic is DST-aware. */",
3614 ),
3615 },
3616 },
3617 );
3618 temporal.types.insert(
3619 "ZonedDateTimeConstructor".to_string(),
3620 TypeSymbol {
3621 name: "Temporal.ZonedDateTimeConstructor".to_string(),
3622 mangled_name: crate::mangle::extend(&temporal_prefix, "ZonedDateTimeConstructor"),
3623 declaration_span: Span::at(crate::FileId::PRELUDE),
3624 kind: TypeKind::Interface { index: None,
3625 generics: Vec::new(),
3626 methods: BTreeMap::from([
3627 (
3628 "from".to_string(),
3629 MethodSig {
3630 optional: false,
3631 generics: Vec::new(),
3632 params: vec![Param::new("iso", Type::String)],
3633 ret: temporal_zdt_ref(),
3634 predicate: None,
3635 doc: doc(
3636 "/** Parse an ISO 8601 zoned datetime with bracketed tz (e.g. `\"2026-01-15T10:00:00-05:00[America/New_York]\"`). Throws on malformed input or unknown zone id. */",
3637 ),
3638 },
3639 ),
3640 (
3641 "compare".to_string(),
3642 MethodSig {
3643 optional: false,
3644 generics: Vec::new(),
3645 params: vec![
3646 Param::new("a", temporal_zdt_ref()),
3647 Param::new("b", temporal_zdt_ref()),
3648 ],
3649 ret: Type::Number,
3650 predicate: None,
3651 doc: doc(
3652 "/** Orders two ZonedDateTimes by their exact instant: -1, 0, or 1. Usable as an Array#sort comparator. */",
3653 ),
3654 },
3655 ),
3656 ]),
3657 properties: BTreeMap::new(),
3658 dispatch: Dispatch::Static,
3659 doc: doc("/** Constructor object for `Temporal.ZonedDateTime`. */"),
3660 },
3661 },
3662 );
3663 temporal.values.insert(
3664 "ZonedDateTime".to_string(),
3665 ValueSymbol {
3666 name: "ZonedDateTime".to_string(),
3667 mangled_name: crate::mangle::extend(&temporal_prefix, "ZonedDateTime"),
3668 declaration_span: Span::at(crate::FileId::PRELUDE),
3669 kind: ValueKind::Const {
3670 ty: temporal_ref("ZonedDateTimeConstructor"),
3671 doc: doc("/** The `Temporal.ZonedDateTime` constructor. */"),
3672 },
3673 },
3674 );
3675
3676 let temporal_plain_date_ref = || temporal_ref("PlainDate");
3678 let temporal_plain_time_ref = || temporal_ref("PlainTime");
3679 let arith_methods = |self_ty: Type| -> Vec<(String, MethodSig)> {
3683 vec![
3684 (
3685 "add".to_string(),
3686 MethodSig {
3687 optional: false,
3688 generics: Vec::new(),
3689 params: vec![Param::new("duration", temporal_duration_like_ref())],
3690 ret: self_ty.clone(),
3691 predicate: None,
3692 doc: doc("/** Returns a new value with `duration` added. */"),
3693 },
3694 ),
3695 (
3696 "subtract".to_string(),
3697 MethodSig {
3698 optional: false,
3699 generics: Vec::new(),
3700 params: vec![Param::new("duration", temporal_duration_like_ref())],
3701 ret: self_ty.clone(),
3702 predicate: None,
3703 doc: doc("/** Returns a new value with `duration` subtracted. */"),
3704 },
3705 ),
3706 (
3707 "until".to_string(),
3708 MethodSig {
3709 optional: false,
3710 generics: Vec::new(),
3711 params: vec![
3712 Param::new("other", self_ty.clone()),
3713 Param::with_default(
3714 "options",
3715 temporal_since_until_options_ref(),
3716 crate::DefaultValue::Undefined,
3717 ),
3718 ],
3719 ret: temporal_duration_ref(),
3720 predicate: None,
3721 doc: doc(
3722 "/** The `Duration` from `this` until `other`. `options` controls unit balancing and rounding. */",
3723 ),
3724 },
3725 ),
3726 (
3727 "since".to_string(),
3728 MethodSig {
3729 optional: false,
3730 generics: Vec::new(),
3731 params: vec![
3732 Param::new("other", self_ty.clone()),
3733 Param::with_default(
3734 "options",
3735 temporal_since_until_options_ref(),
3736 crate::DefaultValue::Undefined,
3737 ),
3738 ],
3739 ret: temporal_duration_ref(),
3740 predicate: None,
3741 doc: doc(
3742 "/** The `Duration` from `other` since which `this` occurs. `options` controls unit balancing and rounding. */",
3743 ),
3744 },
3745 ),
3746 ]
3747 };
3748 let temporal_plain_date_time_ref = || temporal_ref("PlainDateTime");
3749 let temporal_plain_year_month_ref = || temporal_ref("PlainYearMonth");
3750 let with_method = |self_ty: Type, fields_name: &'static str| -> (String, MethodSig) {
3752 (
3753 "with".to_string(),
3754 MethodSig {
3755 optional: false,
3756 generics: Vec::new(),
3757 params: vec![Param::new("fields", temporal_ref(fields_name))],
3758 ret: self_ty,
3759 predicate: None,
3760 doc: doc(
3761 "/** Returns a copy with the listed fields replaced; unlisted fields are kept. Out-of-range values are constrained. */",
3762 ),
3763 },
3764 )
3765 };
3766 let temporal_plain_month_day_ref = || temporal_ref("PlainMonthDay");
3767 let to_year_month_method = || {
3769 (
3770 "toPlainYearMonth".to_string(),
3771 MethodSig {
3772 optional: false,
3773 generics: Vec::new(),
3774 params: Vec::new(),
3775 ret: temporal_plain_year_month_ref(),
3776 predicate: None,
3777 doc: doc("/** The year-and-month part as a `PlainYearMonth`. */"),
3778 },
3779 )
3780 };
3781 let to_month_day_method = || {
3782 (
3783 "toPlainMonthDay".to_string(),
3784 MethodSig {
3785 optional: false,
3786 generics: Vec::new(),
3787 params: Vec::new(),
3788 ret: temporal_plain_month_day_ref(),
3789 predicate: None,
3790 doc: doc("/** The month-and-day part as a `PlainMonthDay`. */"),
3791 },
3792 )
3793 };
3794 insert_temporal_plain_type(
3795 &mut temporal,
3796 &temporal_prefix,
3797 "PlainDate",
3798 "/** A calendar date (year, month, day) with no time or time zone. ISO-8601 proleptic Gregorian. */",
3799 &[
3800 ("year", "/** Calendar year. */"),
3801 ("month", "/** Calendar month (1–12). */"),
3802 ("day", "/** Day of the month (1–31). */"),
3803 (
3804 "dayOfWeek",
3805 "/** Day of the week, 1 (Monday) through 7 (Sunday). */",
3806 ),
3807 ("monthCode", "/** ISO month code, e.g. `\"M06\"`. */"),
3808 ("dayOfYear", "/** Day of the year (1–365 or 366). */"),
3809 ("weekOfYear", "/** ISO week number. */"),
3810 ("yearOfWeek", "/** ISO week-numbering year. */"),
3811 ("daysInWeek", "/** Days in this ISO week. */"),
3812 ("daysInMonth", "/** Days in this month. */"),
3813 ("daysInYear", "/** Days in this year. */"),
3814 ("monthsInYear", "/** Months in this year. */"),
3815 (
3816 "inLeapYear",
3817 "/** True when the ISO calendar year is a leap year. */",
3818 ),
3819 ],
3820 {
3821 let mut m = arith_methods(temporal_plain_date_ref());
3822 m.push(with_method(temporal_plain_date_ref(), "PlainDateFields"));
3823 m.push(to_year_month_method());
3824 m.push(to_month_day_method());
3825 m.push((
3826 "toPlainDateTime".to_string(),
3827 MethodSig {
3828 optional: false,
3829 generics: Vec::new(),
3830 params: vec![Param::with_default(
3831 "time",
3832 temporal_plain_time_ref(),
3833 crate::DefaultValue::Undefined,
3834 )],
3835 ret: temporal_plain_date_time_ref(),
3836 predicate: None,
3837 doc: doc(
3838 "/** Combines this date with `time` (default midnight) into a `PlainDateTime`. */",
3839 ),
3840 },
3841 ));
3842 m.push((
3843 "toZonedDateTime".to_string(),
3844 MethodSig {
3845 optional: false,
3846 generics: Vec::new(),
3847 params: vec![Param::new(
3848 "timeZoneOrOptions",
3849 Type::Union(vec![
3850 Type::String,
3851 temporal_ref("PlainDateToZonedOptions"),
3852 ]),
3853 )],
3854 ret: temporal_zdt_ref(),
3855 predicate: None,
3856 doc: doc(
3857 "/** Interprets this date in a time zone — pass a zone id (midnight) or `{ timeZone, plainTime? }`. Resolves DST gaps with `compatible` disambiguation. */",
3858 ),
3859 },
3860 ));
3861 m
3862 },
3863 true,
3864 );
3865 insert_temporal_plain_type(
3866 &mut temporal,
3867 &temporal_prefix,
3868 "PlainTime",
3869 "/** A wall-clock time (hour, minute, second, nanosecond) with no date or time zone. */",
3870 &[
3871 ("hour", "/** Hour of the day (0–23). */"),
3872 ("minute", "/** Minute of the hour (0–59). */"),
3873 ("second", "/** Second of the minute (0–59). */"),
3874 (
3875 "millisecond",
3876 "/** Millisecond within the second (0–999). */",
3877 ),
3878 (
3879 "microsecond",
3880 "/** Microsecond within the millisecond (0–999). */",
3881 ),
3882 (
3883 "nanosecond",
3884 "/** Nanoseconds within the second (0–999_999_999). */",
3885 ),
3886 ],
3887 {
3888 let mut m = arith_methods(temporal_plain_time_ref());
3889 m.push(with_method(temporal_plain_time_ref(), "PlainTimeFields"));
3890 m
3891 },
3892 true,
3893 );
3894 insert_temporal_plain_type(
3895 &mut temporal,
3896 &temporal_prefix,
3897 "PlainDateTime",
3898 "/** A calendar date and wall-clock time with no time zone. */",
3899 &[
3900 ("year", "/** Calendar year. */"),
3901 ("month", "/** Calendar month (1–12). */"),
3902 ("day", "/** Day of the month (1–31). */"),
3903 ("hour", "/** Hour of the day (0–23). */"),
3904 ("minute", "/** Minute of the hour (0–59). */"),
3905 ("second", "/** Second of the minute (0–59). */"),
3906 (
3907 "dayOfWeek",
3908 "/** Day of the week, 1 (Monday) through 7 (Sunday). */",
3909 ),
3910 ("monthCode", "/** ISO month code, e.g. `\"M06\"`. */"),
3911 ("dayOfYear", "/** Day of the year (1–365 or 366). */"),
3912 ("weekOfYear", "/** ISO week number. */"),
3913 ("yearOfWeek", "/** ISO week-numbering year. */"),
3914 ("daysInWeek", "/** Days in this ISO week. */"),
3915 ("daysInMonth", "/** Days in this month. */"),
3916 ("daysInYear", "/** Days in this year. */"),
3917 ("monthsInYear", "/** Months in this year. */"),
3918 (
3919 "inLeapYear",
3920 "/** True when the ISO calendar year is a leap year. */",
3921 ),
3922 (
3923 "millisecond",
3924 "/** Millisecond within the second (0–999). */",
3925 ),
3926 (
3927 "microsecond",
3928 "/** Microsecond within the millisecond (0–999). */",
3929 ),
3930 (
3931 "nanosecond",
3932 "/** Nanoseconds within the second (0–999_999_999). */",
3933 ),
3934 ],
3935 {
3936 let mut m = arith_methods(temporal_plain_date_time_ref());
3937 m.push((
3938 "toPlainDate".to_string(),
3939 MethodSig {
3940 optional: false,
3941 generics: Vec::new(),
3942 params: Vec::new(),
3943 ret: temporal_plain_date_ref(),
3944 predicate: None,
3945 doc: doc("/** The calendar-date part as a `PlainDate`. */"),
3946 },
3947 ));
3948 m.push((
3949 "toPlainTime".to_string(),
3950 MethodSig {
3951 optional: false,
3952 generics: Vec::new(),
3953 params: Vec::new(),
3954 ret: temporal_plain_time_ref(),
3955 predicate: None,
3956 doc: doc("/** The wall-clock-time part as a `PlainTime`. */"),
3957 },
3958 ));
3959 m.push(to_year_month_method());
3960 m.push(to_month_day_method());
3961 m.push((
3962 "toZonedDateTime".to_string(),
3963 MethodSig {
3964 optional: false,
3965 generics: Vec::new(),
3966 params: vec![Param::new("timeZone", Type::String)],
3967 ret: temporal_zdt_ref(),
3968 predicate: None,
3969 doc: doc(
3970 "/** Interprets this wall clock in `timeZone`, resolving DST gaps with `compatible` disambiguation. */",
3971 ),
3972 },
3973 ));
3974 m.push(with_method(
3975 temporal_plain_date_time_ref(),
3976 "PlainDateTimeFields",
3977 ));
3978 m
3979 },
3980 true,
3981 );
3982 insert_temporal_plain_type(
3983 &mut temporal,
3984 &temporal_prefix,
3985 "PlainYearMonth",
3986 "/** A calendar year and month with no day or time zone (e.g. `2026-06`). */",
3987 &[
3988 ("year", "/** Calendar year. */"),
3989 ("month", "/** Calendar month (1–12). */"),
3990 ("monthCode", "/** ISO month code, e.g. `\"M06\"`. */"),
3991 ("daysInMonth", "/** Days in this month. */"),
3992 ("daysInYear", "/** Days in this year. */"),
3993 ("monthsInYear", "/** Months in this year. */"),
3994 (
3995 "inLeapYear",
3996 "/** True when the ISO calendar year is a leap year. */",
3997 ),
3998 ],
3999 {
4000 let mut m = arith_methods(temporal_plain_year_month_ref());
4001 m.push(with_method(
4002 temporal_plain_year_month_ref(),
4003 "PlainYearMonthFields",
4004 ));
4005 m.push((
4006 "toPlainDate".to_string(),
4007 MethodSig {
4008 optional: false,
4009 generics: Vec::new(),
4010 params: vec![Param::new(
4011 "fields",
4012 temporal_ref("PlainYearMonthToDateFields"),
4013 )],
4014 ret: temporal_plain_date_ref(),
4015 predicate: None,
4016 doc: doc(
4017 "/** Adds a `day` to make a `PlainDate`; the day constrains to the month length. */",
4018 ),
4019 },
4020 ));
4021 m
4022 },
4023 true,
4024 );
4025 insert_temporal_plain_type(
4026 &mut temporal,
4027 &temporal_prefix,
4028 "PlainMonthDay",
4029 "/** A calendar month and day with no year or time zone (e.g. `06-07`). */",
4030 &[
4031 ("month", "/** Calendar month (1–12). */"),
4032 ("monthCode", "/** ISO month code, e.g. `\"M06\"`. */"),
4033 ("day", "/** Day of the month (1–31). */"),
4034 ],
4035 vec![
4036 with_method(temporal_ref("PlainMonthDay"), "PlainMonthDayFields"),
4037 (
4038 "toPlainDate".to_string(),
4039 MethodSig {
4040 optional: false,
4041 generics: Vec::new(),
4042 params: vec![Param::new(
4043 "fields",
4044 temporal_ref("PlainMonthDayToDateFields"),
4045 )],
4046 ret: temporal_plain_date_ref(),
4047 predicate: None,
4048 doc: doc(
4049 "/** Adds a `year` to make a `PlainDate`; a Feb-29 month-day constrains to Feb-28 in a non-leap year. */",
4050 ),
4051 },
4052 ),
4053 ],
4054 false,
4055 );
4056
4057 let now_prefix = crate::mangle::extend(&temporal_prefix, "Now");
4058 let mut now = crate::NamespaceSymbol {
4059 name: "Now".to_string(),
4060 mangled_prefix: now_prefix.clone(),
4061 declaration_span: Span::at(crate::FileId::PRELUDE),
4062 values: BTreeMap::new(),
4063 types: BTreeMap::new(),
4064 namespaces: BTreeMap::new(),
4065 doc: doc(
4066 "/** Non-deterministic time / time-zone queries. Calls land in the durable-execution log so replays can reproduce the original wall-clock values. */",
4067 ),
4068 };
4069 now.values.insert(
4070 "instant".to_string(),
4071 ValueSymbol {
4072 name: "instant".to_string(),
4073 mangled_name: crate::mangle::extend(&now_prefix, "instant"),
4074 declaration_span: Span::at(crate::FileId::PRELUDE),
4075 kind: ValueKind::Function {
4076 generics: Vec::new(),
4077 params: Vec::new(),
4078 ret: temporal_instant_ref(),
4079 type_predicate: None,
4080 doc: doc(
4081 "/** The current wall-clock instant. Non-deterministic — reads the system clock. */",
4082 ),
4083 },
4084 },
4085 );
4086 now.values.insert(
4087 "timeZoneId".to_string(),
4088 ValueSymbol {
4089 name: "timeZoneId".to_string(),
4090 mangled_name: crate::mangle::extend(&now_prefix, "timeZoneId"),
4091 declaration_span: Span::at(crate::FileId::PRELUDE),
4092 kind: ValueKind::Function {
4093 generics: Vec::new(),
4094 params: Vec::new(),
4095 ret: Type::String,
4096 type_predicate: None,
4097 doc: doc(
4098 "/** The system IANA time-zone id (or `\"UTC\"` if the system zone can't be detected). Non-deterministic. */",
4099 ),
4100 },
4101 },
4102 );
4103 now.values.insert(
4104 "zonedDateTimeISO".to_string(),
4105 ValueSymbol {
4106 name: "zonedDateTimeISO".to_string(),
4107 mangled_name: crate::mangle::extend(&now_prefix, "zonedDateTimeISO"),
4108 declaration_span: Span::at(crate::FileId::PRELUDE),
4109 kind: ValueKind::Function {
4110 generics: Vec::new(),
4111 params: vec![Param::with_default(
4112 "tz",
4113 Type::Union(vec![Type::String, Type::Undefined]),
4114 crate::DefaultValue::Undefined,
4115 )],
4116 ret: temporal_zdt_ref(),
4117 type_predicate: None,
4118 doc: doc(
4119 "/** The current ZonedDateTime in `tz`, or the system zone if `tz` is omitted / undefined. Non-deterministic. */",
4120 ),
4121 },
4122 },
4123 );
4124 let mut insert_now_plain = |name: &str, ret: Type, doc_text: &'static str| {
4125 now.values.insert(
4126 name.to_string(),
4127 ValueSymbol {
4128 name: name.to_string(),
4129 mangled_name: crate::mangle::extend(&now_prefix, name),
4130 declaration_span: Span::at(FileId::PRELUDE),
4131 kind: ValueKind::Function {
4132 generics: Vec::new(),
4133 params: vec![Param::with_default(
4134 "tz",
4135 Type::Union(vec![Type::String, Type::Undefined]),
4136 crate::DefaultValue::Undefined,
4137 )],
4138 ret,
4139 type_predicate: None,
4140 doc: doc(doc_text),
4141 },
4142 },
4143 );
4144 };
4145 insert_now_plain(
4146 "plainDateISO",
4147 temporal_plain_date_ref(),
4148 "/** Today's date in `tz`, or the system (local) zone if `tz` is omitted / undefined. Non-deterministic. */",
4149 );
4150 insert_now_plain(
4151 "plainTimeISO",
4152 temporal_plain_time_ref(),
4153 "/** The current wall-clock time in `tz`, or the system (local) zone if `tz` is omitted / undefined. Non-deterministic. */",
4154 );
4155 insert_now_plain(
4156 "plainDateTimeISO",
4157 temporal_ref("PlainDateTime"),
4158 "/** The current date and wall-clock time in `tz`, or the system (local) zone if `tz` is omitted / undefined. Non-deterministic. */",
4159 );
4160 now.values.insert(
4161 "zonedDateTime".to_string(),
4162 ValueSymbol {
4163 name: "zonedDateTime".to_string(),
4164 mangled_name: crate::mangle::extend(&now_prefix, "zonedDateTime"),
4165 declaration_span: Span::at(FileId::PRELUDE),
4166 kind: ValueKind::Function {
4167 generics: Vec::new(),
4168 params: vec![Param::with_default(
4169 "tz",
4170 Type::Union(vec![Type::String, Type::Undefined]),
4171 crate::DefaultValue::Undefined,
4172 )],
4173 ret: temporal_zdt_ref(),
4174 type_predicate: None,
4175 doc: doc(
4176 "/** The current ZonedDateTime in `tz`, or the system zone if `tz` is undefined. Non-deterministic. Alias of `zonedDateTimeISO` (our subset has no calendar argument). */",
4177 ),
4178 },
4179 },
4180 );
4181 temporal.namespaces.insert("Now".to_string(), now);
4182
4183 for (local_name, ty_sym) in &temporal.types {
4184 let flat_key = format!("Temporal#{local_name}");
4185 defs.types.insert(flat_key, ty_sym.clone());
4186 }
4187
4188 defs.namespaces.insert("Temporal".to_string(), temporal);
4189}
4190
4191#[cfg(test)]
4192mod invariant_tests {
4193 use super::*;
4194 #[test]
4195 fn fractional_digits_are_checked_even_for_date_only_durations() {
4196 for digits in [10, u8::MAX] {
4197 assert!(
4198 rounding_for_fractional_second_digits(digits)
4199 .unwrap_err()
4200 .is::<wasmtime::Trap>()
4201 );
4202 assert!(
4203 format_duration_with_fractional_digits(&Span::new().days(1), digits)
4204 .unwrap_err()
4205 .is::<wasmtime::Trap>()
4206 );
4207 }
4208 assert_eq!(
4209 format_duration_with_fractional_digits(&Span::new().seconds(1).milliseconds(25), 3)
4210 .unwrap(),
4211 "PT1.025S"
4212 );
4213 }
4214}