Skip to main content

boa_engine/builtins/temporal/plain_time/
mod.rs

1//! Boa's implementation of the ECMAScript `Temporal.PlainTime` built-in object.
2
3use super::{
4    PlainDateTime, ZonedDateTime, create_temporal_duration,
5    options::{TemporalUnitGroup, get_difference_settings, get_temporal_unit},
6    to_temporal_duration_record,
7};
8use crate::{
9    Context, JsArgs, JsData, JsError, JsNativeError, JsObject, JsResult, JsString, JsSymbol,
10    JsValue,
11    builtins::{
12        BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
13        options::{get_option, get_options_object},
14    },
15    context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
16    js_string,
17    object::internal_methods::get_prototype_from_constructor,
18    property::Attribute,
19    realm::Realm,
20    string::StaticJsStrings,
21};
22use crate::{builtins::temporal::options::get_digits_option, value::JsVariant};
23use boa_gc::{Finalize, Trace};
24use num_traits::{AsPrimitive, PrimInt};
25use temporal_rs::{
26    PlainTime as PlainTimeInner,
27    options::{
28        Overflow, RoundingIncrement, RoundingMode, RoundingOptions, ToStringRoundingOptions, Unit,
29    },
30    partial::PartialTime,
31    primitive::FiniteF64,
32};
33
34#[cfg(test)]
35mod tests;
36
37/// The `Temporal.PlainTime` built-in implementation.
38///
39/// More information:
40///
41/// - [ECMAScript Temporal proposal][spec]
42/// - [MDN reference][mdn]
43/// - [`temporal_rs` documentation][temporal_rs-docs]
44///
45/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-plaintime-objects
46/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime
47/// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainTime.html
48#[derive(Debug, Clone, Copy, Trace, Finalize, JsData)]
49#[boa_gc(unsafe_empty_trace)] // Safety: PlainTimeInner does not contain any traceable types.
50pub struct PlainTime {
51    inner: PlainTimeInner,
52}
53
54impl BuiltInObject for PlainTime {
55    const NAME: JsString = StaticJsStrings::PLAIN_TIME_NAME;
56}
57
58impl IntrinsicObject for PlainTime {
59    fn init(realm: &Realm) {
60        let get_hour = BuiltInBuilder::callable(realm, Self::get_hour)
61            .name(js_string!("get hour"))
62            .build();
63
64        let get_minute = BuiltInBuilder::callable(realm, Self::get_minute)
65            .name(js_string!("get minute"))
66            .build();
67
68        let get_second = BuiltInBuilder::callable(realm, Self::get_second)
69            .name(js_string!("get second"))
70            .build();
71
72        let get_millisecond = BuiltInBuilder::callable(realm, Self::get_millisecond)
73            .name(js_string!("get millisecond"))
74            .build();
75
76        let get_microsecond = BuiltInBuilder::callable(realm, Self::get_microsecond)
77            .name(js_string!("get microsecond"))
78            .build();
79
80        let get_nanosecond = BuiltInBuilder::callable(realm, Self::get_nanosecond)
81            .name(js_string!("get nanosecond"))
82            .build();
83
84        BuiltInBuilder::from_standard_constructor::<Self>(realm)
85            .property(
86                JsSymbol::to_string_tag(),
87                StaticJsStrings::PLAIN_TIME_TAG,
88                Attribute::CONFIGURABLE,
89            )
90            .accessor(
91                js_string!("hour"),
92                Some(get_hour),
93                None,
94                Attribute::CONFIGURABLE,
95            )
96            .accessor(
97                js_string!("minute"),
98                Some(get_minute),
99                None,
100                Attribute::CONFIGURABLE,
101            )
102            .accessor(
103                js_string!("second"),
104                Some(get_second),
105                None,
106                Attribute::CONFIGURABLE,
107            )
108            .accessor(
109                js_string!("millisecond"),
110                Some(get_millisecond),
111                None,
112                Attribute::CONFIGURABLE,
113            )
114            .accessor(
115                js_string!("microsecond"),
116                Some(get_microsecond),
117                None,
118                Attribute::CONFIGURABLE,
119            )
120            .accessor(
121                js_string!("nanosecond"),
122                Some(get_nanosecond),
123                None,
124                Attribute::CONFIGURABLE,
125            )
126            .static_method(Self::from, js_string!("from"), 1)
127            .static_method(Self::compare, js_string!("compare"), 2)
128            .method(Self::add, js_string!("add"), 1)
129            .method(Self::subtract, js_string!("subtract"), 1)
130            .method(Self::with, js_string!("with"), 1)
131            .method(Self::until, js_string!("until"), 1)
132            .method(Self::since, js_string!("since"), 1)
133            .method(Self::round, js_string!("round"), 1)
134            .method(Self::equals, js_string!("equals"), 1)
135            .method(Self::to_string, js_string!("toString"), 0)
136            .method(Self::to_locale_string, js_string!("toLocaleString"), 0)
137            .method(Self::to_json, js_string!("toJSON"), 0)
138            .method(Self::value_of, js_string!("valueOf"), 0)
139            .build();
140    }
141
142    fn get(intrinsics: &Intrinsics) -> JsObject {
143        Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
144    }
145}
146
147impl BuiltInConstructor for PlainTime {
148    const CONSTRUCTOR_ARGUMENTS: usize = 0;
149    const PROTOTYPE_STORAGE_SLOTS: usize = 24;
150    const CONSTRUCTOR_STORAGE_SLOTS: usize = 2;
151
152    const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
153        StandardConstructors::plain_time;
154
155    fn constructor(
156        new_target: &JsValue,
157        args: &[JsValue],
158        context: &mut Context,
159    ) -> JsResult<JsValue> {
160        // 1. If NewTarget is undefined, then
161        if new_target.is_undefined() {
162            // a. Throw a TypeError exception.
163            return Err(JsNativeError::typ()
164                .with_message("NewTarget cannot be undefined.")
165                .into());
166        }
167
168        // 2. If hour is undefined, set hour to 0; else set hour to ? ToIntegerWithTruncation(hour).
169        let hour = args.get_or_undefined(0).map_or(Ok::<u8, JsError>(0), |v| {
170            let finite = v.to_finitef64(context)?;
171            let int = finite.as_integer_with_truncation::<i8>();
172            if int < 0 {
173                return Err(JsNativeError::range()
174                    .with_message("invalid time field")
175                    .into());
176            }
177            Ok(int as u8)
178        })?;
179        // 3. If minute is undefined, set minute to 0; else set minute to ? ToIntegerWithTruncation(minute).
180        let minute = args.get_or_undefined(1).map_or(Ok::<u8, JsError>(0), |v| {
181            let finite = v.to_finitef64(context)?;
182            let int = finite.as_integer_with_truncation::<i8>();
183            if int < 0 {
184                return Err(JsNativeError::range()
185                    .with_message("invalid time field")
186                    .into());
187            }
188            Ok(int as u8)
189        })?;
190        // 4. If second is undefined, set second to 0; else set second to ? ToIntegerWithTruncation(second).
191        let second = args.get_or_undefined(2).map_or(Ok::<u8, JsError>(0), |v| {
192            let finite = v.to_finitef64(context)?;
193            let int = finite.as_integer_with_truncation::<i8>();
194            if int < 0 {
195                return Err(JsNativeError::range()
196                    .with_message("invalid time field")
197                    .into());
198            }
199            Ok(int as u8)
200        })?;
201
202        // 5. If millisecond is undefined, set millisecond to 0; else set millisecond to ? ToIntegerWithTruncation(millisecond).
203        let millisecond = args
204            .get_or_undefined(3)
205            .map_or(Ok::<u16, JsError>(0), |v| {
206                let finite = v.to_finitef64(context)?;
207                let int = finite.as_integer_with_truncation::<i16>();
208                if int < 0 {
209                    return Err(JsNativeError::range()
210                        .with_message("invalid time field")
211                        .into());
212                }
213                Ok(int as u16)
214            })?;
215
216        // 6. If microsecond is undefined, set microsecond to 0; else set microsecond to ? ToIntegerWithTruncation(microsecond).
217        let microsecond = args
218            .get_or_undefined(4)
219            .map_or(Ok::<u16, JsError>(0), |v| {
220                let finite = v.to_finitef64(context)?;
221                let int = finite.as_integer_with_truncation::<i16>();
222                if int < 0 {
223                    return Err(JsNativeError::range()
224                        .with_message("invalid time field")
225                        .into());
226                }
227                Ok(int as u16)
228            })?;
229
230        // 7. If nanosecond is undefined, set nanosecond to 0; else set nanosecond to ? ToIntegerWithTruncation(nanosecond).
231        let nanosecond = args
232            .get_or_undefined(5)
233            .map_or(Ok::<u16, JsError>(0), |v| {
234                let finite = v.to_finitef64(context)?;
235                let int = finite.as_integer_with_truncation::<i16>();
236                if int < 0 {
237                    return Err(JsNativeError::range()
238                        .with_message("invalid time field")
239                        .into());
240                }
241                Ok(int as u16)
242            })?;
243
244        let inner =
245            PlainTimeInner::try_new(hour, minute, second, millisecond, microsecond, nanosecond)?;
246
247        // 8. Return ? CreateTemporalTime(hour, minute, second, millisecond, microsecond, nanosecond, NewTarget).
248        create_temporal_time(inner, Some(new_target), context).map(Into::into)
249    }
250}
251
252// ==== PlainTime accessor methods implementation ====
253
254impl PlainTime {
255    /// 4.3.3 get `Temporal.PlainTime.prototype.hour`
256    ///
257    /// More information:
258    ///
259    /// - [ECMAScript Temporal proposal][spec]
260    /// - [MDN reference][mdn]
261    /// - [`temporal_rs` documentation][temporal_rs-docs]
262    ///
263    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.hour
264    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/hour
265    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainTime.html#method.hour
266    fn get_hour(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
267        // 1. Let temporalTime be the this value.
268        // 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
269        let object = this.as_object();
270        let time = object
271            .as_ref()
272            .and_then(JsObject::downcast_ref::<Self>)
273            .ok_or_else(|| {
274                JsNativeError::typ().with_message("the this object must be a PlainTime object.")
275            })?;
276
277        // 3. Return 𝔽(temporalTime.[[ISOHour]]).
278        Ok(time.inner.hour().into())
279    }
280
281    /// 4.3.4 get `Temporal.PlainTime.prototype.minute`
282    ///
283    /// More information:
284    ///
285    /// - [ECMAScript Temporal proposal][spec]
286    /// - [MDN reference][mdn]
287    /// - [`temporal_rs` documentation][temporal_rs-docs]
288    ///
289    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.minute
290    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/minute
291    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainTime.html#method.minute
292    fn get_minute(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
293        // 1. Let temporalTime be the this value.
294        // 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
295        let object = this.as_object();
296        let time = object
297            .as_ref()
298            .and_then(JsObject::downcast_ref::<Self>)
299            .ok_or_else(|| {
300                JsNativeError::typ().with_message("the this object must be a PlainTime object.")
301            })?;
302
303        // 3. Return 𝔽(temporalTime.[[ISOMinute]]).
304        Ok(time.inner.minute().into())
305    }
306
307    /// 4.3.5 get `Temporal.PlainTime.prototype.second`
308    ///
309    /// More information:
310    ///
311    /// - [ECMAScript Temporal proposal][spec]
312    /// - [MDN reference][mdn]
313    /// - [`temporal_rs` documentation][temporal_rs-docs]
314    ///
315    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.second
316    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/second
317    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainTime.html#method.second
318    fn get_second(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
319        // 1. Let temporalTime be the this value.
320        // 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
321        let object = this.as_object();
322        let time = object
323            .as_ref()
324            .and_then(JsObject::downcast_ref::<Self>)
325            .ok_or_else(|| {
326                JsNativeError::typ().with_message("the this object must be a PlainTime object.")
327            })?;
328
329        // 3. Return 𝔽(temporalTime.[[ISOSecond]]).
330        Ok(time.inner.second().into())
331    }
332
333    /// 4.3.6 get `Temporal.PlainTime.prototype.millisecond`
334    ///
335    /// More information:
336    ///
337    /// - [ECMAScript Temporal proposal][spec]
338    /// - [MDN reference][mdn]
339    /// - [`temporal_rs` documentation][temporal_rs-docs]
340    ///
341    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.millisecond
342    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/millisecond
343    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainTime.html#method.millisecond
344    fn get_millisecond(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
345        // 1. Let temporalTime be the this value.
346        // 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
347        let object = this.as_object();
348        let time = object
349            .as_ref()
350            .and_then(JsObject::downcast_ref::<Self>)
351            .ok_or_else(|| {
352                JsNativeError::typ().with_message("the this object must be a PlainTime object.")
353            })?;
354
355        // 3. Return 𝔽(temporalTime.[[ISOMillisecond]]).
356        Ok(time.inner.millisecond().into())
357    }
358
359    /// 4.3.7 get `Temporal.PlainTime.prototype.microsecond`
360    ///
361    /// More information:
362    ///
363    /// - [ECMAScript Temporal proposal][spec]
364    /// - [MDN reference][mdn]
365    /// - [`temporal_rs` documentation][temporal_rs-docs]
366    ///
367    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.microsecond
368    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/microsecond
369    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainTime.html#method.microsecond
370    fn get_microsecond(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
371        // 1. Let temporalTime be the this value.
372        // 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
373        let object = this.as_object();
374        let time = object
375            .as_ref()
376            .and_then(JsObject::downcast_ref::<Self>)
377            .ok_or_else(|| {
378                JsNativeError::typ().with_message("the this object must be a PlainTime object.")
379            })?;
380
381        // 3. Return 𝔽(temporalTime.[[ISOMicrosecond]]).
382        Ok(time.inner.microsecond().into())
383    }
384
385    /// 4.3.8 get `Temporal.PlainTime.prototype.nanosecond`
386    ///
387    /// More information:
388    ///
389    /// - [ECMAScript Temporal proposal][spec]
390    /// - [MDN reference][mdn]
391    /// - [`temporal_rs` documentation][temporal_rs-docs]
392    ///
393    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.nanosecond
394    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/nanosecond
395    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainTime.html#method.nanosecond
396    fn get_nanosecond(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
397        // 1. Let temporalTime be the this value.
398        // 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
399        let object = this.as_object();
400        let time = object
401            .as_ref()
402            .and_then(JsObject::downcast_ref::<Self>)
403            .ok_or_else(|| {
404                JsNativeError::typ().with_message("the this object must be a PlainTime object.")
405            })?;
406
407        // 3. Return 𝔽(temporalTime.[[ISONanosecond]]).
408        Ok(time.inner.nanosecond().into())
409    }
410}
411
412// ==== PlainTime static methods implementation ====
413
414impl PlainTime {
415    /// 4.2.2 `Temporal.PlainTime.from ( item [ , options ] )`
416    ///
417    /// More information:
418    ///
419    /// - [ECMAScript Temporal proposal][spec]
420    /// - [MDN reference][mdn]
421    ///
422    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaintime.from
423    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/from
424    fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
425        // 1. Return ? ToTemporalTime(item, options).
426        let plain_time = to_temporal_time(args.get_or_undefined(0), args.get(1), context)?;
427        create_temporal_time(plain_time, None, context).map(Into::into)
428    }
429
430    /// 4.2.3 `Temporal.PlainTime.compare ( one, two )`
431    ///
432    /// More information:
433    ///
434    /// - [ECMAScript Temporal proposal][spec]
435    /// - [MDN reference][mdn]
436    /// - [`temporal_rs` documentation][temporal_rs-docs]
437    ///
438    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaintime.compare
439    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/compare
440    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainTime.html#impl-Ord-for-PlainTime
441    fn compare(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
442        // 1. Set one to ? ToTemporalTime(one).
443        let one = to_temporal_time(args.get_or_undefined(0), None, context)?;
444        // 2. Set two to ? ToTemporalTime(two).
445        let two = to_temporal_time(args.get_or_undefined(1), None, context)?;
446        // 3. Return 𝔽(CompareTemporalTime(one.[[ISOHour]], one.[[ISOMinute]], one.[[ISOSecond]],
447        // one.[[ISOMillisecond]], one.[[ISOMicrosecond]], one.[[ISONanosecond]], two.[[ISOHour]],
448        // two.[[ISOMinute]], two.[[ISOSecond]], two.[[ISOMillisecond]], two.[[ISOMicrosecond]],
449        // two.[[ISONanosecond]])).
450        Ok((one.cmp(&two) as i8).into())
451    }
452}
453
454// ==== PlainTime.prototype method implementations ====
455
456impl PlainTime {
457    /// 4.3.9 `Temporal.PlainTime.prototype.add ( temporalDurationLike )`
458    ///
459    /// More information:
460    ///
461    /// - [ECMAScript Temporal proposal][spec]
462    /// - [MDN reference][mdn]
463    /// - [`temporal_rs` documentation][temporal_rs-docs]
464    ///
465    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.add
466    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/add
467    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainTime.html#method.add
468    fn add(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
469        // 1. Let temporalTime be the this value.
470        // 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
471        let object = this.as_object();
472        let time = object
473            .as_ref()
474            .and_then(JsObject::downcast_ref::<Self>)
475            .ok_or_else(|| {
476                JsNativeError::typ().with_message("the this object must be a PlainTime object.")
477            })?;
478
479        let temporal_duration_like = args.get_or_undefined(0);
480        let duration = to_temporal_duration_record(temporal_duration_like, context)?;
481
482        // 3. Return ? AddDurationToOrSubtractDurationFromPlainTime(add, temporalTime, temporalDurationLike).
483        create_temporal_time(time.inner.add(&duration)?, None, context).map(Into::into)
484    }
485
486    /// 4.3.10 `Temporal.PlainTime.prototype.subtract ( temporalDurationLike )`
487    ///
488    /// More information:
489    ///
490    /// - [ECMAScript Temporal proposal][spec]
491    /// - [MDN reference][mdn]
492    /// - [`temporal_rs` documentation][temporal_rs-docs]
493    ///
494    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.subtract
495    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/subtract
496    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainTime.html#method.subtract
497    fn subtract(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
498        // 1. Let temporalTime be the this value.
499        // 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
500        let object = this.as_object();
501        let time = object
502            .as_ref()
503            .and_then(JsObject::downcast_ref::<Self>)
504            .ok_or_else(|| {
505                JsNativeError::typ().with_message("the this object must be a PlainTime object.")
506            })?;
507
508        let temporal_duration_like = args.get_or_undefined(0);
509        let duration = to_temporal_duration_record(temporal_duration_like, context)?;
510
511        // 3. Return ? AddDurationToOrSubtractDurationFromPlainTime(subtract, temporalTime, temporalDurationLike).
512        create_temporal_time(time.inner.subtract(&duration)?, None, context).map(Into::into)
513    }
514
515    /// 4.3.11 `Temporal.PlainTime.prototype.with ( temporalTimeLike [ , options ] )`
516    ///
517    /// More information:
518    ///
519    /// - [ECMAScript Temporal proposal][spec]
520    /// - [MDN reference][mdn]
521    /// - [`temporal_rs` documentation][temporal_rs-docs]
522    ///
523    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.with
524    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/with
525    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainTime.html#method.with
526    fn with(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
527        // 1.Let temporalTime be the this value.
528        // 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
529        let object = this.as_object();
530        let time = object
531            .as_ref()
532            .and_then(JsObject::downcast_ref::<Self>)
533            .ok_or_else(|| {
534                JsNativeError::typ().with_message("the this object must be a PlainTime object.")
535            })?;
536
537        // 3. If ? IsPartialTemporalObject(temporalTimeLike) is false, throw a TypeError exception.
538        // 4. Set options to ? GetOptionsObject(options).
539        let Some(partial_object) =
540            super::is_partial_temporal_object(args.get_or_undefined(0), context)?
541        else {
542            return Err(JsNativeError::typ()
543                .with_message("with object was not a PartialTemporalObject.")
544                .into());
545        };
546
547        // Steps 5-16 equate to the below
548        let partial = to_js_partial_time_record(&partial_object, context)?;
549        // 17. Let resolvedOptions be ? GetOptionsObject(options).
550        // 18. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
551        let options = get_options_object(args.get_or_undefined(1))?;
552        let overflow = get_option::<Overflow>(&options, js_string!("overflow"), context)?;
553
554        create_temporal_time(
555            time.inner
556                .with(partial.as_temporal_partial_time(overflow)?, overflow)?,
557            None,
558            context,
559        )
560        .map(Into::into)
561    }
562
563    /// 4.3.12 `Temporal.PlainTime.prototype.until ( other [ , options ] )`
564    ///
565    /// More information:
566    ///
567    /// - [ECMAScript Temporal proposal][spec]
568    /// - [MDN reference][mdn]
569    /// - [`temporal_rs` documentation][temporal_rs-docs]
570    ///
571    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.until
572    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/until
573    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainTime.html#method.until
574    fn until(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
575        let object = this.as_object();
576        let time = object
577            .as_ref()
578            .and_then(JsObject::downcast_ref::<Self>)
579            .ok_or_else(|| {
580                JsNativeError::typ().with_message("the this object must be a PlainTime object.")
581            })?;
582
583        let other = to_temporal_time(args.get_or_undefined(0), None, context)?;
584
585        let settings =
586            get_difference_settings(&get_options_object(args.get_or_undefined(1))?, context)?;
587
588        let result = time.inner.until(&other, settings)?;
589
590        create_temporal_duration(result, None, context).map(Into::into)
591    }
592
593    /// 4.3.13 `Temporal.PlainTime.prototype.since ( other [ , options ] )`
594    ///
595    /// More information:
596    ///
597    /// - [ECMAScript Temporal proposal][spec]
598    /// - [MDN reference][mdn]
599    /// - [`temporal_rs` documentation][temporal_rs-docs]
600    ///
601    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.since
602    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/since
603    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainTime.html#method.since
604    fn since(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
605        let object = this.as_object();
606        let time = object
607            .as_ref()
608            .and_then(JsObject::downcast_ref::<Self>)
609            .ok_or_else(|| {
610                JsNativeError::typ().with_message("the this object must be a PlainTime object.")
611            })?;
612
613        let other = to_temporal_time(args.get_or_undefined(0), None, context)?;
614
615        let settings =
616            get_difference_settings(&get_options_object(args.get_or_undefined(1))?, context)?;
617
618        let result = time.inner.since(&other, settings)?;
619
620        create_temporal_duration(result, None, context).map(Into::into)
621    }
622
623    /// 4.3.14 Temporal.PlainTime.prototype.round ( roundTo )
624    ///
625    /// More information:
626    ///
627    /// - [ECMAScript Temporal proposal][spec]
628    /// - [MDN reference][mdn]
629    /// - [`temporal_rs` documentation][temporal_rs-docs]
630    ///
631    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.round
632    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/round
633    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainTime.html#method.round
634    fn round(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
635        // 1. Let temporalTime be the this value.
636        // 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
637        let object = this.as_object();
638        let time = object
639            .as_ref()
640            .and_then(JsObject::downcast_ref::<Self>)
641            .ok_or_else(|| {
642                JsNativeError::typ().with_message("the this object must be a PlainTime object.")
643            })?;
644
645        // 3. If roundTo is undefined, then
646        let round_to_arg = args.get_or_undefined(0);
647        if round_to_arg.is_undefined() {
648            return Err(JsNativeError::typ()
649                .with_message("roundTo cannot be undefined.")
650                .into());
651        }
652        // 4. If Type(roundTo) is String, then
653        let round_to = if let Some(param_string) = round_to_arg.as_string() {
654            // a. Let paramString be roundTo.
655            let param_string = param_string.clone();
656            // b. Set roundTo to OrdinaryObjectCreate(null).
657            let new_round_to = JsObject::with_null_proto();
658            // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString).
659            new_round_to.create_data_property_or_throw(
660                js_string!("smallestUnit"),
661                param_string,
662                context,
663            )?;
664            new_round_to
665        } else {
666            // 5. Else,
667            // a. Set roundTo to ? GetOptionsObject(roundTo).
668            get_options_object(round_to_arg)?
669        };
670
671        let mut options = RoundingOptions::default();
672        // 6. NOTE: The following steps read options and perform independent validation in alphabetical order (ToTemporalRoundingIncrement reads "roundingIncrement" and ToTemporalRoundingMode reads "roundingMode").
673        // 7. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo).
674        options.increment =
675            get_option::<RoundingIncrement>(&round_to, js_string!("roundingIncrement"), context)?;
676
677        // 8. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand").
678        options.rounding_mode =
679            get_option::<RoundingMode>(&round_to, js_string!("roundingMode"), context)?;
680
681        // 9. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", time, required).
682        options.smallest_unit = get_temporal_unit(
683            &round_to,
684            js_string!("smallestUnit"),
685            TemporalUnitGroup::Time,
686            None,
687            context,
688        )?;
689
690        // 10. Let maximum be MaximumTemporalDurationRoundingIncrement(smallestUnit).
691        // 11. Assert: maximum is not undefined.
692        // 12. Perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false).
693        // 13. Let result be RoundTime(temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]], temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], temporalTime.[[ISOMicrosecond]], temporalTime.[[ISONanosecond]], roundingIncrement, smallestUnit, roundingMode).
694        let result = time.inner.round(options)?;
695
696        // 14. Return ! CreateTemporalTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]).
697        create_temporal_time(result, None, context).map(Into::into)
698    }
699
700    /// 4.3.15 Temporal.PlainTime.prototype.equals ( other )
701    ///
702    /// More information:
703    ///
704    /// - [ECMAScript Temporal proposal][spec]
705    /// - [MDN reference][mdn]
706    /// - [`temporal_rs` documentation][temporal_rs-docs]
707    ///
708    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.equals
709    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/equals
710    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainTime.html#impl-Eq-for-PlainTime
711    fn equals(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
712        // 1. Let temporalTime be the this value.
713        // 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
714        let object = this.as_object();
715        let time = object
716            .as_ref()
717            .and_then(JsObject::downcast_ref::<Self>)
718            .ok_or_else(|| {
719                JsNativeError::typ().with_message("the this object must be a PlainTime object.")
720            })?;
721
722        // 3. Set other to ? ToTemporalTime(other).
723        let other = to_temporal_time(args.get_or_undefined(0), None, context)?;
724        // 4. If temporalTime.[[ISOHour]] ≠ other.[[ISOHour]], return false.
725        // 5. If temporalTime.[[ISOMinute]] ≠ other.[[ISOMinute]], return false.
726        // 6. If temporalTime.[[ISOSecond]] ≠ other.[[ISOSecond]], return false.
727        // 7. If temporalTime.[[ISOMillisecond]] ≠ other.[[ISOMillisecond]], return false.
728        // 8. If temporalTime.[[ISOMicrosecond]] ≠ other.[[ISOMicrosecond]], return false.
729        // 9. If temporalTime.[[ISONanosecond]] ≠ other.[[ISONanosecond]], return false.
730        // 10. Return true.
731        Ok((time.inner == other).into())
732    }
733
734    /// 4.3.16 `Temporal.PlainTime.prototype.toString ( [ options ] )`
735    ///
736    /// More information:
737    ///
738    /// - [ECMAScript Temporal proposal][spec]
739    /// - [MDN reference][mdn]
740    /// - [`temporal_rs` documentation][temporal_rs-docs]
741    ///
742    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.tostring
743    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/toString
744    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainTime.html#method.to_ixdtf_string
745    fn to_string(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
746        let object = this.as_object();
747        let time = object
748            .as_ref()
749            .and_then(JsObject::downcast_ref::<Self>)
750            .ok_or_else(|| {
751                JsNativeError::typ().with_message("the this object must be a PlainTime object.")
752            })?;
753
754        let options = get_options_object(args.get_or_undefined(0))?;
755
756        let precision = get_digits_option(&options, context)?;
757        let rounding_mode =
758            get_option::<RoundingMode>(&options, js_string!("roundingMode"), context)?;
759        let smallest_unit = get_option::<Unit>(&options, js_string!("smallestUnit"), context)?;
760
761        let options = ToStringRoundingOptions {
762            precision,
763            rounding_mode,
764            smallest_unit,
765        };
766
767        let ixdtf = time.inner.to_ixdtf_string(options)?;
768
769        Ok(JsString::from(ixdtf).into())
770    }
771
772    /// 4.3.17 `Temporal.PlainTime.prototype.toLocaleString ( [ locales [ , options ] ] )`
773    ///
774    /// More information:
775    ///
776    /// - [ECMAScript Temporal proposal][spec]
777    /// - [MDN reference][mdn]
778    ///
779    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.tolocalestring
780    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/toLocaleString
781    fn to_locale_string(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
782        // TODO: Update for ECMA-402 compliance
783        let object = this.as_object();
784        let time = object
785            .as_ref()
786            .and_then(JsObject::downcast_ref::<Self>)
787            .ok_or_else(|| {
788                JsNativeError::typ().with_message("the this object must be a PlainTime object.")
789            })?;
790
791        let ixdtf = time
792            .inner
793            .to_ixdtf_string(ToStringRoundingOptions::default())?;
794        Ok(JsString::from(ixdtf).into())
795    }
796
797    /// 4.3.18 `Temporal.PlainTime.prototype.toJSON ( )`
798    ///
799    /// More information:
800    ///
801    /// - [ECMAScript Temporal proposal][spec]
802    /// - [MDN reference][mdn]
803    ///
804    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.tojson
805    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/toJSON
806    fn to_json(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
807        let object = this.as_object();
808        let time = object
809            .as_ref()
810            .and_then(JsObject::downcast_ref::<Self>)
811            .ok_or_else(|| {
812                JsNativeError::typ().with_message("the this object must be a PlainTime object.")
813            })?;
814
815        let ixdtf = time
816            .inner
817            .to_ixdtf_string(ToStringRoundingOptions::default())?;
818        Ok(JsString::from(ixdtf).into())
819    }
820
821    /// 4.3.19 `Temporal.PlainTime.prototype.valueOf ( )`
822    ///
823    /// More information:
824    ///
825    /// - [ECMAScript Temporal proposal][spec]
826    /// - [MDN reference][mdn]
827    ///
828    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.valueof
829    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/valueOf
830    fn value_of(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
831        // 1. Throw a TypeError exception.
832        Err(JsNativeError::typ()
833            .with_message("`valueOf` not supported by Temporal built-ins. See 'compare', 'equals', or `toString`")
834            .into())
835    }
836}
837
838// ==== PlainTime Abstract Operations ====
839
840pub(crate) fn create_temporal_time(
841    inner: PlainTimeInner,
842    new_target: Option<&JsValue>,
843    context: &mut Context,
844) -> JsResult<JsObject> {
845    // Note: IsValidTime is enforced by Time.
846    // 1. If IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond) is false, throw a RangeError exception.
847
848    // 2. If newTarget is not present, set newTarget to %Temporal.PlainTime%.
849    let new_target = if let Some(new_target) = new_target {
850        new_target.clone()
851    } else {
852        context
853            .realm()
854            .intrinsics()
855            .constructors()
856            .plain_time()
857            .constructor()
858            .into()
859    };
860
861    // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainTime.prototype%", « [[InitializedTemporalTime]], [[ISOHour]], [[ISOMinute]], [[ISOSecond]], [[ISOMillisecond]], [[ISOMicrosecond]], [[ISONanosecond]] »).
862    let prototype =
863        get_prototype_from_constructor(&new_target, StandardConstructors::plain_time, context)?;
864
865    // 4. Set object.[[ISOHour]] to hour.
866    // 5. Set object.[[ISOMinute]] to minute.
867    // 6. Set object.[[ISOSecond]] to second.
868    // 7. Set object.[[ISOMillisecond]] to millisecond.
869    // 8. Set object.[[ISOMicrosecond]] to microsecond.
870    // 9. Set object.[[ISONanosecond]] to nanosecond.
871    let obj = JsObject::from_proto_and_data(prototype, PlainTime { inner });
872
873    // 10. Return object.
874    Ok(obj)
875}
876
877/// 4.5.3 `ToTemporalTime ( item [ , overflow ] )`
878pub(crate) fn to_temporal_time(
879    value: &JsValue,
880    options: Option<&JsValue>,
881    context: &mut Context,
882) -> JsResult<PlainTimeInner> {
883    // 1.If overflow is not present, set overflow to "constrain".
884    let binding = JsValue::undefined();
885    let options = options.unwrap_or(&binding);
886    // 2. If item is an Object, then
887    match value.variant() {
888        JsVariant::Object(object) => {
889            // a. If item has an [[InitializedTemporalTime]] internal slot, then
890            if let Some(time) = object.downcast_ref::<PlainTime>() {
891                // i. Return item.
892                let options = get_options_object(options)?;
893                let _overflow = get_option::<Overflow>(&options, js_string!("overflow"), context)?;
894                return Ok(time.inner);
895            // b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then
896            } else if let Some(zdt) = object.downcast_ref::<ZonedDateTime>() {
897                // i. Let instant be ! CreateTemporalInstant(item.[[Nanoseconds]]).
898                // ii. Let timeZoneRec be ? CreateTimeZoneMethodsRecord(item.[[TimeZone]], « get-offset-nanoseconds-for »).
899                // iii. Let plainDateTime be ? GetPlainDateTimeFor(timeZoneRec, instant, item.[[Calendar]]).
900                // iv. Return ! CreateTemporalTime(plainDateTime.[[ISOHour]], plainDateTime.[[ISOMinute]],
901                // plainDateTime.[[ISOSecond]], plainDateTime.[[ISOMillisecond]], plainDateTime.[[ISOMicrosecond]],
902                // plainDateTime.[[ISONanosecond]]).
903                let options = get_options_object(options)?;
904                let _overflow = get_option::<Overflow>(&options, js_string!("overflow"), context)?;
905                return Ok(zdt.inner.to_plain_time());
906            // c. If item has an [[InitializedTemporalDateTime]] internal slot, then
907            } else if let Some(dt) = object.downcast_ref::<PlainDateTime>() {
908                // i. Return ! CreateTemporalTime(item.[[ISOHour]], item.[[ISOMinute]],
909                // item.[[ISOSecond]], item.[[ISOMillisecond]], item.[[ISOMicrosecond]],
910                // item.[[ISONanosecond]]).
911                let options = get_options_object(options)?;
912                let _overflow = get_option::<Overflow>(&options, js_string!("overflow"), context)?;
913                return Ok(PlainTimeInner::from(dt.inner.clone()));
914            }
915            // d. Let result be ? ToTemporalTimeRecord(item).
916            // e. Set result to ? RegulateTime(result.[[Hour]], result.[[Minute]],
917            // result.[[Second]], result.[[Millisecond]], result.[[Microsecond]],
918            // result.[[Nanosecond]], overflow).
919            let partial = to_js_partial_time_record(&object, context)?;
920
921            let options = get_options_object(options)?;
922            let overflow = get_option::<Overflow>(&options, js_string!("overflow"), context)?;
923
924            PlainTimeInner::from_partial(partial.as_temporal_partial_time(overflow)?, overflow)
925                .map_err(Into::into)
926        }
927        // 3. Else,
928        JsVariant::String(str) => {
929            // b. Let result be ? ParseTemporalTimeString(item).
930            // c. Assert: IsValidTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]) is true.
931            let result = str.to_std_string_escaped().parse::<PlainTimeInner>()?;
932
933            let options = get_options_object(options)?;
934            let _overflow = get_option::<Overflow>(&options, js_string!("overflow"), context)?;
935
936            Ok(result)
937        }
938        // a. If item is not a String, throw a TypeError exception.
939        _ => Err(JsNativeError::typ()
940            .with_message("Invalid value for converting to PlainTime.")
941            .into()),
942    }
943
944    // 4. Return ! CreateTemporalTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]).
945}
946
947/// A `PartialTime` represents partially filled `Time` fields.
948#[derive(Debug, Default, Clone, Copy, PartialEq)]
949pub struct JsPartialTime {
950    /// A potentially set `hour` field.
951    pub hour: Option<FiniteF64>,
952    /// A potentially set `minute` field.
953    pub minute: Option<FiniteF64>,
954    /// A potentially set `second` field.
955    pub second: Option<FiniteF64>,
956    /// A potentially set `millisecond` field.
957    pub millisecond: Option<FiniteF64>,
958    /// A potentially set `microsecond` field.
959    pub microsecond: Option<FiniteF64>,
960    /// A potentially set `nanosecond` field.
961    pub nanosecond: Option<FiniteF64>,
962}
963
964impl JsPartialTime {
965    fn as_temporal_partial_time(&self, overflow: Option<Overflow>) -> JsResult<PartialTime> {
966        fn check(value: Option<FiniteF64>, typ: &'static str, max: u16) -> JsResult<()> {
967            if let Some(value) = value
968                && value.as_inner().is_sign_negative()
969            {
970                return Err(JsNativeError::range()
971                    .with_message(format!("time value '{typ}' not in 0..{max}: {value}"))
972                    .into());
973            }
974            Ok(())
975        }
976
977        fn truncate<T>(value: Option<FiniteF64>) -> Option<T>
978        where
979            T: PrimInt + AsPrimitive<f64>,
980            f64: AsPrimitive<T>,
981        {
982            value
983                .as_ref()
984                .map(FiniteF64::as_integer_with_truncation::<T>)
985        }
986
987        if overflow == Some(Overflow::Reject) {
988            check(self.hour, "hour", 23)?;
989            check(self.minute, "minute", 59)?;
990            check(self.second, "second", 59)?;
991            check(self.millisecond, "millisecond", 999)?;
992            check(self.microsecond, "microsecond", 999)?;
993            check(self.nanosecond, "nanosecond", 999)?;
994        }
995
996        Ok(PartialTime::new()
997            .with_hour(truncate(self.hour))
998            .with_minute(truncate(self.minute))
999            .with_second(truncate(self.second))
1000            .with_millisecond(truncate(self.millisecond))
1001            .with_microsecond(truncate(self.microsecond))
1002            .with_nanosecond(truncate(self.nanosecond)))
1003    }
1004}
1005
1006pub(crate) fn to_js_partial_time_record(
1007    partial_object: &JsObject,
1008    context: &mut Context,
1009) -> JsResult<JsPartialTime> {
1010    let hour = partial_object
1011        .get(js_string!("hour"), context)?
1012        .map(|v| v.to_finitef64(context))
1013        .transpose()?;
1014
1015    let microsecond = partial_object
1016        .get(js_string!("microsecond"), context)?
1017        .map(|v| v.to_finitef64(context))
1018        .transpose()?;
1019
1020    let millisecond = partial_object
1021        .get(js_string!("millisecond"), context)?
1022        .map(|v| v.to_finitef64(context))
1023        .transpose()?;
1024
1025    let minute = partial_object
1026        .get(js_string!("minute"), context)?
1027        .map(|v| v.to_finitef64(context))
1028        .transpose()?;
1029
1030    let nanosecond = partial_object
1031        .get(js_string!("nanosecond"), context)?
1032        .map(|v| v.to_finitef64(context))
1033        .transpose()?;
1034
1035    let second = partial_object
1036        .get(js_string!("second"), context)?
1037        .map(|v| v.to_finitef64(context))
1038        .transpose()?;
1039
1040    Ok(JsPartialTime {
1041        hour,
1042        minute,
1043        second,
1044        millisecond,
1045        microsecond,
1046        nanosecond,
1047    })
1048}