Skip to content

Add missing Temporal built-ins and options support#277

Merged
frostney merged 6 commits intomainfrom
t3code/temporal-missing-features
Apr 12, 2026
Merged

Add missing Temporal built-ins and options support#277
frostney merged 6 commits intomainfrom
t3code/temporal-missing-features

Conversation

@frostney
Copy link
Copy Markdown
Owner

  • Add PlainYearMonth, PlainMonthDay, and ZonedDateTime
  • Extend Temporal.Now and option-bag handling
  • Update docs and coverage for new Temporal APIs

- Add PlainYearMonth, PlainMonthDay, and ZonedDateTime
- Extend Temporal.Now and option-bag handling
- Update docs and coverage for new Temporal APIs
@frostney frostney added new feature New feature or request spec compliance Mismatch against official JavaScript/TypeScript specification labels Apr 11, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 11, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d01d8456-c554-4224-b4f4-b7ddb53c4e0f

📥 Commits

Reviewing files that changed from the base of the PR and between d59dd03 and 430189a.

📒 Files selected for processing (5)
  • tests/built-ins/Temporal/PlainMonthDay/from.js
  • tests/built-ins/Temporal/PlainYearMonth/from.js
  • units/Goccia.Builtins.Temporal.pas
  • units/Goccia.Constants.PropertyNames.pas
  • units/Goccia.Values.TemporalPlainMonthDay.pas
✅ Files skipped from review due to trivial changes (1)
  • tests/built-ins/Temporal/PlainMonthDay/from.js
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/built-ins/Temporal/PlainYearMonth/from.js
  • units/Goccia.Constants.PropertyNames.pas
  • units/Goccia.Builtins.Temporal.pas
  • units/Goccia.Values.TemporalPlainMonthDay.pas

📝 Walkthrough

Walkthrough

Adds comprehensive Temporal support: new types (PlainYearMonth, PlainMonthDay, ZonedDateTime), TZif timezone parsing and cached UTC-offset resolution, an options-parsing module for rounding/overflow/fractional digits, many Temporal API signature updates, and a large reorganization of Temporal tests (many granular additions and several monolithic deletions).

Changes

