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}