test: rm test262 test setup

This commit is contained in:
Nurul Huda (Apon) 2026-07-16 02:09:44 +06:00
parent de05d1303d
commit b0064649dc
No known key found for this signature in database
GPG key ID: 5D3F1DE2855A2F79
10 changed files with 24 additions and 1843 deletions

View file

@ -13,7 +13,7 @@ permissions:
id-token: write
concurrency:
group: 'pages'
group: "pages"
cancel-in-progress: false
jobs:

1
.gitignore vendored
View file

@ -4,4 +4,3 @@ zig-pkg
target
.tool-versions
test/test262/polyfill-injected.js

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "test/temporal-test262-runner"]
path = test/temporal-test262-runner
url = https://github.com/js-temporal/temporal-test262-runner.git

View file

@ -6,7 +6,6 @@ A Zig library for working with temporal types based on the [Temporal Standard](h
Temporalz provides Zig bindings to the Rust-based [temporal_rs](https://github.com/boa-dev/temporal) library for handling dates, times, and durations with proper timezone support.
## Installation
#### Prerequisites
@ -35,19 +34,18 @@ const exe = b.addExecutable(.{...});
exe.root_module.addImport("temporalz", temporalz.module("temporalz"));
```
## Checklist ([Test262](https://github.com/tc39/test262/tree/main/test/built-ins/Temporal))
| Namespace | Test262 (todo) |
| --- | --- |
| Instant | 0/0 |
| Duration | 0/0 |
| PlainDate | 0/0 |
| PlainTime | 0/0 |
| PlainDateTime | 0/0 |
| PlainYearMonth | 0/0 |
| PlainMonthDay | 0/0 |
| ZonedDateTime | 0/0 |
| Namespace | Status |
| -------------- | ------ |
| Instant | ✅ |
| Duration | ✅ |
| PlainDate | ✅ |
| PlainTime | ✅ |
| PlainDateTime | ✅ |
| PlainYearMonth | |
| PlainMonthDay | ✅ |
| ZonedDateTime | ✅ |
## Prebuilt
@ -94,7 +92,3 @@ zig build run -Dtarget=wasm32-wasi
```bash
zig build test
```
## License
MIT

View file

@ -1,16 +1,16 @@
import { readFile } from 'fs/promises';
import { readFile } from "fs/promises";
let memory = null;
const buffer = await readFile('zig-out/bin/temporalz.wasm');
const buffer = await readFile("zig-out/bin/temporalz.wasm");
const temporalz = await WebAssembly.instantiate(buffer, {
env: {
console(ptr, len) {
const bytes = new Uint8Array(memory.buffer, ptr, len);
const message = new TextDecoder().decode(bytes);
console.log(message);
},
env: {
console(ptr, len) {
const bytes = new Uint8Array(memory.buffer, ptr, len);
const message = new TextDecoder().decode(bytes);
console.log(message);
},
},
});
memory = temporalz.instance.exports.memory;

File diff suppressed because it is too large Load diff

@ -1 +0,0 @@
Subproject commit 0b24be41f656615b4e9eb9d339840a7f59971d8d

View file

@ -1,713 +0,0 @@
// Thin polyfill: all real logic lives in the Zig WASM module. JS only handles
// ECMAScript-spec coercion rules (which the host language must enforce) and
// surface plumbing (brand checks, prototypes, descriptors, error mapping).
(function () {
if (!globalThis.__TEMPORALZ_WASM_BYTES__) {
throw new Error("WASM bytes not injected into test context");
}
const wasmModule = new WebAssembly.Module(globalThis.__TEMPORALZ_WASM_BYTES__);
const wasmInstance = new WebAssembly.Instance(wasmModule, {
env: { console: (ptr, len) => globalThis.console.log(decodeUtf8(memory.buffer, ptr, len)) },
});
const x = wasmInstance.exports;
const memory = x.memory;
// --- utf8 codec (vm.Script context has no TextEncoder) -----------------
function encodeUtf8(str) {
const buf = [];
for (let i = 0; i < str.length; i++) {
const c = str.charCodeAt(i);
if (c < 0x80) buf.push(c);
else if (c < 0x800) buf.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f));
else if (c < 0xd800 || c >= 0xe000) buf.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f));
else {
const c2 = str.charCodeAt(++i);
const cp = 0x10000 + (((c & 0x3ff) << 10) | (c2 & 0x3ff));
buf.push(0xf0 | (cp >> 18), 0x80 | ((cp >> 12) & 0x3f), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f));
}
}
return new Uint8Array(buf);
}
function decodeUtf8(buf, ptr, len) {
const bytes = new Uint8Array(buf, ptr, len);
let s = "";
for (let i = 0; i < bytes.length; i++) {
const b = bytes[i];
if (b < 0x80) s += String.fromCharCode(b);
else if ((b & 0xe0) === 0xc0) s += String.fromCharCode(((b & 0x1f) << 6) | (bytes[++i] & 0x3f));
else if ((b & 0xf0) === 0xe0) s += String.fromCharCode(((b & 0x0f) << 12) | ((bytes[++i] & 0x3f) << 6) | (bytes[++i] & 0x3f));
else {
const cp = ((b & 0x07) << 18) | ((bytes[++i] & 0x3f) << 12) | ((bytes[++i] & 0x3f) << 6) | (bytes[++i] & 0x3f);
const hi = ((cp - 0x10000) >> 10) + 0xd800;
const lo = ((cp - 0x10000) & 0x3ff) + 0xdc00;
s += String.fromCharCode(hi, lo);
}
}
return s;
}
// --- error mapping using Zig's reported error kind ---------------------
function lastError() {
const kind = Number(x.temporalz_last_error_kind());
const ptr = Number(x.temporalz_last_error_ptr());
const len = Number(x.temporalz_last_error_len());
const msg = ptr && len ? decodeUtf8(memory.buffer, ptr, len) : "temporalz error";
x.temporalz_last_error_clear();
switch (kind) {
case 2: return new TypeError(msg);
case 3: return new RangeError(msg);
case 4: return new SyntaxError(msg);
default: return new RangeError(msg);
}
}
function need(h) {
if (!h) throw lastError();
return Number(h);
}
function takeString(packed) {
if (!packed) throw lastError();
const v = BigInt(packed);
const ptr = Number(v >> 32n);
const len = Number(v & 0xffffffffn);
const text = decodeUtf8(memory.buffer, ptr, len);
x.temporalz_string_free(ptr, len);
return text;
}
function withCString(str, fn) {
const bytes = encodeUtf8(str);
const ptr = Number(x.temporalz_alloc(bytes.length));
if (!ptr && bytes.length) throw lastError();
if (bytes.length) new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes);
try { return fn(ptr, bytes.length); }
finally { if (bytes.length) x.temporalz_free(ptr, bytes.length); }
}
// --- enums (must match wasm.zig codes) ---------------------------------
const UNIT_CODES = {
nanosecond: 1, nanoseconds: 1,
microsecond: 2, microseconds: 2,
millisecond: 3, milliseconds: 3,
second: 4, seconds: 4,
minute: 5, minutes: 5,
hour: 6, hours: 6,
day: 7, days: 7,
week: 8, weeks: 8,
month: 9, months: 9,
year: 10, years: 10,
auto: 11,
};
const ROUNDING_MODE_CODES = { ceil: 1, floor: 2, expand: 3, trunc: 4, halfCeil: 5, halfFloor: 6, halfExpand: 7, halfTrunc: 8, halfEven: 9 };
function toStringOrThrow(v, name) {
if (typeof v === "symbol") throw new TypeError(`Cannot convert symbol to string for ${name}`);
return String(v);
}
function unitCode(value) {
if (value === undefined) return 255;
const s = toStringOrThrow(value, "unit");
const c = UNIT_CODES[s];
if (c === undefined) throw new RangeError(`Invalid unit: ${s}`);
return c;
}
function roundingModeCode(value) {
if (value === undefined) return 255;
const s = toStringOrThrow(value, "roundingMode");
const c = ROUNDING_MODE_CODES[s];
if (c === undefined) throw new RangeError(`Invalid roundingMode: ${s}`);
return c;
}
function roundingIncrementValue(opts) {
if (!opts || opts.roundingIncrement === undefined) return 0;
const v = opts.roundingIncrement;
if (typeof v === "bigint") throw new TypeError("Cannot convert BigInt to number for roundingIncrement");
const n = Number(v);
if (!Number.isFinite(n)) throw new RangeError("Invalid roundingIncrement");
const i = Math.trunc(n);
if (i < 1 || i > 1e9) throw new RangeError("Invalid roundingIncrement");
return i;
}
function requireOptionsObject(o, name) {
if (o === undefined) return {};
if (o === null) throw new TypeError(`${name} options cannot be null`);
if (typeof o !== "object" && typeof o !== "function") {
throw new TypeError(`${name} options must be an object`);
}
return o;
}
// --- BigInt i128 split for the wasm boundary ---------------------------
function splitI128(value) {
const v = typeof value === "bigint" ? value : BigInt(value);
return { hi: v >> 64n, lo: v & 0xffffffffffffffffn };
}
function joinI128(hi, lo) {
return (BigInt(hi) << 64n) | (BigInt(lo) & 0xffffffffffffffffn);
}
// --- brand-check helpers -----------------------------------------------
const HANDLE = Symbol("temporalz.handle");
function brand(obj, klass, name) {
if (typeof obj !== "object" || obj === null || !klass.prototype.isPrototypeOf(obj)) {
throw new TypeError(`this is not a ${name}`);
}
const h = obj[HANDLE];
if (h === undefined) throw new TypeError(`this is not a ${name} (missing brand)`);
return h;
}
function defineMethods(proto, methods) {
for (const name of Object.keys(methods)) {
Object.defineProperty(proto, name, {
value: methods[name],
writable: true,
enumerable: false,
configurable: true,
});
}
}
function defineGetters(proto, getters) {
for (const name of Object.keys(getters)) {
Object.defineProperty(proto, name, {
get: getters[name],
enumerable: false,
configurable: true,
});
}
}
// =======================================================================
// Instant
// =======================================================================
const NS_LIMIT = 8_640_000_000_000_000_000_000n;
function Instant(epochNanoseconds) {
if (!new.target) throw new TypeError("Instant must be called with new");
if (typeof epochNanoseconds !== "bigint") {
throw new TypeError("epochNanoseconds must be a BigInt");
}
if (epochNanoseconds > NS_LIMIT || epochNanoseconds < -NS_LIMIT) {
throw new RangeError("epochNanoseconds out of range");
}
const { hi, lo } = splitI128(epochNanoseconds);
const handle = need(x.temporalz_instant_from_epoch_nanoseconds_parts(hi, lo));
Object.defineProperty(this, HANDLE, { value: handle });
}
function instantFromHandle(handle) {
const obj = Object.create(Instant.prototype);
Object.defineProperty(obj, HANDLE, { value: handle });
return obj;
}
function isInstant(v) {
return typeof v === "object" && v !== null && Instant.prototype.isPrototypeOf(v) && v[HANDLE] !== undefined;
}
// Static methods
Object.defineProperty(Instant, "from", {
value: { from(item) {
if (isInstant(item)) return new Instant(getInstantEpochNs(item));
return parseInstantString(coerceToInstantString(item));
} }.from,
writable: true, enumerable: false, configurable: true,
});
Object.defineProperty(Instant, "fromEpochMilliseconds", {
value: { fromEpochMilliseconds(epochMilliseconds) {
if (typeof epochMilliseconds === "bigint") throw new TypeError("epochMilliseconds must not be BigInt");
const n = Number(epochMilliseconds);
if (!Number.isFinite(n)) throw new RangeError("epochMilliseconds must be a finite integer");
if (!Number.isInteger(n)) throw new RangeError("epochMilliseconds must be an integer");
const MAX = 8.64e15;
if (n > MAX || n < -MAX) throw new RangeError("epochMilliseconds out of range");
return instantFromHandle(need(x.temporalz_instant_from_epoch_milliseconds(BigInt(n))));
} }.fromEpochMilliseconds,
writable: true, enumerable: false, configurable: true,
});
Object.defineProperty(Instant, "fromEpochNanoseconds", {
value: { fromEpochNanoseconds(epochNanoseconds) {
if (typeof epochNanoseconds !== "bigint") throw new TypeError("epochNanoseconds must be a BigInt");
const MAX = 8_640_000_000_000_000_000_000n;
if (epochNanoseconds > MAX || epochNanoseconds < -MAX) throw new RangeError("epochNanoseconds out of range");
const { hi, lo } = splitI128(epochNanoseconds);
return instantFromHandle(need(x.temporalz_instant_from_epoch_nanoseconds_parts(hi, lo)));
} }.fromEpochNanoseconds,
writable: true, enumerable: false, configurable: true,
});
Object.defineProperty(Instant, "compare", {
value: { compare(a, b) {
const ha = toInstantHandle(a);
const hb = toInstantHandle(b);
return Number(x.temporalz_instant_compare(ha, hb));
} }.compare,
writable: true, enumerable: false, configurable: true,
});
function parseInstantString(str) {
return withCString(str, (ptr, len) => instantFromHandle(need(x.temporalz_instant_from_utf8(ptr, len))));
}
// Spec: ToTemporalInstant. If Instant, copy. Else, ToPrimitive(string-hint) then
// parse as ISO string. bigint/symbol/Temporal.Instant.prototype branding fail → TypeError.
function coerceToInstantString(value) {
if (value === undefined || value === null) throw new TypeError("Instant argument is required");
if (typeof value === "boolean") throw new TypeError("Boolean is not a valid Instant");
if (typeof value === "number") throw new TypeError("Number is not a valid Instant");
if (typeof value === "bigint") throw new TypeError("BigInt is not a valid Instant");
if (typeof value === "symbol") throw new TypeError("Symbol is not a valid Instant");
if (typeof value === "string") return value;
// Object case: must not be an Instant.prototype (brand fails) — but ordinary object → ToString → "[object Object]" parsed → RangeError
if (value === Instant.prototype) throw new TypeError("Instant.prototype is not a valid Instant");
return String(value);
}
function toInstantHandle(value) {
if (isInstant(value)) return value[HANDLE];
const str = coerceToInstantString(value);
return withCString(str, (ptr, len) => need(x.temporalz_instant_from_utf8(ptr, len)));
}
function getInstantEpochNs(inst) {
const h = inst[HANDLE];
const hi = x.temporalz_instant_epoch_nanoseconds_hi(h);
const lo = x.temporalz_instant_epoch_nanoseconds_lo(h);
return joinI128(hi, lo);
}
defineGetters(Instant.prototype, {
epochMilliseconds() {
const h = brand(this, Instant, "Temporal.Instant");
return Number(x.temporalz_instant_epoch_milliseconds(h));
},
epochNanoseconds() {
const h = brand(this, Instant, "Temporal.Instant");
const hi = x.temporalz_instant_epoch_nanoseconds_hi(h);
const lo = x.temporalz_instant_epoch_nanoseconds_lo(h);
return joinI128(hi, lo);
},
});
defineMethods(Instant.prototype, {
add(durationLike) {
const h = brand(this, Instant, "Temporal.Instant");
const dh = toDurationHandle(durationLike);
return instantFromHandle(need(x.temporalz_instant_add(h, dh)));
},
subtract(durationLike) {
const h = brand(this, Instant, "Temporal.Instant");
const dh = toDurationHandle(durationLike);
return instantFromHandle(need(x.temporalz_instant_subtract(h, dh)));
},
until(other, options) {
const h = brand(this, Instant, "Temporal.Instant");
const oh = toInstantHandle(other);
const o = requireOptionsObject(options, "until");
return durationFromHandle(need(x.temporalz_instant_until(
h, oh,
unitCode(o.largestUnit),
unitCode(o.smallestUnit),
roundingModeCode(o.roundingMode),
roundingIncrementValue(o),
)));
},
since(other, options) {
const h = brand(this, Instant, "Temporal.Instant");
const oh = toInstantHandle(other);
const o = requireOptionsObject(options, "since");
return durationFromHandle(need(x.temporalz_instant_since(
h, oh,
unitCode(o.largestUnit),
unitCode(o.smallestUnit),
roundingModeCode(o.roundingMode),
roundingIncrementValue(o),
)));
},
round(options) {
const h = brand(this, Instant, "Temporal.Instant");
let o;
if (typeof options === "string") o = { smallestUnit: options };
else if (options === undefined) throw new TypeError("round options required");
else o = requireOptionsObject(options, "round");
const su = unitCode(o.smallestUnit);
if (su === 255) throw new RangeError("smallestUnit is required");
return instantFromHandle(need(x.temporalz_instant_round(
h, su, roundingModeCode(o.roundingMode), roundingIncrementValue(o),
)));
},
equals(other) {
const h = brand(this, Instant, "Temporal.Instant");
const oh = toInstantHandle(other);
return x.temporalz_instant_equals(h, oh) === 1;
},
toString(options) {
const h = brand(this, Instant, "Temporal.Instant");
const o = requireOptionsObject(options, "toString");
const su = unitCode(o.smallestUnit);
const rm = roundingModeCode(o.roundingMode);
let fd = -1;
if (o.fractionalSecondDigits !== undefined) {
const v = o.fractionalSecondDigits;
if (typeof v === "symbol") throw new TypeError("Cannot convert Symbol to fractionalSecondDigits");
if (typeof v === "bigint") throw new TypeError("Cannot convert BigInt to number for fractionalSecondDigits");
if (typeof v === "number") {
if (!Number.isFinite(v)) throw new RangeError("Invalid fractionalSecondDigits");
const i = Math.floor(v);
if (i < 0 || i > 9) throw new RangeError("Invalid fractionalSecondDigits");
fd = i;
} else {
// ToString path; only "auto" is acceptable
const s = String(v);
if (s !== "auto") throw new RangeError("Invalid fractionalSecondDigits");
fd = -1;
}
}
let tzPtr = 0, tzLen = 0;
const tz = o.timeZone;
if (tz !== undefined) {
if (typeof tz !== "string") {
// primitive bool/number/bigint convert to a string but won't be a valid time zone → RangeError
// null/symbol/object → TypeError per spec
if (tz === null) throw new TypeError("timeZone must be a string");
if (typeof tz === "object") throw new TypeError("timeZone must be a string");
if (typeof tz === "symbol") throw new TypeError("Cannot convert Symbol to string for timeZone");
// boolean/number/bigint → string conversion, then range check happens via Zig parse
}
const tzStr = typeof tz === "string" ? tz : String(tz);
const bytes = encodeUtf8(tzStr);
tzPtr = Number(x.temporalz_alloc(bytes.length));
if (!tzPtr && bytes.length) throw lastError();
if (bytes.length) new Uint8Array(memory.buffer, tzPtr, bytes.length).set(bytes);
tzLen = bytes.length;
}
try {
return takeString(x.temporalz_instant_to_string_with(h, su, rm, fd, tzPtr, tzLen));
} finally {
if (tzLen) x.temporalz_free(tzPtr, tzLen);
}
},
toJSON() {
const h = brand(this, Instant, "Temporal.Instant");
return takeString(x.temporalz_instant_to_string(h));
},
toLocaleString() {
const h = brand(this, Instant, "Temporal.Instant");
return takeString(x.temporalz_instant_to_string(h));
},
valueOf() {
throw new TypeError("Cannot convert Temporal.Instant to a primitive value");
},
toZonedDateTimeISO(timeZone) {
const h = brand(this, Instant, "Temporal.Instant");
if (typeof timeZone !== "string") throw new TypeError("timeZone must be a string");
return withCString(timeZone, (ptr, len) => {
const zh = need(x.temporalz_instant_to_zoned_date_time_iso(h, ptr, len));
return zonedDateTimeFromHandle(zh);
});
},
});
Object.defineProperty(Instant.prototype, Symbol.toStringTag, {
value: "Temporal.Instant", writable: false, enumerable: false, configurable: true,
});
Object.defineProperty(Instant, "prototype", { writable: false, enumerable: false, configurable: false });
// =======================================================================
// ZonedDateTime (minimal: only what Instant.toZonedDateTimeISO returns)
// =======================================================================
function ZonedDateTime() {
throw new TypeError("Use Temporal.Instant.prototype.toZonedDateTimeISO instead");
}
function zonedDateTimeFromHandle(handle) {
const obj = Object.create(ZonedDateTime.prototype);
Object.defineProperty(obj, HANDLE, { value: handle });
return obj;
}
defineGetters(ZonedDateTime.prototype, {
epochNanoseconds() {
const h = brand(this, ZonedDateTime, "Temporal.ZonedDateTime");
const hi = x.temporalz_zoned_date_time_epoch_nanoseconds_hi(h);
const lo = x.temporalz_zoned_date_time_epoch_nanoseconds_lo(h);
return joinI128(hi, lo);
},
});
Object.defineProperty(ZonedDateTime.prototype, Symbol.toStringTag, {
value: "Temporal.ZonedDateTime", writable: false, enumerable: false, configurable: true,
});
// =======================================================================
// Duration
// =======================================================================
function Duration(years, months, weeks, days, hours, minutes, seconds, ms, us, ns) {
if (!new.target) throw new TypeError("Duration must be called with new");
const Y = intArg(years, "years");
const Mo = intArg(months, "months");
const W = intArg(weeks, "weeks");
const D = intArg(days, "days");
const H = intArg(hours, "hours");
const Mi = intArg(minutes, "minutes");
const S = intArg(seconds, "seconds");
const MS = intArg(ms, "milliseconds");
const US = numArg(us, "microseconds");
const NS = numArg(ns, "nanoseconds");
const handle = need(x.temporalz_duration_init(Y, Mo, W, D, H, Mi, S, MS, US, NS));
Object.defineProperty(this, HANDLE, { value: handle });
}
function intArg(v, name) {
if (v === undefined) return 0n;
if (typeof v === "bigint") return v;
const n = Number(v);
if (!Number.isFinite(n)) throw new RangeError(`${name} must be finite`);
if (!Number.isInteger(n)) throw new RangeError(`${name} must be an integer`);
return BigInt(n);
}
function numArg(v, name) {
if (v === undefined) return 0;
const n = Number(v);
if (!Number.isFinite(n)) throw new RangeError(`${name} must be finite`);
if (!Number.isInteger(n)) throw new RangeError(`${name} must be an integer`);
return n;
}
function durationFromHandle(handle) {
const obj = Object.create(Duration.prototype);
Object.defineProperty(obj, HANDLE, { value: handle });
return obj;
}
function isDuration(v) {
return typeof v === "object" && v !== null && Duration.prototype.isPrototypeOf(v) && v[HANDLE] !== undefined;
}
function toDurationHandle(value) {
if (isDuration(value)) return value[HANDLE];
if (typeof value === "string") {
return withCString(value, (ptr, len) => need(x.temporalz_duration_from_utf8(ptr, len)));
}
if (value === null || typeof value !== "object") {
throw new TypeError("expected a Duration, string, or object");
}
// Wasm-side parts order (matches mask bits): years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds
const wireOrder = ["years","months","weeks","days","hours","minutes","seconds","milliseconds","microseconds","nanoseconds"];
// Spec: properties are read in alphabetical order.
const readOrder = ["days","hours","microseconds","milliseconds","minutes","months","nanoseconds","seconds","weeks","years"];
const vals = [0n,0n,0n,0n,0n,0n,0n,0n,0,0];
let mask = 0;
let any = false;
for (const f of readOrder) {
const raw = value[f];
if (raw === undefined) continue;
any = true;
const i = wireOrder.indexOf(f);
mask |= 1 << i;
vals[i] = (f === "microseconds" || f === "nanoseconds") ? numArg(raw, f) : intArg(raw, f);
}
if (!any) throw new TypeError("Duration object must have at least one duration field");
return need(x.temporalz_duration_from_parts(mask, vals[0], vals[1], vals[2], vals[3], vals[4], vals[5], vals[6], vals[7], vals[8], vals[9]));
}
Object.defineProperty(Duration, "from", {
value: function from(value) {
if (isDuration(value)) return durationFromHandle(need(x.temporalz_duration_init(
x.temporalz_duration_years(value[HANDLE]),
x.temporalz_duration_months(value[HANDLE]),
x.temporalz_duration_weeks(value[HANDLE]),
x.temporalz_duration_days(value[HANDLE]),
x.temporalz_duration_hours(value[HANDLE]),
x.temporalz_duration_minutes(value[HANDLE]),
x.temporalz_duration_seconds(value[HANDLE]),
x.temporalz_duration_milliseconds(value[HANDLE]),
x.temporalz_duration_microseconds(value[HANDLE]),
x.temporalz_duration_nanoseconds(value[HANDLE]),
)));
return durationFromHandle(toDurationHandle(value));
},
writable: true, enumerable: false, configurable: true,
});
Object.defineProperty(Duration, "compare", {
value: function compare(a, b, options) {
const ah = toDurationHandle(a);
const bh = toDurationHandle(b);
const relativeTo = options && options.relativeTo;
if (relativeTo !== undefined && relativeTo !== null) {
const date = plainDateFromAny(relativeTo);
return Number(x.temporalz_duration_compare_plain_date(ah, bh, date[HANDLE]));
}
return Number(x.temporalz_duration_compare(ah, bh));
},
writable: true, enumerable: false, configurable: true,
});
defineGetters(Duration.prototype, {
years() { return Number(x.temporalz_duration_years(brand(this, Duration, "Temporal.Duration"))); },
months() { return Number(x.temporalz_duration_months(brand(this, Duration, "Temporal.Duration"))); },
weeks() { return Number(x.temporalz_duration_weeks(brand(this, Duration, "Temporal.Duration"))); },
days() { return Number(x.temporalz_duration_days(brand(this, Duration, "Temporal.Duration"))); },
hours() { return Number(x.temporalz_duration_hours(brand(this, Duration, "Temporal.Duration"))); },
minutes() { return Number(x.temporalz_duration_minutes(brand(this, Duration, "Temporal.Duration"))); },
seconds() { return Number(x.temporalz_duration_seconds(brand(this, Duration, "Temporal.Duration"))); },
milliseconds() { return Number(x.temporalz_duration_milliseconds(brand(this, Duration, "Temporal.Duration"))); },
microseconds() { return Number(x.temporalz_duration_microseconds(brand(this, Duration, "Temporal.Duration"))); },
nanoseconds() { return Number(x.temporalz_duration_nanoseconds(brand(this, Duration, "Temporal.Duration"))); },
sign() { return Number(x.temporalz_duration_sign(brand(this, Duration, "Temporal.Duration"))); },
blank() { return x.temporalz_duration_blank(brand(this, Duration, "Temporal.Duration")) === 1; },
});
defineMethods(Duration.prototype, {
add(other) {
const h = brand(this, Duration, "Temporal.Duration");
return durationFromHandle(need(x.temporalz_duration_add(h, toDurationHandle(other))));
},
subtract(other) {
const h = brand(this, Duration, "Temporal.Duration");
return durationFromHandle(need(x.temporalz_duration_subtract(h, toDurationHandle(other))));
},
abs() {
const h = brand(this, Duration, "Temporal.Duration");
return durationFromHandle(need(x.temporalz_duration_abs(h)));
},
negated() {
const h = brand(this, Duration, "Temporal.Duration");
return durationFromHandle(need(x.temporalz_duration_negated(h)));
},
round(options) {
const h = brand(this, Duration, "Temporal.Duration");
const o = options ?? {};
if (o === null || typeof o !== "object") throw new TypeError("round options must be an object");
const relativeTo = o.relativeTo;
if (relativeTo !== undefined && relativeTo !== null) {
const date = plainDateFromAny(relativeTo);
return durationFromHandle(need(x.temporalz_duration_round_plain_date(
h, unitCode(o.smallestUnit), unitCode(o.largestUnit), roundingModeCode(o.roundingMode), roundingIncrementValue(o), date[HANDLE],
)));
}
return durationFromHandle(need(x.temporalz_duration_round(
h, unitCode(o.smallestUnit), unitCode(o.largestUnit), roundingModeCode(o.roundingMode), roundingIncrementValue(o),
)));
},
total(options) {
const h = brand(this, Duration, "Temporal.Duration");
if (!options || typeof options !== "object") throw new TypeError("total options must be an object");
const unit = unitCode(options.unit);
if (unit === 255) throw new RangeError("unit is required");
const relativeTo = options.relativeTo;
if (relativeTo !== undefined && relativeTo !== null) {
const date = plainDateFromAny(relativeTo);
return x.temporalz_duration_total_plain_date(h, unit, date[HANDLE]);
}
return x.temporalz_duration_total(h, unit);
},
toString() {
return takeString(x.temporalz_duration_to_string(brand(this, Duration, "Temporal.Duration")));
},
toJSON() {
return takeString(x.temporalz_duration_to_string(brand(this, Duration, "Temporal.Duration")));
},
toLocaleString() {
return takeString(x.temporalz_duration_to_string(brand(this, Duration, "Temporal.Duration")));
},
valueOf() {
throw new TypeError("Cannot convert Temporal.Duration to a primitive value");
},
});
Object.defineProperty(Duration.prototype, Symbol.toStringTag, {
value: "Temporal.Duration", writable: false, enumerable: false, configurable: true,
});
Object.defineProperty(Duration, "prototype", { writable: false, enumerable: false, configurable: false });
// =======================================================================
// PlainDate (minimal — used as relativeTo for Duration)
// =======================================================================
function PlainDate(year, month, day) {
if (!new.target) throw new TypeError("PlainDate must be called with new");
const Y = Number(year), M = Number(month), D = Number(day);
if (!Number.isInteger(Y) || !Number.isInteger(M) || !Number.isInteger(D)) {
throw new RangeError("PlainDate fields must be integers");
}
const handle = need(x.temporalz_plain_date_init(Y, M, D));
Object.defineProperty(this, HANDLE, { value: handle });
}
function plainDateFromHandle(handle) {
const obj = Object.create(PlainDate.prototype);
Object.defineProperty(obj, HANDLE, { value: handle });
return obj;
}
function isPlainDate(v) {
return typeof v === "object" && v !== null && PlainDate.prototype.isPrototypeOf(v) && v[HANDLE] !== undefined;
}
function plainDateFromAny(value) {
if (isPlainDate(value)) return value;
if (typeof value === "string") {
return withCString(value, (ptr, len) => plainDateFromHandle(need(x.temporalz_plain_date_from_utf8(ptr, len))));
}
if (value === null || typeof value !== "object") throw new TypeError("Invalid relativeTo");
const Y = Number(value.year), M = Number(value.month), D = Number(value.day);
if (!Number.isInteger(Y) || !Number.isInteger(M) || !Number.isInteger(D)) {
throw new RangeError("Invalid relativeTo fields");
}
return plainDateFromHandle(need(x.temporalz_plain_date_init(Y, M, D)));
}
Object.defineProperty(PlainDate, "from", {
value: function from(value) { return plainDateFromAny(value); },
writable: true, enumerable: false, configurable: true,
});
defineMethods(PlainDate.prototype, {
toString() { return takeString(x.temporalz_plain_date_to_string(brand(this, PlainDate, "Temporal.PlainDate"))); },
});
Object.defineProperty(PlainDate.prototype, Symbol.toStringTag, {
value: "Temporal.PlainDate", writable: false, enumerable: false, configurable: true,
});
// =======================================================================
// Unimplemented stubs
// =======================================================================
function notImplemented(name) {
return function () { throw new Error(`${name} is not implemented`); };
}
const TemporalNow = {};
defineMethods(TemporalNow, {
instant() {
// Delegate creation semantics to the Zig-backed Instant constructor path.
return Instant.fromEpochMilliseconds(Date.now());
},
plainDateISO() {
throw new Error("Temporal.Now.plainDateISO is not implemented");
},
plainDateTimeISO() {
throw new Error("Temporal.Now.plainDateTimeISO is not implemented");
},
plainTimeISO() {
throw new Error("Temporal.Now.plainTimeISO is not implemented");
},
timeZoneId() {
// Keep this deterministic for now; Zig Now.zig currently returns UTC too.
return "UTC";
},
zonedDateTimeISO() {
const tz = arguments.length === 0 || arguments[0] === undefined ? TemporalNow.timeZoneId() : arguments[0];
if (tz === null) throw new TypeError("timeZone must be a string");
if (typeof tz === "symbol") throw new TypeError("Cannot convert Symbol to string for timeZone");
return TemporalNow.instant().toZonedDateTimeISO(String(tz));
},
});
Object.defineProperty(TemporalNow, Symbol.toStringTag, {
value: "Temporal.Now", writable: false, enumerable: false, configurable: true,
});
const Temporal = {
Instant,
Duration,
PlainDate,
ZonedDateTime,
PlainTime: notImplemented("Temporal.PlainTime"),
PlainDateTime: notImplemented("Temporal.PlainDateTime"),
PlainYearMonth: notImplemented("Temporal.PlainYearMonth"),
PlainMonthDay: notImplemented("Temporal.PlainMonthDay"),
Now: TemporalNow,
};
Object.defineProperty(Temporal, Symbol.toStringTag, {
value: "Temporal", writable: false, enumerable: false, configurable: true,
});
// Ensure constructors have non-enumerable descriptors expected by prop-desc tests
for (const [name, ctor] of Object.entries({ Instant, Duration, PlainDate, ZonedDateTime, Now: TemporalNow })) {
Object.defineProperty(Temporal, name, {
value: ctor, writable: true, enumerable: false, configurable: true,
});
}
globalThis.Temporal = Temporal;
})();

View file

@ -1,52 +0,0 @@
import runTest262 from "../temporal-test262-runner/index.mjs";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const wasmPath =
process.env.TEMPORALZ_WASM ||
path.resolve(__dirname, "../../zig-out/bin/temporalz.wasm");
const wasmBytes = fs.readFileSync(wasmPath);
const polyfillPath = path.resolve(__dirname, "../test262/polyfill.js");
let polyfillCode = fs.readFileSync(polyfillPath, "utf-8");
const EXCLUDE_TESTS = [
// JS surface-shape tests are intentionally out of scope for this thin polyfill.
"(?:^|/)(?:builtin|name|not-a-constructor|prop-desc)\\.js$",
"/toStringTag/",
// These rely on PlainDate/PlainDateTime/PlainTime objects that are not wired yet.
"^built-ins/Temporal/Now/plainDateISO/",
"^built-ins/Temporal/Now/plainDateTimeISO/",
"^built-ins/Temporal/Now/plainTimeISO/",
"^built-ins/Temporal/Now/zonedDateTimeISO/",
"^intl402/Temporal/Now/plainDateISO/",
"^intl402/Temporal/Now/plainDateTimeISO/",
"^intl402/Temporal/Now/plainTimeISO/",
"^intl402/Temporal/Now/zonedDateTimeISO/",
];
const bytesArray = JSON.stringify(Array.from(wasmBytes));
// TextEncoder/TextDecoder need to be manually constructed and passed via context
// Since we can't serialize them, we inject polyfill-compatible shim versions
const injection = `globalThis.__TEMPORALZ_WASM_BYTES__ = new Uint8Array(${bytesArray});
`;
polyfillCode = injection + polyfillCode;
const tempPolyfillPath = path.resolve(__dirname, "../test262/polyfill-injected.js");
fs.writeFileSync(tempPolyfillPath, polyfillCode);
const result = runTest262({
test262Dir: "test/temporal-test262-runner/test262",
polyfillCodeFile: tempPolyfillPath,
testGlobs: process.argv.slice(2),
excludeTests: EXCLUDE_TESTS,
});
fs.unlinkSync(tempPolyfillPath);
process.exit(result ? 0 : 1);