@@ -170,14 +170,19 @@ impl BuiltInConstructor for DateTimeFormat {
170170 let options = args. get_or_undefined ( 1 ) ;
171171
172172 // 2. Let dateTimeFormat be ? CreateDateTimeFormat(newTarget, locales, options, any, date).
173- let date_time_format = create_date_time_format (
174- new_target_inner,
173+ let dtf = create_date_time_format (
175174 locales,
176175 options,
177176 FormatType :: Any ,
178177 FormatDefaults :: Date ,
179178 context,
180179 ) ?;
180+ let prototype = get_prototype_from_constructor (
181+ new_target_inner,
182+ StandardConstructors :: date_time_format,
183+ context,
184+ ) ?;
185+ let date_time_format = JsObject :: from_proto_and_data ( prototype, dtf) ;
181186
182187 // 3. If the implementation supports the normative optional constructor mode of 4.3 Note 1, then
183188 // a. Let this be the this value.
@@ -551,23 +556,16 @@ impl ToLocalTime {
551556
552557// ==== Abstract Operations ====
553558
559+ /// Creates a [`DateTimeFormat`] struct (internal slots only). The constructor wraps this in a
560+ /// `JsObject` with the correct prototype; Date.prototype.toLocaleString (and friends) use it
561+ /// directly with [`format_timestamp_with_dtf`] without allocating a JS object.
554562pub ( crate ) fn create_date_time_format (
555- new_target : & JsValue ,
556563 locales : & JsValue ,
557564 options : & JsValue ,
558565 date_time_format_type : FormatType ,
559566 defaults : FormatDefaults ,
560567 context : & mut Context ,
561- ) -> JsResult < JsObject > {
562- // 1. Let dateTimeFormat be ? OrdinaryCreateFromConstructor(newTarget, "%Intl.DateTimeFormat.prototype%",
563- // « [[InitializedDateTimeFormat]], [[Locale]], [[Calendar]], [[NumberingSystem]], [[TimeZone]],
564- // [[HourCycle]], [[DateStyle]], [[TimeStyle]], [[DateTimeFormat]], [[BoundFormat]] »).
565- let prototype = get_prototype_from_constructor (
566- new_target,
567- StandardConstructors :: date_time_format,
568- context,
569- ) ?;
570-
568+ ) -> JsResult < DateTimeFormat > {
571569 // 2. Let hour12 be undefined. <- TODO
572570 // 3. Let modifyResolutionOptions be a new Abstract Closure with parameters (options) that captures hour12 and performs the following steps when called:
573571 // a. Set hour12 to options.[[hour12]].
@@ -843,21 +841,57 @@ pub(crate) fn create_date_time_format(
843841 )
844842 . map_err ( |e| JsNativeError :: range ( ) . with_message ( format ! ( "failed to load formatter: {e}" ) ) ) ?;
845843
846- Ok ( JsObject :: from_proto_and_data (
847- prototype,
848- DateTimeFormat {
849- locale : resolved_locale,
850- calendar_algorithm : intl_options. preferences . calendar_algorithm ,
851- numbering_system : intl_options. preferences . numbering_system ,
852- hour_cycle : intl_options. preferences . hour_cycle ,
853- date_style,
854- time_style,
855- time_zone,
856- fieldset,
857- formatter,
858- bound_format : None ,
859- } ,
860- ) )
844+ Ok ( DateTimeFormat {
845+ locale : resolved_locale,
846+ calendar_algorithm : intl_options. preferences . calendar_algorithm ,
847+ numbering_system : intl_options. preferences . numbering_system ,
848+ hour_cycle : intl_options. preferences . hour_cycle ,
849+ date_style,
850+ time_style,
851+ time_zone,
852+ fieldset,
853+ formatter,
854+ bound_format : None ,
855+ } )
856+ }
857+
858+ /// Formats a timestamp (epoch milliseconds) using the given [`DateTimeFormat`] internals.
859+ /// Used by the bound `format` function and by [`format_date_time_locale`] without creating a JS object.
860+ fn format_timestamp_with_dtf (
861+ dtf : & DateTimeFormat ,
862+ timestamp : f64 ,
863+ context : & mut Context ,
864+ ) -> JsResult < JsString > {
865+ // FormatDateTime / PartitionDateTimePattern: TimeClip, then ToLocalTime, then format.
866+ let x = time_clip ( timestamp) ;
867+ if x. is_nan ( ) {
868+ return Err ( js_error ! ( RangeError : "formatted date cannot be NaN" ) ) ;
869+ }
870+ let time_zone_offset = match dtf. time_zone {
871+ FormatTimeZone :: UtcOffset ( offset) => offset. to_seconds ( ) ,
872+ FormatTimeZone :: Identifier ( ( _, time_zone_id) ) => {
873+ let epoch_ns = x as i128 * 1_000_000 ;
874+ let offset_seconds = context
875+ . timezone_provider ( )
876+ . transition_nanoseconds_for_utc_epoch_nanoseconds ( time_zone_id, epoch_ns)
877+ . map_err (
878+ |_e| js_error ! ( RangeError : "unable to determine transition nanoseconds" ) ,
879+ ) ?;
880+ offset_seconds. 0 as i32
881+ }
882+ } ;
883+ let tz = x + f64:: from ( time_zone_offset * 1_000 ) ;
884+ let fields = ToLocalTime :: from_local_epoch_milliseconds ( tz) ?;
885+ let dt = fields. to_formattable_datetime ( ) ?;
886+ let tz_info = dtf. time_zone . to_time_zone_info ( ) ;
887+ let tz_info_at_time = tz_info. at_date_time_iso ( dt) ;
888+ let zdt = ZonedDateTime {
889+ date : dt. date ,
890+ time : dt. time ,
891+ zone : tz_info_at_time,
892+ } ;
893+ let result = dtf. formatter . format ( & zdt) . to_string ( ) ;
894+ Ok ( JsString :: from ( result) )
861895}
862896
863897fn date_time_style_format (
@@ -974,8 +1008,8 @@ fn unwrap_date_time_format(
9741008
9751009/// Shared helper used by Date.prototype.toLocaleString,
9761010/// Date.prototype.toLocaleDateString, and Date.prototype.toLocaleTimeString.
977- /// Applies `ToDateTimeOptions` defaults, constructs `Intl.DateTimeFormat`,
978- /// and formats the provided timestamp .
1011+ /// Applies `ToDateTimeOptions` defaults, calls [`create_date_time_format`], and formats
1012+ /// the timestamp via [`format_timestamp_with_dtf`] without allocating a JS object .
9791013#[ allow( clippy:: too_many_arguments) ]
9801014pub ( crate ) fn format_date_time_locale (
9811015 locales : & JsValue ,
@@ -1004,24 +1038,8 @@ pub(crate) fn format_date_time_locale(
10041038 context,
10051039 ) ?;
10061040 }
1007- let new_target = context
1008- . intrinsics ( )
1009- . constructors ( )
1010- . date_time_format ( )
1011- . constructor ( )
1012- . into ( ) ;
10131041 let options_value = options. into ( ) ;
1014- let dtf = create_date_time_format (
1015- & new_target,
1016- locales,
1017- & options_value,
1018- format_type,
1019- defaults,
1020- context,
1021- ) ?;
1022- let format_val = dtf. get ( js_string ! ( "format" ) , context) ?;
1023- let format_fn = format_val
1024- . as_callable ( )
1025- . ok_or_else ( || JsNativeError :: typ ( ) . with_message ( "format is not callable" ) ) ?;
1026- format_fn. call ( & dtf. into ( ) , & [ JsValue :: from ( timestamp) ] , context)
1042+ let dtf = create_date_time_format ( locales, & options_value, format_type, defaults, context) ?;
1043+ let result = format_timestamp_with_dtf ( & dtf, timestamp, context) ?;
1044+ Ok ( JsValue :: from ( result) )
10271045}
0 commit comments