Cohort / File(s) Summary
Docs
AGENTS.md, docs/built-ins.md
Documented new Temporal types/methods and updated signatures (round/toString options, PlainDate.from overflow option, Temporal.Now additions).
Builtins & Constants
units/Goccia.Builtins.Temporal.pas, units/Goccia.Constants.PropertyNames.pas
Registered new constructors/statics (PlainYearMonth, PlainMonthDay, ZonedDateTime), added Temporal.Now.timeZoneId/zonedDateTimeISO, and added PROP_* temporal property-name constants.
Timezone runtime
units/Goccia.Temporal.TimeZone.pas
New TZ module: system TZ id resolution, TZif v1/v2/v3 loader & parser, in-process cache, GetUtcOffsetSeconds, and offset parse/format helpers.
Options & Utils
units/Goccia.Temporal.Options.pas, units/Goccia.Temporal.Utils.pas
New enums/parsers and option helpers (units, rounding modes, overflow, fractionalSecondDigits), Unit→nanoseconds, RoundWithMode, FormatTimeWithPrecision, and ISO YearMonth/MonthDay/DateTime-with-offset parsing/formatting.
New value types
units/Goccia.Values.TemporalPlainYearMonth.pas, units/Goccia.Values.TemporalPlainMonthDay.pas, units/Goccia.Values.TemporalZonedDateTime.pas
Introduced PlainYearMonth, PlainMonthDay, and ZonedDateTime classes with constructors, coercion/from/compare and full prototype surfaces (getters, with/add/subtract/until/since/equals/toString/toJSON/valueOf/toPlain*/toInstant/etc.).
Temporal value updates
units/Goccia.Values.TemporalDuration.pas, units/Goccia.Values.TemporalInstant.pas, units/Goccia.Values.TemporalPlainDate.pas, units/Goccia.Values.TemporalPlainDateTime.pas, units/Goccia.Values.TemporalPlainTime.pas
Added Duration.prototype.round; rewired Instant/PlainTime/PlainDateTime rounding and toString behavior to use options helpers; fixed Instant nanosecond arithmetic; added toPlainYearMonth/toPlainMonthDay/toZonedDateTime.
Tests — removals
tests/built-ins/Temporal/{duration.js,instant.js,plain-date.js,plain-date-time.js,plain-time.js,now.js}
Deleted several legacy monolithic Temporal test suites.
Tests — additions
tests/built-ins/Temporal/**
Added ~200 granular Temporal tests covering Duration, Instant, PlainDate/DateTime/Time/YearMonth/MonthDay/ZonedDateTime/Now: constructors, from/compare, prototype methods, getters, toString/toJSON/valueOf, rounding/overflow options, and timezone behaviors.

Sequence Diagram(s)

sequenceDiagram
  participant Caller as rgba(30,144,255,0.5) Caller
  participant Builtin as rgba(34,139,34,0.5) Temporal.Now / API
  participant TZModule as rgba(255,165,0,0.5) Goccia.Temporal.TimeZone
  participant Parser as rgba(220,20,60,0.5) TZif Parser / Cache
  participant FS as rgba(128,0,128,0.5) Zoneinfo File System

  Caller->>Builtin: Temporal.Now.zonedDateTimeISO([timeZone])
  Builtin->>TZModule: GetUtcOffsetSeconds(epochSeconds, timeZone?)
  TZModule->>Parser: Lookup cached TTimeZoneData for timeZoneId
  alt cache miss
    Parser->>FS: Read /usr/share/zoneinfo/<id> (TZif)
    FS-->>Parser: TZif bytes
    Parser->>Parser: Parse TZif v1/v2/v3 -> transitions, offsets
    Parser-->>TZModule: Cache and return TTimeZoneData
  else cache hit
    Parser-->>TZModule: Return cached TTimeZoneData
  end
  TZModule-->>Builtin: offsetSeconds
  Builtin-->>Caller: Construct ZonedDateTime with epoch±offset and timeZoneId
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • Add Temporal API #3 — Overlapping Temporal API work: earlier additions of Temporal infrastructure that this PR extends with new types, timezone parsing, and options parsing.
  • Refactor: Split constants #25 — Related changes to centralized property-name constants; this PR adds the temporal PROP_* entries into that constants unit.
  • Add Performance built-in API #63 — Related builtins/prototype initialization adjustments affecting shared prototype/@@toStringTag patterns used by the new Temporal types.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
units/Goccia.Values.TemporalPlainDateTime.pas (1)

683-696: ⚠️ Potential issue | 🟠 Major

roundingIncrement is ignored when smallestUnit is day.

The non-day path uses Increment in the divisor, but the tuDay branch hardcodes a single-day divisor and never validates or applies the caller-supplied increment. Right now round({ smallestUnit: "day", roundingIncrement: 2 }) behaves the same as increment 1.

Suggested fix
   if SmallestUnit = tuDay then
   begin
+    if Increment <> 1 then
+      ThrowRangeError('roundingIncrement must be 1 when smallestUnit is day');
     // Round to nearest day using the specified rounding mode
     Divisor := NANOSECONDS_PER_DAY;
     Rounded := RoundWithMode(TotalNs, Divisor, Mode);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Values.TemporalPlainDateTime.pas` around lines 683 - 696, The
day-branch ignores the caller's roundingIncrement: replace the hardcoded
single-day divisor with one that uses the existing Increment (roundingIncrement)
and validate it, i.e. compute Divisor := NANOSECONDS_PER_DAY * Increment (after
asserting Increment is within the allowed range), call Rounded :=
RoundWithMode(TotalNs, Divisor, Mode) and use that Divisor/Rounded to decide how
many days to add (add Increment days when Rounded >= Divisor) so the tuDay path
behaves the same as the non-day path; update references in this block to use
Increment, RoundWithMode, TotalNs, Mode, and AddDaysToDate accordingly.
🧹 Nitpick comments (5)
units/Goccia.Values.TemporalDuration.pas (1)

492-492: Add the TC39 Temporal annotation above this method.

This is new spec-defined behavior, so it should keep the // TC39 Temporal §... cross-reference immediately above the implementation.

As per coding guidelines, "ECMAScript spec annotations: Add // ESYYYY §X.Y.Z SpecMethodName(specParams) comments immediately above function body in implementation section when implementing spec-defined behavior".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Values.TemporalDuration.pas` at line 492, Add the TC39 Temporal
spec annotation comment immediately above the implementation of
TGocciaTemporalDurationValue.DurationRound; specifically insert a line like "//
TC39 Temporal §<section> Temporal.Duration.prototype.round(temporalDuration,
options)" (use the correct § number for Duration.round) directly above the
function header for DurationRound in the implementation section so the
cross-reference to the spec is preserved per the coding guidelines.
tests/built-ins/Temporal/plain-year-month.js (1)

8-156: Break this suite up by Temporal.PlainYearMonth method.

This file currently tests construction, accessors, statics, arithmetic, conversion, and branding together. Splitting it by method/operation will match the existing test conventions and make regressions much easier to pinpoint.

As per coding guidelines, "JavaScript test structure: One method per file, each test file focuses on a single method or operation. Never bundle multiple methods into one file".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Temporal/plain-year-month.js` around lines 8 - 156, The suite
bundles many Temporal.PlainYearMonth behaviors in one file; split tests so each
file focuses on a single method/operation (e.g., constructor, calendarId,
monthCode, daysInMonth, daysInYear, monthsInYear, inLeapYear, from, compare,
with, add, subtract, until, since, equals, toString, toJSON, valueOf,
toPlainDate, Symbol.toStringTag). For each method create a new test file using
describe.runIf(isTemporal)("Temporal.PlainYearMonth.<method>") and move only the
relevant test cases for that method (keeping test bodies intact) into that file,
leaving shared helpers (if any) imported; ensure static tests like
PlainYearMonth.from and PlainYearMonth.compare are placed in their own files
named for the static method, and update any test names to reflect the
single-method focus.
tests/built-ins/Temporal/options-bags.js (1)

8-132: Split this into per-method Temporal test files.

This suite mixes several unrelated surfaces (round, from, toString, Now.timeZoneId) into one file, which makes failures harder to localize and goes against the repo’s Temporal test layout.

As per coding guidelines, "JavaScript test structure: One method per file, each test file focuses on a single method or operation. Never bundle multiple methods into one file".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Temporal/options-bags.js` around lines 8 - 132, The test file
bundles many unrelated Temporal behaviors; split each top-level describe block
into its own test file so each file tests a single method/operation (e.g.,
create separate files for PlainTime.round, PlainDateTime.round, Instant.round,
Duration.round, PlainDate.from overflow, PlainTime.toString
fractionalSecondDigits, and Temporal.Now.timeZoneId). For each new file, move
the corresponding describe(...) block (and its tests that reference symbols like
PlainTime.round, PlainDateTime.round, Instant.round, Duration.round,
PlainDate.from, PlainTime.toString, Temporal.Now.timeZoneId) into a new file,
keep the outer describe.runIf(isTemporal) wrapper, preserve test names and
expectations, and remove the moved blocks from the original file; ensure any
shared helpers/fixtures remain imported or available to each new file and run
the test suite to verify no duplicate definitions remain.
tests/built-ins/Temporal/zoned-date-time.js (1)

8-189: Split the Temporal.ZonedDateTime coverage by operation.

This file groups constructor behavior, getters, arithmetic, conversions, formatting, and comparison into one suite. Keeping one operation per file will align with the repo’s test structure and reduce the blast radius of individual failures.

As per coding guidelines, "JavaScript test structure: One method per file, each test file focuses on a single method or operation. Never bundle multiple methods into one file".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Temporal/zoned-date-time.js` around lines 8 - 189, The test
file bundles many Temporal.ZonedDateTime operations into one suite; split it so
each file tests a single operation. Create separate test files (or suites) for
constructor behavior (new Temporal.ZonedDateTime / epochNanoseconds),
calendarId, epochMilliseconds/epochNanoseconds, offset/offsetNanoseconds,
calendar getters (dayOfWeek, dayOfYear, daysInWeek, daysInMonth, daysInYear,
monthsInYear, inLeapYear, monthCode), withTimeZone, withPlainTime, add,
subtract, equals, startOfDay, toInstant, toPlainDate, toPlainTime,
toPlainDateTime, formatting/toString/toJSON, valueOf (throws), compare,
hoursInDay, and Symbol.toStringTag; move each corresponding test block (e.g.
tests referencing Temporal.ZonedDateTime constructor, .add, .withPlainTime,
.toPlainDateTime, Temporal.ZonedDateTime.compare, etc.) into its own file
named/organized per operation and keep test names focused on that single method.
Ensure imports/describe.runIf(isTemporal) and any shared epochNs constants are
preserved or duplicated minimally per file.
units/Goccia.Values.TemporalPlainMonthDay.pas (1)

179-292: Add the required ES spec annotations above the new PlainMonthDay methods.

This file implements spec-defined Temporal behavior, but the getters/methods are missing the repo-standard // ESYYYY §X.Y.Z SpecMethodName(...) comments used for spec-traceability.

As per coding guidelines, "ECMAScript spec annotations: Add // ESYYYY §X.Y.Z SpecMethodName(specParams) comments immediately above function body in implementation section when implementing spec-defined behavior".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Values.TemporalPlainMonthDay.pas` around lines 179 - 292, Add
the required ECMAScript spec annotation comments immediately above each
implementation of the spec-defined PlainMonthDay routines: GetCalendarId,
GetMonthCode, GetDay, MonthDayWith, MonthDayEquals, MonthDayToString,
MonthDayToJSON, MonthDayValueOf, and MonthDayToPlainDate; use the repo-standard
format "// ESYYYY §X.Y.Z SpecMethodName(specParams)" (replace
ESYYYY/§X.Y.Z/specParams with the correct spec reference and signature) and
place each comment directly above the corresponding function header in the
implementation section so each method is traceable to the spec.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@units/Goccia.Builtins.Temporal.pas`:
- Around line 53-56: PlainMonthDay is missing its static compare implementation;
add a function PlainMonthDayCompare(const AArgs: TGocciaArgumentsCollection;
const AThisValue: TGocciaValue): TGocciaValue that implements the same compare
semantics used by PlainYearMonth/ZonedDateTime compare helpers, then register
that function on the PlainMonthDay constructor statics alongside
PlainMonthDayFrom so the exported API includes PlainMonthDay.compare (mirror the
pattern used for PlainYearMonth and ZonedDateTime).
- Around line 496-509: The code calls Obj.GetProperty('year'/'month'/'day') and
immediately uses ToNumberLiteral, which will access nil if the property is
missing; update the blocks around TGocciaObjectValue/Obj.GetProperty usages
(including the shown block that constructs TGocciaTemporalPlainDateValue.Create
and the other occurrences at 875-878 and 979-1000) to guard each GetProperty
result: if the returned value is nil or represents undefined, raise a JS
TypeError (use the same error reporting pattern as ThrowRangeError) before
calling ToNumberLiteral; keep existing overflow handling (Overflow/toReject and
ThrowRangeError) and only proceed to Trunc/DaysInMonth and Create after
validating year, month, and day are present and numeric.
- Around line 1067-1083: The code currently overwrites an empty TZ with 'UTC',
which converts offset-only (zone-less) ISO strings into UTC-zoned values;
instead, when TZ = '' do not assign 'UTC'—reject unannotated/offset-only inputs
to match CoerceZonedDateTime behavior. Modify the block after parsing (in the
routine using TryParseISODateTimeWithOffset and creating
TGocciaTemporalZonedDateTimeValue) to remove the TZ := 'UTC' assignment and call
ThrowRangeError (or otherwise fail) if TZ = '' so you do not fabricate a UTC
zone for zone-less strings.

In `@units/Goccia.Temporal.Options.pas`:
- Around line 268-349: The midpoint logic uses HalfDivisor := ADivisor div 2
which truncates for odd ADivisor and misclassifies half ties; replace the
HalfDivisor-based checks with comparisons that double the remainder instead
(e.g. use AbsRemainder * 2 < ADivisor, = ADivisor, > ADivisor or >=/<= as
appropriate) across all rmHalf* branches (symbols: AbsRemainder, ADivisor,
AMode, rmHalfExpand, rmHalfTrunc, rmHalfCeil, rmHalfFloor, rmHalfEven) so ties
are determined by AbsRemainder * 2 = ADivisor and "greater than half" by
AbsRemainder * 2 > ADivisor; remove or stop using HalfDivisor to avoid the
off-by-one on odd divisors.

In `@units/Goccia.Temporal.TimeZone.pas`:
- Around line 257-259: ATimeZone is concatenated into ZONEINFO_PATH to form
FilePath and can contain malicious segments (e.g., "../" or absolute paths) that
escape the zoneinfo directory; before using FilePath, validate and normalize
ATimeZone by rejecting empty, absolute paths, and any '.' or '..' path segments
and ensure it does not contain path separators that would traverse up (normalize
by splitting on path separators and rejecting any segment equal to '.' or '..');
then build FilePath as ZONEINFO_PATH + validated ATimeZone and only then call
FileExists or TFileStream.Create (update the code paths referenced by FilePath,
ZONEINFO_PATH, ATimeZone, FileExists, and TFileStream.Create accordingly).
- Around line 114-150: The code advances offsets by TZIF_MAGIC_LENGTH extra
bytes when skipping the v1 data and when locating the v2 header, causing reads
to be 4 bytes too far; update the offset calculations in the parsing flow (the
block that computes Offset before skipping v1 data and the similar reuse around
the v2 header check in the same routine) to use TZIF_HEADER_SIZE alone (do not
add TZIF_MAGIC_LENGTH) so that Offset := TZIF_HEADER_SIZE + V1DataSize (or
equivalently start from the existing TZIF_MAGIC_LENGTH+TZIF_HEADER_SIZE base
only once where appropriate) and apply the same correction for the second
occurrence referenced around the 183-186 range to ensure subsequent reads
(ReadBigEndianInt32, transition parsing) use the correct positions.

In `@units/Goccia.Temporal.Utils.pas`:
- Around line 813-827: The offset parsing currently ignores TryParseDigits in
the compact-offset branch and fails to validate ranges or trailing characters;
update the block that reads OffsetPart (variables OffsetPart, ParsePos, OffsetH,
OffsetM, AOffsetSeconds and function TryParseDigits) to (1) check the boolean
return from TryParseDigits when parsing the compact form and Exit on failure,
(2) validate OffsetM is in 0..59 and OffsetH is in an appropriate non-negative
range (reject out-of-range values), and (3) ensure there are no extra trailing
characters by confirming ParsePos has consumed the entire OffsetPart before
computing AOffsetSeconds; if any check fails, Exit instead of normalizing to
:00.

In `@units/Goccia.Values.TemporalDuration.pas`:
- Around line 533-600: The rounding logic incorrectly overwrites LargestUnit
with SmallestUnit and drops weeks; revert the assignment so LargestUnit remains
"auto" (don’t set LargestUnit := SmallestUnit), implement reconstruction of
weeks when breaking RemNs into components (add ResultWeeks variable, compute it
using NANOSECONDS_PER_DAY*7 when Ord(LargestUnit) <= Ord(tuWeek) before days,
include D.FWeeks in TotalNs handling already present), and pass ResultWeeks into
TGocciaTemporalDurationValue.Create instead of the hardcoded 0; also add the
required ECMA-262/Temporal spec annotation comment immediately before this
implementation block to document spec-defined behavior.

In `@units/Goccia.Values.TemporalPlainDate.pas`:
- Around line 583-597: The conversions in DateToPlainYearMonth and
DateToPlainMonthDay drop the source date's hidden reference fields (D.FDay and
D.FYear); update DateToPlainYearMonth
(TGocciaTemporalPlainDateValue.DateToPlainYearMonth) to create the
TGocciaTemporalPlainYearMonthValue with the original reference day (preserve
D.FDay) and update DateToPlainMonthDay
(TGocciaTemporalPlainDateValue.DateToPlainMonthDay) to create the
TGocciaTemporalPlainMonthDayValue with the original reference year (preserve
D.FYear) — do this by using the constructor overloads that accept the reference
fields or by setting the appropriate reference properties on the returned
TGocciaTemporalPlainYearMonthValue / TGocciaTemporalPlainMonthDayValue instances
after creation so the hidden reference metadata from D is retained.

In `@units/Goccia.Values.TemporalPlainDateTime.pas`:
- Around line 769-783: DateTimeToPlainYearMonth and DateTimeToPlainMonthDay drop
original date metadata because they call AsPlainDateTime then construct
PlainYearMonth/PlainMonthDay without passing reference day/year; update
DateTimeToPlainYearMonth (function
TGocciaTemporalPlainDateTimeValue.DateTimeToPlainYearMonth) to pass D.FDay into
the PlainYearMonth creation (use the overload that accepts a reference day) and
update DateTimeToPlainMonthDay (function
TGocciaTemporalPlainDateTimeValue.DateTimeToPlainMonthDay) to pass D.FYear into
the PlainMonthDay creation (use the overload that accepts a reference year),
keeping the call to AsPlainDateTime as-is so the original
D.FYear/D.FMonth/D.FDay are preserved when constructing
TGocciaTemporalPlainYearMonthValue and TGocciaTemporalPlainMonthDayValue.

In `@units/Goccia.Values.TemporalZonedDateTime.pas`:
- Around line 887-946: The round implementation only reads UnitStr and always
does half-expand with increment 1; fix by reading and validating roundingMode
and roundingIncrement from the options object (when Arg is TGocciaObjectValue)
using the shared option-bag helpers, defaulting roundingMode to 'halfExpand' and
roundingIncrement to 1, then compute Divisor as NANOSECONDS_PER_<unit> *
roundingIncrement (e.g. for minute: NANOSECONDS_PER_MINUTE * roundingIncrement)
and apply the correct rounding behavior based on roundingMode ('trunc' =>
truncate/floor, 'ceil' => ceiling, 'halfExpand' => current half-up logic using
Divisor), updating how Rounded is computed before calling BalanceTime; refer to
UnitStr, Divisor, roundingMode, roundingIncrement, TotalNs, Rounded and Balanced
to locate where to add extraction/validation and to replace the
always-half-expand logic.
- Around line 511-515: GetHoursInDay currently returns the constant
HOURS_PER_DAY which is wrong for days with DST shifts; instead use
AsZonedDateTime to get the TGocciaTemporalZonedDateTimeValue's zoned datetime
instance, compute the start of that calendar day (set time to 00:00) and the
start of the following day in the same zone, measure the difference in hours
between those two instants (accounting for zone offsets/DST), and return
TGocciaNumberLiteralValue.Create(calculatedHours) rather than HOURS_PER_DAY.
- Around line 711-726: The code in the ZonedDateTime withPlainTime handling
currently leaves NewHour/NewMinute/etc. at zero when Arg is non-undefined but
not a TGocciaTemporalPlainTimeValue or TGocciaStringLiteralValue, silently
changing the instant; update the branch so that after checking Assigned(Arg) and
not TGocciaUndefinedLiteralValue, if Arg is TGocciaTemporalPlainTimeValue set
New* from PlainTime, else if Arg is TGocciaStringLiteralValue parse via
TryParseISOTime, else call ThrowTypeError('Invalid time string for
ZonedDateTime.prototype.withPlainTime') (or a clearer message) to reject
unsupported plainTimeLike inputs; adjust the logic around
TGocciaTemporalPlainTimeValue, TGocciaStringLiteralValue, TryParseISOTime and
ThrowTypeError accordingly.

---

Outside diff comments:
In `@units/Goccia.Values.TemporalPlainDateTime.pas`:
- Around line 683-696: The day-branch ignores the caller's roundingIncrement:
replace the hardcoded single-day divisor with one that uses the existing
Increment (roundingIncrement) and validate it, i.e. compute Divisor :=
NANOSECONDS_PER_DAY * Increment (after asserting Increment is within the allowed
range), call Rounded := RoundWithMode(TotalNs, Divisor, Mode) and use that
Divisor/Rounded to decide how many days to add (add Increment days when Rounded
>= Divisor) so the tuDay path behaves the same as the non-day path; update
references in this block to use Increment, RoundWithMode, TotalNs, Mode, and
AddDaysToDate accordingly.

---

Nitpick comments:
In `@tests/built-ins/Temporal/options-bags.js`:
- Around line 8-132: The test file bundles many unrelated Temporal behaviors;
split each top-level describe block into its own test file so each file tests a
single method/operation (e.g., create separate files for PlainTime.round,
PlainDateTime.round, Instant.round, Duration.round, PlainDate.from overflow,
PlainTime.toString fractionalSecondDigits, and Temporal.Now.timeZoneId). For
each new file, move the corresponding describe(...) block (and its tests that
reference symbols like PlainTime.round, PlainDateTime.round, Instant.round,
Duration.round, PlainDate.from, PlainTime.toString, Temporal.Now.timeZoneId)
into a new file, keep the outer describe.runIf(isTemporal) wrapper, preserve
test names and expectations, and remove the moved blocks from the original file;
ensure any shared helpers/fixtures remain imported or available to each new file
and run the test suite to verify no duplicate definitions remain.

In `@tests/built-ins/Temporal/plain-year-month.js`:
- Around line 8-156: The suite bundles many Temporal.PlainYearMonth behaviors in
one file; split tests so each file focuses on a single method/operation (e.g.,
constructor, calendarId, monthCode, daysInMonth, daysInYear, monthsInYear,
inLeapYear, from, compare, with, add, subtract, until, since, equals, toString,
toJSON, valueOf, toPlainDate, Symbol.toStringTag). For each method create a new
test file using describe.runIf(isTemporal)("Temporal.PlainYearMonth.<method>")
and move only the relevant test cases for that method (keeping test bodies
intact) into that file, leaving shared helpers (if any) imported; ensure static
tests like PlainYearMonth.from and PlainYearMonth.compare are placed in their
own files named for the static method, and update any test names to reflect the
single-method focus.

In `@tests/built-ins/Temporal/zoned-date-time.js`:
- Around line 8-189: The test file bundles many Temporal.ZonedDateTime
operations into one suite; split it so each file tests a single operation.
Create separate test files (or suites) for constructor behavior (new
Temporal.ZonedDateTime / epochNanoseconds), calendarId,
epochMilliseconds/epochNanoseconds, offset/offsetNanoseconds, calendar getters
(dayOfWeek, dayOfYear, daysInWeek, daysInMonth, daysInYear, monthsInYear,
inLeapYear, monthCode), withTimeZone, withPlainTime, add, subtract, equals,
startOfDay, toInstant, toPlainDate, toPlainTime, toPlainDateTime,
formatting/toString/toJSON, valueOf (throws), compare, hoursInDay, and
Symbol.toStringTag; move each corresponding test block (e.g. tests referencing
Temporal.ZonedDateTime constructor, .add, .withPlainTime, .toPlainDateTime,
Temporal.ZonedDateTime.compare, etc.) into its own file named/organized per
operation and keep test names focused on that single method. Ensure
imports/describe.runIf(isTemporal) and any shared epochNs constants are
preserved or duplicated minimally per file.

In `@units/Goccia.Values.TemporalDuration.pas`:
- Line 492: Add the TC39 Temporal spec annotation comment immediately above the
implementation of TGocciaTemporalDurationValue.DurationRound; specifically
insert a line like "// TC39 Temporal §<section>
Temporal.Duration.prototype.round(temporalDuration, options)" (use the correct §
number for Duration.round) directly above the function header for DurationRound
in the implementation section so the cross-reference to the spec is preserved
per the coding guidelines.

In `@units/Goccia.Values.TemporalPlainMonthDay.pas`:
- Around line 179-292: Add the required ECMAScript spec annotation comments
immediately above each implementation of the spec-defined PlainMonthDay
routines: GetCalendarId, GetMonthCode, GetDay, MonthDayWith, MonthDayEquals,
MonthDayToString, MonthDayToJSON, MonthDayValueOf, and MonthDayToPlainDate; use
the repo-standard format "// ESYYYY §X.Y.Z SpecMethodName(specParams)" (replace
ESYYYY/§X.Y.Z/specParams with the correct spec reference and signature) and
place each comment directly above the corresponding function header in the
implementation section so each method is traceable to the spec.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: de97cee3-4d22-438a-b6e6-2b1d8b0ce417

📥 Commits

Reviewing files that changed from the base of the PR and between d149d12 and 46eb927.

📒 Files selected for processing (22)
  • AGENTS.md
  • docs/built-ins.md
  • tests/built-ins/Temporal/now.js
  • tests/built-ins/Temporal/options-bags.js
  • tests/built-ins/Temporal/plain-date-time.js
  • tests/built-ins/Temporal/plain-date.js
  • tests/built-ins/Temporal/plain-month-day.js
  • tests/built-ins/Temporal/plain-year-month.js
  • tests/built-ins/Temporal/toStringTag.js
  • tests/built-ins/Temporal/zoned-date-time.js
  • units/Goccia.Builtins.Temporal.pas
  • units/Goccia.Temporal.Options.pas
  • units/Goccia.Temporal.TimeZone.pas
  • units/Goccia.Temporal.Utils.pas
  • units/Goccia.Values.TemporalDuration.pas
  • units/Goccia.Values.TemporalInstant.pas
  • units/Goccia.Values.TemporalPlainDate.pas
  • units/Goccia.Values.TemporalPlainDateTime.pas
  • units/Goccia.Values.TemporalPlainMonthDay.pas
  • units/Goccia.Values.TemporalPlainTime.pas
  • units/Goccia.Values.TemporalPlainYearMonth.pas
  • units/Goccia.Values.TemporalZonedDateTime.pas

Comment thread units/Goccia.Builtins.Temporal.pas
Comment thread units/Goccia.Builtins.Temporal.pas
Comment thread units/Goccia.Builtins.Temporal.pas
Comment thread units/Goccia.Temporal.Options.pas Outdated
Comment thread units/Goccia.Temporal.TimeZone.pas Outdated
Comment thread units/Goccia.Values.TemporalPlainDate.pas
Comment thread units/Goccia.Values.TemporalPlainDateTime.pas
Comment thread units/Goccia.Values.TemporalZonedDateTime.pas
Comment thread units/Goccia.Values.TemporalZonedDateTime.pas
Comment thread units/Goccia.Values.TemporalZonedDateTime.pas
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 11, 2026

Suite Timing

Suite Metric Interpreted Bytecode
Tests Total 5137 5137
Tests Passed 5096 ✅ 5137 ✅
Tests Skipped 41 0
Tests Test Duration 361.6ms 347.9ms
Tests Lex 103.6ms 66.0ms
Tests Parse 130.2ms 130.9ms
Tests Compile 83.6ms
Tests Execute 380.5ms 383.1ms
Tests Engine Total 614.2ms 663.7ms
Benchmarks Total 364 364
Benchmarks Duration 9.90min 8.88min

Measured on ubuntu-latest x64.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 11, 2026

Benchmark Results

364 benchmarks

Interpreted: 🟢 84 improved · 🔴 163 regressed · 117 unchanged · avg -0.7%
Bytecode: 🟢 141 improved · 🔴 103 regressed · 120 unchanged · avg +0.3%

arraybuffer.js — Interp: 🟢 6, 🔴 2, 6 unch. · avg +0.8% · Bytecode: 🟢 9, 🔴 2, 3 unch. · avg +5.0%
Benchmark Interpreted Δ Bytecode Δ
create ArrayBuffer(0) 452,250 ops/sec [451,471..453,811] → 446,233 ops/sec [436,190..467,014] ~ overlap (-1.3%) 523,390 ops/sec [515,149..526,985] → 584,491 ops/sec [500,333..593,323] ~ overlap (+11.7%)
create ArrayBuffer(64) 430,575 ops/sec [428,563..431,446] → 447,151 ops/sec [442,894..448,913] 🟢 +3.8% 499,653 ops/sec [492,752..500,274] → 551,018 ops/sec [545,544..553,408] 🟢 +10.3%
create ArrayBuffer(1024) 341,848 ops/sec [340,319..343,320] → 349,140 ops/sec [347,225..352,126] 🟢 +2.1% 381,524 ops/sec [378,705..385,303] → 385,135 ops/sec [378,161..389,456] ~ overlap (+0.9%)
create ArrayBuffer(8192) 153,222 ops/sec [152,412..154,494] → 152,779 ops/sec [152,127..153,233] ~ overlap (-0.3%) 164,163 ops/sec [162,767..165,643] → 146,003 ops/sec [144,660..147,579] 🔴 -11.1%
slice full buffer (64 bytes) 494,565 ops/sec [493,279..495,371] → 496,449 ops/sec [490,370..500,905] ~ overlap (+0.4%) 620,783 ops/sec [611,415..625,065] → 655,304 ops/sec [649,307..657,207] 🟢 +5.6%
slice half buffer (512 of 1024 bytes) 429,171 ops/sec [427,919..430,395] → 426,401 ops/sec [423,320..428,846] ~ overlap (-0.6%) 533,733 ops/sec [531,962..534,752] → 553,537 ops/sec [550,334..554,117] 🟢 +3.7%
slice with negative indices 416,464 ops/sec [414,778..416,971] → 413,574 ops/sec [412,514..414,404] 🔴 -0.7% 576,881 ops/sec [572,935..577,451] → 601,387 ops/sec [550,571..602,862] ~ overlap (+4.2%)
slice empty range 471,053 ops/sec [469,464..473,010] → 471,835 ops/sec [471,167..473,439] ~ overlap (+0.2%) 599,128 ops/sec [595,274..602,580] → 643,356 ops/sec [639,778..647,621] 🟢 +7.4%
byteLength access 1,331,530 ops/sec [1,328,762..1,335,887] → 1,312,362 ops/sec [1,307,648..1,318,431] 🔴 -1.4% 1,812,418 ops/sec [1,801,961..1,814,230] → 2,059,511 ops/sec [2,040,553..2,064,896] 🟢 +13.6%
Symbol.toStringTag access 997,694 ops/sec [992,989..1,001,552] → 989,392 ops/sec [987,111..997,826] ~ overlap (-0.8%) 1,127,277 ops/sec [1,126,291..1,129,354] → 1,240,356 ops/sec [1,234,455..1,243,717] 🟢 +10.0%
ArrayBuffer.isView 714,370 ops/sec [712,120..716,307] → 722,448 ops/sec [721,461..724,969] 🟢 +1.1% 891,179 ops/sec [885,857..894,967] → 978,685 ops/sec [976,359..979,646] 🟢 +9.8%
clone ArrayBuffer(64) 407,787 ops/sec [405,377..408,464] → 426,508 ops/sec [425,260..427,408] 🟢 +4.6% 519,248 ops/sec [516,720..520,272] → 532,283 ops/sec [530,801..533,270] 🟢 +2.5%
clone ArrayBuffer(1024) 321,749 ops/sec [320,654..323,651] → 331,372 ops/sec [328,202..334,467] 🟢 +3.0% 385,658 ops/sec [382,557..386,824] → 370,955 ops/sec [369,828..374,647] 🔴 -3.8%
clone ArrayBuffer inside object 265,035 ops/sec [263,828..265,865] → 269,163 ops/sec [266,797..271,566] 🟢 +1.6% 308,959 ops/sec [307,221..313,728] → 323,201 ops/sec [321,722..324,972] 🟢 +4.6%
arrays.js — Interp: 🟢 15, 🔴 1, 3 unch. · avg +3.1% · Bytecode: 🟢 18, 1 unch. · avg +8.7%
Benchmark Interpreted Δ Bytecode Δ
Array.from length 100 12,872 ops/sec [12,867..12,882] → 13,346 ops/sec [13,300..13,382] 🟢 +3.7% 17,644 ops/sec [17,538..17,719] → 18,917 ops/sec [18,757..18,993] 🟢 +7.2%
Array.from 10 elements 205,234 ops/sec [204,574..205,508] → 224,276 ops/sec [223,706..224,533] 🟢 +9.3% 248,974 ops/sec [244,250..249,584] → 260,228 ops/sec [259,161..261,916] 🟢 +4.5%
Array.of 10 elements 272,047 ops/sec [266,156..272,662] → 276,499 ops/sec [275,220..277,308] 🟢 +1.6% 306,216 ops/sec [304,480..306,471] → 332,447 ops/sec [330,702..333,243] 🟢 +8.6%
spread into new array 337,795 ops/sec [334,911..340,962] → 353,227 ops/sec [351,667..354,173] 🟢 +4.6% 202,585 ops/sec [200,216..203,042] → 209,789 ops/sec [205,270..210,714] 🟢 +3.6%
map over 50 elements 23,759 ops/sec [23,663..23,835] → 24,875 ops/sec [24,837..24,967] 🟢 +4.7% 34,313 ops/sec [34,064..34,445] → 36,580 ops/sec [36,378..36,900] 🟢 +6.6%
filter over 50 elements 21,556 ops/sec [21,460..21,643] → 22,458 ops/sec [22,329..22,495] 🟢 +4.2% 26,585 ops/sec [26,275..26,663] → 33,401 ops/sec [33,300..33,510] 🟢 +25.6%
reduce sum 50 elements 25,169 ops/sec [25,113..25,250] → 25,860 ops/sec [25,831..25,954] 🟢 +2.7% 32,223 ops/sec [32,152..32,265] → 34,512 ops/sec [34,321..34,672] 🟢 +7.1%
forEach over 50 elements 22,322 ops/sec [22,249..22,376] → 22,709 ops/sec [22,686..22,791] 🟢 +1.7% 33,709 ops/sec [33,601..33,787] → 35,612 ops/sec [35,348..35,760] 🟢 +5.6%
find in 50 elements 31,211 ops/sec [31,122..31,259] → 32,489 ops/sec [32,382..32,607] 🟢 +4.1% 48,784 ops/sec [48,539..48,830] → 52,362 ops/sec [52,315..52,426] 🟢 +7.3%
sort 20 elements 11,940 ops/sec [11,868..11,954] → 12,311 ops/sec [10,946..12,357] ~ overlap (+3.1%) 17,356 ops/sec [17,343..17,428] → 18,790 ops/sec [18,690..18,876] 🟢 +8.3%
flat nested array 114,866 ops/sec [114,833..115,202] → 117,567 ops/sec [117,090..117,973] 🟢 +2.4% 130,126 ops/sec [129,088..131,470] → 140,517 ops/sec [139,248..140,924] 🟢 +8.0%
flatMap 69,054 ops/sec [68,690..69,298] → 74,107 ops/sec [73,513..74,395] 🟢 +7.3% 88,334 ops/sec [87,503..89,505] → 95,720 ops/sec [93,934..96,677] 🟢 +8.4%
map inside map (5x5) 19,551 ops/sec [19,507..19,586] → 20,123 ops/sec [20,053..20,166] 🟢 +2.9% 24,942 ops/sec [23,212..25,176] → 27,337 ops/sec [27,039..27,474] 🟢 +9.6%
filter inside map (5x10) 14,743 ops/sec [14,656..14,795] → 15,217 ops/sec [15,161..15,248] 🟢 +3.2% 18,215 ops/sec [18,156..18,267] → 21,752 ops/sec [21,628..21,764] 🟢 +19.4%
reduce inside map (5x10) 18,183 ops/sec [18,067..18,204] → 18,679 ops/sec [18,628..18,804] 🟢 +2.7% 23,770 ops/sec [23,680..23,920] → 25,423 ops/sec [25,281..25,848] 🟢 +7.0%
forEach inside forEach (5x10) 16,303 ops/sec [16,186..16,336] → 16,194 ops/sec [16,087..16,324] ~ overlap (-0.7%) 26,015 ops/sec [25,739..26,078] → 27,654 ops/sec [27,568..27,747] 🟢 +6.3%
find inside some (10x10) 13,052 ops/sec [13,023..13,107] → 13,129 ops/sec [13,077..13,218] ~ overlap (+0.6%) 18,631 ops/sec [18,515..18,657] → 19,691 ops/sec [19,591..19,797] 🟢 +5.7%
map+filter chain nested (5x20) 4,991 ops/sec [4,970..5,004] → 5,098 ops/sec [5,074..5,120] 🟢 +2.2% 6,476 ops/sec [6,456..6,479] → 7,434 ops/sec [7,421..7,456] 🟢 +14.8%
reduce flatten (10x5) 39,029 ops/sec [38,978..39,184] → 38,636 ops/sec [38,448..38,742] 🔴 -1.0% 18,038 ops/sec [15,694..18,908] → 18,229 ops/sec [17,971..18,513] ~ overlap (+1.1%)
async-await.js — Interp: 🟢 1, 🔴 1, 4 unch. · avg -0.2% · Bytecode: 🟢 6 · avg +8.4%
Benchmark Interpreted Δ Bytecode Δ
single await 387,244 ops/sec [385,706..390,162] → 389,793 ops/sec [387,719..390,576] ~ overlap (+0.7%) 415,068 ops/sec [411,720..416,029] → 455,468 ops/sec [453,860..458,543] 🟢 +9.7%
multiple awaits 173,010 ops/sec [171,836..173,347] → 174,393 ops/sec [173,694..174,518] 🟢 +0.8% 176,999 ops/sec [176,537..177,768] → 194,916 ops/sec [193,840..196,631] 🟢 +10.1%
await non-Promise value 862,567 ops/sec [860,988..864,217] → 867,119 ops/sec [861,483..869,348] ~ overlap (+0.5%) 1,206,902 ops/sec [1,201,378..1,212,455] → 1,289,609 ops/sec [1,287,015..1,299,030] 🟢 +6.9%
await with try/catch 374,719 ops/sec [373,762..375,886] → 373,230 ops/sec [370,708..376,208] ~ overlap (-0.4%) 408,829 ops/sec [408,590..409,300] → 444,935 ops/sec [444,032..449,799] 🟢 +8.8%
await Promise.all 52,243 ops/sec [52,153..52,362] → 50,902 ops/sec [50,578..51,160] 🔴 -2.6% 51,420 ops/sec [51,187..51,931] → 55,436 ops/sec [55,183..55,648] 🟢 +7.8%
nested async function call 197,482 ops/sec [197,328..198,282] → 197,517 ops/sec [196,384..197,946] ~ overlap (+0.0%) 240,059 ops/sec [239,589..241,892] → 257,348 ops/sec [253,602..257,711] 🟢 +7.2%
base64.js — Interp: 🟢 6, 🔴 3, 1 unch. · avg +1.6% · Bytecode: 🟢 7, 3 unch. · avg +7.5%
Benchmark Interpreted Δ Bytecode Δ
short ASCII (13 chars) 513,945 ops/sec [506,271..521,883] → 507,393 ops/sec [505,173..508,838] ~ overlap (-1.3%) 631,669 ops/sec [628,566..635,499] → 707,312 ops/sec [703,394..707,819] 🟢 +12.0%
medium ASCII (450 chars) 148,602 ops/sec [148,195..148,956] → 151,744 ops/sec [151,482..151,909] 🟢 +2.1% 156,618 ops/sec [156,392..156,822] → 156,788 ops/sec [156,462..157,029] ~ overlap (+0.1%)
Latin-1 characters 545,291 ops/sec [538,561..558,380] → 531,425 ops/sec [530,028..532,964] 🔴 -2.5% 646,408 ops/sec [639,719..650,205] → 722,900 ops/sec [719,340..725,835] 🟢 +11.8%
short base64 (20 chars) 370,778 ops/sec [369,931..371,082] → 378,023 ops/sec [375,110..383,889] 🟢 +2.0% 438,562 ops/sec [437,671..441,067] → 481,082 ops/sec [480,081..481,507] 🟢 +9.7%
medium base64 (600 chars) 83,895 ops/sec [83,828..84,012] → 82,082 ops/sec [81,863..82,195] 🔴 -2.2% 87,286 ops/sec [86,177..88,027] → 88,254 ops/sec [87,484..88,377] ~ overlap (+1.1%)
Latin-1 output 385,605 ops/sec [381,326..387,689] → 403,305 ops/sec [399,026..406,340] 🟢 +4.6% 462,286 ops/sec [460,924..462,840] → 506,419 ops/sec [505,353..506,823] 🟢 +9.5%
forgiving (no padding) 409,002 ops/sec [401,580..415,707] → 436,716 ops/sec [428,628..440,063] 🟢 +6.8% 442,523 ops/sec [440,010..444,807] → 488,230 ops/sec [461,030..494,946] 🟢 +10.3%
with whitespace 382,013 ops/sec [380,428..385,806] → 396,978 ops/sec [392,399..401,874] 🟢 +3.9% 404,308 ops/sec [397,786..407,396] → 444,441 ops/sec [441,295..448,096] 🟢 +9.9%
atob(btoa(short)) 244,365 ops/sec [243,113..246,230] → 255,021 ops/sec [253,168..256,130] 🟢 +4.4% 272,162 ops/sec [270,586..272,687] → 301,284 ops/sec [300,569..302,334] 🟢 +10.7%
atob(btoa(medium)) 55,528 ops/sec [55,334..55,637] → 54,511 ops/sec [54,328..54,693] 🔴 -1.8% 56,498 ops/sec [56,268..56,619] → 56,500 ops/sec [56,058..56,663] ~ overlap (+0.0%)
classes.js — Interp: 🟢 8, 🔴 4, 19 unch. · avg +0.4% · Bytecode: 🟢 9, 🔴 4, 18 unch. · avg +1.9%
Benchmark Interpreted Δ Bytecode Δ
simple class new 141,575 ops/sec [141,168..142,105] → 140,931 ops/sec [139,873..142,279] ~ overlap (-0.5%) 212,906 ops/sec [211,059..215,120] → 224,451 ops/sec [222,835..229,271] 🟢 +5.4%
class with defaults 112,236 ops/sec [111,261..113,184] → 112,768 ops/sec [112,291..112,954] ~ overlap (+0.5%) 143,692 ops/sec [142,606..145,868] → 151,286 ops/sec [146,736..152,377] 🟢 +5.3%
50 instances via Array.from 5,730 ops/sec [5,716..5,767] → 5,707 ops/sec [5,664..5,729] ~ overlap (-0.4%) 8,795 ops/sec [8,729..8,869] → 9,288 ops/sec [9,203..9,340] 🟢 +5.6%
instance method call 71,233 ops/sec [71,124..71,261] → 71,785 ops/sec [71,173..72,044] ~ overlap (+0.8%) 102,259 ops/sec [101,163..103,749] → 109,548 ops/sec [108,476..109,875] 🟢 +7.1%
static method call 113,977 ops/sec [113,858..114,701] → 114,351 ops/sec [113,916..114,530] ~ overlap (+0.3%) 198,632 ops/sec [196,241..199,650] → 208,888 ops/sec [206,852..211,669] 🟢 +5.2%
single-level inheritance 56,651 ops/sec [56,463..56,689] → 56,358 ops/sec [56,207..56,600] ~ overlap (-0.5%) 74,918 ops/sec [74,194..75,281] → 78,332 ops/sec [76,529..79,512] 🟢 +4.6%
two-level inheritance 48,846 ops/sec [48,686..48,959] → 48,458 ops/sec [48,384..48,734] ~ overlap (-0.8%) 60,726 ops/sec [60,123..61,751] → 63,149 ops/sec [60,218..65,069] ~ overlap (+4.0%)
private field access 71,351 ops/sec [71,270..71,549] → 72,317 ops/sec [72,063..72,640] 🟢 +1.4% 99,208 ops/sec [96,823..100,078] → 104,977 ops/sec [103,602..106,883] 🟢 +5.8%
private methods 80,627 ops/sec [80,257..80,876] → 79,396 ops/sec [79,189..79,687] 🔴 -1.5% 103,905 ops/sec [102,877..104,820] → 106,358 ops/sec [105,397..106,554] 🟢 +2.4%
getter/setter access 76,193 ops/sec [76,052..76,352] → 77,245 ops/sec [76,927..77,301] 🟢 +1.4% 110,098 ops/sec [109,801..110,920] → 116,988 ops/sec [115,418..117,490] 🟢 +6.3%
class decorator (identity) 93,678 ops/sec [92,630..94,454] → 96,192 ops/sec [94,986..97,169] 🟢 +2.7% 108,050 ops/sec [107,246..109,445] → 108,265 ops/sec [106,537..112,001] ~ overlap (+0.2%)
class decorator (wrapping) 55,712 ops/sec [55,422..56,031] → 56,048 ops/sec [55,641..56,475] ~ overlap (+0.6%) 62,217 ops/sec [60,820..64,009] → 61,507 ops/sec [59,098..64,656] ~ overlap (-1.1%)
identity method decorator 66,119 ops/sec [65,879..66,392] → 66,252 ops/sec [65,963..67,013] ~ overlap (+0.2%) 87,013 ops/sec [83,118..93,150] → 92,423 ops/sec [82,614..100,558] ~ overlap (+6.2%)
wrapping method decorator 53,418 ops/sec [53,003..53,601] → 52,955 ops/sec [52,597..53,066] ~ overlap (-0.9%) 65,341 ops/sec [61,804..71,029] → 69,675 ops/sec [61,120..74,711] ~ overlap (+6.6%)
stacked method decorators (x3) 35,819 ops/sec [35,640..35,911] → 35,939 ops/sec [35,696..36,124] ~ overlap (+0.3%) 40,560 ops/sec [39,241..45,219] → 41,540 ops/sec [40,329..46,388] ~ overlap (+2.4%)
identity field decorator 73,447 ops/sec [72,907..74,210] → 73,539 ops/sec [72,934..74,351] ~ overlap (+0.1%) 72,347 ops/sec [70,220..77,701] → 72,488 ops/sec [68,656..76,341] ~ overlap (+0.2%)
field initializer decorator 61,117 ops/sec [60,858..61,256] → 61,136 ops/sec [60,539..61,332] ~ overlap (+0.0%) 61,666 ops/sec [60,658..66,653] → 63,315 ops/sec [60,855..69,090] ~ overlap (+2.7%)
getter decorator (identity) 66,171 ops/sec [65,598..66,584] → 66,690 ops/sec [66,558..66,701] ~ overlap (+0.8%) 73,935 ops/sec [73,251..75,004] → 71,856 ops/sec [70,507..73,097] 🔴 -2.8%
setter decorator (identity) 55,618 ops/sec [55,470..55,672] → 56,237 ops/sec [55,872..56,487] 🟢 +1.1% 59,439 ops/sec [59,203..60,077] → 59,810 ops/sec [58,732..61,885] ~ overlap (+0.6%)
static method decorator 72,296 ops/sec [72,054..72,603] → 71,984 ops/sec [71,708..72,489] ~ overlap (-0.4%) 80,294 ops/sec [76,372..82,598] → 79,024 ops/sec [76,735..81,865] ~ overlap (-1.6%)
static field decorator 85,870 ops/sec [85,449..86,347] → 84,261 ops/sec [84,089..84,674] 🔴 -1.9% 83,045 ops/sec [79,904..87,944] → 96,561 ops/sec [84,979..103,324] ~ overlap (+16.3%)
private method decorator 54,321 ops/sec [54,225..54,543] → 54,136 ops/sec [53,886..54,342] ~ overlap (-0.3%) 69,194 ops/sec [66,283..72,000] → 67,614 ops/sec [66,013..70,889] ~ overlap (-2.3%)
private field decorator 59,194 ops/sec [59,041..59,294] → 60,779 ops/sec [60,590..61,272] 🟢 +2.7% 66,238 ops/sec [61,195..70,514] → 58,331 ops/sec [52,904..61,868] ~ overlap (-11.9%)
plain auto-accessor (no decorator) 103,737 ops/sec [101,506..106,324] → 106,823 ops/sec [104,817..109,503] ~ overlap (+3.0%) 90,077 ops/sec [87,091..93,501] → 104,249 ops/sec [92,511..116,465] ~ overlap (+15.7%)
auto-accessor with decorator 55,801 ops/sec [55,016..56,964] → 55,858 ops/sec [55,100..57,034] ~ overlap (+0.1%) 58,821 ops/sec [54,235..66,153] → 53,536 ops/sec [49,586..57,321] ~ overlap (-9.0%)
decorator writing metadata 46,003 ops/sec [45,931..46,190] → 44,740 ops/sec [44,631..45,024] 🔴 -2.7% 45,827 ops/sec [45,054..49,242] → 46,656 ops/sec [45,185..51,301] ~ overlap (+1.8%)
static getter read 125,236 ops/sec [124,175..125,330] → 128,586 ops/sec [128,282..128,779] 🟢 +2.7% 168,841 ops/sec [166,167..170,311] → 159,325 ops/sec [152,782..162,633] 🔴 -5.6%
static getter/setter pair 99,348 ops/sec [99,176..99,600] → 98,977 ops/sec [98,784..99,128] 🔴 -0.4% 119,978 ops/sec [116,982..123,475] → 125,073 ops/sec [121,801..136,018] ~ overlap (+4.2%)
inherited static getter 77,213 ops/sec [76,853..77,326] → 76,935 ops/sec [76,735..77,492] ~ overlap (-0.4%) 93,863 ops/sec [92,997..95,960] → 87,808 ops/sec [87,237..89,215] 🔴 -6.5%
inherited static setter 82,127 ops/sec [81,996..82,283] → 83,641 ops/sec [83,575..83,707] 🟢 +1.8% 100,035 ops/sec [96,794..100,650] → 96,364 ops/sec [95,880..102,397] ~ overlap (-3.7%)
inherited static getter with this binding 70,366 ops/sec [70,177..70,557] → 71,852 ops/sec [71,536..72,177] 🟢 +2.1% 91,177 ops/sec [90,015..91,938] → 85,369 ops/sec [84,795..86,382] 🔴 -6.4%
closures.js — Interp: 🟢 2, 🔴 3, 6 unch. · avg +0.3% · Bytecode: 🟢 2, 🔴 4, 5 unch. · avg -1.4%
Benchmark Interpreted Δ Bytecode Δ
closure over single variable 135,183 ops/sec [133,414..135,938] → 132,476 ops/sec [130,886..133,667] ~ overlap (-2.0%) 337,973 ops/sec [336,993..340,428] → 320,971 ops/sec [312,939..325,276] 🔴 -5.0%
closure over multiple variables 119,400 ops/sec [118,970..119,872] → 118,718 ops/sec [116,806..119,236] ~ overlap (-0.6%) 330,549 ops/sec [328,067..332,323] → 322,872 ops/sec [311,368..325,326] 🔴 -2.3%
nested closures 126,589 ops/sec [126,197..126,777] → 127,267 ops/sec [126,484..127,516] ~ overlap (+0.5%) 302,013 ops/sec [286,546..311,362] → 280,432 ops/sec [275,666..288,988] ~ overlap (-7.1%)
function as argument 93,369 ops/sec [93,098..93,821] → 92,401 ops/sec [92,086..92,706] 🔴 -1.0% 326,104 ops/sec [321,735..332,254] → 330,482 ops/sec [327,005..331,866] ~ overlap (+1.3%)
function returning function 117,321 ops/sec [114,290..117,854] → 118,118 ops/sec [117,612..119,592] ~ overlap (+0.7%) 349,462 ops/sec [342,978..367,313] → 361,192 ops/sec [353,007..376,162] ~ overlap (+3.4%)
compose two functions 70,512 ops/sec [70,462..70,573] → 71,850 ops/sec [71,637..72,074] 🟢 +1.9% 198,081 ops/sec [194,110..207,765] → 186,442 ops/sec [182,401..197,786] ~ overlap (-5.9%)
fn.call 149,200 ops/sec [148,586..149,734] → 150,824 ops/sec [149,580..153,744] ~ overlap (+1.1%) 236,016 ops/sec [231,926..236,567] → 230,390 ops/sec [227,546..231,197] 🔴 -2.4%
fn.apply 105,557 ops/sec [105,025..105,736] → 113,914 ops/sec [112,852..114,915] 🟢 +7.9% 207,127 ops/sec [205,144..209,955] → 199,424 ops/sec [198,405..200,841] 🔴 -3.7%
fn.bind 142,325 ops/sec [140,819..142,932] → 142,539 ops/sec [142,191..143,118] ~ overlap (+0.2%) 378,124 ops/sec [371,843..386,804] → 372,308 ops/sec [368,270..375,477] ~ overlap (-1.5%)
recursive sum to 50 12,213 ops/sec [12,117..12,261] → 11,761 ops/sec [11,694..11,821] 🔴 -3.7% 50,004 ops/sec [49,278..50,247] → 51,587 ops/sec [51,241..52,600] 🟢 +3.2%
recursive tree traversal 20,769 ops/sec [20,733..20,889] → 20,483 ops/sec [20,366..20,554] 🔴 -1.4% 44,825 ops/sec [44,402..45,220] → 46,774 ops/sec [46,176..47,705] 🟢 +4.3%
collections.js — Interp: 🟢 5, 🔴 5, 2 unch. · avg -0.2% · Bytecode: 🟢 9, 🔴 2, 1 unch. · avg +3.1%
Benchmark Interpreted Δ Bytecode Δ
add 50 elements 7,014 ops/sec [6,963..7,057] → 6,852 ops/sec [6,822..6,887] 🔴 -2.3% 7,434 ops/sec [7,395..7,471] → 7,740 ops/sec [7,686..7,758] 🟢 +4.1%
has lookup (50 elements) 88,807 ops/sec [88,378..88,968] → 88,662 ops/sec [88,385..89,034] ~ overlap (-0.2%) 96,023 ops/sec [95,972..96,138] → 103,582 ops/sec [103,159..103,629] 🟢 +7.9%
delete elements 46,794 ops/sec [46,535..46,983] → 45,961 ops/sec [45,462..46,606] ~ overlap (-1.8%) 47,936 ops/sec [47,539..48,073] → 51,302 ops/sec [51,057..51,699] 🟢 +7.0%
forEach iteration 15,461 ops/sec [15,407..15,556] → 15,732 ops/sec [15,680..15,790] 🟢 +1.8% 21,703 ops/sec [21,546..21,728] → 23,697 ops/sec [23,607..23,787] 🟢 +9.2%
spread to array 29,623 ops/sec [29,438..29,736] → 29,141 ops/sec [28,840..29,436] 🔴 -1.6% 222,766 ops/sec [221,123..224,850] → 212,012 ops/sec [208,598..214,105] 🔴 -4.8%
deduplicate array 38,977 ops/sec [38,109..39,211] → 37,631 ops/sec [37,218..37,865] 🔴 -3.5% 70,153 ops/sec [69,288..71,529] → 73,060 ops/sec [72,503..73,434] 🟢 +4.1%
set 50 entries 5,366 ops/sec [5,349..5,400] → 5,047 ops/sec [5,014..5,058] 🔴 -5.9% 5,924 ops/sec [5,906..5,955] → 5,899 ops/sec [5,735..5,978] ~ overlap (-0.4%)
get lookup (50 entries) 84,137 ops/sec [83,840..84,263] → 87,216 ops/sec [86,405..87,684] 🟢 +3.7% 90,020 ops/sec [89,833..90,131] → 94,441 ops/sec [94,133..94,612] 🟢 +4.9%
has check 125,276 ops/sec [124,794..125,577] → 132,036 ops/sec [131,539..132,301] 🟢 +5.4% 137,316 ops/sec [137,066..137,661] → 147,013 ops/sec [146,297..147,316] 🟢 +7.1%
delete entries 46,221 ops/sec [46,111..46,324] → 47,281 ops/sec [46,974..47,377] 🟢 +2.3% 47,110 ops/sec [47,009..47,179] → 48,857 ops/sec [48,667..48,961] 🟢 +3.7%
forEach iteration 15,436 ops/sec [15,362..15,557] → 15,657 ops/sec [15,569..15,735] 🟢 +1.4% 22,216 ops/sec [22,147..22,305] → 23,784 ops/sec [23,540..23,895] 🟢 +7.1%
keys/values/entries 8,155 ops/sec [8,124..8,203] → 7,987 ops/sec [7,955..8,081] 🔴 -2.1% 27,482 ops/sec [27,413..27,960] → 24,001 ops/sec [23,905..24,163] 🔴 -12.7%
destructuring.js — Interp: 🟢 12, 🔴 5, 5 unch. · avg +1.0% · Bytecode: 🟢 1, 🔴 15, 6 unch. · avg -5.2%
Benchmark Interpreted Δ Bytecode Δ
simple array destructuring 384,385 ops/sec [384,009..386,137] → 386,357 ops/sec [377,985..387,982] ~ overlap (+0.5%) 282,963 ops/sec [279,948..287,530] → 250,394 ops/sec [249,269..254,070] 🔴 -11.5%
with rest element 250,489 ops/sec [249,551..251,291] → 252,302 ops/sec [250,513..253,152] ~ overlap (+0.7%) 206,905 ops/sec [204,424..208,277] → 198,256 ops/sec [197,166..199,896] 🔴 -4.2%
with defaults 403,749 ops/sec [402,212..403,886] → 389,770 ops/sec [388,533..392,867] 🔴 -3.5% 321,284 ops/sec [303,220..324,913] → 292,839 ops/sec [290,449..300,337] 🔴 -8.9%
skip elements 405,783 ops/sec [403,664..406,642] → 405,729 ops/sec [404,057..406,803] ~ overlap (-0.0%) 294,803 ops/sec [290,040..296,855] → 281,632 ops/sec [279,181..283,363] 🔴 -4.5%
nested array destructuring 174,538 ops/sec [173,720..174,950] → 178,143 ops/sec [177,268..178,967] 🟢 +2.1% 93,561 ops/sec [91,247..94,170] → 80,507 ops/sec [79,982..81,138] 🔴 -14.0%
swap variables 513,975 ops/sec [512,597..514,820] → 511,696 ops/sec [510,082..513,202] ~ overlap (-0.4%) 356,032 ops/sec [354,980..357,447] → 332,985 ops/sec [327,496..339,438] 🔴 -6.5%
simple object destructuring 308,822 ops/sec [308,632..309,985] → 318,682 ops/sec [312,222..321,987] 🟢 +3.2% 411,435 ops/sec [397,503..413,451] → 404,242 ops/sec [400,591..407,241] ~ overlap (-1.7%)
with defaults 353,754 ops/sec [353,053..355,643] → 351,473 ops/sec [349,545..352,293] 🔴 -0.6% 567,646 ops/sec [563,226..573,177] → 564,068 ops/sec [559,829..579,455] ~ overlap (-0.6%)
with renaming 325,247 ops/sec [324,320..331,150] → 331,727 ops/sec [331,343..332,914] 🟢 +2.0% 414,943 ops/sec [411,547..417,368] → 403,033 ops/sec [351,384..412,491] ~ overlap (-2.9%)
nested object destructuring 161,021 ops/sec [160,284..161,077] → 151,483 ops/sec [150,085..151,944] 🔴 -5.9% 190,297 ops/sec [188,101..191,438] → 175,832 ops/sec [172,246..180,113] 🔴 -7.6%
rest properties 178,308 ops/sec [176,488..180,722] → 185,457 ops/sec [183,269..186,419] 🟢 +4.0% 178,199 ops/sec [177,079..179,477] → 169,437 ops/sec [168,832..169,911] 🔴 -4.9%
object parameter 96,589 ops/sec [95,135..97,256] → 95,345 ops/sec [93,488..96,296] ~ overlap (-1.3%) 156,930 ops/sec [156,065..158,623] → 145,711 ops/sec [144,834..146,291] 🔴 -7.1%
array parameter 115,948 ops/sec [115,590..116,136] → 121,109 ops/sec [120,277..121,533] 🟢 +4.5% 123,227 ops/sec [117,616..129,752] → 117,788 ops/sec [116,672..118,576] ~ overlap (-4.4%)
mixed destructuring in map 32,629 ops/sec [32,413..32,745] → 32,982 ops/sec [32,814..33,139] 🟢 +1.1% 47,301 ops/sec [46,685..48,903] → 47,381 ops/sec [46,576..47,693] ~ overlap (+0.2%)
forEach with array destructuring 63,795 ops/sec [63,537..63,890] → 62,281 ops/sec [61,996..62,467] 🔴 -2.4% 48,651 ops/sec [48,212..49,517] → 45,114 ops/sec [44,886..46,412] 🔴 -7.3%
map with array destructuring 60,523 ops/sec [59,909..60,786] → 62,345 ops/sec [62,173..62,390] 🟢 +3.0% 46,775 ops/sec [46,159..47,523] → 43,091 ops/sec [41,954..43,507] 🔴 -7.9%
filter with array destructuring 62,595 ops/sec [62,397..62,946] → 65,207 ops/sec [64,983..65,879] 🟢 +4.2% 48,538 ops/sec [48,039..49,236] → 45,501 ops/sec [45,208..46,259] 🔴 -6.3%
reduce with array destructuring 72,434 ops/sec [72,200..73,087] → 71,127 ops/sec [70,885..71,586] 🔴 -1.8% 51,993 ops/sec [50,703..52,936] → 47,561 ops/sec [47,255..48,027] 🔴 -8.5%
map with object destructuring 70,910 ops/sec [70,370..71,293] → 73,096 ops/sec [72,887..73,424] 🟢 +3.1% 101,140 ops/sec [99,760..101,493] → 99,158 ops/sec [98,755..99,712] 🔴 -2.0%
map with nested destructuring 59,791 ops/sec [59,307..60,086] → 60,654 ops/sec [60,425..60,924] 🟢 +1.4% 95,026 ops/sec [91,435..95,573] → 96,070 ops/sec [94,785..97,592] ~ overlap (+1.1%)
map with rest in destructuring 39,290 ops/sec [38,812..39,484] → 41,702 ops/sec [41,318..41,995] 🟢 +6.1% 25,818 ops/sec [25,638..26,338] → 24,408 ops/sec [24,150..24,595] 🔴 -5.5%
map with defaults in destructuring 54,812 ops/sec [54,760..54,833] → 56,300 ops/sec [56,081..56,436] 🟢 +2.7% 80,929 ops/sec [79,248..81,148] → 82,054 ops/sec [81,347..83,265] 🟢 +1.4%
fibonacci.js — Interp: 🔴 6, 2 unch. · avg -2.6% · Bytecode: 🟢 4, 🔴 2, 2 unch. · avg +2.4%
Benchmark Interpreted Δ Bytecode Δ
recursive fib(15) 330 ops/sec [328..332] → 315 ops/sec [313..315] 🔴 -4.6% 1,420 ops/sec [1,420..1,422] → 1,571 ops/sec [1,567..1,577] 🟢 +10.6%
recursive fib(20) 29 ops/sec [29..29] → 29 ops/sec [28..29] 🔴 -1.7% 129 ops/sec [128..129] → 141 ops/sec [141..142] 🟢 +9.7%
recursive fib(15) typed 335 ops/sec [333..337] → 315 ops/sec [314..318] 🔴 -6.0% 1,145 ops/sec [1,140..1,155] → 1,194 ops/sec [1,182..1,204] 🟢 +4.3%
recursive fib(20) typed 29 ops/sec [29..29] → 29 ops/sec [28..29] 🔴 -1.7% 104 ops/sec [103..105] → 113 ops/sec [113..113] 🟢 +8.6%
iterative fib(20) via reduce 12,376 ops/sec [12,298..12,434] → 12,009 ops/sec [11,951..12,073] 🔴 -3.0% 21,712 ops/sec [21,699..21,854] → 19,827 ops/sec [19,697..20,105] 🔴 -8.7%
iterator fib(20) 9,483 ops/sec [9,451..9,509] → 9,434 ops/sec [9,362..9,487] ~ overlap (-0.5%) 19,556 ops/sec [19,447..19,680] → 18,959 ops/sec [18,689..19,450] ~ overlap (-3.1%)
iterator fib(20) via Iterator.from + take 15,934 ops/sec [15,865..15,962] → 15,453 ops/sec [15,356..15,516] 🔴 -3.0% 20,942 ops/sec [20,638..21,646] → 21,068 ops/sec [20,933..21,480] ~ overlap (+0.6%)
iterator fib(20) last value via reduce 11,801 ops/sec [11,625..11,940] → 11,760 ops/sec [11,677..11,791] ~ overlap (-0.3%) 15,861 ops/sec [15,657..15,977] → 15,424 ops/sec [15,335..15,575] 🔴 -2.8%
float16array.js — Interp: 🟢 3, 🔴 13, 16 unch. · avg -1.3% · Bytecode: 🟢 8, 🔴 17, 7 unch. · avg -6.2%
Benchmark Interpreted Δ Bytecode Δ
new Float16Array(0) 341,055 ops/sec [340,287..342,013] → 336,899 ops/sec [336,527..337,225] 🔴 -1.2% 373,176 ops/sec [367,203..381,754] → 381,229 ops/sec [378,276..384,328] ~ overlap (+2.2%)
new Float16Array(100) 320,404 ops/sec [318,536..321,040] → 313,233 ops/sec [311,324..314,168] 🔴 -2.2% 359,019 ops/sec [357,108..361,567] → 352,482 ops/sec [350,117..354,892] 🔴 -1.8%
new Float16Array(1000) 236,395 ops/sec [235,655..236,869] → 234,128 ops/sec [232,009..236,160] ~ overlap (-1.0%) 257,224 ops/sec [255,913..258,257] → 237,786 ops/sec [232,745..240,210] 🔴 -7.6%
Float16Array.from([...100]) 150,154 ops/sec [147,693..152,897] → 151,913 ops/sec [148,181..156,044] ~ overlap (+1.2%) 156,627 ops/sec [153,568..158,023] → 173,992 ops/sec [170,225..175,198] 🟢 +11.1%
Float16Array.of(1.5, 2.5, 3.5, 4.5, 5.5) 300,342 ops/sec [299,801..301,023] → 297,209 ops/sec [296,618..300,254] ~ overlap (-1.0%) 247,281 ops/sec [246,009..248,155] → 234,902 ops/sec [233,433..237,248] 🔴 -5.0%
new Float16Array(float64Array) 204,886 ops/sec [203,833..205,900] → 201,562 ops/sec [200,609..202,302] 🔴 -1.6% 223,448 ops/sec [221,118..224,256] → 218,053 ops/sec [217,347..220,139] 🔴 -2.4%
sequential write 100 elements 3,285 ops/sec [3,248..3,305] → 3,208 ops/sec [3,190..3,226] 🔴 -2.3% 11,186 ops/sec [10,835..11,320] → 8,382 ops/sec [8,353..8,545] 🔴 -25.1%
sequential read 100 elements 3,578 ops/sec [3,572..3,592] → 3,537 ops/sec [3,500..3,574] ~ overlap (-1.2%) 14,454 ops/sec [14,256..14,590] → 14,356 ops/sec [14,196..14,452] ~ overlap (-0.7%)
write special values (NaN, Inf, -0) 200,721 ops/sec [200,330..200,918] → 182,989 ops/sec [173,534..184,502] 🔴 -8.8% 329,682 ops/sec [328,977..331,611] → 347,900 ops/sec [345,217..350,002] 🟢 +5.5%
Float16Array write 3,273 ops/sec [3,256..3,289] → 3,254 ops/sec [3,225..3,281] ~ overlap (-0.6%) 11,227 ops/sec [11,043..11,499] → 8,450 ops/sec [8,397..8,519] 🔴 -24.7%
Float32Array write 3,317 ops/sec [3,295..3,358] → 3,258 ops/sec [3,243..3,284] 🔴 -1.8% 11,282 ops/sec [10,943..11,374] → 8,488 ops/sec [8,439..8,529] 🔴 -24.8%
Float64Array write 3,318 ops/sec [3,307..3,350] → 3,240 ops/sec [3,215..3,303] 🔴 -2.3% 11,310 ops/sec [11,111..11,385] → 8,496 ops/sec [8,411..8,592] 🔴 -24.9%
Float16Array read 3,438 ops/sec [3,422..3,456] → 3,399 ops/sec [3,354..3,431] ~ overlap (-1.1%) 13,545 ops/sec [13,489..13,647] → 13,245 ops/sec [13,179..13,298] 🔴 -2.2%
Float32Array read 3,508 ops/sec [3,484..3,528] → 3,486 ops/sec [3,464..3,521] ~ overlap (-0.6%) 15,116 ops/sec [14,820..15,335] → 15,295 ops/sec [15,075..15,392] ~ overlap (+1.2%)
Float64Array read 3,523 ops/sec [3,502..3,548] → 3,465 ops/sec [3,443..3,508] ~ overlap (-1.6%) 14,815 ops/sec [14,626..14,884] → 15,123 ops/sec [15,092..15,204] 🟢 +2.1%
fill(1.5) 39,881 ops/sec [39,846..39,946] → 40,178 ops/sec [40,147..40,230] 🟢 +0.7% 39,913 ops/sec [39,822..39,966] → 39,395 ops/sec [38,936..39,867] ~ overlap (-1.3%)
slice() 142,736 ops/sec [141,789..143,399] → 139,958 ops/sec [139,477..140,196] 🔴 -1.9% 150,577 ops/sec [150,307..151,202] → 137,665 ops/sec [135,910..137,940] 🔴 -8.6%
map(x => x * 2) 7,231 ops/sec [7,198..7,377] → 7,169 ops/sec [7,127..7,262] ~ overlap (-0.9%) 9,450 ops/sec [9,258..9,558] → 9,332 ops/sec [9,278..9,361] ~ overlap (-1.2%)
filter(x => x > 25) 7,861 ops/sec [7,724..7,900] → 7,439 ops/sec [7,407..7,462] 🔴 -5.4% 10,132 ops/sec [9,969..10,200] → 10,135 ops/sec [10,091..10,211] ~ overlap (+0.0%)
reduce (sum) 7,475 ops/sec [7,445..7,511] → 7,312 ops/sec [7,248..7,428] 🔴 -2.2% 8,426 ops/sec [8,394..8,479] → 8,630 ops/sec [8,553..8,659] 🟢 +2.4%
sort() 27,710 ops/sec [27,687..27,730] → 27,654 ops/sec [27,626..27,693] ~ overlap (-0.2%) 27,886 ops/sec [27,858..27,905] → 21,045 ops/sec [21,007..21,072] 🔴 -24.5%
indexOf() 181,510 ops/sec [180,063..181,871] → 176,162 ops/sec [175,790..176,506] 🔴 -2.9% 193,722 ops/sec [193,654..193,763] → 151,106 ops/sec [150,207..151,288] 🔴 -22.0%
reverse() 187,643 ops/sec [187,314..187,946] → 192,604 ops/sec [192,274..192,849] 🟢 +2.6% 198,878 ops/sec [197,855..199,068] → 181,120 ops/sec [180,817..181,216] 🔴 -8.9%
toReversed() 77,722 ops/sec [77,522..77,947] → 75,405 ops/sec [74,620..75,830] 🔴 -3.0% 82,262 ops/sec [82,175..82,322] → 64,947 ops/sec [64,549..65,149] 🔴 -21.0%
toSorted() 989 ops/sec [987..989] → 1,017 ops/sec [1,015..1,019] 🟢 +2.9% 989 ops/sec [988..989] → 754 ops/sec [752..757] 🔴 -23.8%
create view over existing buffer 405,755 ops/sec [403,234..410,220] → 399,827 ops/sec [395,015..402,600] 🔴 -1.5% 470,894 ops/sec [466,322..473,710] → 475,971 ops/sec [474,181..478,211] 🟢 +1.1%
subarray() 425,898 ops/sec [422,792..427,414] → 430,917 ops/sec [424,692..433,943] ~ overlap (+1.2%) 583,703 ops/sec [578,262..585,578] → 577,722 ops/sec [574,151..582,436] ~ overlap (-1.0%)
set() from array 542,590 ops/sec [538,966..544,057] → 540,053 ops/sec [537,933..544,066] ~ overlap (-0.5%) 626,129 ops/sec [624,206..628,969] → 692,321 ops/sec [690,181..692,826] 🟢 +10.6%
for-of loop 4,845 ops/sec [4,803..4,878] → 4,819 ops/sec [4,744..4,859] ~ overlap (-0.5%) 20,419 ops/sec [20,324..20,568] → 18,900 ops/sec [18,704..18,989] 🔴 -7.4%
spread into array 16,660 ops/sec [16,470..16,885] → 16,481 ops/sec [16,242..16,561] ~ overlap (-1.1%) 96,334 ops/sec [95,420..98,283] → 87,543 ops/sec [87,072..89,611] 🔴 -9.1%
f16round(1.337) 700,050 ops/sec [693,042..703,304] → 699,610 ops/sec [695,163..708,481] ~ overlap (-0.1%) 703,879 ops/sec [666,189..705,590] → 777,794 ops/sec [775,537..777,950] 🟢 +10.5%
f16round over 100 values 3,526 ops/sec [3,470..3,571] → 3,485 ops/sec [3,392..3,506] ~ overlap (-1.2%) 7,993 ops/sec [7,974..8,004] → 8,163 ops/sec [8,125..8,227] 🟢 +2.1%
for-of.js — Interp: 🟢 2, 🔴 3, 2 unch. · avg +0.1% · Bytecode: 🟢 2, 🔴 2, 3 unch. · avg -1.5%
Benchmark Interpreted Δ Bytecode Δ
for...of with 10-element array 45,783 ops/sec [45,603..46,062] → 44,405 ops/sec [42,601..44,612] 🔴 -3.0% 294,983 ops/sec [289,090..296,947] → 298,265 ops/sec [297,049..303,336] 🟢 +1.1%
for...of with 100-element array 5,224 ops/sec [5,189..5,262] → 5,109 ops/sec [5,073..5,167] 🔴 -2.2% 36,737 ops/sec [36,243..36,764] → 36,283 ops/sec [35,896..36,863] ~ overlap (-1.2%)
for...of with string (10 chars) 33,073 ops/sec [32,678..33,271] → 33,060 ops/sec [32,497..33,350] ~ overlap (-0.0%) 86,573 ops/sec [85,624..87,195] → 89,132 ops/sec [88,451..89,955] 🟢 +3.0%
for...of with Set (10 elements) 45,108 ops/sec [44,633..45,258] → 44,922 ops/sec [44,850..45,045] ~ overlap (-0.4%) 266,322 ops/sec [259,023..269,487] → 262,084 ops/sec [260,284..265,338] ~ overlap (-1.6%)
for...of with Map entries (10 entries) 27,700 ops/sec [27,548..27,860] → 28,455 ops/sec [28,171..28,641] 🟢 +2.7% 34,047 ops/sec [33,834..34,394] → 31,227 ops/sec [31,013..31,400] 🔴 -8.3%
for...of with destructuring 39,061 ops/sec [38,724..39,261] → 38,342 ops/sec [38,131..38,551] 🔴 -1.8% 46,833 ops/sec [46,280..47,527] → 44,244 ops/sec [43,889..44,614] 🔴 -5.5%
for-await-of with sync array 41,280 ops/sec [40,958..41,617] → 43,459 ops/sec [43,091..43,694] 🟢 +5.3% 181,300 ops/sec [179,026..184,088] → 185,139 ops/sec [183,924..185,719] ~ overlap (+2.1%)
helpers/bench-module.js — Interp: 0 · Bytecode: 0
Benchmark Interpreted Δ Bytecode Δ
iterators.js — Interp: 🟢 2, 🔴 27, 13 unch. · avg -3.0% · Bytecode: 🟢 11, 🔴 20, 11 unch. · avg -1.4%
Benchmark Interpreted Δ Bytecode Δ
Iterator.from({next}).toArray() — 20 elements 15,448 ops/sec [15,410..15,480] → 15,408 ops/sec [15,238..15,494] ~ overlap (-0.3%) 20,928 ops/sec [20,012..21,187] → 21,377 ops/sec [21,118..21,427] ~ overlap (+2.1%)
Iterator.from({next}).toArray() — 50 elements 6,743 ops/sec [6,655..6,811] → 6,683 ops/sec [6,639..6,754] ~ overlap (-0.9%) 9,830 ops/sec [9,784..9,961] → 9,670 ops/sec [9,514..9,706] 🔴 -1.6%
spread pre-wrapped iterator — 20 elements 11,176 ops/sec [11,126..11,240] → 11,281 ops/sec [11,247..11,332] 🟢 +0.9% 20,697 ops/sec [20,695..20,731] → 22,387 ops/sec [22,111..22,581] 🟢 +8.2%
Iterator.from({next}).forEach — 50 elements 4,551 ops/sec [4,536..4,553] → 4,563 ops/sec [4,512..4,579] ~ overlap (+0.3%) 6,714 ops/sec [6,616..6,752] → 6,852 ops/sec [6,806..6,915] 🟢 +2.1%
Iterator.from({next}).reduce — 50 elements 4,601 ops/sec [4,588..4,643] → 4,595 ops/sec [4,566..4,633] ~ overlap (-0.1%) 6,475 ops/sec [6,374..6,540] → 6,781 ops/sec [6,744..6,809] 🟢 +4.7%
wrap array iterator 152,579 ops/sec [151,904..152,807] → 162,615 ops/sec [162,140..163,119] 🟢 +6.6% 185,930 ops/sec [184,415..187,851] → 176,819 ops/sec [176,151..179,074] 🔴 -4.9%
wrap plain {next()} object 10,748 ops/sec [10,678..10,834] → 10,628 ops/sec [10,528..10,680] ~ overlap (-1.1%) 14,539 ops/sec [14,379..14,730] → 15,319 ops/sec [14,861..15,439] 🟢 +5.4%
map + toArray (50 elements) 4,661 ops/sec [4,596..4,676] → 4,608 ops/sec [4,576..4,616] ~ overlap (-1.1%) 6,775 ops/sec [6,710..6,816] → 6,847 ops/sec [6,816..6,882] ~ overlap (+1.1%)
filter + toArray (50 elements) 4,534 ops/sec [4,468..4,552] → 4,518 ops/sec [4,486..4,536] ~ overlap (-0.4%) 6,222 ops/sec [5,851..6,336] → 6,558 ops/sec [6,521..6,601] 🟢 +5.4%
take(10) + toArray (50 element source) 28,090 ops/sec [27,851..28,383] → 27,989 ops/sec [27,904..28,065] ~ overlap (-0.4%) 38,727 ops/sec [38,267..38,957] → 38,901 ops/sec [38,465..39,540] ~ overlap (+0.4%)
drop(40) + toArray (50 element source) 6,695 ops/sec [6,594..6,738] → 6,603 ops/sec [6,543..6,632] ~ overlap (-1.4%) 9,272 ops/sec [9,191..9,325] → 9,160 ops/sec [8,988..9,294] ~ overlap (-1.2%)
chained map + filter + take (100 element source) 8,347 ops/sec [8,241..8,375] → 8,029 ops/sec [7,945..8,063] 🔴 -3.8% 11,421 ops/sec [11,398..11,461] → 11,976 ops/sec [11,874..12,164] 🟢 +4.9%
some + every (50 elements) 2,668 ops/sec [2,658..2,679] → 2,621 ops/sec [2,604..2,631] 🔴 -1.8% 3,748 ops/sec [3,742..3,757] → 3,814 ops/sec [3,748..3,852] ~ overlap (+1.8%)
find (50 elements) 5,775 ops/sec [5,741..5,786] → 5,709 ops/sec [5,684..5,761] ~ overlap (-1.1%) 8,432 ops/sec [8,408..8,468] → 8,593 ops/sec [8,561..8,653] 🟢 +1.9%
concat 2 arrays (10 + 10 elements) 161,563 ops/sec [160,168..163,244] → 163,779 ops/sec [162,905..164,430] ~ overlap (+1.4%) 172,655 ops/sec [172,030..174,640] → 171,637 ops/sec [168,590..172,485] ~ overlap (-0.6%)
concat 5 arrays (10 elements each) 98,489 ops/sec [98,071..98,823] → 97,461 ops/sec [97,282..97,830] 🔴 -1.0% 108,477 ops/sec [107,826..108,987] → 104,300 ops/sec [103,376..105,405] 🔴 -3.9%
concat 2 arrays (20 + 20 elements) 144,193 ops/sec [143,785..144,809] → 144,331 ops/sec [143,567..145,263] ~ overlap (+0.1%) 159,082 ops/sec [157,167..160,545] → 148,597 ops/sec [147,865..148,923] 🔴 -6.6%
concat + filter + toArray (20 + 20 elements) 16,446 ops/sec [16,304..16,680] → 15,884 ops/sec [15,829..15,944] 🔴 -3.4% 21,299 ops/sec [20,671..21,709] → 24,190 ops/sec [24,120..24,292] 🟢 +13.6%
concat + map + take (20 + 20 elements, take 10) 48,427 ops/sec [48,346..48,444] → 46,611 ops/sec [45,966..46,826] 🔴 -3.7% 62,791 ops/sec [60,349..63,677] → 61,375 ops/sec [60,856..61,755] ~ overlap (-2.3%)
concat Sets (15 + 15 elements) 145,655 ops/sec [144,451..146,128] → 143,200 ops/sec [142,716..143,972] 🔴 -1.7% 156,107 ops/sec [155,156..156,787] → 144,539 ops/sec [143,115..145,528] 🔴 -7.4%
concat strings (13 + 13 characters) 109,479 ops/sec [108,992..109,812] → 103,144 ops/sec [102,713..103,538] 🔴 -5.8% 105,069 ops/sec [104,487..105,969] → 101,371 ops/sec [100,365..102,232] 🔴 -3.5%
zip 2 arrays (10 + 10 elements) 58,131 ops/sec [57,825..58,552] → 55,481 ops/sec [55,240..55,978] 🔴 -4.6% 56,985 ops/sec [56,350..57,131] → 53,098 ops/sec [52,521..53,311] 🔴 -6.8%
zip 3 arrays (10 elements each) 54,575 ops/sec [54,096..54,732] → 51,660 ops/sec [51,429..51,973] 🔴 -5.3% 52,968 ops/sec [51,880..53,866] → 49,417 ops/sec [48,783..50,083] 🔴 -6.7%
zip 2 arrays (20 + 20 elements) 38,928 ops/sec [38,797..39,275] → 37,246 ops/sec [37,038..37,574] 🔴 -4.3% 37,873 ops/sec [36,644..38,133] → 34,852 ops/sec [34,429..35,029] 🔴 -8.0%
zip 2 arrays (50 + 50 elements) 19,518 ops/sec [19,491..19,785] → 18,870 ops/sec [18,743..18,976] 🔴 -3.3% 18,549 ops/sec [18,066..18,638] → 16,686 ops/sec [16,595..16,845] 🔴 -10.0%
zip shortest mode (20 + 10 elements) 57,975 ops/sec [57,614..58,194] → 55,321 ops/sec [55,162..55,815] 🔴 -4.6% 56,835 ops/sec [54,288..57,739] → 52,342 ops/sec [51,887..52,689] 🔴 -7.9%
zip longest mode (10 + 20 elements) 34,179 ops/sec [34,050..34,318] → 32,503 ops/sec [32,479..32,659] 🔴 -4.9% 32,239 ops/sec [32,224..32,298] → 30,720 ops/sec [30,668..30,892] 🔴 -4.7%
zip strict mode (20 + 20 elements) 37,147 ops/sec [36,790..37,480] → 35,341 ops/sec [35,162..35,540] 🔴 -4.9% 35,683 ops/sec [35,379..36,134] → 33,102 ops/sec [32,743..33,438] 🔴 -7.2%
zip + map + toArray (20 + 20 elements) 18,016 ops/sec [17,927..18,115] → 17,083 ops/sec [17,036..17,161] 🔴 -5.2% 12,072 ops/sec [11,893..12,183] → 11,644 ops/sec [11,565..11,811] 🔴 -3.5%
zip + filter + toArray (20 + 20 elements) 17,083 ops/sec [16,857..17,184] → 16,278 ops/sec [16,241..16,315] 🔴 -4.7% 12,056 ops/sec [11,961..12,147] → 11,298 ops/sec [11,191..11,373] 🔴 -6.3%
zip Sets (15 + 15 elements) 46,233 ops/sec [46,163..46,271] → 42,752 ops/sec [40,853..44,297] 🔴 -7.5% 43,683 ops/sec [43,174..44,086] → 40,321 ops/sec [39,952..41,125] 🔴 -7.7%
zipKeyed 2 keys (10 elements each) 55,864 ops/sec [55,067..55,991] → 52,541 ops/sec [52,354..53,248] 🔴 -5.9% 54,828 ops/sec [48,630..55,626] → 54,295 ops/sec [53,804..54,642] ~ overlap (-1.0%)
zipKeyed 3 keys (20 elements each) 28,231 ops/sec [27,699..28,283] → 26,681 ops/sec [26,610..26,856] 🔴 -5.5% 28,159 ops/sec [27,946..29,357] → 26,821 ops/sec [26,520..27,811] 🔴 -4.8%
zipKeyed longest mode (10 + 20 elements) 31,526 ops/sec [29,829..32,141] → 30,114 ops/sec [29,627..30,167] ~ overlap (-4.5%) 31,725 ops/sec [31,281..31,858] → 30,058 ops/sec [29,835..30,456] 🔴 -5.3%
zipKeyed strict mode (20 + 20 elements) 33,368 ops/sec [32,867..33,765] → 31,920 ops/sec [31,709..31,997] 🔴 -4.3% 32,867 ops/sec [32,046..33,372] → 31,825 ops/sec [31,241..32,150] ~ overlap (-3.2%)
zipKeyed + filter + map (20 elements) 12,903 ops/sec [12,724..13,030] → 11,935 ops/sec [11,842..12,034] 🔴 -7.5% 14,907 ops/sec [14,259..15,064] → 15,291 ops/sec [15,143..15,409] 🟢 +2.6%
array.values().map().filter().toArray() 8,156 ops/sec [8,095..8,211] → 7,881 ops/sec [7,776..7,921] 🔴 -3.4% 11,440 ops/sec [11,143..11,660] → 11,838 ops/sec [11,751..11,870] 🟢 +3.5%
array.values().take(5).toArray() 217,244 ops/sec [215,136..218,427] → 212,255 ops/sec [209,468..213,402] 🔴 -2.3% 244,883 ops/sec [240,580..251,474] → 236,740 ops/sec [234,980..239,864] 🔴 -3.3%
array.values().drop(45).toArray() 204,158 ops/sec [201,321..205,675] → 193,100 ops/sec [190,524..198,461] 🔴 -5.4% 227,805 ops/sec [222,359..232,977] → 224,511 ops/sec [220,900..225,845] ~ overlap (-1.4%)
map.entries() chained helpers 10,421 ops/sec [10,383..10,490] → 9,812 ops/sec [9,759..9,873] 🔴 -5.8% 6,378 ops/sec [6,329..6,443] → 6,073 ops/sec [5,991..6,120] 🔴 -4.8%
set.values() chained helpers 17,605 ops/sec [17,494..17,788] → 16,293 ops/sec [14,009..16,392] 🔴 -7.5% 22,370 ops/sec [21,160..22,484] → 24,945 ops/sec [24,662..25,657] 🟢 +11.5%
string iterator map + toArray 14,366 ops/sec [14,291..14,473] → 13,290 ops/sec [13,252..13,385] 🔴 -7.5% 16,062 ops/sec [15,671..16,287] → 15,650 ops/sec [15,320..15,926] ~ overlap (-2.6%)
json.js — Interp: 🔴 18, 2 unch. · avg -3.3% · Bytecode: 🟢 11, 🔴 1, 8 unch. · avg +2.8%
Benchmark Interpreted Δ Bytecode Δ
parse simple object 149,195 ops/sec [145,704..151,122] → 141,186 ops/sec [140,745..142,378] 🔴 -5.4% 156,571 ops/sec [152,929..158,527] → 161,564 ops/sec [160,588..161,977] 🟢 +3.2%
parse nested object 93,627 ops/sec [93,098..94,227] → 92,314 ops/sec [91,393..92,536] 🔴 -1.4% 98,797 ops/sec [96,862..100,322] → 100,025 ops/sec [98,555..101,423] ~ overlap (+1.2%)
parse array of objects 56,439 ops/sec [56,199..56,706] → 54,754 ops/sec [54,377..54,927] 🔴 -3.0% 57,720 ops/sec [56,566..58,098] → 59,060 ops/sec [58,277..59,832] 🟢 +2.3%
parse large flat object 61,284 ops/sec [60,745..61,752] → 60,160 ops/sec [59,987..60,396] 🔴 -1.8% 66,483 ops/sec [65,565..67,211] → 64,618 ops/sec [64,126..64,773] 🔴 -2.8%
parse mixed types 68,597 ops/sec [68,001..68,819] → 66,522 ops/sec [65,560..66,679] 🔴 -3.0% 73,249 ops/sec [70,345..73,538] → 73,349 ops/sec [72,882..74,233] ~ overlap (+0.1%)
stringify simple object 156,830 ops/sec [156,010..157,126] → 147,404 ops/sec [146,833..147,558] 🔴 -6.0% 158,424 ops/sec [155,607..159,311] → 168,299 ops/sec [167,223..168,997] 🟢 +6.2%
stringify nested object 88,076 ops/sec [87,765..88,206] → 83,116 ops/sec [82,701..83,871] 🔴 -5.6% 87,079 ops/sec [86,185..88,475] → 90,577 ops/sec [90,201..91,118] 🟢 +4.0%
stringify array of objects 37,210 ops/sec [37,026..37,272] → 36,082 ops/sec [35,739..36,276] 🔴 -3.0% 37,406 ops/sec [37,365..37,469] → 41,647 ops/sec [41,504..41,817] 🟢 +11.3%
stringify mixed types 65,798 ops/sec [65,525..66,414] → 62,983 ops/sec [62,276..63,185] 🔴 -4.3% 66,118 ops/sec [65,354..66,298] → 67,065 ops/sec [66,452..67,625] 🟢 +1.4%
reviver doubles numbers 34,422 ops/sec [33,747..34,784] → 32,761 ops/sec [32,604..32,904] 🔴 -4.8% 36,271 ops/sec [36,020..37,230] → 38,299 ops/sec [37,637..39,089] 🟢 +5.6%
reviver filters properties 30,610 ops/sec [30,519..30,680] → 29,609 ops/sec [29,498..29,707] 🔴 -3.3% 32,429 ops/sec [32,090..32,518] → 32,581 ops/sec [32,378..32,707] ~ overlap (+0.5%)
reviver on nested object 37,858 ops/sec [37,604..37,935] → 36,512 ops/sec [36,364..36,585] 🔴 -3.6% 39,317 ops/sec [38,838..39,583] → 40,443 ops/sec [40,148..40,588] 🟢 +2.9%
reviver on array 20,989 ops/sec [20,926..21,169] → 20,536 ops/sec [20,407..20,718] 🔴 -2.2% 23,662 ops/sec [23,319..23,857] → 23,333 ops/sec [22,790..23,891] ~ overlap (-1.4%)
replacer function doubles numbers 39,899 ops/sec [39,473..40,513] → 39,674 ops/sec [38,657..39,791] ~ overlap (-0.6%) 44,918 ops/sec [43,938..46,135] → 45,676 ops/sec [44,584..47,404] ~ overlap (+1.7%)
replacer function excludes properties 53,657 ops/sec [53,293..54,346] → 53,519 ops/sec [52,970..54,094] ~ overlap (-0.3%) 56,749 ops/sec [52,589..57,676] → 60,939 ops/sec [59,585..61,601] 🟢 +7.4%
array replacer (allowlist) 93,828 ops/sec [93,440..95,276] → 91,824 ops/sec [91,204..92,104] 🔴 -2.1% 94,842 ops/sec [92,801..95,548] → 95,753 ops/sec [94,549..96,740] ~ overlap (+1.0%)
stringify with 2-space indent 75,745 ops/sec [74,960..76,118] → 72,536 ops/sec [72,450..72,957] 🔴 -4.2% 78,370 ops/sec [75,314..78,829] → 79,002 ops/sec [78,351..79,297] ~ overlap (+0.8%)
stringify with tab indent 76,114 ops/sec [75,175..76,319] → 73,258 ops/sec [73,026..73,471] 🔴 -3.8% 76,930 ops/sec [75,578..77,610] → 77,967 ops/sec [77,106..78,532] ~ overlap (+1.3%)
parse then stringify 44,944 ops/sec [44,607..45,370] → 42,929 ops/sec [42,599..43,244] 🔴 -4.5% 47,801 ops/sec [46,435..48,415] → 48,928 ops/sec [48,656..49,177] 🟢 +2.4%
stringify then parse 26,145 ops/sec [25,964..26,255] → 25,301 ops/sec [25,232..25,386] 🔴 -3.2% 26,854 ops/sec [26,417..26,927] → 28,761 ops/sec [28,648..28,790] 🟢 +7.1%
jsx.jsx — Interp: 🔴 17, 4 unch. · avg -2.0% · Bytecode: 🟢 6, 🔴 3, 12 unch. · avg +0.8%
Benchmark Interpreted Δ Bytecode Δ
simple element 214,333 ops/sec [211,546..215,683] → 214,436 ops/sec [213,183..214,644] ~ overlap (+0.0%) 318,985 ops/sec [317,799..320,850] → 327,388 ops/sec [326,403..328,854] 🟢 +2.6%
self-closing element 224,792 ops/sec [224,418..225,925] → 222,317 ops/sec [221,755..224,035] 🔴 -1.1% 361,098 ops/sec [360,081..363,874] → 355,312 ops/sec [352,194..359,298] 🔴 -1.6%
element with string attribute 184,455 ops/sec [184,402..184,829] → 181,300 ops/sec [180,347..182,547] 🔴 -1.7% 262,127 ops/sec [259,197..265,028] → 256,722 ops/sec [254,014..260,424] ~ overlap (-2.1%)
element with multiple attributes 160,460 ops/sec [159,153..160,919] → 161,181 ops/sec [160,159..161,418] ~ overlap (+0.4%) 195,056 ops/sec [191,819..196,585] → 195,648 ops/sec [193,993..197,846] ~ overlap (+0.3%)
element with expression attribute 172,071 ops/sec [169,823..172,761] → 167,892 ops/sec [167,162..168,202] 🔴 -2.4% 258,671 ops/sec [252,599..263,069] → 260,497 ops/sec [257,963..264,977] ~ overlap (+0.7%)
text child 215,829 ops/sec [214,320..216,626] → 212,683 ops/sec [211,766..213,398] 🔴 -1.5% 323,568 ops/sec [301,088..325,673] → 327,586 ops/sec [326,577..332,666] 🟢 +1.2%
expression child 211,650 ops/sec [208,536..212,917] → 208,222 ops/sec [207,421..208,472] 🔴 -1.6% 321,843 ops/sec [316,312..328,083] → 330,674 ops/sec [326,486..333,576] ~ overlap (+2.7%)
mixed text and expression 197,711 ops/sec [197,271..197,988] → 197,302 ops/sec [196,191..198,271] ~ overlap (-0.2%) 277,028 ops/sec [276,171..278,759] → 277,587 ops/sec [275,519..278,704] ~ overlap (+0.2%)
nested elements (3 levels) 81,999 ops/sec [81,787..82,164] → 81,216 ops/sec [81,047..81,336] 🔴 -1.0% 120,835 ops/sec [120,395..122,803] → 126,512 ops/sec [126,258..126,704] 🟢 +4.7%
sibling children 61,102 ops/sec [60,854..61,318] → 60,662 ops/sec [60,407..60,849] 🔴 -0.7% 85,345 ops/sec [84,853..85,814] → 89,691 ops/sec [89,081..90,201] 🟢 +5.1%
component element 153,574 ops/sec [152,580..155,012] → 152,435 ops/sec [151,005..153,139] ~ overlap (-0.7%) 230,010 ops/sec [227,273..234,391] → 228,132 ops/sec [223,545..230,174] ~ overlap (-0.8%)
component with children 94,769 ops/sec [94,218..94,875] → 93,886 ops/sec [93,639..94,089] 🔴 -0.9% 135,078 ops/sec [134,667..135,154] → 136,473 ops/sec [135,206..137,007] 🟢 +1.0%
dotted component 130,305 ops/sec [130,079..130,768] → 128,039 ops/sec [126,604..128,886] 🔴 -1.7% 175,920 ops/sec [174,701..179,607] → 172,120 ops/sec [171,412..172,838] 🔴 -2.2%
empty fragment 227,215 ops/sec [226,387..228,047] → 222,571 ops/sec [221,527..223,411] 🔴 -2.0% 394,176 ops/sec [393,823..394,254] → 392,122 ops/sec [389,393..396,592] ~ overlap (-0.5%)
fragment with children 61,017 ops/sec [60,814..61,368] → 60,116 ops/sec [59,840..60,264] 🔴 -1.5% 87,971 ops/sec [81,097..89,984] → 89,007 ops/sec [88,095..89,459] ~ overlap (+1.2%)
spread attributes 112,878 ops/sec [111,931..113,293] → 106,905 ops/sec [106,751..107,085] 🔴 -5.3% 130,288 ops/sec [130,008..133,115] → 131,533 ops/sec [115,297..135,977] ~ overlap (+1.0%)
spread with overrides 98,648 ops/sec [98,142..98,961] → 94,327 ops/sec [93,404..94,479] 🔴 -4.4% 109,736 ops/sec [109,026..112,687] → 115,001 ops/sec [114,677..116,318] 🟢 +4.8%
shorthand props 165,171 ops/sec [164,773..165,528] → 161,862 ops/sec [160,785..162,676] 🔴 -2.0% 237,737 ops/sec [232,468..241,983] → 228,857 ops/sec [227,843..229,738] 🔴 -3.7%
nav bar structure 29,287 ops/sec [29,086..29,355] → 27,701 ops/sec [27,664..27,752] 🔴 -5.4% 38,832 ops/sec [37,931..40,231] → 39,576 ops/sec [39,190..39,863] ~ overlap (+1.9%)
card component tree 33,810 ops/sec [33,712..33,869] → 32,168 ops/sec [32,107..32,229] 🔴 -4.9% 42,841 ops/sec [41,305..44,287] → 42,889 ops/sec [42,498..43,153] ~ overlap (+0.1%)
10 list items via Array.from 15,270 ops/sec [15,224..15,308] → 14,782 ops/sec [14,666..14,855] 🔴 -3.2% 18,904 ops/sec [18,061..19,047] → 19,069 ops/sec [18,717..19,359] ~ overlap (+0.9%)
modules.js — Interp: 🟢 2, 🔴 2, 5 unch. · avg +0.4% · Bytecode: 🟢 9 · avg +6.2%
Benchmark Interpreted Δ Bytecode Δ
call imported function 464,067 ops/sec [462,016..465,010] → 459,439 ops/sec [459,143..459,984] 🔴 -1.0% 688,911 ops/sec [678,690..693,439] → 706,082 ops/sec [699,592..709,704] 🟢 +2.5%
call two imported functions 266,190 ops/sec [265,045..269,658] → 258,499 ops/sec [257,128..259,806] 🔴 -2.9% 357,500 ops/sec [348,351..363,053] → 385,348 ops/sec [383,692..393,455] 🟢 +7.8%
read imported constant 1,599,388 ops/sec [1,575,091..1,609,753] → 1,606,087 ops/sec [1,598,617..1,611,009] ~ overlap (+0.4%) 6,080,382 ops/sec [6,077,193..6,083,523] → 6,493,108 ops/sec [6,480,483..6,499,729] 🟢 +6.8%
read imported string 1,592,890 ops/sec [1,588,341..1,597,986] → 1,590,875 ops/sec [1,576,857..1,602,304] ~ overlap (-0.1%) 6,049,412 ops/sec [6,038,498..6,052,396] → 6,468,658 ops/sec [6,451,712..6,483,380] 🟢 +6.9%
read JSON string property 1,607,610 ops/sec [1,597,650..1,612,934] → 1,618,824 ops/sec [1,614,889..1,623,467] 🟢 +0.7% 6,049,959 ops/sec [6,043,717..6,054,880] → 6,380,250 ops/sec [6,374,511..6,387,042] 🟢 +5.5%
read JSON number property 1,589,603 ops/sec [1,585,367..1,592,323] → 1,589,757 ops/sec [1,303,040..1,598,709] ~ overlap (+0.0%) 6,047,813 ops/sec [6,041,382..6,057,132] → 6,479,300 ops/sec [6,478,098..6,489,366] 🟢 +7.1%
read JSON boolean property 1,606,038 ops/sec [1,598,282..1,607,655] → 1,595,512 ops/sec [1,587,971..1,605,451] ~ overlap (-0.7%) 6,046,190 ops/sec [6,040,478..6,055,199] → 6,477,441 ops/sec [6,472,433..6,482,470] 🟢 +7.1%
read JSON array property 1,589,102 ops/sec [1,583,263..1,592,965] → 1,593,280 ops/sec [1,351,035..1,600,194] ~ overlap (+0.3%) 6,046,817 ops/sec [6,039,253..6,055,333] → 6,269,127 ops/sec [6,258,193..6,272,714] 🟢 +3.7%
read multiple JSON properties 880,907 ops/sec [880,061..881,964] → 939,040 ops/sec [930,076..948,222] 🟢 +6.6% 5,006,785 ops/sec [5,003,646..5,011,979] → 5,433,223 ops/sec [5,412,837..5,437,189] 🟢 +8.5%
numbers.js — Interp: 🟢 1, 🔴 4, 6 unch. · avg -0.6% · Bytecode: 🟢 6, 🔴 3, 2 unch. · avg +4.0%
Benchmark Interpreted Δ Bytecode Δ
integer arithmetic 513,433 ops/sec [501,355..520,808] → 518,321 ops/sec [516,840..519,290] ~ overlap (+1.0%) 1,802,869 ops/sec [1,792,782..1,805,240] → 1,708,702 ops/sec [1,694,082..1,722,950] 🔴 -5.2%
floating point arithmetic 600,504 ops/sec [587,879..606,183] → 579,155 ops/sec [576,798..580,568] 🔴 -3.6% 980,647 ops/sec [974,089..984,006] → 935,313 ops/sec [898,657..945,688] 🔴 -4.6%
number coercion 186,151 ops/sec [185,470..186,449] → 179,496 ops/sec [179,049..179,961] 🔴 -3.6% 216,320 ops/sec [215,017..217,287] → 235,376 ops/sec [234,395..236,180] 🟢 +8.8%
toFixed 101,906 ops/sec [101,348..102,062] → 101,434 ops/sec [101,131..101,885] ~ overlap (-0.5%) 114,786 ops/sec [113,312..115,991] → 112,056 ops/sec [111,823..112,296] 🔴 -2.4%
toString 153,683 ops/sec [153,264..153,976] → 153,758 ops/sec [152,861..154,808] ~ overlap (+0.0%) 187,953 ops/sec [181,162..189,109] → 187,584 ops/sec [186,135..188,682] ~ overlap (-0.2%)
valueOf 231,729 ops/sec [230,609..233,418] → 230,011 ops/sec [229,184..230,851] ~ overlap (-0.7%) 263,734 ops/sec [262,205..265,335] → 271,725 ops/sec [267,668..274,183] 🟢 +3.0%
toPrecision 134,821 ops/sec [134,218..136,631] → 142,125 ops/sec [141,760..142,594] 🟢 +5.4% 162,393 ops/sec [160,981..165,128] → 166,631 ops/sec [159,217..168,301] ~ overlap (+2.6%)
Number.isNaN 307,424 ops/sec [303,441..310,101] → 306,532 ops/sec [304,168..307,549] ~ overlap (-0.3%) 311,608 ops/sec [306,267..314,050] → 344,222 ops/sec [343,591..344,804] 🟢 +10.5%
Number.isFinite 304,184 ops/sec [301,870..305,759] → 297,447 ops/sec [296,692..297,909] 🔴 -2.2% 294,940 ops/sec [294,252..295,966] → 334,266 ops/sec [332,761..334,936] 🟢 +13.3%
Number.isInteger 317,560 ops/sec [315,364..318,733] → 311,300 ops/sec [309,704..313,169] 🔴 -2.0% 315,913 ops/sec [314,598..316,448] → 357,444 ops/sec [355,802..357,955] 🟢 +13.1%
Number.parseInt and parseFloat 246,713 ops/sec [243,321..247,734] → 245,272 ops/sec [244,263..246,194] ~ overlap (-0.6%) 245,300 ops/sec [244,323..245,855] → 256,453 ops/sec [254,605..257,966] 🟢 +4.5%
objects.js — Interp: 🟢 2, 🔴 4, 1 unch. · avg -1.0% · Bytecode: 🟢 2, 🔴 1, 4 unch. · avg +1.5%
Benchmark Interpreted Δ Bytecode Δ
create simple object 486,447 ops/sec [484,602..488,529] → 480,854 ops/sec [479,445..482,147] 🔴 -1.1% 573,998 ops/sec [569,437..577,750] → 570,308 ops/sec [567,989..571,753] ~ overlap (-0.6%)
create nested object 238,278 ops/sec [237,637..239,381] → 243,170 ops/sec [241,757..244,162] 🟢 +2.1% 243,359 ops/sec [243,349..244,266] → 238,125 ops/sec [236,198..241,918] 🔴 -2.2%
create 50 objects via Array.from 9,611 ops/sec [9,594..9,651] → 9,499 ops/sec [9,449..9,562] 🔴 -1.2% 9,591 ops/sec [9,231..9,817] → 9,927 ops/sec [9,885..10,070] 🟢 +3.5%
property read 567,962 ops/sec [556,989..569,113] → 536,648 ops/sec [534,811..538,305] 🔴 -5.5% 793,015 ops/sec [790,737..793,501] → 863,994 ops/sec [848,221..870,473] 🟢 +9.0%
Object.keys 270,933 ops/sec [269,345..271,258] → 268,745 ops/sec [267,862..270,887] ~ overlap (-0.8%) 303,361 ops/sec [300,235..306,407] → 307,007 ops/sec [291,801..314,221] ~ overlap (+1.2%)
Object.entries 101,361 ops/sec [100,867..101,867] → 99,550 ops/sec [99,332..99,697] 🔴 -1.8% 109,287 ops/sec [108,877..110,598] → 108,878 ops/sec [108,481..109,488] ~ overlap (-0.4%)
spread operator 179,302 ops/sec [178,974..179,879] → 181,593 ops/sec [180,639..181,803] 🟢 +1.3% 209,419 ops/sec [208,979..209,715] → 210,079 ops/sec [208,297..211,379] ~ overlap (+0.3%)
promises.js — Interp: 🔴 10, 2 unch. · avg -1.9% · Bytecode: 🟢 2, 🔴 2, 8 unch. · avg -0.3%
Benchmark Interpreted Δ Bytecode Δ
Promise.resolve(value) 534,045 ops/sec [528,960..540,989] → 530,161 ops/sec [527,974..533,099] ~ overlap (-0.7%) 571,960 ops/sec [567,983..572,274] → 584,826 ops/sec [577,169..587,945] 🟢 +2.2%
new Promise(resolve => resolve(value)) 191,453 ops/sec [190,516..191,734] → 189,155 ops/sec [188,635..189,485] 🔴 -1.2% 234,141 ops/sec [232,532..235,808] → 233,165 ops/sec [230,406..234,390] ~ overlap (-0.4%)
Promise.reject(reason) 547,294 ops/sec [538,872..551,069] → 544,303 ops/sec [542,525..545,245] ~ overlap (-0.5%) 542,370 ops/sec [529,587..545,865] → 553,695 ops/sec [550,824..559,028] 🟢 +2.1%
resolve + then (1 handler) 179,610 ops/sec [177,684..180,279] → 176,603 ops/sec [175,781..177,496] 🔴 -1.7% 219,832 ops/sec [214,019..222,369] → 221,435 ops/sec [220,976..222,508] ~ overlap (+0.7%)
resolve + then chain (3 deep) 72,246 ops/sec [71,900..72,689] → 70,940 ops/sec [70,668..71,163] 🔴 -1.8% 88,328 ops/sec [88,205..88,875] → 85,651 ops/sec [85,323..86,024] 🔴 -3.0%
resolve + then chain (10 deep) 23,378 ops/sec [23,324..23,436] → 22,423 ops/sec [22,264..22,643] 🔴 -4.1% 29,141 ops/sec [28,982..29,474] → 28,525 ops/sec [28,036..28,929] 🔴 -2.1%
reject + catch + then 102,243 ops/sec [101,607..102,573] → 100,333 ops/sec [100,074..100,786] 🔴 -1.9% 116,836 ops/sec [115,552..117,737] → 116,208 ops/sec [115,693..117,541] ~ overlap (-0.5%)
resolve + finally + then 86,327 ops/sec [85,916..86,808] → 85,607 ops/sec [85,387..85,857] 🔴 -0.8% 98,248 ops/sec [97,644..98,584] → 96,470 ops/sec [96,386..98,230] ~ overlap (-1.8%)
Promise.all (5 resolved) 31,815 ops/sec [31,735..31,964] → 30,961 ops/sec [30,686..31,065] 🔴 -2.7% 33,104 ops/sec [32,946..33,363] → 32,862 ops/sec [32,648..33,471] ~ overlap (-0.7%)
Promise.race (5 resolved) 34,586 ops/sec [34,336..34,812] → 33,383 ops/sec [33,111..33,585] 🔴 -3.5% 35,728 ops/sec [35,481..35,880] → 35,972 ops/sec [35,521..36,151] ~ overlap (+0.7%)
Promise.allSettled (5 mixed) 26,976 ops/sec [26,879..27,186] → 26,486 ops/sec [26,240..26,577] 🔴 -1.8% 27,840 ops/sec [27,811..27,980] → 27,548 ops/sec [27,467..28,184] ~ overlap (-1.0%)
Promise.any (5 mixed) 31,944 ops/sec [31,764..32,015] → 31,361 ops/sec [31,223..31,477] 🔴 -1.8% 33,323 ops/sec [33,184..33,586] → 33,594 ops/sec [32,963..34,025] ~ overlap (+0.8%)
regexp.js — Interp: 🔴 11 · avg -3.1% · Bytecode: 🟢 2, 🔴 5, 4 unch. · avg -0.7%
Benchmark Interpreted Δ Bytecode Δ
regex literal creation 154,149 ops/sec [152,597..155,137] → 150,396 ops/sec [149,549..150,955] 🔴 -2.4% 142,072 ops/sec [139,063..143,619] → 137,659 ops/sec [136,244..138,811] 🔴 -3.1%
new RegExp(pattern, flags) 133,549 ops/sec [132,763..134,108] → 131,651 ops/sec [131,023..131,811] 🔴 -1.4% 141,440 ops/sec [140,724..143,500] → 135,252 ops/sec [134,159..136,190] 🔴 -4.4%
RegExp(existingRegex) returns the same regex 710,358 ops/sec [708,494..712,135] → 692,106 ops/sec [688,919..694,758] 🔴 -2.6% 1,054,017 ops/sec [1,051,021..1,057,727] → 1,144,146 ops/sec [1,139,738..1,145,781] 🟢 +8.6%
test() on a global regex 136,664 ops/sec [136,218..137,345] → 134,664 ops/sec [133,096..136,034] 🔴 -1.5% 170,419 ops/sec [169,707..170,810] → 165,441 ops/sec [164,244..166,242] 🔴 -2.9%
exec() with capture groups 118,586 ops/sec [118,187..119,321] → 115,686 ops/sec [115,159..116,241] 🔴 -2.4% 136,298 ops/sec [134,876..138,574] → 137,838 ops/sec [137,168..139,686] ~ overlap (+1.1%)
toString() 483,187 ops/sec [475,291..484,485] → 469,214 ops/sec [464,188..470,198] 🔴 -2.9% 656,360 ops/sec [653,736..658,589] → 681,520 ops/sec [677,288..685,343] 🟢 +3.8%
match() with global regex 40,337 ops/sec [40,128..40,963] → 37,753 ops/sec [37,615..37,889] 🔴 -6.4% 40,373 ops/sec [38,507..40,701] → 39,388 ops/sec [38,316..39,863] ~ overlap (-2.4%)
matchAll() with capture groups 21,124 ops/sec [21,061..21,163] → 19,962 ops/sec [19,857..19,986] 🔴 -5.5% 25,443 ops/sec [25,035..25,485] → 24,915 ops/sec [24,429..25,461] ~ overlap (-2.1%)
replace() with global regex 37,726 ops/sec [37,678..37,859] → 36,734 ops/sec [36,546..36,844] 🔴 -2.6% 38,513 ops/sec [38,275..38,864] → 37,913 ops/sec [37,719..38,140] 🔴 -1.6%
search() with regex 79,060 ops/sec [78,960..80,305] → 76,957 ops/sec [76,796..77,233] 🔴 -2.7% 78,191 ops/sec [77,485..78,883] → 74,631 ops/sec [73,847..77,001] 🔴 -4.6%
split() with regex separator 39,447 ops/sec [39,234..39,606] → 37,790 ops/sec [37,629..37,982] 🔴 -4.2% 39,315 ops/sec [39,209..39,542] → 39,347 ops/sec [39,175..39,584] ~ overlap (+0.1%)
strings.js — Interp: 🟢 9, 🔴 6, 4 unch. · avg +0.7% · Bytecode: 🟢 7, 🔴 1, 11 unch. · avg +1.7%
Benchmark Interpreted Δ Bytecode Δ
string concatenation 382,859 ops/sec [382,064..383,883] → 395,575 ops/sec [394,280..397,290] 🟢 +3.3% 295,870 ops/sec [286,721..302,296] → 279,462 ops/sec [276,697..280,173] 🔴 -5.5%
template literal 788,125 ops/sec [783,534..791,723] → 753,216 ops/sec [652,463..760,391] 🔴 -4.4% 531,294 ops/sec [516,167..532,472] → 521,591 ops/sec [518,575..527,322] ~ overlap (-1.8%)
string repeat 371,495 ops/sec [370,622..372,142] → 401,190 ops/sec [397,880..404,571] 🟢 +8.0% 470,537 ops/sec [457,151..475,904] → 467,543 ops/sec [464,373..470,702] ~ overlap (-0.6%)
split and join 132,666 ops/sec [131,618..134,371] → 130,538 ops/sec [129,057..131,670] ~ overlap (-1.6%) 149,219 ops/sec [148,745..150,376] → 150,450 ops/sec [149,995..152,305] ~ overlap (+0.8%)
indexOf and includes 164,055 ops/sec [161,143..164,877] → 168,084 ops/sec [166,909..168,910] 🟢 +2.5% 182,806 ops/sec [182,264..183,231] → 189,349 ops/sec [188,065..191,003] 🟢 +3.6%
toUpperCase and toLowerCase 235,739 ops/sec [234,243..237,922] → 254,110 ops/sec [253,119..255,679] 🟢 +7.8% 321,028 ops/sec [319,525..322,165] → 348,270 ops/sec [344,835..350,404] 🟢 +8.5%
slice and substring 141,082 ops/sec [139,249..141,547] → 149,703 ops/sec [148,300..151,190] 🟢 +6.1% 194,713 ops/sec [192,265..198,153] → 198,729 ops/sec [197,069..200,783] ~ overlap (+2.1%)
trim operations 177,573 ops/sec [176,763..178,147] → 185,909 ops/sec [183,115..187,482] 🟢 +4.7% 234,951 ops/sec [233,023..236,924] → 253,107 ops/sec [251,613..254,851] 🟢 +7.7%
replace and replaceAll 198,348 ops/sec [195,663..200,826] → 205,525 ops/sec [204,724..205,998] 🟢 +3.6% 223,057 ops/sec [217,360..224,269] → 228,251 ops/sec [223,995..228,906] ~ overlap (+2.3%)
startsWith and endsWith 131,187 ops/sec [130,555..132,130] → 133,998 ops/sec [133,135..134,498] 🟢 +2.1% 151,756 ops/sec [149,204..153,612] → 159,479 ops/sec [157,769..162,417] 🟢 +5.1%
padStart and padEnd 188,260 ops/sec [187,643..188,631] → 192,257 ops/sec [191,157..193,812] 🟢 +2.1% 214,233 ops/sec [213,118..216,728] → 225,031 ops/sec [221,205..226,180] 🟢 +5.0%
identity tag, no substitutions 466,285 ops/sec [465,174..471,313] → 464,982 ops/sec [460,511..467,176] ~ overlap (-0.3%) 1,541,052 ops/sec [1,540,169..1,542,691] → 1,703,368 ops/sec [1,700,215..1,705,748] 🟢 +10.5%
tag with 1 substitution 88,482 ops/sec [87,830..89,193] → 85,253 ops/sec [84,680..85,879] 🔴 -3.6% 119,194 ops/sec [118,546..119,701] → 116,736 ops/sec [113,821..119,736] ~ overlap (-2.1%)
tag with 3 substitutions 48,147 ops/sec [48,026..48,517] → 46,215 ops/sec [45,956..46,444] 🔴 -4.0% 69,131 ops/sec [66,542..70,003] → 66,072 ops/sec [64,250..68,147] ~ overlap (-4.4%)
tag with 6 substitutions 28,967 ops/sec [28,843..29,264] → 27,770 ops/sec [27,359..27,865] 🔴 -4.1% 41,109 ops/sec [40,391..41,492] → 41,024 ops/sec [40,518..41,914] ~ overlap (-0.2%)
String.raw, no substitutions 590,367 ops/sec [584,540..592,502] → 585,044 ops/sec [582,732..588,996] ~ overlap (-0.9%) 605,664 ops/sec [599,225..607,005] → 606,307 ops/sec [604,760..607,261] ~ overlap (+0.1%)
String.raw, 2 substitutions 409,051 ops/sec [406,437..411,156] → 407,868 ops/sec [403,508..410,484] ~ overlap (-0.3%) 437,741 ops/sec [435,671..440,527] → 432,765 ops/sec [424,097..436,467] ~ overlap (-1.1%)
tag accessing .raw array 171,440 ops/sec [170,448..172,668] → 168,110 ops/sec [166,637..169,906] 🔴 -1.9% 216,968 ops/sec [212,371..221,627] → 213,160 ops/sec [204,584..221,756] ~ overlap (-1.8%)
method as tag (this binding) 64,153 ops/sec [63,632..64,672] → 61,056 ops/sec [60,600..61,611] 🔴 -4.8% 93,054 ops/sec [92,846..94,045] → 97,434 ops/sec [95,642..100,281] 🟢 +4.7%
typed-arrays.js — Interp: 🟢 2, 🔴 12, 8 unch. · avg -1.6% · Bytecode: 🟢 5, 🔴 9, 8 unch. · avg -4.4%
Benchmark Interpreted Δ Bytecode Δ
new Int32Array(0) 336,984 ops/sec [335,853..337,931] → 326,493 ops/sec [291,563..330,805] 🔴 -3.1% 386,020 ops/sec [379,882..388,079] → 392,748 ops/sec [389,278..397,531] 🟢 +1.7%
new Int32Array(100) 307,481 ops/sec [306,419..308,775] → 292,521 ops/sec [290,127..294,945] 🔴 -4.9% 345,824 ops/sec [344,115..347,959] → 353,818 ops/sec [351,981..360,140] 🟢 +2.3%
new Int32Array(1000) 186,224 ops/sec [185,793..188,039] → 181,743 ops/sec [177,792..186,269] ~ overlap (-2.4%) 203,954 ops/sec [201,726..206,602] → 179,997 ops/sec [177,880..184,038] 🔴 -11.7%
new Float64Array(100) 279,833 ops/sec [279,298..282,104] → 282,213 ops/sec [280,581..284,106] ~ overlap (+0.9%) 322,137 ops/sec [319,837..323,938] → 314,845 ops/sec [311,698..322,408] ~ overlap (-2.3%)
Int32Array.from([...]) 184,474 ops/sec [183,667..185,756] → 182,836 ops/sec [181,915..183,278] 🔴 -0.9% 195,051 ops/sec [192,312..197,914] → 172,707 ops/sec [171,222..173,622] 🔴 -11.5%
Int32Array.of(1, 2, 3, 4, 5) 307,108 ops/sec [304,554..311,688] → 296,876 ops/sec [290,654..305,257] ~ overlap (-3.3%) 351,951 ops/sec [347,083..357,320] → 362,000 ops/sec [349,628..365,887] ~ overlap (+2.9%)
sequential write 100 elements 3,533 ops/sec [3,501..3,555] → 3,422 ops/sec [3,385..3,466] 🔴 -3.1% 16,515 ops/sec [15,826..16,737] → 11,232 ops/sec [11,166..11,304] 🔴 -32.0%
sequential read 100 elements 3,571 ops/sec [3,524..3,596] → 3,508 ops/sec [3,460..3,560] ~ overlap (-1.8%) 16,794 ops/sec [16,155..16,869] → 16,383 ops/sec [16,270..16,563] ~ overlap (-2.5%)
Float64Array write 100 elements 3,283 ops/sec [3,264..3,301] → 3,153 ops/sec [3,129..3,200] 🔴 -4.0% 11,089 ops/sec [10,846..11,189] → 7,980 ops/sec [6,419..8,112] 🔴 -28.0%
fill(42) 45,629 ops/sec [45,601..45,664] → 46,631 ops/sec [46,598..46,652] 🟢 +2.2% 46,115 ops/sec [46,040..46,125] → 43,157 ops/sec [43,047..43,212] 🔴 -6.4%
slice() 205,629 ops/sec [203,399..207,839] → 199,013 ops/sec [197,315..200,284] 🔴 -3.2% 226,696 ops/sec [225,399..227,905] → 209,232 ops/sec [208,088..210,423] 🔴 -7.7%
map(x => x * 2) 7,729 ops/sec [7,636..7,759] → 7,555 ops/sec [7,536..7,697] ~ overlap (-2.3%) 10,335 ops/sec [9,920..10,492] → 10,354 ops/sec [10,233..10,441] ~ overlap (+0.2%)
filter(x => x > 50) 8,103 ops/sec [8,064..8,189] → 7,906 ops/sec [7,896..7,913] 🔴 -2.4% 11,196 ops/sec [11,127..11,347] → 11,101 ops/sec [11,042..11,199] ~ overlap (-0.9%)
reduce (sum) 7,849 ops/sec [7,770..7,867] → 7,667 ops/sec [7,642..7,689] 🔴 -2.3% 9,908 ops/sec [9,828..10,033] → 10,109 ops/sec [10,057..10,204] 🟢 +2.0%
sort() 168,050 ops/sec [167,738..168,445] → 165,066 ops/sec [164,704..165,360] 🔴 -1.8% 181,808 ops/sec [181,696..181,948] → 167,582 ops/sec [167,374..167,756] 🔴 -7.8%
indexOf() 425,534 ops/sec [422,698..428,481] → 415,635 ops/sec [413,901..418,548] 🔴 -2.3% 513,427 ops/sec [509,589..515,401] → 511,326 ops/sec [509,186..513,371] ~ overlap (-0.4%)
reverse() 326,234 ops/sec [323,920..326,485] → 318,790 ops/sec [317,127..319,741] 🔴 -2.3% 361,816 ops/sec [361,243..361,991] → 349,285 ops/sec [348,845..349,609] 🔴 -3.5%
create view over existing buffer 404,179 ops/sec [403,860..404,509] → 402,959 ops/sec [401,033..404,682] ~ overlap (-0.3%) 479,622 ops/sec [477,539..481,310] → 500,282 ops/sec [497,144..501,401] 🟢 +4.3%
subarray() 421,567 ops/sec [417,008..423,416] → 421,293 ops/sec [420,119..424,044] ~ overlap (-0.1%) 539,349 ops/sec [529,067..542,036] → 537,775 ops/sec [535,909..540,151] ~ overlap (-0.3%)
set() from array 560,093 ops/sec [556,753..562,548] → 553,068 ops/sec [551,592..554,923] 🔴 -1.3% 639,352 ops/sec [638,048..646,718] → 716,744 ops/sec [715,917..717,426] 🟢 +12.1%
for-of loop 4,939 ops/sec [4,916..4,988] → 5,012 ops/sec [4,975..5,040] ~ overlap (+1.5%) 29,414 ops/sec [29,212..29,512] → 29,371 ops/sec [29,179..29,649] ~ overlap (-0.1%)
spread into array 16,895 ops/sec [16,828..17,046] → 17,370 ops/sec [17,174..17,406] 🟢 +2.8% 102,371 ops/sec [101,563..102,624] → 95,572 ops/sec [95,188..96,368] 🔴 -6.6%
uint8array-encoding.js — Interp: 🟢 6, 🔴 6, 6 unch. · avg +0.4% · Bytecode: 🟢 5, 🔴 10, 3 unch. · avg -3.5%
Benchmark Interpreted Δ Bytecode Δ
short (5 bytes) 770,966 ops/sec [768,185..775,067] → 754,766 ops/sec [748,944..759,338] 🔴 -2.1% 1,207,618 ops/sec [1,201,789..1,211,290] → 1,293,839 ops/sec [1,279,473..1,301,231] 🟢 +7.1%
medium (450 bytes) 283,726 ops/sec [282,950..284,730] → 285,767 ops/sec [282,319..286,167] ~ overlap (+0.7%) 345,402 ops/sec [344,542..345,710] → 330,622 ops/sec [328,911..336,291] 🔴 -4.3%
large (4096 bytes) 45,544 ops/sec [43,951..45,908] → 45,821 ops/sec [45,686..46,174] ~ overlap (+0.6%) 47,818 ops/sec [45,056..48,688] → 46,749 ops/sec [45,385..47,208] ~ overlap (-2.2%)
base64url alphabet 204,588 ops/sec [204,267..205,588] → 200,351 ops/sec [198,328..200,478] 🔴 -2.1% 229,130 ops/sec [227,429..230,875] → 213,300 ops/sec [211,139..213,860] 🔴 -6.9%
omitPadding 376,449 ops/sec [375,458..377,162] → 369,481 ops/sec [367,502..373,014] 🔴 -1.9% 438,156 ops/sec [434,842..442,273] → 460,269 ops/sec [457,751..462,848] 🟢 +5.0%
short (8 chars) 353,596 ops/sec [347,449..355,358] → 344,550 ops/sec [342,880..345,708] 🔴 -2.6% 385,458 ops/sec [383,691..388,432] → 363,238 ops/sec [360,603..366,065] 🔴 -5.8%
medium (600 chars) 145,090 ops/sec [144,853..145,610] → 147,868 ops/sec [146,671..148,241] 🟢 +1.9% 150,535 ops/sec [148,381..151,079] → 149,482 ops/sec [149,051..151,382] ~ overlap (-0.7%)
large (5464 chars) 25,030 ops/sec [24,902..25,057] → 27,303 ops/sec [27,293..27,316] 🟢 +9.1% 25,744 ops/sec [25,086..26,003] → 24,982 ops/sec [23,047..26,183] ~ overlap (-3.0%)
short (5 bytes) 805,199 ops/sec [797,562..806,453] → 796,046 ops/sec [793,106..801,753] ~ overlap (-1.1%) 1,244,772 ops/sec [1,182,841..1,252,901] → 1,353,576 ops/sec [1,351,456..1,357,973] 🟢 +8.7%
medium (450 bytes) 275,357 ops/sec [273,209..276,698] → 280,041 ops/sec [278,455..281,476] 🟢 +1.7% 338,799 ops/sec [332,279..339,610] → 289,798 ops/sec [289,135..290,832] 🔴 -14.5%
large (4096 bytes) 44,593 ops/sec [43,297..44,845] → 44,432 ops/sec [44,205..44,462] ~ overlap (-0.4%) 44,143 ops/sec [43,930..49,362] → 38,530 ops/sec [38,055..40,088] 🔴 -12.7%
short (10 chars) 394,437 ops/sec [393,302..396,611] → 376,297 ops/sec [368,135..381,480] 🔴 -4.6% 425,413 ops/sec [422,000..427,913] → 419,079 ops/sec [417,486..420,944] 🔴 -1.5%
medium (900 chars) 241,980 ops/sec [241,495..242,422] → 232,289 ops/sec [231,418..233,412] 🔴 -4.0% 249,858 ops/sec [248,993..250,812] → 237,246 ops/sec [236,694..239,600] 🔴 -5.0%
large (8192 chars) 56,113 ops/sec [55,255..56,329] → 59,129 ops/sec [58,619..59,617] 🟢 +5.4% 61,449 ops/sec [60,815..61,827] → 53,408 ops/sec [53,281..53,785] 🔴 -13.1%
setFromBase64 (450 bytes) 134,261 ops/sec [133,865..134,993] → 135,028 ops/sec [134,549..135,939] ~ overlap (+0.6%) 146,586 ops/sec [146,054..146,720] → 150,194 ops/sec [149,749..150,886] 🟢 +2.5%
setFromHex (450 bytes) 205,704 ops/sec [204,431..206,531] → 203,994 ops/sec [201,082..205,271] ~ overlap (-0.8%) 234,576 ops/sec [228,709..236,966] → 220,638 ops/sec [217,138..222,655] 🔴 -5.9%
toBase64 → fromBase64 (450 bytes) 100,872 ops/sec [100,375..101,416] → 104,705 ops/sec [103,436..105,825] 🟢 +3.8% 106,304 ops/sec [105,345..107,053] → 107,578 ops/sec [107,173..108,372] 🟢 +1.2%
toHex → fromHex (450 bytes) 136,406 ops/sec [135,915..136,825] → 141,270 ops/sec [140,832..141,809] 🟢 +3.6% 150,069 ops/sec [149,901..150,474] → 133,109 ops/sec [132,445..133,788] 🔴 -11.3%

Measured on ubuntu-latest x64. Benchmark ranges compare cached main-branch min/max ops/sec with the PR run; overlapping ranges are treated as unchanged noise. Percentage deltas are secondary context.

…thod-per-file

Address 14 CodeRabbit review findings:
- Fix TZif offset off-by-4 from redundant TZIF_MAGIC_LENGTH additions
- Add path traversal validation in timezone name handling
- Fix half-rounding truncation for odd divisors (AbsRemainder*2 comparison)
- Fix nil dereference in PlainDate/YearMonth/MonthDay.from property access
- Fix ISO offset parsing to validate ranges and reject trailing characters
- Fix Duration.round largestUnit default to largest defined unit
- Add week support in Duration.round result
- Preserve reference fields in PlainDate→PlainYearMonth/PlainMonthDay conversions
- Fix PlainDateTime.round day increment computation
- Add PlainMonthDay.compare static method
- Fix ZonedDateTime.from to throw on zone-less strings instead of defaulting UTC
- Fix ZonedDateTime.round to read roundingMode/roundingIncrement from options
- Fix ZonedDateTime.withPlainTime to throw on unsupported argument types
- Compute actual hoursInDay via epoch difference instead of constant 24

Add TC39 Temporal spec annotations to PlainMonthDay methods.
Restructure tests from 10 monolithic files to 136 one-method-per-file tests
following project conventions (prototype/ subdirectories, edge cases co-located).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread units/Goccia.Builtins.Temporal.pas
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (16)
tests/built-ins/Temporal/PlainDate/compare.js (1)

9-15: Expand compare() coverage with edge/error cases in this same file.

This currently validates only PlainDate-instance happy paths. Please add edge/error assertions (e.g., ISO-string inputs and invalid-input throws) in this file so method coverage stays complete and co-located.

Suggested test additions
 describe.runIf(isTemporal)("Temporal.PlainDate.compare", () => {
   test("compare()", () => {
     const d1 = new Temporal.PlainDate(2024, 1, 1);
     const d2 = new Temporal.PlainDate(2024, 6, 1);
     expect(Temporal.PlainDate.compare(d1, d2)).toBe(-1);
     expect(Temporal.PlainDate.compare(d2, d1)).toBe(1);
     expect(Temporal.PlainDate.compare(d1, d1)).toBe(0);
   });
+
+  test("compare() accepts ISO date strings", () => {
+    expect(Temporal.PlainDate.compare("2024-01-01", "2024-01-02")).toBe(-1);
+    expect(Temporal.PlainDate.compare("2024-01-02", "2024-01-01")).toBe(1);
+    expect(Temporal.PlainDate.compare("2024-01-01", "2024-01-01")).toBe(0);
+  });
+
+  test("compare() throws for invalid input", () => {
+    expect(() => Temporal.PlainDate.compare("not-a-date", "2024-01-01")).toThrow();
+  });
 });

As per coding guidelines, edge case tests should live in the same file as happy-path tests for each method.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Temporal/PlainDate/compare.js` around lines 9 - 15, Add
edge-case assertions to the existing compare() test in
tests/built-ins/Temporal/PlainDate/compare.js: call Temporal.PlainDate.compare
with ISO date strings (e.g., "2024-01-01" and "2024-06-01") and assert the same
-1/1/0 results as the PlainDate instances, and add assertions that invalid
inputs (e.g., null, an object, or a non-ISO string) throw a TypeError when
passed to Temporal.PlainDate.compare; update the single "compare()" test to
include these ISO-string happy-path checks and the invalid-input throws so all
cases are co-located with the existing Temporal.PlainDate.compare(d1, d2)
assertions.
tests/built-ins/Temporal/PlainDateTime/prototype/valueOf.js (1)

11-11: Optional: assert the valueOf TypeError message for stricter spec lock-in.

The type check is correct; adding message matching would better guard against future regressions (implementation currently throws a specific message in units/Goccia.Values.TemporalPlainDateTime.pas, Line 742-744).

Suggested tweak
-    expect(() => dt.valueOf()).toThrow(TypeError);
+    expect(() => dt.valueOf()).toThrow(TypeError);
+    expect(() => dt.valueOf()).toThrow(
+      "Temporal.PlainDateTime.prototype.valueOf cannot be used; use toString or compare instead"
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Temporal/PlainDateTime/prototype/valueOf.js` at line 11, The
test currently only asserts that dt.valueOf() throws a TypeError; update the
assertion to also verify the exact error message thrown by valueOf so the test
locks to the implementation message from
units/Goccia.Values.TemporalPlainDateTime.pas (the message emitted around lines
742–744). Replace or extend the current expect(() =>
dt.valueOf()).toThrow(TypeError) with a message-aware assertion (e.g., the test
harness's toThrow/toThrowError variant that accepts a message or regex) and use
the exact string used in the implementation to ensure strict matching.
tests/built-ins/Temporal/Instant/from.js (2)

9-13: Strengthen the Instant-input case with a non-alias assertion.

Line 12 checks value equality, but it doesn’t verify object identity behavior. Add expect(i2).not.toBe(i1) to ensure this path is validated as a copy flow, not passthrough.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Temporal/Instant/from.js` around lines 9 - 13, The
Instant.from test currently only asserts epoch milliseconds equality for the
Instant-input path; add an identity assertion to ensure Instant.from produces a
distinct object. In the "from() with Instant" test that creates i1 via
Temporal.Instant.fromEpochMilliseconds and i2 via Temporal.Instant.from(i1),
insert an assertion expect(i2).not.toBe(i1) (in addition to the existing
epochMilliseconds check) to validate the copy-not-alias behavior of
Instant.from.

15-18: Add edge/error coverage for Temporal.Instant.from in this file.

You already have happy paths; add invalid-input/error cases here (e.g., unsupported type, malformed string) to keep method coverage complete in one place.

Proposed test additions
 describe.runIf(isTemporal)("Temporal.Instant.from", () => {
   test("from() with Instant", () => {
     const i1 = Temporal.Instant.fromEpochMilliseconds(5000);
     const i2 = Temporal.Instant.from(i1);
+    expect(i2).not.toBe(i1);
     expect(i2.epochMilliseconds).toBe(5000);
   });
 
   test("from() with ISO string", () => {
     const instant = Temporal.Instant.from("2024-01-15T12:30:45Z");
     expect(instant.epochMilliseconds).toBe(1705321845000);
   });
+
+  test("from() throws on unsupported input type", () => {
+    expect(() => Temporal.Instant.from(123)).toThrow();
+  });
 });

As per coding guidelines: “Edge case tests (...) belong in the same file as happy-path tests for that method.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Temporal/Instant/from.js` around lines 15 - 18, Add negative
test cases for Temporal.Instant.from to cover invalid inputs: add tests that
call Temporal.Instant.from with a malformed ISO string (e.g., "not-a-date") and
with unsupported types (e.g., an object, null, or a non-string/non-temporal
value) and assert that each call throws a RangeError/TypeError as appropriate;
locate calls to Temporal.Instant.from in this file and create new tests named
like "from() with malformed string" and "from() with unsupported type" that use
expect(() => Temporal.Instant.from(...)).toThrow() to verify error behavior.
tests/built-ins/Temporal/Instant/constructor.js (1)

9-12: Add edge/error cases in this same method-focused file.

This file currently has only a happy-path check. Add at least one boundary/error case here (e.g., invalid input handling) so the method coverage is complete in-place.

Based on learnings: “tests should cover happy paths, edge cases, and error cases — all in the same file for each method.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Temporal/Instant/constructor.js` around lines 9 - 12, Add
edge and error-case tests to the existing "epochNanoseconds getter" test file by
extending or adding tests around Temporal.Instant.fromEpochMilliseconds and the
epochNanoseconds property: include at least one invalid-input case (e.g., pass a
non-numeric value like a string or NaN and assert it throws), and a boundary
case (e.g., very large or negative epoch milliseconds to assert correct
epochNanoseconds or an out-of-range error). Locate the existing test block using
the test name "epochNanoseconds getter" and the
Temporal.Instant.fromEpochMilliseconds call, and add assertions that expect
throws for invalid inputs and precise conversions for boundary values to ensure
full method coverage.
tests/built-ins/Temporal/Duration/prototype/total.js (1)

9-25: Add options-bag test for total() to ensure option parsing doesn't regress.

The current tests only exercise the string overload (d.total("minutes")). The Temporal API also accepts an options object (d.total({ unit: "minutes" })). Add at least one options-bag call to catch regressions in option handling for this method.

Suggested test addition
  test("total()", () => {
    const d = new Temporal.Duration(0, 0, 0, 0, 1, 30);
    expect(d.total("minutes")).toBe(90);
    expect(d.total("hours")).toBe(1.5);
    expect(d.total("seconds")).toBe(5400);
+   expect(d.total({ unit: "minutes" })).toBe(90);
  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Temporal/Duration/prototype/total.js` around lines 9 - 25,
Add a test that calls Temporal.Duration.prototype.total using the options-bag
form (e.g., d.total({ unit: "minutes" })) to ensure option parsing is exercised;
update the tests in tests/built-ins/Temporal/Duration/prototype/total.js to
include at least one assertion using the object overload (for example mirroring
an existing string-based assertion like expect(d.total({ unit: "minutes"
})).toBe(90)) and optionally add a corresponding RangeError assertion that uses
the options object (e.g., expect(() => withYears.total({ unit: "days"
})).toThrow(RangeError)).
units/Goccia.Temporal.TimeZone.pas (2)

126-133: Consider extracting magic offset 20 to a named constant.

The offset 20 appears to be the byte position where TZif header fields begin (after magic + version + reserved bytes). Extracting this to a named constant would clarify the intent.

 const
   TZIF_MAGIC = 'TZif';
   TZIF_MAGIC_LENGTH = 4;
   TZIF_HEADER_SIZE = 44;
+  TZIF_HEADER_FIELDS_OFFSET = 20;
   ...

-  Offset := 20;
+  Offset := TZIF_HEADER_FIELDS_OFFSET;

As per coding guidelines: "Extract bare numeric literals in implementation sections into named constants so the value is defined once and the name conveys intent."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Temporal.TimeZone.pas` around lines 126 - 133, Extract the magic
offset 20 into a named constant (e.g. TZIFHeaderOffset or HeaderStartOffset)
declared in the implementation section and replace the literal in the line
"Offset := 20;" with that constant; update any related comments to reference the
constant and leave the subsequent ReadBigEndianInt32 calls (IsUtCnt, IsStdCnt,
LeapCnt, TimeCnt, TypeCnt, CharCnt) unchanged so the intent that the header
starts after magic+version+reserved bytes is clear.

288-307: Resource management could use try..finally for clarity.

The current pattern manually frees Stream in multiple places (lines 294, 300, 305). A try..finally block would be cleaner and safer:

♻️ Suggested refactor
   Stream := nil;
   try
     Stream := TFileStream.Create(FilePath, fmOpenRead or fmShareDenyNone);
-    FileSize := Stream.Size;
-    if FileSize < TZIF_MAGIC_LENGTH + TZIF_HEADER_SIZE then
-    begin
-      Stream.Free;
-      Exit;
-    end;
-
-    SetLength(Buffer, FileSize);
-    Stream.ReadBuffer(Buffer[0], FileSize);
-    Stream.Free;
-    Stream := nil;
-
-    Result := ParseTZifData(ATimeZone, Buffer, FileSize, AData);
-  except
-    Stream.Free;
+    try
+      FileSize := Stream.Size;
+      if FileSize < TZIF_MAGIC_LENGTH + TZIF_HEADER_SIZE then
+        Exit;
+      SetLength(Buffer, FileSize);
+      Stream.ReadBuffer(Buffer[0], FileSize);
+      Result := ParseTZifData(ATimeZone, Buffer, FileSize, AData);
+    finally
+      Stream.Free;
+    end;
+  except
+    // Silently fail - Result remains False
   end;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Temporal.TimeZone.pas` around lines 288 - 307, Replace manual
Stream.Free calls with a try..finally to guarantee Stream is freed; create the
TFileStream (TFileStream.Create) before a try, perform FileSize checks,
SetLength(Buffer) and Stream.ReadBuffer(Buffer[0], FileSize) inside the try,
then in the finally call Stream.Free or FreeAndNil(Stream) if assigned. Remove
the extra Stream.Free calls inside the if and except blocks and ensure the
function still calls ParseTZifData(ATimeZone, Buffer, FileSize, AData) after
reading; reference symbols: TFileStream.Create, Stream.ReadBuffer,
ParseTZifData, Stream.Free/FreeAndNil.
tests/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime.js (1)

1-20: LGTM! Consider adding edge case coverage.

The test correctly validates that withPlainTime preserves the date portion while setting the time components. The guard pattern and structure follow repository conventions.

Per coding guidelines, edge cases belong in the same file. Consider adding tests for:

  • Calling with undefined (should reset to midnight per TC39 spec)
  • Calling with a time string instead of PlainTime object
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime.js` around
lines 1 - 20, Add two edge-case tests to
tests/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime.js: one that
calls ZonedDateTime.prototype.withPlainTime with undefined (e.g.,
zdt.withPlainTime(undefined)) and asserts the time resets to midnight
(hour/minute/second = 0) while date remains unchanged, and another that passes a
time string (e.g., "00:00" or "12:34:56") to withPlainTime and asserts the
resulting hour/minute/second reflect that parsed time while year/month/day
remain unchanged; locate the existing test using the zdt variable and function
under test (withPlainTime / Temporal.ZonedDateTime) and follow the same
describe.runIf(isTemporal) / test structure and assertion style as the existing
cases.
tests/built-ins/Temporal/PlainYearMonth/prototype/toPlainDate.js (1)

1-16: LGTM! Consider edge case for day overflow.

The test correctly validates that toPlainDate({ day: 15 }) combines year/month with the provided day.

Per coding guidelines, edge cases should be in the same file. Consider testing behavior when day exceeds the month's days (e.g., { day: 31 } for February) — the implementation passes the day directly to the PlainDate constructor which may clamp or throw.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Temporal/PlainYearMonth/prototype/toPlainDate.js` around
lines 1 - 16, Add an edge-case test in the same file that verifies
Temporal.PlainYearMonth.prototype.toPlainDate behavior when the provided day
overflows the month: create a PlainYearMonth (e.g., ym = new
Temporal.PlainYearMonth(2024, 2)) and assert that calling ym.toPlainDate({ day:
31 }) throws (use expect(() => ym.toPlainDate({ day: 31 })).toThrow() or the
appropriate RangeError expectation) so the test documents whether toPlainDate
rejects out-of-range days.
tests/built-ins/Temporal/Now/zonedDateTimeISO.js (1)

9-15: Expand coverage to include argument and error paths.

This currently checks only the no-arg happy path. Add an explicit timeZone argument case and one invalid-input/error case in this file to harden the new Now.zonedDateTimeISO behavior.

As per coding guidelines, “tests should cover happy paths, edge cases, and error cases — all in the same file for each method.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Temporal/Now/zonedDateTimeISO.js` around lines 9 - 15, Add
two new tests to tests/built-ins/Temporal/Now/zonedDateTimeISO.js: one that
calls Temporal.Now.zonedDateTimeISO("America/New_York") (or another valid IANA
zone) and asserts the returned object is a ZonedDateTime with a non-empty
timeZoneId equal to the provided zone or that its timeZoneId includes the
expected zone identifier and epochMilliseconds > 0; and another that calls
Temporal.Now.zonedDateTimeISO with an invalid argument (e.g., a number or
"Invalid/Zone") and asserts that it throws the appropriate error (RangeError or
TypeError) using expect(() => Temporal.Now.zonedDateTimeISO(badArg)).toThrow();
reference the Temporal.Now.zonedDateTimeISO call site in the file when adding
these tests.
tests/built-ins/Temporal/ZonedDateTime/prototype/add.js (1)

9-15: Add boundary/error coverage in this method file.

Line 12 currently validates only a simple same-day add. Please add one boundary case (e.g., crossing day/month) plus one invalid-duration/error case in this same file.

As per coding guidelines, “tests should cover happy paths, edge cases, and error cases — all in the same file for each method.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Temporal/ZonedDateTime/prototype/add.js` around lines 9 - 15,
Add two new tests in the same file alongside the existing "add duration" test:
one boundary test that uses a ZonedDateTime near the end of a day/month (e.g.,
create zdt at 23:30 on Jan 31 or just before midnight) and call zdt.add with a
duration that crosses into the next day/month, then assert the expected
hour/day/month values; and one error test that calls zdt.add with an invalid
duration (e.g., a non-object, NaN, or a disallowed property) and asserts it
throws (use expect(() => zdt.add(...)).toThrow()). Reference the existing
variable and method names (zdt, ZonedDateTime, zdt.add) so the new tests match
style and placement with the current "add duration" test.
tests/built-ins/Temporal/ZonedDateTime/prototype/startOfDay.js (1)

8-19: Add an in-file timezone edge case for startOfDay().

Line 11 only validates the UTC happy path. Please add at least one non-UTC boundary case (e.g., DST transition) and keep it in this same file so method coverage includes edge behavior.

As per coding guidelines, “Edge case tests (NaN, Infinity, negative indices, clamping, empty collections, boundary conditions) belong in the same file as happy-path tests for that method.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Temporal/ZonedDateTime/prototype/startOfDay.js` around lines
8 - 19, Add a non‑UTC boundary test for
Temporal.ZonedDateTime.prototype.startOfDay in the same file: create a second
test (e.g., "startOfDay DST/boundary") that constructs a ZonedDateTime (zdt) in
a DST‑observing zone such as "America/New_York" at an instant that lies on or
around a DST transition, call zdt.startOfDay() to get sod, and assert the
expected hour/minute/second/year/month/day for that local start of day so the
edge behaviour (clock jump/back) is covered alongside the existing UTC happy
path.
tests/built-ins/Temporal/PlainDate/prototype/add.js (1)

9-21: Good rollover coverage; add one invalid-input assertion.

Lines 10–20 cover happy + boundary behavior well. Add one explicit error-path test (invalid duration-like input) to complete per-method coverage in this file.

As per coding guidelines, “tests should cover happy paths, edge cases, and error cases — all in the same file for each method.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Temporal/PlainDate/prototype/add.js` around lines 9 - 21, Add
one error-path test for Temporal.PlainDate.prototype.add that asserts a
TypeError is thrown for an invalid duration-like input: create a PlainDate (e.g.
new Temporal.PlainDate(2024,1,1)) and call its add method with an invalid value
(e.g. a plain string or an object with wrong-typed fields), then use expect(()
=> d.add(...)).toThrow(TypeError) — name the test "add() with invalid duration"
to match existing tests.
tests/built-ins/Temporal/ZonedDateTime/prototype/subtract.js (1)

9-15: Please add underflow/error scenarios for subtract().

Current assertions validate a straightforward hour subtraction only. Add one boundary case that crosses date boundaries and one invalid-duration/error case in this same file.

As per coding guidelines, “tests should cover happy paths, edge cases, and error cases — all in the same file for each method.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Temporal/ZonedDateTime/prototype/subtract.js` around lines 9
- 15, Add two tests for Temporal.ZonedDateTime.prototype.subtract in this file:
(1) an edge-case that crosses a date boundary — create a ZonedDateTime near
midnight (e.g., hour 0 and minute 30), call zdt.subtract({ hours: 1 }) and
assert the resulting day and hour reflect the previous calendar day (use the
same zdt variable and check .day and .hour or equivalent properties); (2) an
error-case for invalid duration input — call zdt.subtract with an invalid
duration like { hours: "foo" } and assert it throws (use expect(() =>
zdt.subtract(...)).toThrow() with the appropriate error). Ensure both tests live
alongside the existing "subtract duration" test and reference the same
ZonedDateTime/ subtract usage.
tests/built-ins/Temporal/Duration/prototype/round.js (1)

9-27: Nice baseline set; add one explicit invalid-options case.

These tests cover multiple happy paths well. Please add at least one invalid-options assertion (e.g., unsupported roundingMode or unit) and a boundary tie case to lock behavior.

As per coding guidelines, “tests should cover happy paths, edge cases, and error cases — all in the same file for each method.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Temporal/Duration/prototype/round.js` around lines 9 - 27,
Add two tests for Temporal.Duration.prototype.round: one invalid-options case
and one boundary tie case. For the invalid case call Duration.prototype.round on
a Duration (e.g., new Temporal.Duration(...)) with an unsupported roundingMode
or unit (e.g., roundingMode: "invalid" or smallestUnit: "lightyear") and assert
it throws the appropriate error (expect(...).toThrow(RangeError/TypeError as
applicable). For the tie/boundary case create a duration that ties exactly on
the midpoint for hours (e.g., 0 hours 30 minutes) and call round({ smallestUnit:
"hour", roundingMode: "halfExpand" }) and assert the resulting hours value
matches the expected tie-breaking behavior (1 for halfExpand); add clear test
names like "round invalid options" and "round tie boundary" to match existing
test style.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/built-ins/Temporal/Instant/constructor.js`:
- Around line 2-12: The suite title "Temporal.Instant constructor" is misleading
because the test exercises Temporal.Instant.fromEpochMilliseconds and the
epochNanoseconds getter; update the suite to match the API under test or add a
real constructor test: either rename the describe.runIf title to something like
"Temporal.Instant.fromEpochMilliseconds / epochNanoseconds" and leave the
existing test (referencing Temporal.Instant.fromEpochMilliseconds and
instant.epochNanoseconds), or add a new test that constructs an Instant via the
constructor (if supported) and asserts its epochNanoseconds, keeping test names
and the describe.runIf title consistent with the symbols under test.

In `@units/Goccia.Builtins.Temporal.pas`:
- Around line 997-1021: The code currently lets an absent or malformed month
fall through as M = 0 and defers failure to
TGocciaTemporalPlainMonthDayValue.Create; update the parsing logic around
Obj.GetProperty('monthCode') / Obj.GetProperty('month') so that if neither
property is present/defined or the parsed month value (M) is not a valid month
(e.g., not in 1..12) you call ThrowTypeError('Temporal.PlainMonthDay.from
requires month property') immediately (similar to the existing day check),
ensuring TGocciaObjectValue handling, monthCode parsing and numeric month
truncation are validated before calling
TGocciaTemporalPlainMonthDayValue.Create.
- Around line 1157-1175: CoerceZDT currently only accepts
TGocciaTemporalZonedDateTimeValue instances; change it to also accept string
inputs by reusing the same string coercion/parsing path used by the
ZonedDateTime.from surface: if AArg is TGocciaTemporalZonedDateTimeValue cast to
TGocciaTemporalZonedDateTimeValue, else if AArg is a string value call the
existing/from-parsing function used by ZonedDateTime.from to produce a
TGocciaTemporalZonedDateTimeValue, otherwise call ThrowTypeError; update
ZonedDateTimeCompare to use this revised CoerceZDT so ISO strings compare
correctly.
- Around line 497-519: The code only constrains Day > MaxDay but doesn't clamp
month or low day values when Overflow is set to constrain; update the
Temporal.PlainDate.from flow so after reading Y, Mo, Dy (via Obj.GetProperty and
Trunc(V.ToNumberLiteral.Value)) you first clamp Mo into 1..12 (if Overflow <>
toReject) then recompute MaxDay := DaysInMonth(Y, clampedMo) and clamp Dy into
1..MaxDay (also handling Dy < 1) before calling
TGocciaTemporalPlainDateValue.Create; keep existing ThrowRangeError behavior
when Overflow = toReject and use ThrowTypeError for missing props as before.

In `@units/Goccia.Values.TemporalDuration.pas`:
- Around line 564-567: SmallestUnit may be tuWeek and UnitToNanoseconds rejects
calendar units, so handle tuWeek before calling UnitToNanoseconds: if
SmallestUnit = tuWeek compute Divisor as 7 * UnitToNanoseconds(tuDay) *
Increment (or otherwise convert weeks to days/nanoseconds) and then call
RoundWithMode; for all other non-calendar units continue to call
UnitToNanoseconds(SmallestUnit) * Increment. Refer to SmallestUnit, tuWeek,
UnitToNanoseconds and RoundWithMode to locate and update the branch.

In `@units/Goccia.Values.TemporalPlainDateTime.pas`:
- Around line 790-815: GetUtcOffsetSeconds currently falls back to 0 when zone
data is missing, so update the code to reject unknown zone IDs: change
GetUtcOffsetSeconds to either raise an exception on unknown/failed zone lookup
or provide a boolean/sentinel indicating failure, then in the
PlainDateTime->toZonedDateTime code (the block constructing EpochMs and calling
GetUtcOffsetSeconds) check that lookup result and if it indicates failure call
ThrowTypeError('PlainDateTime.prototype.toZonedDateTime requires a valid
timeZone') instead of using offset 0; reference the GetUtcOffsetSeconds function
and the TGocciaTemporalZonedDateTimeValue creation site so the invalid ID is
rejected before creating the result.

In `@units/Goccia.Values.TemporalPlainMonthDay.pas`:
- Around line 227-239: The current branch only reads monthCode and never honors
a numeric month property, so when callers pass a property-bag like { month: 8 }
NewMonth remains MD.FMonth; fix by reading and applying the numeric 'month'
property before/alongside the existing monthCode handling: call
Obj.GetProperty('month'), check Assigned(V) and not
TGocciaUndefinedLiteralValue, extract/validate the integer (using TryStrToInt or
the existing month-field helper) and assign NewMonth accordingly, then keep the
monthCode logic to validate/override per the specification; update the code
around NewMonth, Obj.GetProperty('monthCode'), and the subsequent
TGocciaTemporalPlainMonthDayValue.Create(...) use.
- Around line 94-110: Coerce the PlainMonthDay object to accept numeric "month"
like the constructor: when AValue is TGocciaObjectValue, if
Obj.GetProperty('monthCode') is nil or undefined then try
Obj.GetProperty('month') and, if present and numeric, use its integer value as
MonthPart (validate range as needed) instead of throwing; otherwise fall back to
parsing monthCode as currently done; update the code paths around
VMonthCode/VDay, MonthPart and the call to
TGocciaTemporalPlainMonthDayValue.Create to use the derived MonthPart from
either monthCode or month and keep ThrowTypeError for missing/invalid inputs.

In `@units/Goccia.Values.TemporalZonedDateTime.pas`:
- Around line 267-276: The constructor TGocciaTemporalZonedDateTimeValue.Create
currently accepts any non-empty ATimeZone and stores bogus zones; before
assigning FTimeZone call a timezone-validation helper (e.g. a new
IsValidTimeZoneId or by attempting to load the zone via the existing TimeZone
loader) and throw a TypeError if the identifier cannot be resolved; reference
TGocciaTemporalZonedDateTimeValue.Create and the TimeZone
loader/GetUtcOffsetSeconds logic to implement the check so unknown IDs are
rejected at construction (or add a small helper in the TimeZone unit to detect
load failures and use it here).
- Around line 939-959: The day-branch currently uses a fixed NANOSECONDS_PER_DAY
which breaks across DST; instead compute the actual length of the local day in
nanoseconds and use that as the divisor. Specifically: compute StartEpochMs :=
LocalToEpochMs(LYear,LMonth,LDay,0,0,0,0,Zdt.FTimeZone), compute NextDate :=
AddDaysToDate(LYear,LMonth,LDay,1) and NextEpochMs :=
LocalToEpochMs(NextDate.Year,NextDate.Month,NextDate.Day,0,0,0,0,Zdt.FTimeZone),
set DayNs := Int64(NextEpochMs - StartEpochMs) * NANOSECONDS_PER_MILLISECOND,
set Divisor := DayNs * Increment, call Rounded := RoundWithMode(TotalNs,
Divisor, Mode), compute ExtraDays := Rounded div DayNs and then use
AddDaysToDate/LYear/LMonth/LDay/ExtraDays (or set DateRec to LYear/LMonth/LDay
when ExtraDays=0) and use StartEpochMs (or adjusted epoch) when creating
TGocciaTemporalZonedDateTimeValue; you can also use GetHoursInDay as a helper to
validate the computed day length if desired.

---

Nitpick comments:
In `@tests/built-ins/Temporal/Duration/prototype/round.js`:
- Around line 9-27: Add two tests for Temporal.Duration.prototype.round: one
invalid-options case and one boundary tie case. For the invalid case call
Duration.prototype.round on a Duration (e.g., new Temporal.Duration(...)) with
an unsupported roundingMode or unit (e.g., roundingMode: "invalid" or
smallestUnit: "lightyear") and assert it throws the appropriate error
(expect(...).toThrow(RangeError/TypeError as applicable). For the tie/boundary
case create a duration that ties exactly on the midpoint for hours (e.g., 0
hours 30 minutes) and call round({ smallestUnit: "hour", roundingMode:
"halfExpand" }) and assert the resulting hours value matches the expected
tie-breaking behavior (1 for halfExpand); add clear test names like "round
invalid options" and "round tie boundary" to match existing test style.

In `@tests/built-ins/Temporal/Duration/prototype/total.js`:
- Around line 9-25: Add a test that calls Temporal.Duration.prototype.total
using the options-bag form (e.g., d.total({ unit: "minutes" })) to ensure option
parsing is exercised; update the tests in
tests/built-ins/Temporal/Duration/prototype/total.js to include at least one
assertion using the object overload (for example mirroring an existing
string-based assertion like expect(d.total({ unit: "minutes" })).toBe(90)) and
optionally add a corresponding RangeError assertion that uses the options object
(e.g., expect(() => withYears.total({ unit: "days" })).toThrow(RangeError)).

In `@tests/built-ins/Temporal/Instant/constructor.js`:
- Around line 9-12: Add edge and error-case tests to the existing
"epochNanoseconds getter" test file by extending or adding tests around
Temporal.Instant.fromEpochMilliseconds and the epochNanoseconds property:
include at least one invalid-input case (e.g., pass a non-numeric value like a
string or NaN and assert it throws), and a boundary case (e.g., very large or
negative epoch milliseconds to assert correct epochNanoseconds or an
out-of-range error). Locate the existing test block using the test name
"epochNanoseconds getter" and the Temporal.Instant.fromEpochMilliseconds call,
and add assertions that expect throws for invalid inputs and precise conversions
for boundary values to ensure full method coverage.

In `@tests/built-ins/Temporal/Instant/from.js`:
- Around line 9-13: The Instant.from test currently only asserts epoch
milliseconds equality for the Instant-input path; add an identity assertion to
ensure Instant.from produces a distinct object. In the "from() with Instant"
test that creates i1 via Temporal.Instant.fromEpochMilliseconds and i2 via
Temporal.Instant.from(i1), insert an assertion expect(i2).not.toBe(i1) (in
addition to the existing epochMilliseconds check) to validate the copy-not-alias
behavior of Instant.from.
- Around line 15-18: Add negative test cases for Temporal.Instant.from to cover
invalid inputs: add tests that call Temporal.Instant.from with a malformed ISO
string (e.g., "not-a-date") and with unsupported types (e.g., an object, null,
or a non-string/non-temporal value) and assert that each call throws a
RangeError/TypeError as appropriate; locate calls to Temporal.Instant.from in
this file and create new tests named like "from() with malformed string" and
"from() with unsupported type" that use expect(() =>
Temporal.Instant.from(...)).toThrow() to verify error behavior.

In `@tests/built-ins/Temporal/Now/zonedDateTimeISO.js`:
- Around line 9-15: Add two new tests to
tests/built-ins/Temporal/Now/zonedDateTimeISO.js: one that calls
Temporal.Now.zonedDateTimeISO("America/New_York") (or another valid IANA zone)
and asserts the returned object is a ZonedDateTime with a non-empty timeZoneId
equal to the provided zone or that its timeZoneId includes the expected zone
identifier and epochMilliseconds > 0; and another that calls
Temporal.Now.zonedDateTimeISO with an invalid argument (e.g., a number or
"Invalid/Zone") and asserts that it throws the appropriate error (RangeError or
TypeError) using expect(() => Temporal.Now.zonedDateTimeISO(badArg)).toThrow();
reference the Temporal.Now.zonedDateTimeISO call site in the file when adding
these tests.

In `@tests/built-ins/Temporal/PlainDate/compare.js`:
- Around line 9-15: Add edge-case assertions to the existing compare() test in
tests/built-ins/Temporal/PlainDate/compare.js: call Temporal.PlainDate.compare
with ISO date strings (e.g., "2024-01-01" and "2024-06-01") and assert the same
-1/1/0 results as the PlainDate instances, and add assertions that invalid
inputs (e.g., null, an object, or a non-ISO string) throw a TypeError when
passed to Temporal.PlainDate.compare; update the single "compare()" test to
include these ISO-string happy-path checks and the invalid-input throws so all
cases are co-located with the existing Temporal.PlainDate.compare(d1, d2)
assertions.

In `@tests/built-ins/Temporal/PlainDate/prototype/add.js`:
- Around line 9-21: Add one error-path test for Temporal.PlainDate.prototype.add
that asserts a TypeError is thrown for an invalid duration-like input: create a
PlainDate (e.g. new Temporal.PlainDate(2024,1,1)) and call its add method with
an invalid value (e.g. a plain string or an object with wrong-typed fields),
then use expect(() => d.add(...)).toThrow(TypeError) — name the test "add() with
invalid duration" to match existing tests.

In `@tests/built-ins/Temporal/PlainDateTime/prototype/valueOf.js`:
- Line 11: The test currently only asserts that dt.valueOf() throws a TypeError;
update the assertion to also verify the exact error message thrown by valueOf so
the test locks to the implementation message from
units/Goccia.Values.TemporalPlainDateTime.pas (the message emitted around lines
742–744). Replace or extend the current expect(() =>
dt.valueOf()).toThrow(TypeError) with a message-aware assertion (e.g., the test
harness's toThrow/toThrowError variant that accepts a message or regex) and use
the exact string used in the implementation to ensure strict matching.

In `@tests/built-ins/Temporal/PlainYearMonth/prototype/toPlainDate.js`:
- Around line 1-16: Add an edge-case test in the same file that verifies
Temporal.PlainYearMonth.prototype.toPlainDate behavior when the provided day
overflows the month: create a PlainYearMonth (e.g., ym = new
Temporal.PlainYearMonth(2024, 2)) and assert that calling ym.toPlainDate({ day:
31 }) throws (use expect(() => ym.toPlainDate({ day: 31 })).toThrow() or the
appropriate RangeError expectation) so the test documents whether toPlainDate
rejects out-of-range days.

In `@tests/built-ins/Temporal/ZonedDateTime/prototype/add.js`:
- Around line 9-15: Add two new tests in the same file alongside the existing
"add duration" test: one boundary test that uses a ZonedDateTime near the end of
a day/month (e.g., create zdt at 23:30 on Jan 31 or just before midnight) and
call zdt.add with a duration that crosses into the next day/month, then assert
the expected hour/day/month values; and one error test that calls zdt.add with
an invalid duration (e.g., a non-object, NaN, or a disallowed property) and
asserts it throws (use expect(() => zdt.add(...)).toThrow()). Reference the
existing variable and method names (zdt, ZonedDateTime, zdt.add) so the new
tests match style and placement with the current "add duration" test.

In `@tests/built-ins/Temporal/ZonedDateTime/prototype/startOfDay.js`:
- Around line 8-19: Add a non‑UTC boundary test for
Temporal.ZonedDateTime.prototype.startOfDay in the same file: create a second
test (e.g., "startOfDay DST/boundary") that constructs a ZonedDateTime (zdt) in
a DST‑observing zone such as "America/New_York" at an instant that lies on or
around a DST transition, call zdt.startOfDay() to get sod, and assert the
expected hour/minute/second/year/month/day for that local start of day so the
edge behaviour (clock jump/back) is covered alongside the existing UTC happy
path.

In `@tests/built-ins/Temporal/ZonedDateTime/prototype/subtract.js`:
- Around line 9-15: Add two tests for Temporal.ZonedDateTime.prototype.subtract
in this file: (1) an edge-case that crosses a date boundary — create a
ZonedDateTime near midnight (e.g., hour 0 and minute 30), call zdt.subtract({
hours: 1 }) and assert the resulting day and hour reflect the previous calendar
day (use the same zdt variable and check .day and .hour or equivalent
properties); (2) an error-case for invalid duration input — call zdt.subtract
with an invalid duration like { hours: "foo" } and assert it throws (use
expect(() => zdt.subtract(...)).toThrow() with the appropriate error). Ensure
both tests live alongside the existing "subtract duration" test and reference
the same ZonedDateTime/ subtract usage.

In `@tests/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime.js`:
- Around line 1-20: Add two edge-case tests to
tests/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime.js: one that
calls ZonedDateTime.prototype.withPlainTime with undefined (e.g.,
zdt.withPlainTime(undefined)) and asserts the time resets to midnight
(hour/minute/second = 0) while date remains unchanged, and another that passes a
time string (e.g., "00:00" or "12:34:56") to withPlainTime and asserts the
resulting hour/minute/second reflect that parsed time while year/month/day
remain unchanged; locate the existing test using the zdt variable and function
under test (withPlainTime / Temporal.ZonedDateTime) and follow the same
describe.runIf(isTemporal) / test structure and assertion style as the existing
cases.

In `@units/Goccia.Temporal.TimeZone.pas`:
- Around line 126-133: Extract the magic offset 20 into a named constant (e.g.
TZIFHeaderOffset or HeaderStartOffset) declared in the implementation section
and replace the literal in the line "Offset := 20;" with that constant; update
any related comments to reference the constant and leave the subsequent
ReadBigEndianInt32 calls (IsUtCnt, IsStdCnt, LeapCnt, TimeCnt, TypeCnt, CharCnt)
unchanged so the intent that the header starts after magic+version+reserved
bytes is clear.
- Around line 288-307: Replace manual Stream.Free calls with a try..finally to
guarantee Stream is freed; create the TFileStream (TFileStream.Create) before a
try, perform FileSize checks, SetLength(Buffer) and Stream.ReadBuffer(Buffer[0],
FileSize) inside the try, then in the finally call Stream.Free or
FreeAndNil(Stream) if assigned. Remove the extra Stream.Free calls inside the if
and except blocks and ensure the function still calls ParseTZifData(ATimeZone,
Buffer, FileSize, AData) after reading; reference symbols: TFileStream.Create,
Stream.ReadBuffer, ParseTZifData, Stream.Free/FreeAndNil.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d06e50a3-7789-4d93-972d-6fa8c1b86911

📥 Commits

Reviewing files that changed from the base of the PR and between 46eb927 and 53c5852.

📒 Files selected for processing (151)
  • tests/built-ins/Temporal/Duration/compare.js
  • tests/built-ins/Temporal/Duration/constructor.js
  • tests/built-ins/Temporal/Duration/from.js
  • tests/built-ins/Temporal/Duration/prototype/abs.js
  • tests/built-ins/Temporal/Duration/prototype/add.js
  • tests/built-ins/Temporal/Duration/prototype/blank.js
  • tests/built-ins/Temporal/Duration/prototype/negated.js
  • tests/built-ins/Temporal/Duration/prototype/round.js
  • tests/built-ins/Temporal/Duration/prototype/sign.js
  • tests/built-ins/Temporal/Duration/prototype/subtract.js
  • tests/built-ins/Temporal/Duration/prototype/toJSON.js
  • tests/built-ins/Temporal/Duration/prototype/toString.js
  • tests/built-ins/Temporal/Duration/prototype/toStringTag.js
  • tests/built-ins/Temporal/Duration/prototype/total.js
  • tests/built-ins/Temporal/Duration/prototype/valueOf.js
  • tests/built-ins/Temporal/Duration/prototype/with.js
  • tests/built-ins/Temporal/Instant/compare.js
  • tests/built-ins/Temporal/Instant/constructor.js
  • tests/built-ins/Temporal/Instant/from.js
  • tests/built-ins/Temporal/Instant/fromEpochMilliseconds.js
  • tests/built-ins/Temporal/Instant/fromEpochNanoseconds.js
  • tests/built-ins/Temporal/Instant/prototype/add.js
  • tests/built-ins/Temporal/Instant/prototype/equals.js
  • tests/built-ins/Temporal/Instant/prototype/round.js
  • tests/built-ins/Temporal/Instant/prototype/since.js
  • tests/built-ins/Temporal/Instant/prototype/subtract.js
  • tests/built-ins/Temporal/Instant/prototype/toJSON.js
  • tests/built-ins/Temporal/Instant/prototype/toString.js
  • tests/built-ins/Temporal/Instant/prototype/toStringTag.js
  • tests/built-ins/Temporal/Instant/prototype/until.js
  • tests/built-ins/Temporal/Instant/prototype/valueOf.js
  • tests/built-ins/Temporal/Now/instant.js
  • tests/built-ins/Temporal/Now/plainDateISO.js
  • tests/built-ins/Temporal/Now/plainDateTimeISO.js
  • tests/built-ins/Temporal/Now/plainTimeISO.js
  • tests/built-ins/Temporal/Now/timeZoneId.js
  • tests/built-ins/Temporal/Now/zonedDateTimeISO.js
  • tests/built-ins/Temporal/PlainDate/compare.js
  • tests/built-ins/Temporal/PlainDate/constructor.js
  • tests/built-ins/Temporal/PlainDate/from.js
  • tests/built-ins/Temporal/PlainDate/prototype/add.js
  • tests/built-ins/Temporal/PlainDate/prototype/calendarId.js
  • tests/built-ins/Temporal/PlainDate/prototype/dayOfWeek.js
  • tests/built-ins/Temporal/PlainDate/prototype/equals.js
  • tests/built-ins/Temporal/PlainDate/prototype/since.js
  • tests/built-ins/Temporal/PlainDate/prototype/subtract.js
  • tests/built-ins/Temporal/PlainDate/prototype/toJSON.js
  • tests/built-ins/Temporal/PlainDate/prototype/toPlainDateTime.js
  • tests/built-ins/Temporal/PlainDate/prototype/toPlainMonthDay.js
  • tests/built-ins/Temporal/PlainDate/prototype/toPlainYearMonth.js
  • tests/built-ins/Temporal/PlainDate/prototype/toString.js
  • tests/built-ins/Temporal/PlainDate/prototype/toStringTag.js
  • tests/built-ins/Temporal/PlainDate/prototype/toZonedDateTime.js
  • tests/built-ins/Temporal/PlainDate/prototype/until.js
  • tests/built-ins/Temporal/PlainDate/prototype/valueOf.js
  • tests/built-ins/Temporal/PlainDate/prototype/with.js
  • tests/built-ins/Temporal/PlainDateTime/compare.js
  • tests/built-ins/Temporal/PlainDateTime/constructor.js
  • tests/built-ins/Temporal/PlainDateTime/from.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/add.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/equals.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/round.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/since.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/subtract.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/toJSON.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/toPlainDate.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/toPlainMonthDay.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/toPlainTime.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/toPlainYearMonth.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/toString.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/toStringTag.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/until.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/valueOf.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/with.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/withPlainTime.js
  • tests/built-ins/Temporal/PlainMonthDay/compare.js
  • tests/built-ins/Temporal/PlainMonthDay/constructor.js
  • tests/built-ins/Temporal/PlainMonthDay/from.js
  • tests/built-ins/Temporal/PlainMonthDay/prototype/equals.js
  • tests/built-ins/Temporal/PlainMonthDay/prototype/toJSON.js
  • tests/built-ins/Temporal/PlainMonthDay/prototype/toPlainDate.js
  • tests/built-ins/Temporal/PlainMonthDay/prototype/toString.js
  • tests/built-ins/Temporal/PlainMonthDay/prototype/toStringTag.js
  • tests/built-ins/Temporal/PlainMonthDay/prototype/valueOf.js
  • tests/built-ins/Temporal/PlainTime/compare.js
  • tests/built-ins/Temporal/PlainTime/constructor.js
  • tests/built-ins/Temporal/PlainTime/from.js
  • tests/built-ins/Temporal/PlainTime/prototype/add.js
  • tests/built-ins/Temporal/PlainTime/prototype/equals.js
  • tests/built-ins/Temporal/PlainTime/prototype/round.js
  • tests/built-ins/Temporal/PlainTime/prototype/since.js
  • tests/built-ins/Temporal/PlainTime/prototype/subtract.js
  • tests/built-ins/Temporal/PlainTime/prototype/toJSON.js
  • tests/built-ins/Temporal/PlainTime/prototype/toPlainDateTime.js
  • tests/built-ins/Temporal/PlainTime/prototype/toString.js
  • tests/built-ins/Temporal/PlainTime/prototype/toStringTag.js
  • tests/built-ins/Temporal/PlainTime/prototype/until.js
  • tests/built-ins/Temporal/PlainTime/prototype/valueOf.js
  • tests/built-ins/Temporal/PlainTime/prototype/with.js
  • tests/built-ins/Temporal/PlainYearMonth/compare.js
  • tests/built-ins/Temporal/PlainYearMonth/constructor.js
  • tests/built-ins/Temporal/PlainYearMonth/from.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/add.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/equals.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/since.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/subtract.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/toJSON.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/toPlainDate.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/toString.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/toStringTag.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/until.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/valueOf.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/with.js
  • tests/built-ins/Temporal/ZonedDateTime/compare.js
  • tests/built-ins/Temporal/ZonedDateTime/constructor.js
  • tests/built-ins/Temporal/ZonedDateTime/from.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/add.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/calendarGetters.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/equals.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/round.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/startOfDay.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/subtract.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/toInstant.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/toJSON.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/toPlainDate.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/toPlainDateTime.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/toPlainTime.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/toString.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/toStringTag.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/valueOf.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/with.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/withPlainTime.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/withTimeZone.js
  • tests/built-ins/Temporal/duration.js
  • tests/built-ins/Temporal/instant.js
  • tests/built-ins/Temporal/now.js
  • tests/built-ins/Temporal/plain-date-time.js
  • tests/built-ins/Temporal/plain-date.js
  • tests/built-ins/Temporal/plain-time.js
  • tests/built-ins/Temporal/toStringTag.js
  • units/Goccia.Builtins.Temporal.pas
  • units/Goccia.Temporal.Options.pas
  • units/Goccia.Temporal.TimeZone.pas
  • units/Goccia.Temporal.Utils.pas
  • units/Goccia.Values.TemporalDuration.pas
  • units/Goccia.Values.TemporalPlainDate.pas
  • units/Goccia.Values.TemporalPlainDateTime.pas
  • units/Goccia.Values.TemporalPlainMonthDay.pas
  • units/Goccia.Values.TemporalZonedDateTime.pas
💤 Files with no reviewable changes (6)
  • tests/built-ins/Temporal/plain-time.js
  • tests/built-ins/Temporal/plain-date.js
  • tests/built-ins/Temporal/plain-date-time.js
  • tests/built-ins/Temporal/duration.js
  • tests/built-ins/Temporal/instant.js
  • tests/built-ins/Temporal/now.js
✅ Files skipped from review due to trivial changes (107)
  • tests/built-ins/Temporal/PlainTime/prototype/with.js
  • tests/built-ins/Temporal/Duration/compare.js
  • tests/built-ins/Temporal/Duration/prototype/abs.js
  • tests/built-ins/Temporal/Duration/prototype/add.js
  • tests/built-ins/Temporal/Duration/prototype/blank.js
  • tests/built-ins/Temporal/Duration/prototype/negated.js
  • tests/built-ins/Temporal/Duration/prototype/sign.js
  • tests/built-ins/Temporal/Duration/prototype/subtract.js
  • tests/built-ins/Temporal/Duration/prototype/toJSON.js
  • tests/built-ins/Temporal/Duration/prototype/toString.js
  • tests/built-ins/Temporal/Duration/prototype/valueOf.js
  • tests/built-ins/Temporal/Duration/prototype/with.js
  • tests/built-ins/Temporal/Instant/fromEpochNanoseconds.js
  • tests/built-ins/Temporal/Instant/prototype/equals.js
  • tests/built-ins/Temporal/Instant/prototype/subtract.js
  • tests/built-ins/Temporal/Instant/prototype/since.js
  • tests/built-ins/Temporal/Instant/prototype/toString.js
  • tests/built-ins/Temporal/Instant/prototype/toStringTag.js
  • tests/built-ins/Temporal/Instant/prototype/until.js
  • tests/built-ins/Temporal/Instant/prototype/valueOf.js
  • tests/built-ins/Temporal/Now/instant.js
  • tests/built-ins/Temporal/Now/plainDateTimeISO.js
  • tests/built-ins/Temporal/Now/plainTimeISO.js
  • tests/built-ins/Temporal/Now/timeZoneId.js
  • tests/built-ins/Temporal/PlainDate/constructor.js
  • tests/built-ins/Temporal/PlainDate/prototype/calendarId.js
  • tests/built-ins/Temporal/PlainDate/prototype/equals.js
  • tests/built-ins/Temporal/PlainDate/prototype/since.js
  • tests/built-ins/Temporal/PlainDate/prototype/subtract.js
  • tests/built-ins/Temporal/PlainDate/prototype/toJSON.js
  • tests/built-ins/Temporal/PlainDate/prototype/toPlainDateTime.js
  • tests/built-ins/Temporal/PlainDate/prototype/toPlainMonthDay.js
  • tests/built-ins/Temporal/PlainDate/prototype/toPlainYearMonth.js
  • tests/built-ins/Temporal/PlainDate/prototype/toString.js
  • tests/built-ins/Temporal/PlainDate/prototype/toStringTag.js
  • tests/built-ins/Temporal/PlainDate/prototype/toZonedDateTime.js
  • tests/built-ins/Temporal/PlainDate/prototype/until.js
  • tests/built-ins/Temporal/PlainDate/prototype/valueOf.js
  • tests/built-ins/Temporal/PlainDate/prototype/with.js
  • tests/built-ins/Temporal/PlainDateTime/compare.js
  • tests/built-ins/Temporal/PlainDateTime/from.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/add.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/equals.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/round.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/since.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/subtract.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/toJSON.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/toPlainDate.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/toPlainMonthDay.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/toPlainYearMonth.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/toString.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/until.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/with.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/withPlainTime.js
  • tests/built-ins/Temporal/PlainMonthDay/compare.js
  • tests/built-ins/Temporal/PlainMonthDay/constructor.js
  • tests/built-ins/Temporal/PlainMonthDay/from.js
  • tests/built-ins/Temporal/PlainMonthDay/prototype/equals.js
  • tests/built-ins/Temporal/PlainMonthDay/prototype/toJSON.js
  • tests/built-ins/Temporal/PlainMonthDay/prototype/toPlainDate.js
  • tests/built-ins/Temporal/PlainMonthDay/prototype/toString.js
  • tests/built-ins/Temporal/PlainMonthDay/prototype/toStringTag.js
  • tests/built-ins/Temporal/PlainTime/compare.js
  • tests/built-ins/Temporal/PlainTime/from.js
  • tests/built-ins/Temporal/PlainTime/constructor.js
  • tests/built-ins/Temporal/PlainTime/prototype/add.js
  • tests/built-ins/Temporal/PlainTime/prototype/equals.js
  • tests/built-ins/Temporal/PlainTime/prototype/since.js
  • tests/built-ins/Temporal/PlainTime/prototype/subtract.js
  • tests/built-ins/Temporal/PlainTime/prototype/toJSON.js
  • tests/built-ins/Temporal/PlainTime/prototype/toPlainDateTime.js
  • tests/built-ins/Temporal/PlainTime/prototype/toString.js
  • tests/built-ins/Temporal/PlainTime/prototype/toStringTag.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/add.js
  • tests/built-ins/Temporal/PlainYearMonth/from.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/subtract.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/since.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/toJSON.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/equals.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/toStringTag.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/toString.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/until.js
  • tests/built-ins/Temporal/ZonedDateTime/compare.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/valueOf.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/equals.js
  • tests/built-ins/Temporal/PlainYearMonth/prototype/with.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/calendarGetters.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/toPlainTime.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/round.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/toPlainDateTime.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/toPlainDate.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay.js
  • tests/built-ins/Temporal/PlainMonthDay/prototype/valueOf.js
  • tests/built-ins/Temporal/ZonedDateTime/from.js
  • tests/built-ins/Temporal/Now/plainDateISO.js
  • tests/built-ins/Temporal/PlainDate/prototype/dayOfWeek.js
  • tests/built-ins/Temporal/PlainDateTime/constructor.js
  • tests/built-ins/Temporal/PlainDateTime/prototype/toPlainTime.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/valueOf.js
  • tests/built-ins/Temporal/PlainTime/prototype/until.js
  • tests/built-ins/Temporal/PlainTime/prototype/round.js
  • tests/built-ins/Temporal/PlainYearMonth/compare.js
  • tests/built-ins/Temporal/Instant/compare.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/withTimeZone.js
  • tests/built-ins/Temporal/ZonedDateTime/prototype/toStringTag.js
  • tests/built-ins/Temporal/Instant/fromEpochMilliseconds.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/built-ins/Temporal/toStringTag.js
  • units/Goccia.Values.TemporalPlainDate.pas

Comment thread tests/built-ins/Temporal/Instant/constructor.js Outdated
Comment thread units/Goccia.Builtins.Temporal.pas
Comment thread units/Goccia.Builtins.Temporal.pas
Comment thread units/Goccia.Builtins.Temporal.pas
Comment thread units/Goccia.Values.TemporalDuration.pas
Comment thread units/Goccia.Values.TemporalPlainDateTime.pas
Comment thread units/Goccia.Values.TemporalPlainMonthDay.pas
Comment thread units/Goccia.Values.TemporalPlainMonthDay.pas
Comment thread units/Goccia.Values.TemporalZonedDateTime.pas
Comment thread units/Goccia.Values.TemporalZonedDateTime.pas
Address 11 new CodeRabbit findings:
- Fix PlainDate.from overflow constrain: clamp month to 1..12 and day >= 1
- Fix PlainMonthDay.from: throw TypeError on missing/invalid month
- Fix CoercePlainMonthDay to accept numeric month (not just monthCode)
- Fix PlainMonthDay.with to accept numeric month property
- Fix ZonedDateTime.compare to accept ISO strings (same as from)
- Fix Duration.round: handle smallestUnit "week" before UnitToNanoseconds
- Add timezone validation in ZonedDateTime constructor via IsValidTimeZone
- Add timezone validation in PlainDateTime.toZonedDateTime
- Fix ZonedDateTime.round day branch to use DST-aware day length
- Fix Instant/constructor.js test title mismatch (merged into fromEpochMilliseconds.js)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
units/Goccia.Values.TemporalDuration.pas (1)

538-550: ⚠️ Potential issue | 🟠 Major

Normalize and validate largestUnit before decomposition.

This block still mishandles two cases: the default scan skips FWeeks, so Temporal.Duration.from({ weeks: 1 }).round({ smallestUnit: 'day' }) collapses to days, and explicit contradictory options like { largestUnit: 'minute', smallestUnit: 'day' } are accepted because the ordering check only runs when largestUnit was omitted. Both should be fixed before the breakdown step.

Suggested fix
   if LargestUnit = tuNone then
   begin
     // Default largestUnit to the largest defined unit of the duration
-    if D.FDays <> 0 then LargestUnit := tuDay
+    if D.FWeeks <> 0 then LargestUnit := tuWeek
+    else if D.FDays <> 0 then LargestUnit := tuDay
     else if D.FHours <> 0 then LargestUnit := tuHour
     else if D.FMinutes <> 0 then LargestUnit := tuMinute
     else if D.FSeconds <> 0 then LargestUnit := tuSecond
     else if D.FMilliseconds <> 0 then LargestUnit := tuMillisecond
     else if D.FMicroseconds <> 0 then LargestUnit := tuMicrosecond
     else LargestUnit := SmallestUnit;
     // Ensure largestUnit >= smallestUnit
     if Ord(LargestUnit) > Ord(SmallestUnit) then
       LargestUnit := SmallestUnit;
   end;
+  if (LargestUnit <> tuNone) and (Ord(LargestUnit) > Ord(SmallestUnit)) then
+    ThrowRangeError('largestUnit must be greater than or equal to smallestUnit');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Values.TemporalDuration.pas` around lines 538 - 550, The
LargestUnit defaulting/validation block must include weeks in the default scan
and always enforce ordering regardless of whether LargestUnit was provided: when
LargestUnit = tuNone, include a check for D.FWeeks before days (use tuWeek), and
after determining LargestUnit (whether defaulted or explicit) validate that
Ord(LargestUnit) <= Ord(SmallestUnit) and if not, set LargestUnit :=
SmallestUnit; apply this change in the existing block around
LargestUnit/SmallestUnit before the decomposition code (symbols: LargestUnit,
SmallestUnit, D.FWeeks, tuWeek).
🧹 Nitpick comments (1)
units/Goccia.Values.TemporalPlainMonthDay.pas (1)

188-297: Normalize ECMAScript spec comment format to the repository standard.

The current // TC39 Temporal §... annotations should follow the required // ESYYYY §X.Y.Z SpecMethodName(specParams) format for spec-traceability consistency.

As per coding guidelines, "When implementing ECMAScript-specified behavior, annotate each function with a comment referencing the spec section. Use format // ESYYYY §X.Y.Z SpecMethodName(specParams) ..."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Values.TemporalPlainMonthDay.pas` around lines 188 - 297, Update
the spec-comment headers to the repository standard "// ESYYYY §X.Y.Z
SpecMethodName(specParams)" for each implementation in this file: GetCalendarId,
GetMonthCode, GetDay, MonthDayWith, MonthDayEquals, MonthDayToString,
MonthDayToJSON, MonthDayValueOf and MonthDayToPlainDate; replace the existing
"// TC39 Temporal §11.3.x ..." lines with the standard format using the
appropriate ES year and the spec method names (e.g. the PlainMonthDay.prototype
methods) so each function's comment reads "// ESYYYY §11.3.X
PlainMonthDay.prototype.<method>(params)".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@units/Goccia.Values.TemporalPlainMonthDay.pas`:
- Around line 101-116: When parsing monthCode in the block that sets MonthPart
from VMonthCode (variables: VMonthCode, MonthCodeStr, MonthPart, ThrowTypeError,
AMethod, Obj.GetProperty), additionally check whether a 'month' property is
present; if both exist compute the numeric month from monthCode and compare it
to Trunc(V.ToNumberLiteral.Value) for the 'month' property and throw a type
error (e.g. via ThrowTypeError with a message mentioning AMethod) when they
differ. Apply the same consistency check in the second parsing location (the
similar block around lines 237-251) so that any mismatched month/monthCode pairs
are rejected rather than ignoring one of them.
- Around line 97-115: The code uses hardcoded property name string literals
(e.g. 'day', 'monthCode', 'month') when calling Obj.GetProperty and in related
checks inside the temporal parsing routine (see variables VDay, VMonthCode, V
and the ThrowTypeError calls); replace these raw strings with the shared
constants from Goccia.Constants.PropertyNames (use the appropriate constant
names for Day, MonthCode, Month) across the function and the other referenced
accessors (lines around the Obj.GetProperty calls and the checks) so all
property accesses use the central constants rather than inline literals.

---

Duplicate comments:
In `@units/Goccia.Values.TemporalDuration.pas`:
- Around line 538-550: The LargestUnit defaulting/validation block must include
weeks in the default scan and always enforce ordering regardless of whether
LargestUnit was provided: when LargestUnit = tuNone, include a check for
D.FWeeks before days (use tuWeek), and after determining LargestUnit (whether
defaulted or explicit) validate that Ord(LargestUnit) <= Ord(SmallestUnit) and
if not, set LargestUnit := SmallestUnit; apply this change in the existing block
around LargestUnit/SmallestUnit before the decomposition code (symbols:
LargestUnit, SmallestUnit, D.FWeeks, tuWeek).

---

Nitpick comments:
In `@units/Goccia.Values.TemporalPlainMonthDay.pas`:
- Around line 188-297: Update the spec-comment headers to the repository
standard "// ESYYYY §X.Y.Z SpecMethodName(specParams)" for each implementation
in this file: GetCalendarId, GetMonthCode, GetDay, MonthDayWith, MonthDayEquals,
MonthDayToString, MonthDayToJSON, MonthDayValueOf and MonthDayToPlainDate;
replace the existing "// TC39 Temporal §11.3.x ..." lines with the standard
format using the appropriate ES year and the spec method names (e.g. the
PlainMonthDay.prototype methods) so each function's comment reads "// ESYYYY
§11.3.X PlainMonthDay.prototype.<method>(params)".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e9e304e5-4b83-4975-b7e3-b871d885ef8c

📥 Commits

Reviewing files that changed from the base of the PR and between 53c5852 and a036afc.

📒 Files selected for processing (12)
  • tests/built-ins/Temporal/Duration/prototype/round.js
  • tests/built-ins/Temporal/Instant/fromEpochMilliseconds.js
  • tests/built-ins/Temporal/PlainDate/from.js
  • tests/built-ins/Temporal/PlainMonthDay/from.js
  • tests/built-ins/Temporal/PlainMonthDay/prototype/equals.js
  • tests/built-ins/Temporal/PlainMonthDay/prototype/with.js
  • tests/built-ins/Temporal/ZonedDateTime/compare.js
  • units/Goccia.Builtins.Temporal.pas
  • units/Goccia.Values.TemporalDuration.pas
  • units/Goccia.Values.TemporalPlainDateTime.pas
  • units/Goccia.Values.TemporalPlainMonthDay.pas
  • units/Goccia.Values.TemporalZonedDateTime.pas
✅ Files skipped from review due to trivial changes (5)
  • tests/built-ins/Temporal/PlainMonthDay/prototype/with.js
  • tests/built-ins/Temporal/Instant/fromEpochMilliseconds.js
  • tests/built-ins/Temporal/PlainMonthDay/prototype/equals.js
  • tests/built-ins/Temporal/ZonedDateTime/compare.js
  • tests/built-ins/Temporal/PlainDate/from.js
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/built-ins/Temporal/PlainMonthDay/from.js
  • tests/built-ins/Temporal/Duration/prototype/round.js
  • units/Goccia.Values.TemporalZonedDateTime.pas
  • units/Goccia.Builtins.Temporal.pas

Comment thread units/Goccia.Values.TemporalPlainMonthDay.pas Outdated
Comment thread units/Goccia.Values.TemporalPlainMonthDay.pas
…y in PlainMonthDay

Add Temporal property constants (PROP_YEAR, PROP_MONTH, PROP_MONTH_CODE,
PROP_DAY, PROP_CALENDAR_ID) to Goccia.Constants.PropertyNames and replace
all hardcoded property-name literals in PlainMonthDay with the shared
constants.

Add month/monthCode consistency validation in both CoercePlainMonthDay
and MonthDayWith — when both properties are present and conflict, throw
RangeError instead of silently ignoring one.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@units/Goccia.Values.TemporalPlainMonthDay.pas`:
- Around line 105-108: The parser currently accepts any "M"+integer (e.g., "M1"
or "M001"); require the canonical monthCode format produced by GetMonthCode
instead: ensure MonthCodeStr[1] = 'M' and MonthCodeStr has exactly three
characters, verify the two trailing characters are digits (use TryStrToInt on
Copy(MonthCodeStr,2,2) into MonthPart) and validate MonthPart is in the 1..12
range; if any check fails call ThrowTypeError('Invalid monthCode for ' +
AMethod). Apply the same exact validation at the other parse site that uses
MonthCodeStr/MonthPart.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 57ebb989-359f-49e8-8e3e-528fc38425e5

📥 Commits

Reviewing files that changed from the base of the PR and between a036afc and d59dd03.

📒 Files selected for processing (4)
  • tests/built-ins/Temporal/PlainMonthDay/prototype/equals.js
  • tests/built-ins/Temporal/PlainMonthDay/prototype/with.js
  • units/Goccia.Constants.PropertyNames.pas
  • units/Goccia.Values.TemporalPlainMonthDay.pas
✅ Files skipped from review due to trivial changes (2)
  • tests/built-ins/Temporal/PlainMonthDay/prototype/with.js
  • units/Goccia.Constants.PropertyNames.pas
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/built-ins/Temporal/PlainMonthDay/prototype/equals.js

Comment thread units/Goccia.Values.TemporalPlainMonthDay.pas Outdated
Require exactly 3 characters (M + two digits) for monthCode instead of
accepting any M+integer (e.g., M1, M001). Validate the parsed month
is in 1..12 range. Applied to all three parse sites: CoercePlainMonthDay,
MonthDayWith, and PlainMonthDayFrom.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
units/Goccia.Builtins.Temporal.pas (2)

923-944: ⚠️ Potential issue | 🟠 Major

compare() is still narrower than the companion from() APIs.

These coercers only accept existing values or strings, even though the new from() routines already accept property bags. Temporal.PlainYearMonth.compare({ year: 2024, month: 1 }, '2024-02') and the analogous PlainMonthDay call will throw here. Reuse the same coercion path as from() so the new statics stay internally consistent.

Also applies to: 1053-1073

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Builtins.Temporal.pas` around lines 923 - 944, CoerceYearMonth
only accepts TGocciaTemporalPlainYearMonthValue or ISO strings; update it to
also accept property bags the same way the PlainYearMonth.from() path does:
detect TGocciaObject/record-like inputs and route them through the same
construction/parsing logic used by the static from routine (reuse the from()
coercion path or shared helper used by from), calling the same internal
parser/constructor instead of failing; apply the same change to the analogous
CoerceMonthDay routine so both coercers accept value objects, strings, and
property-bag inputs (reference: CoerceYearMonth, TryParseISOYearMonth,
TGocciaTemporalPlainYearMonthValue.Create and the PlainYearMonth.from() code
path).

877-885: ⚠️ Potential issue | 🟠 Major

The new from() helpers drop options.overflow entirely.

The overflow helper is already in scope, but neither function ever reads AArgs[1], so { overflow: 'constrain' } never takes effect. Inputs like { year: 2024, month: 13 } for PlainYearMonth.from() or { month: 2, day: 30 } for PlainMonthDay.from() still go straight to the constructors and throw instead of being clamped/rejected intentionally.

Also applies to: 988-997

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Builtins.Temporal.pas` around lines 877 - 885,
PlainYearMonthFrom and PlainMonthDayFrom currently ignore the second argument so
options.overflow is never used; fix both functions to read AArgs.GetElement(1),
extract the overflow option using the existing overflow helper in scope, and
pass that overflow value into the code that constructs or normalizes the result
(e.g., feed it into the PlainYearMonth/PlainMonthDay constructor or the
normalization routine you call) so inputs like {year:2024,month:13} or
{month:2,day:30} honor the overflow setting; specifically update
TGocciaTemporalBuiltin.PlainYearMonthFrom and
TGocciaTemporalBuiltin.PlainMonthDayFrom to consume AArgs[1] and apply the
overflow behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@units/Goccia.Builtins.Temporal.pas`:
- Around line 898-909: The PlainYearMonth.from property-bag path currently
requires a numeric 'month' and rejects objects that only provide 'monthCode';
change the TGocciaObjectValue handling so it first checks for a 'monthCode'
property and, if present, parses it using the same canonical monthCode parser
used by PlainMonthDay.from (reuse that parser/function rather than duplicating
logic), apply the parsed numeric month when 'month' is absent, and if both
'monthCode' and 'month' are present verify they match and ThrowTypeError on
mismatch; update the branch that constructs TGocciaTemporalPlainYearMonthValue
to accept the resolved month and year (using Trunc(V.ToNumberLiteral.Value for
year) and the month resolved from either month or parsed monthCode).
- Around line 498-518: Replace hardcoded Temporal property strings and messages
in the PlainDate.from path by using the shared Temporal property-name constants:
use PROP_YEAR instead of 'year', PROP_MONTH instead of 'month', PROP_DAY instead
of 'day' (and PROP_MONTH_CODE where month-code access is needed) in all
Obj.GetProperty(...) calls and in the ThrowTypeError/ThrowRangeError messages;
update the calls and string concatenations around ThrowRangeError('Month ' +
IntToStr(Mo) + ' out of range') to reference PROP_MONTH (or include PROP_MONTH
in the message) so that GetProperty, ThrowTypeError and ThrowRangeError usages
in this block consistently use PROP_YEAR, PROP_MONTH, PROP_DAY (and
PROP_MONTH_CODE when applicable).

---

Duplicate comments:
In `@units/Goccia.Builtins.Temporal.pas`:
- Around line 923-944: CoerceYearMonth only accepts
TGocciaTemporalPlainYearMonthValue or ISO strings; update it to also accept
property bags the same way the PlainYearMonth.from() path does: detect
TGocciaObject/record-like inputs and route them through the same
construction/parsing logic used by the static from routine (reuse the from()
coercion path or shared helper used by from), calling the same internal
parser/constructor instead of failing; apply the same change to the analogous
CoerceMonthDay routine so both coercers accept value objects, strings, and
property-bag inputs (reference: CoerceYearMonth, TryParseISOYearMonth,
TGocciaTemporalPlainYearMonthValue.Create and the PlainYearMonth.from() code
path).
- Around line 877-885: PlainYearMonthFrom and PlainMonthDayFrom currently ignore
the second argument so options.overflow is never used; fix both functions to
read AArgs.GetElement(1), extract the overflow option using the existing
overflow helper in scope, and pass that overflow value into the code that
constructs or normalizes the result (e.g., feed it into the
PlainYearMonth/PlainMonthDay constructor or the normalization routine you call)
so inputs like {year:2024,month:13} or {month:2,day:30} honor the overflow
setting; specifically update TGocciaTemporalBuiltin.PlainYearMonthFrom and
TGocciaTemporalBuiltin.PlainMonthDayFrom to consume AArgs[1] and apply the
overflow behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 62eb9eb8-8e74-412e-9f9c-fbb2b8b1e6ee

📥 Commits

Reviewing files that changed from the base of the PR and between d59dd03 and 07da77c.

📒 Files selected for processing (3)
  • tests/built-ins/Temporal/PlainMonthDay/from.js
  • units/Goccia.Builtins.Temporal.pas
  • units/Goccia.Values.TemporalPlainMonthDay.pas
✅ Files skipped from review due to trivial changes (1)
  • tests/built-ins/Temporal/PlainMonthDay/from.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • units/Goccia.Values.TemporalPlainMonthDay.pas

Comment thread units/Goccia.Builtins.Temporal.pas Outdated
Comment thread units/Goccia.Builtins.Temporal.pas Outdated
…pport to PlainYearMonth.from

Replace all hardcoded property-name strings ('year', 'month', 'day', 'hour',
etc.) in Goccia.Builtins.Temporal with shared PROP_* constants from
Goccia.Constants.PropertyNames. Add time-related property constants
(PROP_HOUR through PROP_NANOSECOND). Add monthCode support to
PlainYearMonth.from() property-bag path with canonical format validation
and month/monthCode consistency checking, matching PlainMonthDay.from().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new feature New feature or request spec compliance Mismatch against official JavaScript/TypeScript specification

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant