feat: add wasi target

This commit is contained in:
Nurul Huda (Apon) 2026-02-12 01:26:36 +06:00
parent 7a30f17d43
commit 7abcd0b2ed
No known key found for this signature in database
GPG key ID: 5D3F1DE2855A2F79
8 changed files with 1575 additions and 854 deletions

4
.gitignore vendored
View file

@ -7,4 +7,6 @@ lib
!lib/*/libtemporal_capi.a
!lib/*/temporal_capi.lib
tmp
tmp
test/test262/polyfill-injected.js

3
.gitmodules vendored Normal file
View file

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

View file

@ -4,7 +4,7 @@ const build_crab = @import("build_crab");
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const is_wasm_freestanding = target.result.cpu.arch.isWasm();
const is_wasm_freestanding = target.result.cpu.arch.isWasm() and target.result.os.tag == .freestanding;
// --- Zig Module: temporalz --- //
const mod = b.addModule("temporalz", .{
@ -108,20 +108,31 @@ pub fn build(b: *std.Build) !void {
},
}),
});
exe.root_module.link_libc = !is_wasm_freestanding;
exe.rdynamic = is_wasm_freestanding;
b.installArtifact(exe);
// --- Steps: Run --- //
{
const run_step = b.step("run", "Run the app");
if (is_wasm_freestanding) {
const run_cmd = b.addSystemCommand(&.{ "node", "example/src/main.mjs" });
run_cmd.step.dependOn(b.getInstallStep());
run_step.dependOn(&run_cmd.step);
} else {
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| run_cmd.addArgs(args);
run_step.dependOn(&run_cmd.step);
switch (target.result.os.tag) {
.freestanding => {
const run_cmd = b.addSystemCommand(&.{ "node", "example/src/main.mjs" });
run_cmd.step.dependOn(b.getInstallStep());
run_step.dependOn(&run_cmd.step);
},
.wasi => {
const run_cmd = b.addSystemCommand(&.{"wasmtime"});
run_cmd.addFileArg(exe.getEmittedBin());
run_cmd.step.dependOn(b.getInstallStep());
run_step.dependOn(&run_cmd.step);
},
else => {
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| run_cmd.addArgs(args);
run_step.dependOn(&run_cmd.step);
},
}
}
@ -159,22 +170,10 @@ pub fn build(b: *std.Build) !void {
// --- Steps: test-262 --- //
{
const test262_step = b.step("test262", "Run test-262 tests");
const test262_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("test/test262/root.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "temporalz", .module = mod },
},
}),
.test_runner = .{
.path = b.path("test/runner.zig"),
.mode = .simple,
},
});
test262_tests.root_module.link_libc = !is_wasm_freestanding;
test262_step.dependOn(&b.addRunArtifact(test262_tests).step);
const run_cmd = b.addSystemCommand(&.{ "node", "test/test262/runner.mjs" });
if (b.args) |args| run_cmd.addArgs(args);
run_cmd.step.dependOn(b.getInstallStep());
test262_step.dependOn(&run_cmd.step);
}
// --- Steps: Build all platforms --- //

View file

@ -1,11 +1,837 @@
const std = @import("std");
const program = @import("root.zig");
const Temporal = @import("temporalz");
export fn _start() void {
const allocator = std.heap.wasm_allocator;
program.run(allocator, null) catch {};
}
const wasm_allocator = std.heap.wasm_allocator;
const PolyfillError = error{InvalidHandle};
var instants_init = false;
var durations_init = false;
var instants: std.ArrayList(?Temporal.Instant) = .empty;
var durations: std.ArrayList(?Temporal.Duration) = .empty;
var plain_dates_init = false;
var plain_dates: std.ArrayList(?Temporal.PlainDate) = .empty;
var last_error: ?[]u8 = null;
fn ensureInstants() void {
if (!instants_init) {
instants = .empty;
instants_init = true;
}
}
fn ensureDurations() void {
if (!durations_init) {
durations = .empty;
durations_init = true;
}
}
fn ensurePlainDates() void {
if (!plain_dates_init) {
plain_dates = .empty;
plain_dates_init = true;
}
}
fn addInstant(inst: Temporal.Instant) u32 {
ensureInstants();
instants.append(wasm_allocator, inst) catch return 0;
return @intCast(instants.items.len);
}
fn getInstant(handle: u32) !Temporal.Instant {
ensureInstants();
if (handle == 0 or handle > instants.items.len) return PolyfillError.InvalidHandle;
return instants.items[handle - 1] orelse PolyfillError.InvalidHandle;
}
fn removeInstant(handle: u32) void {
if (!instants_init or handle == 0 or handle > instants.items.len) return;
if (instants.items[handle - 1]) |inst| {
inst.deinit();
instants.items[handle - 1] = null;
}
}
fn addDuration(dur: Temporal.Duration) u32 {
ensureDurations();
durations.append(wasm_allocator, dur) catch return 0;
return @intCast(durations.items.len);
}
fn getDuration(handle: u32) !Temporal.Duration {
ensureDurations();
if (handle == 0 or handle > durations.items.len) return PolyfillError.InvalidHandle;
return durations.items[handle - 1] orelse PolyfillError.InvalidHandle;
}
fn removeDuration(handle: u32) void {
if (!durations_init or handle == 0 or handle > durations.items.len) return;
if (durations.items[handle - 1]) |dur| {
dur.deinit();
durations.items[handle - 1] = null;
}
}
fn addPlainDate(date: Temporal.PlainDate) u32 {
ensurePlainDates();
plain_dates.append(wasm_allocator, date) catch return 0;
return @intCast(plain_dates.items.len);
}
fn getPlainDate(handle: u32) !Temporal.PlainDate {
ensurePlainDates();
if (handle == 0 or handle > plain_dates.items.len) return PolyfillError.InvalidHandle;
return plain_dates.items[handle - 1] orelse PolyfillError.InvalidHandle;
}
fn removePlainDate(handle: u32) void {
if (!plain_dates_init or handle == 0 or handle > plain_dates.items.len) return;
if (plain_dates.items[handle - 1]) |date| {
date.deinit();
plain_dates.items[handle - 1] = null;
}
}
fn clearLastError() void {
if (last_error) |msg| {
wasm_allocator.free(msg);
last_error = null;
}
}
fn setLastError(err: anyerror) void {
clearLastError();
const msg = std.fmt.allocPrint(wasm_allocator, "{s}", .{@errorName(err)}) catch return;
last_error = msg;
}
fn setLastErrorMessage(msg: []const u8) void {
clearLastError();
const owned = wasm_allocator.alloc(u8, msg.len) catch return;
std.mem.copyForwards(u8, owned, msg);
last_error = owned;
}
fn packPtrLen(ptr: [*]u8, len: usize) u64 {
const ptr_u32: u32 = @intCast(@intFromPtr(ptr));
const len_u32: u32 = @intCast(len);
return (@as(u64, ptr_u32) << 32) | @as(u64, len_u32);
}
fn unpackI128Hi(value: i128) i64 {
const bits: u128 = @bitCast(value);
const hi_bits: u64 = @intCast(bits >> 64);
return @bitCast(hi_bits);
}
fn unpackI128Lo(value: i128) u64 {
const bits: u128 = @bitCast(value);
return @intCast(bits & 0xFFFFFFFFFFFFFFFF);
}
fn joinI128(hi: i64, lo: u64) i128 {
const hi_bits: u64 = @bitCast(hi);
const value: u128 = (@as(u128, hi_bits) << 64) | @as(u128, lo);
return @bitCast(value);
}
fn unitFromCode(code: u8) ?Temporal.Duration.Unit {
return switch (code) {
1 => .nanosecond,
2 => .microsecond,
3 => .millisecond,
4 => .second,
5 => .minute,
6 => .hour,
7 => .day,
8 => .week,
9 => .month,
10 => .year,
11 => .auto,
else => null,
};
}
fn roundingModeFromCode(code: u8) ?Temporal.Duration.RoundingMode {
return switch (code) {
1 => .ceil,
2 => .floor,
3 => .expand,
4 => .trunc,
5 => .half_ceil,
6 => .half_floor,
7 => .half_expand,
8 => .half_trunc,
9 => .half_even,
else => null,
};
}
export fn temporalz_last_error_ptr() usize {
return if (last_error) |msg| @intFromPtr(msg.ptr) else 0;
}
export fn temporalz_last_error_len() usize {
return if (last_error) |msg| msg.len else 0;
}
export fn temporalz_last_error_clear() void {
clearLastError();
}
export fn temporalz_alloc(len: usize) usize {
clearLastError();
const buf = wasm_allocator.alloc(u8, len) catch |err| {
setLastError(err);
return 0;
};
return @intFromPtr(buf.ptr);
}
export fn temporalz_free(ptr: usize, len: usize) void {
if (ptr == 0 or len == 0) return;
const slice = @as([*]u8, @ptrFromInt(ptr))[0..len];
wasm_allocator.free(slice);
}
export fn temporalz_string_free(ptr: usize, len: usize) void {
temporalz_free(ptr, len);
}
export fn temporalz_instant_from_utf8(ptr: [*]const u8, len: usize) u32 {
clearLastError();
const text = ptr[0..len];
const inst = Temporal.Instant.from(text) catch |err| {
setLastError(err);
return 0;
};
return addInstant(inst);
}
export fn temporalz_instant_from_epoch_milliseconds(epoch_ms: i64) u32 {
clearLastError();
const inst = Temporal.Instant.fromEpochMilliseconds(epoch_ms) catch |err| {
setLastError(err);
return 0;
};
return addInstant(inst);
}
export fn temporalz_instant_from_epoch_nanoseconds_parts(hi: i64, lo: u64) u32 {
clearLastError();
const epoch_ns = joinI128(hi, lo);
const inst = Temporal.Instant.fromEpochNanoseconds(epoch_ns) catch |err| {
setLastError(err);
return 0;
};
return addInstant(inst);
}
export fn temporalz_instant_epoch_milliseconds(handle: u32) i64 {
clearLastError();
const inst = getInstant(handle) catch |err| {
setLastError(err);
return 0;
};
return inst.epochMilliseconds();
}
export fn temporalz_instant_epoch_nanoseconds_hi(handle: u32) i64 {
clearLastError();
const inst = getInstant(handle) catch |err| {
setLastError(err);
return 0;
};
return unpackI128Hi(inst.epochNanoseconds());
}
export fn temporalz_instant_epoch_nanoseconds_lo(handle: u32) u64 {
clearLastError();
const inst = getInstant(handle) catch |err| {
setLastError(err);
return 0;
};
return unpackI128Lo(inst.epochNanoseconds());
}
export fn temporalz_instant_to_string(handle: u32) u64 {
clearLastError();
const inst = getInstant(handle) catch |err| {
setLastError(err);
return 0;
};
const text = inst.toString(wasm_allocator, .{}) catch |err| {
setLastError(err);
return 0;
};
return packPtrLen(text.ptr, text.len);
}
export fn temporalz_instant_add(handle: u32, duration_handle: u32) u32 {
clearLastError();
const inst = getInstant(handle) catch |err| {
setLastError(err);
return 0;
};
var dur = getDuration(duration_handle) catch |err| {
setLastError(err);
return 0;
};
const res = inst.add(&dur) catch |err| {
setLastError(err);
return 0;
};
return addInstant(res);
}
export fn temporalz_instant_subtract(handle: u32, duration_handle: u32) u32 {
clearLastError();
const inst = getInstant(handle) catch |err| {
setLastError(err);
return 0;
};
var dur = getDuration(duration_handle) catch |err| {
setLastError(err);
return 0;
};
const res = inst.subtract(&dur) catch |err| {
setLastError(err);
return 0;
};
return addInstant(res);
}
export fn temporalz_instant_compare(handle_a: u32, handle_b: u32) i32 {
clearLastError();
const a = getInstant(handle_a) catch |err| {
setLastError(err);
return 0;
};
const b = getInstant(handle_b) catch |err| {
setLastError(err);
return 0;
};
return @intCast(Temporal.Instant.compare(a, b));
}
export fn temporalz_instant_equals(handle_a: u32, handle_b: u32) u8 {
clearLastError();
const a = getInstant(handle_a) catch |err| {
setLastError(err);
return 0;
};
const b = getInstant(handle_b) catch |err| {
setLastError(err);
return 0;
};
return if (Temporal.Instant.equals(a, b)) 1 else 0;
}
export fn temporalz_instant_round(
handle: u32,
smallest_unit: u8,
rounding_mode: u8,
rounding_increment: u32,
) u32 {
clearLastError();
const inst = getInstant(handle) catch |err| {
setLastError(err);
return 0;
};
var opts = Temporal.Instant.RoundingOptions{};
if (smallest_unit != 255) {
opts.smallest_unit = unitFromCode(smallest_unit) orelse {
setLastErrorMessage("Invalid smallestUnit");
return 0;
};
}
if (rounding_mode != 255) {
opts.rounding_mode = roundingModeFromCode(rounding_mode) orelse {
setLastErrorMessage("Invalid roundingMode");
return 0;
};
}
if (rounding_increment != 0) {
opts.rounding_increment = rounding_increment;
}
const res = inst.round(opts) catch |err| {
setLastError(err);
return 0;
};
return addInstant(res);
}
export fn temporalz_instant_destroy(handle: u32) void {
removeInstant(handle);
}
export fn temporalz_duration_from_utf8(ptr: [*]const u8, len: usize) u32 {
clearLastError();
const text = ptr[0..len];
const dur = Temporal.Duration.from(text) catch |err| {
setLastError(err);
return 0;
};
return addDuration(dur);
}
export fn temporalz_duration_from_parts(
mask: u32,
years: i64,
months: i64,
weeks: i64,
days: i64,
hours: i64,
minutes: i64,
seconds: i64,
milliseconds: i64,
microseconds: f64,
nanoseconds: f64,
) u32 {
clearLastError();
var partial = Temporal.Duration.PartialDuration{};
if ((mask & 0x1) != 0) partial.years = years;
if ((mask & 0x2) != 0) partial.months = months;
if ((mask & 0x4) != 0) partial.weeks = weeks;
if ((mask & 0x8) != 0) partial.days = days;
if ((mask & 0x10) != 0) partial.hours = hours;
if ((mask & 0x20) != 0) partial.minutes = minutes;
if ((mask & 0x40) != 0) partial.seconds = seconds;
if ((mask & 0x80) != 0) partial.milliseconds = milliseconds;
if ((mask & 0x100) != 0) partial.microseconds = microseconds;
if ((mask & 0x200) != 0) partial.nanoseconds = nanoseconds;
const dur = Temporal.Duration.from(partial) catch |err| {
setLastError(err);
return 0;
};
return addDuration(dur);
}
export fn temporalz_duration_init(
years: i64,
months: i64,
weeks: i64,
days: i64,
hours: i64,
minutes: i64,
seconds: i64,
milliseconds: i64,
microseconds: f64,
nanoseconds: f64,
) u32 {
clearLastError();
const dur = Temporal.Duration.init(
years,
months,
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
) catch |err| {
setLastError(err);
return 0;
};
return addDuration(dur);
}
export fn temporalz_duration_to_string(handle: u32) u64 {
clearLastError();
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
const text = dur.toString(wasm_allocator, .{}) catch |err| {
setLastError(err);
return 0;
};
return packPtrLen(text.ptr, text.len);
}
export fn temporalz_duration_years(handle: u32) i64 {
clearLastError();
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
return dur.years();
}
export fn temporalz_duration_months(handle: u32) i64 {
clearLastError();
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
return dur.months();
}
export fn temporalz_duration_weeks(handle: u32) i64 {
clearLastError();
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
return dur.weeks();
}
export fn temporalz_duration_days(handle: u32) i64 {
clearLastError();
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
return dur.days();
}
export fn temporalz_duration_hours(handle: u32) i64 {
clearLastError();
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
return dur.hours();
}
export fn temporalz_duration_minutes(handle: u32) i64 {
clearLastError();
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
return dur.minutes();
}
export fn temporalz_duration_seconds(handle: u32) i64 {
clearLastError();
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
return dur.seconds();
}
export fn temporalz_duration_milliseconds(handle: u32) i64 {
clearLastError();
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
return dur.milliseconds();
}
export fn temporalz_duration_microseconds(handle: u32) f64 {
clearLastError();
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
return dur.microseconds();
}
export fn temporalz_duration_nanoseconds(handle: u32) f64 {
clearLastError();
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
return dur.nanoseconds();
}
export fn temporalz_duration_sign(handle: u32) i32 {
clearLastError();
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
return switch (dur.sign()) {
.positive => 1,
.zero => 0,
.negative => -1,
};
}
export fn temporalz_duration_blank(handle: u32) u8 {
clearLastError();
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
return if (dur.blank()) 1 else 0;
}
export fn temporalz_duration_abs(handle: u32) u32 {
clearLastError();
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
return addDuration(dur.abs());
}
export fn temporalz_duration_negated(handle: u32) u32 {
clearLastError();
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
return addDuration(dur.negated());
}
export fn temporalz_duration_add(handle_a: u32, handle_b: u32) u32 {
clearLastError();
const a = getDuration(handle_a) catch |err| {
setLastError(err);
return 0;
};
const b = getDuration(handle_b) catch |err| {
setLastError(err);
return 0;
};
const res = a.add(b) catch |err| {
setLastError(err);
return 0;
};
return addDuration(res);
}
export fn temporalz_duration_subtract(handle_a: u32, handle_b: u32) u32 {
clearLastError();
const a = getDuration(handle_a) catch |err| {
setLastError(err);
return 0;
};
const b = getDuration(handle_b) catch |err| {
setLastError(err);
return 0;
};
const res = a.subtract(b) catch |err| {
setLastError(err);
return 0;
};
return addDuration(res);
}
export fn temporalz_duration_compare(handle_a: u32, handle_b: u32) i32 {
clearLastError();
const a = getDuration(handle_a) catch |err| {
setLastError(err);
return 0;
};
const b = getDuration(handle_b) catch |err| {
setLastError(err);
return 0;
};
const res = a.compare(b, .{}) catch |err| {
setLastError(err);
return 0;
};
return @intCast(res);
}
export fn temporalz_duration_total(handle: u32, unit_code: u8) f64 {
clearLastError();
const unit = unitFromCode(unit_code) orelse {
setLastErrorMessage("Invalid unit");
return 0;
};
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
return dur.total(.{ .unit = unit }) catch |err| {
setLastError(err);
return 0;
};
}
export fn temporalz_duration_round(
handle: u32,
smallest_unit: u8,
largest_unit: u8,
rounding_mode: u8,
rounding_increment: u32,
) u32 {
clearLastError();
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
var opts = Temporal.Duration.RoundingOptions{};
if (smallest_unit != 255) {
opts.smallest_unit = unitFromCode(smallest_unit) orelse {
setLastErrorMessage("Invalid smallestUnit");
return 0;
};
}
if (largest_unit != 255) {
opts.largest_unit = unitFromCode(largest_unit) orelse {
setLastErrorMessage("Invalid largestUnit");
return 0;
};
}
if (rounding_mode != 255) {
opts.rounding_mode = roundingModeFromCode(rounding_mode) orelse {
setLastErrorMessage("Invalid roundingMode");
return 0;
};
}
if (rounding_increment != 0) {
opts.rounding_increment = rounding_increment;
}
const res = dur.round(opts) catch |err| {
setLastError(err);
return 0;
};
return addDuration(res);
}
export fn temporalz_duration_compare_plain_date(handle_a: u32, handle_b: u32, date_handle: u32) i32 {
clearLastError();
const a = getDuration(handle_a) catch |err| {
setLastError(err);
return 0;
};
const b = getDuration(handle_b) catch |err| {
setLastError(err);
return 0;
};
const date = getPlainDate(date_handle) catch |err| {
setLastError(err);
return 0;
};
const res = a.compare(b, .{ .relative_to = .{ .plain_date = date } }) catch |err| {
setLastError(err);
return 0;
};
return @intCast(res);
}
export fn temporalz_duration_total_plain_date(handle: u32, unit_code: u8, date_handle: u32) f64 {
clearLastError();
const unit = unitFromCode(unit_code) orelse {
setLastErrorMessage("Invalid unit");
return 0;
};
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
const date = getPlainDate(date_handle) catch |err| {
setLastError(err);
return 0;
};
return dur.total(.{ .unit = unit, .relative_to = .{ .plain_date = date } }) catch |err| {
setLastError(err);
return 0;
};
}
export fn temporalz_duration_round_plain_date(
handle: u32,
smallest_unit: u8,
largest_unit: u8,
rounding_mode: u8,
rounding_increment: u32,
date_handle: u32,
) u32 {
clearLastError();
const dur = getDuration(handle) catch |err| {
setLastError(err);
return 0;
};
const date = getPlainDate(date_handle) catch |err| {
setLastError(err);
return 0;
};
var opts = Temporal.Duration.RoundingOptions{};
if (smallest_unit != 255) {
opts.smallest_unit = unitFromCode(smallest_unit) orelse {
setLastErrorMessage("Invalid smallestUnit");
return 0;
};
}
if (largest_unit != 255) {
opts.largest_unit = unitFromCode(largest_unit) orelse {
setLastErrorMessage("Invalid largestUnit");
return 0;
};
}
if (rounding_mode != 255) {
opts.rounding_mode = roundingModeFromCode(rounding_mode) orelse {
setLastErrorMessage("Invalid roundingMode");
return 0;
};
}
if (rounding_increment != 0) {
opts.rounding_increment = rounding_increment;
}
opts.relative_to = .{ .plain_date = date };
const res = dur.round(opts) catch |err| {
setLastError(err);
return 0;
};
return addDuration(res);
}
export fn temporalz_plain_date_from_utf8(ptr: [*]const u8, len: usize) u32 {
clearLastError();
const text = ptr[0..len];
const date = Temporal.PlainDate.from(text) catch |err| {
setLastError(err);
return 0;
};
return addPlainDate(date);
}
export fn temporalz_plain_date_init(year: i32, month: u8, day: u8) u32 {
clearLastError();
const date = Temporal.PlainDate.init(year, month, day) catch |err| {
setLastError(err);
return 0;
};
return addPlainDate(date);
}
export fn temporalz_plain_date_to_string(handle: u32) u64 {
clearLastError();
const date = getPlainDate(handle) catch |err| {
setLastError(err);
return 0;
};
const text = date.toString(wasm_allocator, .{}) catch |err| {
setLastError(err);
return 0;
};
return packPtrLen(text.ptr, text.len);
}
export fn temporalz_plain_date_destroy(handle: u32) void {
removePlainDate(handle);
}
export fn temporalz_duration_destroy(handle: u32) void {
removeDuration(handle);
}
extern fn console(ptr: [*]u8, len: u32) void;
fn logFn(comptime _: anytype, comptime _: anytype, comptime format: []const u8, args: anytype) void {

View file

@ -1,824 +0,0 @@
const std = @import("std");
const Temporal = @import("temporalz");
// Mirrors test262 test/built-ins/Temporal/Duration/basic.js (constructor fields and sign)
test "Temporal.Duration constructor components" {
const pos = try Temporal.Duration.init(5, 5, 5, 5, 5, 5, 5, 5, 5, 0);
defer pos.deinit();
try std.testing.expectEqual(@as(i64, 5), pos.years());
try std.testing.expectEqual(@as(i64, 5), pos.months());
try std.testing.expectEqual(@as(i64, 5), pos.weeks());
try std.testing.expectEqual(@as(i64, 5), pos.days());
try std.testing.expectEqual(@as(i64, 5), pos.hours());
try std.testing.expectEqual(@as(i64, 5), pos.minutes());
try std.testing.expectEqual(@as(i64, 5), pos.seconds());
try std.testing.expectEqual(@as(i64, 5), pos.milliseconds());
try std.testing.expectEqual(@as(f64, 5), pos.microseconds());
try std.testing.expectEqual(@as(f64, 0), pos.nanoseconds());
try std.testing.expectEqual(Temporal.Duration.Sign.positive, pos.sign());
const neg = try Temporal.Duration.init(-5, -5, -5, -5, -5, -5, -5, -5, -5, 0);
defer neg.deinit();
try std.testing.expectEqual(@as(i64, -5), neg.years());
try std.testing.expectEqual(@as(i64, -5), neg.months());
try std.testing.expectEqual(@as(i64, -5), neg.weeks());
try std.testing.expectEqual(@as(i64, -5), neg.days());
try std.testing.expectEqual(@as(i64, -5), neg.hours());
try std.testing.expectEqual(@as(i64, -5), neg.minutes());
try std.testing.expectEqual(@as(i64, -5), neg.seconds());
try std.testing.expectEqual(@as(i64, -5), neg.milliseconds());
try std.testing.expectEqual(@as(f64, -5), neg.microseconds());
try std.testing.expectEqual(@as(f64, 0), neg.nanoseconds());
try std.testing.expectEqual(Temporal.Duration.Sign.negative, neg.sign());
const neg_zero = try Temporal.Duration.init(0, 0, 0, 0, 0, 0, 0, 0, 0.0, 0.0);
defer neg_zero.deinit();
try std.testing.expectEqual(@as(i64, 0), neg_zero.years());
try std.testing.expectEqual(@as(i64, 0), neg_zero.months());
try std.testing.expectEqual(@as(i64, 0), neg_zero.weeks());
try std.testing.expectEqual(@as(i64, 0), neg_zero.days());
try std.testing.expectEqual(@as(i64, 0), neg_zero.hours());
try std.testing.expectEqual(@as(i64, 0), neg_zero.minutes());
try std.testing.expectEqual(@as(i64, 0), neg_zero.seconds());
try std.testing.expectEqual(@as(i64, 0), neg_zero.milliseconds());
try std.testing.expectEqual(@as(f64, 0), neg_zero.microseconds());
try std.testing.expectEqual(@as(f64, 0), neg_zero.nanoseconds());
try std.testing.expectEqual(Temporal.Duration.Sign.zero, neg_zero.sign());
}
// Mirrors test262 test/built-ins/Temporal/Duration/compare/basic.js
test "Temporal.Duration compare time-only" {
const td1 = try Temporal.Duration.init(0, 0, 0, 0, 5, 5, 5, 5, 5, 5);
defer td1.deinit();
const td2 = try Temporal.Duration.init(0, 0, 0, 0, 5, 4, 5, 5, 5, 5);
defer td2.deinit();
const td1_neg = try Temporal.Duration.init(0, 0, 0, 0, -5, -5, -5, -5, -5, -5);
defer td1_neg.deinit();
const td2_neg = try Temporal.Duration.init(0, 0, 0, 0, -5, -4, -5, -5, -5, -5);
defer td2_neg.deinit();
try std.testing.expectEqual(@as(i8, 0), try td1.compare(td1, .{}));
try std.testing.expectEqual(@as(i8, -1), try td2.compare(td1, .{}));
try std.testing.expectEqual(@as(i8, 1), try td1.compare(td2, .{}));
try std.testing.expectEqual(@as(i8, 0), try td1_neg.compare(td1_neg, .{}));
try std.testing.expectEqual(@as(i8, 1), try td2_neg.compare(td1_neg, .{}));
try std.testing.expectEqual(@as(i8, -1), try td1_neg.compare(td2_neg, .{}));
try std.testing.expectEqual(@as(i8, -1), try td1_neg.compare(td2, .{}));
try std.testing.expectEqual(@as(i8, 1), try td1.compare(td2_neg, .{}));
}
// Mirrors test262 test/built-ins/Temporal/Duration/from/argument-string.js
test "Temporal.Duration from string parsing" {
// P1D
const d1 = try Temporal.Duration.from("P1D");
defer d1.deinit();
try std.testing.expectEqual(@as(i64, 0), d1.years());
try std.testing.expectEqual(@as(i64, 0), d1.months());
try std.testing.expectEqual(@as(i64, 0), d1.weeks());
try std.testing.expectEqual(@as(i64, 1), d1.days());
try std.testing.expectEqual(@as(i64, 0), d1.hours());
try std.testing.expectEqual(@as(i64, 0), d1.minutes());
try std.testing.expectEqual(@as(i64, 0), d1.seconds());
try std.testing.expectEqual(@as(i64, 0), d1.milliseconds());
// p1y1m1dt1h1m1s
const d2 = try Temporal.Duration.from("p1y1m1dt1h1m1s");
defer d2.deinit();
try std.testing.expectEqual(@as(i64, 1), d2.years());
try std.testing.expectEqual(@as(i64, 1), d2.months());
try std.testing.expectEqual(@as(i64, 0), d2.weeks());
try std.testing.expectEqual(@as(i64, 1), d2.days());
try std.testing.expectEqual(@as(i64, 1), d2.hours());
try std.testing.expectEqual(@as(i64, 1), d2.minutes());
try std.testing.expectEqual(@as(i64, 1), d2.seconds());
// P1Y1M1W1DT1H1M1.123456789S
const d3 = try Temporal.Duration.from("P1Y1M1W1DT1H1M1.123456789S");
defer d3.deinit();
try std.testing.expectEqual(@as(i64, 1), d3.years());
try std.testing.expectEqual(@as(i64, 1), d3.months());
try std.testing.expectEqual(@as(i64, 1), d3.weeks());
try std.testing.expectEqual(@as(i64, 1), d3.days());
try std.testing.expectEqual(@as(i64, 1), d3.hours());
try std.testing.expectEqual(@as(i64, 1), d3.minutes());
try std.testing.expectEqual(@as(i64, 1), d3.seconds());
try std.testing.expectEqual(@as(i64, 123), d3.milliseconds());
try std.testing.expectEqual(@as(f64, 456), d3.microseconds());
try std.testing.expectEqual(@as(f64, 789), d3.nanoseconds());
// P1DT0.5M (0.5 minutes)
const d4 = try Temporal.Duration.from("P1DT0.5M");
defer d4.deinit();
try std.testing.expectEqual(@as(i64, 0), d4.years());
try std.testing.expectEqual(@as(i64, 0), d4.months());
try std.testing.expectEqual(@as(i64, 0), d4.weeks());
try std.testing.expectEqual(@as(i64, 1), d4.days());
try std.testing.expectEqual(@as(i64, 0), d4.hours());
try std.testing.expectEqual(@as(i64, 0), d4.minutes());
try std.testing.expectEqual(@as(i64, 30), d4.seconds());
// -P1Y1M1W1DT1H1M1.123456789S (negative)
const d5 = try Temporal.Duration.from("-P1Y1M1W1DT1H1M1.123456789S");
defer d5.deinit();
try std.testing.expectEqual(@as(i64, -1), d5.years());
try std.testing.expectEqual(@as(i64, -1), d5.months());
try std.testing.expectEqual(@as(i64, -1), d5.weeks());
try std.testing.expectEqual(@as(i64, -1), d5.days());
try std.testing.expectEqual(@as(i64, -1), d5.hours());
try std.testing.expectEqual(@as(i64, -1), d5.minutes());
try std.testing.expectEqual(@as(i64, -1), d5.seconds());
// PT100M
const d6 = try Temporal.Duration.from("PT100M");
defer d6.deinit();
try std.testing.expectEqual(@as(i64, 0), d6.years());
try std.testing.expectEqual(@as(i64, 0), d6.months());
try std.testing.expectEqual(@as(i64, 0), d6.weeks());
try std.testing.expectEqual(@as(i64, 0), d6.days());
try std.testing.expectEqual(@as(i64, 0), d6.hours());
try std.testing.expectEqual(@as(i64, 100), d6.minutes());
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/abs/basic.js
test "Temporal.Duration abs" {
// blank duration
const d1 = try Temporal.Duration.init(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer d1.deinit();
const abs1 = d1.abs();
defer abs1.deinit();
try std.testing.expectEqual(@as(i64, 0), abs1.years());
try std.testing.expectEqual(@as(i64, 0), abs1.months());
try std.testing.expectEqual(@as(i64, 0), abs1.weeks());
try std.testing.expectEqual(@as(i64, 0), abs1.days());
// positive values
const d2 = try Temporal.Duration.init(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
defer d2.deinit();
const abs2 = d2.abs();
defer abs2.deinit();
try std.testing.expectEqual(@as(i64, 1), abs2.years());
try std.testing.expectEqual(@as(i64, 2), abs2.months());
try std.testing.expectEqual(@as(i64, 3), abs2.weeks());
try std.testing.expectEqual(@as(i64, 4), abs2.days());
try std.testing.expectEqual(@as(i64, 5), abs2.hours());
try std.testing.expectEqual(@as(i64, 6), abs2.minutes());
try std.testing.expectEqual(@as(i64, 7), abs2.seconds());
try std.testing.expectEqual(@as(i64, 8), abs2.milliseconds());
try std.testing.expectEqual(@as(f64, 9), abs2.microseconds());
try std.testing.expectEqual(@as(f64, 10), abs2.nanoseconds());
// negative values
const d3 = try Temporal.Duration.init(-1, -2, -3, -4, -5, -6, -7, -8, -9, -10);
defer d3.deinit();
const abs3 = d3.abs();
defer abs3.deinit();
try std.testing.expectEqual(@as(i64, 1), abs3.years());
try std.testing.expectEqual(@as(i64, 2), abs3.months());
try std.testing.expectEqual(@as(i64, 3), abs3.weeks());
try std.testing.expectEqual(@as(i64, 4), abs3.days());
try std.testing.expectEqual(@as(i64, 5), abs3.hours());
try std.testing.expectEqual(@as(i64, 6), abs3.minutes());
try std.testing.expectEqual(@as(i64, 7), abs3.seconds());
try std.testing.expectEqual(@as(i64, 8), abs3.milliseconds());
try std.testing.expectEqual(@as(f64, 9), abs3.microseconds());
try std.testing.expectEqual(@as(f64, 10), abs3.nanoseconds());
// some zeros
const d4 = try Temporal.Duration.init(1, 0, 3, 0, 5, 0, 7, 0, 9, 0);
defer d4.deinit();
const abs4 = d4.abs();
defer abs4.deinit();
try std.testing.expectEqual(@as(i64, 1), abs4.years());
try std.testing.expectEqual(@as(i64, 0), abs4.months());
try std.testing.expectEqual(@as(i64, 3), abs4.weeks());
try std.testing.expectEqual(@as(i64, 0), abs4.days());
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/negated/basic.js
test "Temporal.Duration negated" {
// blank duration
const d1 = try Temporal.Duration.init(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer d1.deinit();
const neg1 = d1.negated();
defer neg1.deinit();
try std.testing.expectEqual(@as(i64, 0), neg1.years());
try std.testing.expectEqual(Temporal.Duration.Sign.zero, neg1.sign());
// positive values
const d2 = try Temporal.Duration.init(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
defer d2.deinit();
const neg2 = d2.negated();
defer neg2.deinit();
try std.testing.expectEqual(@as(i64, -1), neg2.years());
try std.testing.expectEqual(@as(i64, -2), neg2.months());
try std.testing.expectEqual(@as(i64, -3), neg2.weeks());
try std.testing.expectEqual(@as(i64, -4), neg2.days());
try std.testing.expectEqual(@as(i64, -5), neg2.hours());
try std.testing.expectEqual(@as(i64, -6), neg2.minutes());
try std.testing.expectEqual(@as(i64, -7), neg2.seconds());
try std.testing.expectEqual(@as(i64, -8), neg2.milliseconds());
try std.testing.expectEqual(@as(f64, -9), neg2.microseconds());
try std.testing.expectEqual(@as(f64, -10), neg2.nanoseconds());
try std.testing.expectEqual(Temporal.Duration.Sign.negative, neg2.sign());
// negative values
const d3 = try Temporal.Duration.init(-1, -2, -3, -4, -5, -6, -7, -8, -9, -10);
defer d3.deinit();
const neg3 = d3.negated();
defer neg3.deinit();
try std.testing.expectEqual(@as(i64, 1), neg3.years());
try std.testing.expectEqual(@as(i64, 2), neg3.months());
try std.testing.expectEqual(@as(i64, 3), neg3.weeks());
try std.testing.expectEqual(@as(i64, 4), neg3.days());
try std.testing.expectEqual(Temporal.Duration.Sign.positive, neg3.sign());
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/add/basic.js
test "Temporal.Duration add time-only" {
// Simple time additions
const td1 = try Temporal.Duration.init(0, 0, 0, 0, 1, 2, 3, 4, 5, 6);
defer td1.deinit();
const td2 = try Temporal.Duration.init(0, 0, 0, 0, 1, 2, 3, 4, 5, 6);
defer td2.deinit();
const result = try td1.add(td2);
defer result.deinit();
try std.testing.expectEqual(@as(i64, 0), result.years());
try std.testing.expectEqual(@as(i64, 0), result.months());
try std.testing.expectEqual(@as(i64, 0), result.weeks());
try std.testing.expectEqual(@as(i64, 0), result.days());
try std.testing.expectEqual(@as(i64, 2), result.hours());
try std.testing.expectEqual(@as(i64, 4), result.minutes());
try std.testing.expectEqual(@as(i64, 6), result.seconds());
try std.testing.expectEqual(@as(i64, 8), result.milliseconds());
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/subtract/basic.js
test "Temporal.Duration subtract time-only" {
// Simple time subtraction
const td1 = try Temporal.Duration.init(0, 0, 0, 0, 5, 5, 5, 5, 5, 5);
defer td1.deinit();
const td2 = try Temporal.Duration.init(0, 0, 0, 0, 1, 1, 1, 1, 1, 1);
defer td2.deinit();
const result = try td1.subtract(td2);
defer result.deinit();
try std.testing.expectEqual(@as(i64, 4), result.hours());
try std.testing.expectEqual(@as(i64, 4), result.minutes());
try std.testing.expectEqual(@as(i64, 4), result.seconds());
try std.testing.expectEqual(@as(i64, 4), result.milliseconds());
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/blank/basic.js
test "Temporal.Duration blank" {
// Blank duration
const blank = try Temporal.Duration.init(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer blank.deinit();
try std.testing.expectEqual(true, blank.blank());
// Non-blank durations
const d1 = try Temporal.Duration.init(1, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer d1.deinit();
try std.testing.expectEqual(false, d1.blank());
const d2 = try Temporal.Duration.init(0, 1, 0, 0, 0, 0, 0, 0, 0, 0);
defer d2.deinit();
try std.testing.expectEqual(false, d2.blank());
const d3 = try Temporal.Duration.init(0, 0, 1, 0, 0, 0, 0, 0, 0, 0);
defer d3.deinit();
try std.testing.expectEqual(false, d3.blank());
const d4 = try Temporal.Duration.init(0, 0, 0, 1, 0, 0, 0, 0, 0, 0);
defer d4.deinit();
try std.testing.expectEqual(false, d4.blank());
const d5 = try Temporal.Duration.init(0, 0, 0, 0, 1, 0, 0, 0, 0, 0);
defer d5.deinit();
try std.testing.expectEqual(false, d5.blank());
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/toString/basic.js
test "Temporal.Duration toString" {
const allocator = std.testing.allocator;
// Blank duration
const blank = try Temporal.Duration.init(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer blank.deinit();
const str1 = try blank.toString(allocator, .{});
defer allocator.free(str1);
try std.testing.expectEqualSlices(u8, "PT0S", str1);
// Hours and minutes
const td1 = try Temporal.Duration.init(0, 0, 0, 0, 1, 30, 0, 0, 0, 0);
defer td1.deinit();
const str2 = try td1.toString(allocator, .{});
defer allocator.free(str2);
try std.testing.expectEqualSlices(u8, "PT1H30M", str2);
// Full duration
const td2 = try Temporal.Duration.init(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
defer td2.deinit();
const str3 = try td2.toString(allocator, .{});
defer allocator.free(str3);
// Result should contain Y, M, W, D, H, M, S, etc.
try std.testing.expect(std.mem.containsAtLeast(u8, str3, 1, "Y"));
try std.testing.expect(std.mem.containsAtLeast(u8, str3, 1, "W"));
try std.testing.expect(std.mem.containsAtLeast(u8, str3, 1, "D"));
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/toJSON/basic.js
test "Temporal.Duration toJSON" {
const allocator = std.testing.allocator;
const blank = try Temporal.Duration.init(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer blank.deinit();
const json = try blank.toJSON(allocator);
defer allocator.free(json);
try std.testing.expectEqualSlices(u8, "PT0S", json);
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/total/basic.js
test "Temporal.Duration total" {
const td = try Temporal.Duration.init(0, 0, 0, 0, 1, 30, 0, 0, 0, 0);
defer td.deinit();
// total hours
const hours = try td.total(.{ .unit = Temporal.Duration.Unit.hour });
try std.testing.expectApproxEqAbs(@as(f64, 1.5), hours, 0.0001);
// total minutes
const minutes = try td.total(.{ .unit = Temporal.Duration.Unit.minute });
try std.testing.expectApproxEqAbs(@as(f64, 90), minutes, 0.0001);
// total seconds
const seconds = try td.total(.{ .unit = Temporal.Duration.Unit.second });
try std.testing.expectApproxEqAbs(@as(f64, 5400), seconds, 0.0001);
}
// Mirrors test262 test/built-ins/Temporal/Duration/constructor.js
test "Temporal.Duration cannot be called without new" {
// Note: In Zig we use a function, not a constructor, so this test is about ensuring proper init
// Testing that we can successfully create a duration
const dur = try Temporal.Duration.init(1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
defer dur.deinit();
try std.testing.expectEqual(@as(i64, 1), dur.years());
}
// Mirrors test262 test/built-ins/Temporal/Duration/days-undefined.js
test "Temporal.Duration with specific fields from ISO string" {
const d1 = try Temporal.Duration.from("P1Y");
defer d1.deinit();
try std.testing.expectEqual(@as(i64, 1), d1.years());
try std.testing.expectEqual(@as(i64, 0), d1.months());
try std.testing.expectEqual(@as(i64, 0), d1.days());
try std.testing.expectEqual(@as(i64, 0), d1.hours());
const d2 = try Temporal.Duration.from("PT1H");
defer d2.deinit();
try std.testing.expectEqual(@as(i64, 0), d2.years());
try std.testing.expectEqual(@as(i64, 0), d2.days());
try std.testing.expectEqual(@as(i64, 1), d2.hours());
try std.testing.expectEqual(@as(i64, 0), d2.minutes());
}
// Mirrors test262 test/built-ins/Temporal/Duration/mixed.js
test "Temporal.Duration mixed components" {
const d1 = try Temporal.Duration.init(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
defer d1.deinit();
try std.testing.expectEqual(@as(i64, 1), d1.years());
try std.testing.expectEqual(@as(i64, 2), d1.months());
try std.testing.expectEqual(@as(i64, 3), d1.weeks());
try std.testing.expectEqual(@as(i64, 4), d1.days());
try std.testing.expectEqual(@as(i64, 5), d1.hours());
try std.testing.expectEqual(@as(i64, 6), d1.minutes());
try std.testing.expectEqual(@as(i64, 7), d1.seconds());
try std.testing.expectEqual(@as(i64, 8), d1.milliseconds());
try std.testing.expectEqual(@as(f64, 9), d1.microseconds());
try std.testing.expectEqual(@as(f64, 10), d1.nanoseconds());
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/sign/basic.js
test "Temporal.Duration sign property" {
const blank = try Temporal.Duration.init(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer blank.deinit();
try std.testing.expectEqual(Temporal.Duration.Sign.zero, blank.sign());
const pos = try Temporal.Duration.init(1, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer pos.deinit();
try std.testing.expectEqual(Temporal.Duration.Sign.positive, pos.sign());
const neg = try Temporal.Duration.init(-1, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer neg.deinit();
try std.testing.expectEqual(Temporal.Duration.Sign.negative, neg.sign());
}
// Mirrors test262 test/built-ins/Temporal/Duration/large-number.js
test "Temporal.Duration with large numbers" {
const d = try Temporal.Duration.init(100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000);
defer d.deinit();
try std.testing.expectEqual(@as(i64, 100000), d.years());
try std.testing.expectEqual(@as(i64, 100000), d.months());
try std.testing.expectEqual(@as(i64, 100000), d.weeks());
}
// Mirrors test262 test/built-ins/Temporal/Duration/from/blank-duration.js
test "Temporal.Duration blank from string" {
const blank1 = try Temporal.Duration.from("PT0S");
defer blank1.deinit();
try std.testing.expectEqual(true, blank1.blank());
const blank2 = try Temporal.Duration.from("P0D");
defer blank2.deinit();
try std.testing.expectEqual(true, blank2.blank());
}
// Mirrors test262 test/built-ins/Temporal/Duration/length.js
test "Temporal.Duration constructor has correct arity" {
// In Zig, we test that init can be called with the expected parameters
const d = try Temporal.Duration.init(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
defer d.deinit();
try std.testing.expect(true); // If we get here, init works
}
// Mirrors test262 test/built-ins/Temporal/Duration/from/length.js
test "Temporal.Duration from has correct arity" {
const d = try Temporal.Duration.from("P1D");
defer d.deinit();
try std.testing.expect(true); // If we get here, from works
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/add/basic.js with normal values
test "Temporal.Duration add with time fields" {
const d1 = try Temporal.Duration.init(0, 0, 0, 0, 1, 1, 1, 1, 0, 0);
defer d1.deinit();
const d2 = try Temporal.Duration.init(0, 0, 0, 0, 2, 2, 2, 2, 0, 0);
defer d2.deinit();
const result = try d1.add(d2);
defer result.deinit();
try std.testing.expectEqual(@as(i64, 0), result.years());
try std.testing.expectEqual(@as(i64, 3), result.hours());
try std.testing.expectEqual(@as(i64, 3), result.minutes());
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/add/basic.js with date and time
test "Temporal.Duration add with date and time components" {
// Note: Adding durations with date components together requires a RelativeTo context
// which is not always available, causing a RangeError
if (true) return error.SkipZigTest;
const d1 = try Temporal.Duration.init(100, 100, 100, 100, 100, 100, 100, 100, 100, 100);
defer d1.deinit();
const d2 = try Temporal.Duration.init(100, 100, 100, 100, 100, 100, 100, 100, 100, 100);
defer d2.deinit();
const result = try d1.add(d2);
defer result.deinit();
try std.testing.expectEqual(@as(i64, 200), result.years());
try std.testing.expectEqual(@as(i64, 200), result.months());
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/subtract/basic.js with normal values
test "Temporal.Duration subtract with time fields" {
const d1 = try Temporal.Duration.init(0, 0, 0, 0, 5, 5, 5, 5, 0, 0);
defer d1.deinit();
const d2 = try Temporal.Duration.init(0, 0, 0, 0, 2, 2, 2, 2, 0, 0);
defer d2.deinit();
const result = try d1.subtract(d2);
defer result.deinit();
try std.testing.expectEqual(@as(i64, 0), result.years());
try std.testing.expectEqual(@as(i64, 3), result.hours());
try std.testing.expectEqual(@as(i64, 3), result.minutes());
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/subtract/basic.js with date and time
test "Temporal.Duration subtract with date and time components" {
// Note: Subtracting durations with date components together requires a RelativeTo context
// which is not always available, causing a RangeError
if (true) return error.SkipZigTest;
const d1 = try Temporal.Duration.init(200, 200, 200, 200, 200, 200, 200, 200, 200, 200);
defer d1.deinit();
const d2 = try Temporal.Duration.init(100, 100, 100, 100, 100, 100, 100, 100, 100, 100);
defer d2.deinit();
const result = try d1.subtract(d2);
defer result.deinit();
try std.testing.expectEqual(@as(i64, 100), result.years());
try std.testing.expectEqual(@as(i64, 100), result.months());
}
// Mirrors test262 test/built-ins/Temporal/Duration/name.js
test "Temporal.Duration constructor name" {
// In Zig we can't directly test function name, but we test that the constructor exists and works
const d = try Temporal.Duration.init(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer d.deinit();
try std.testing.expect(true);
}
// Mirrors test262 test/built-ins/Temporal/Duration/from/argument-duration.js
test "Temporal.Duration from duration object" {
const d1 = try Temporal.Duration.init(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
defer d1.deinit();
// In Zig binding, we'd typically test string parsing instead
const d2 = try Temporal.Duration.from("P1Y2M3W4DT5H6M7.008009010S");
defer d2.deinit();
try std.testing.expectEqual(@as(i64, 1), d2.years());
try std.testing.expectEqual(@as(i64, 2), d2.months());
try std.testing.expectEqual(@as(i64, 3), d2.weeks());
try std.testing.expectEqual(@as(i64, 4), d2.days());
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/with/basic.js (approximation)
test "Temporal.Duration with method basic" {
// Skip - requires with() method which may not be available
if (true) return error.SkipZigTest;
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/with/all-positive.js
test "Temporal.Duration with positive components" {
// Skip - requires with() method
if (true) return error.SkipZigTest;
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/round/basic.js
test "Temporal.Duration round method" {
// Skip - requires round() method with RoundingOptions
if (true) return error.SkipZigTest;
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/round/largest-unit.js
test "Temporal.Duration round with largest unit" {
// Skip - requires round() method
if (true) return error.SkipZigTest;
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/round/smallest-unit.js
test "Temporal.Duration round with smallest unit" {
// Skip - requires round() method
if (true) return error.SkipZigTest;
}
// Mirrors test262 test/built-ins/Temporal/Duration/years-undefined.js
test "Temporal.Duration years property undefined behavior" {
const d = try Temporal.Duration.from("PT1H");
defer d.deinit();
try std.testing.expectEqual(@as(i64, 0), d.years());
}
// Mirrors test262 test/built-ins/Temporal/Duration/months-undefined.js
test "Temporal.Duration months property undefined behavior" {
const d = try Temporal.Duration.from("PT1H");
defer d.deinit();
try std.testing.expectEqual(@as(i64, 0), d.months());
}
// Mirrors test262 test/built-ins/Temporal/Duration/weeks-undefined.js
test "Temporal.Duration weeks property undefined behavior" {
const d = try Temporal.Duration.from("PT1H");
defer d.deinit();
try std.testing.expectEqual(@as(i64, 0), d.weeks());
}
// Mirrors test262 test/built-ins/Temporal/Duration/days-undefined.js (property access)
test "Temporal.Duration days property undefined behavior" {
const d = try Temporal.Duration.from("PT1H");
defer d.deinit();
try std.testing.expectEqual(@as(i64, 0), d.days());
}
// Mirrors test262 test/built-ins/Temporal/Duration/hours-undefined.js
test "Temporal.Duration hours property undefined behavior" {
const d = try Temporal.Duration.from("P1Y");
defer d.deinit();
try std.testing.expectEqual(@as(i64, 0), d.hours());
}
// Mirrors test262 test/built-ins/Temporal/Duration/minutes-undefined.js
test "Temporal.Duration minutes property undefined behavior" {
const d = try Temporal.Duration.from("P1Y");
defer d.deinit();
try std.testing.expectEqual(@as(i64, 0), d.minutes());
}
// Mirrors test262 test/built-ins/Temporal/Duration/seconds-undefined.js
test "Temporal.Duration seconds property undefined behavior" {
const d = try Temporal.Duration.from("P1Y");
defer d.deinit();
try std.testing.expectEqual(@as(i64, 0), d.seconds());
}
// Mirrors test262 test/built-ins/Temporal/Duration/milliseconds-undefined.js
test "Temporal.Duration milliseconds property undefined behavior" {
const d = try Temporal.Duration.from("P1Y");
defer d.deinit();
try std.testing.expectEqual(@as(i64, 0), d.milliseconds());
}
// Mirrors test262 test/built-ins/Temporal/Duration/microseconds-undefined.js
test "Temporal.Duration microseconds property undefined behavior" {
const d = try Temporal.Duration.from("P1Y");
defer d.deinit();
try std.testing.expectEqual(@as(f64, 0), d.microseconds());
}
// Mirrors test262 test/built-ins/Temporal/Duration/nanoseconds-undefined.js
test "Temporal.Duration nanoseconds property undefined behavior" {
const d = try Temporal.Duration.from("P1Y");
defer d.deinit();
try std.testing.expectEqual(@as(f64, 0), d.nanoseconds());
}
// Mirrors test262 test/built-ins/Temporal/Duration/max.js
test "Temporal.Duration max value handling" {
// Test that very large durations can be created
const d = try Temporal.Duration.init(999999999, 999999999, 999999999, 999999999, 999999999, 999999999, 999999999, 999999999, 999999999, 999999999);
defer d.deinit();
try std.testing.expectEqual(@as(i64, 999999999), d.years());
}
// Mirrors test262 test/built-ins/Temporal/Duration/lower-limit.js
test "Temporal.Duration lower limit handling" {
// Test negative duration handling
const d = try Temporal.Duration.init(-999999999, -999999999, -999999999, -999999999, -999999999, -999999999, -999999999, -999999999, -999999999, -999999999);
defer d.deinit();
try std.testing.expectEqual(@as(i64, -999999999), d.years());
}
// Mirrors test262 test/built-ins/Temporal/Duration/out-of-range.js
test "Temporal.Duration out of range behavior" {
// Skip - would require error handling for invalid ranges
if (true) return error.SkipZigTest;
}
// Mirrors test262 test/built-ins/Temporal/Duration/invalid-type.js
test "Temporal.Duration invalid type handling" {
// Skip - type validation would be at binding level
if (true) return error.SkipZigTest;
}
// Mirrors test262 test/built-ins/Temporal/Duration/fractional-throws-rangeerror.js
test "Temporal.Duration fractional range error" {
// Skip - fractional components validation
if (true) return error.SkipZigTest;
}
// Mirrors test262 test/built-ins/Temporal/Duration/infinity-throws-rangeerror.js
test "Temporal.Duration infinity range error" {
// Skip - infinity handling at binding level
if (true) return error.SkipZigTest;
}
// Mirrors test262 test/built-ins/Temporal/Duration/negative-infinity-throws-rangeerror.js
test "Temporal.Duration negative infinity range error" {
// Skip - infinity handling at binding level
if (true) return error.SkipZigTest;
}
// Mirrors test262 test/built-ins/Temporal/Duration/builtin.js
test "Temporal.Duration builtin verification" {
const d = try Temporal.Duration.init(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer d.deinit();
try std.testing.expect(true);
}
// Mirrors test262 test/built-ins/Temporal/Duration/call-builtin.js
test "Temporal.Duration call as builtin" {
// In Zig, Duration is created via init function, not called as builtin
const d = try Temporal.Duration.init(1, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer d.deinit();
try std.testing.expectEqual(@as(i64, 1), d.years());
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/valueOf/basic.js
test "Temporal.Duration valueOf method" {
// Skip - valueOf may not be directly comparable in Zig
if (true) return error.SkipZigTest;
}
// Mirrors test262 test/built-ins/Temporal/Duration/compare/blank-duration.js
test "Temporal.Duration compare blank durations" {
const blank1 = try Temporal.Duration.init(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer blank1.deinit();
const blank2 = try Temporal.Duration.init(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer blank2.deinit();
const result = try blank1.compare(blank2, .{});
try std.testing.expectEqual(@as(i8, 0), result);
}
// Mirrors test262 test/built-ins/Temporal/Duration/from/blank-duration.js (property test)
test "Temporal.Duration blank property from string" {
const d = try Temporal.Duration.from("PT0S");
defer d.deinit();
try std.testing.expectEqual(true, d.blank());
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/abs/new-object.js
test "Temporal.Duration abs creates new object" {
const d1 = try Temporal.Duration.init(-5, -5, -5, -5, -5, -5, -5, -5, -5, -5);
defer d1.deinit();
const d2 = d1.abs();
defer d2.deinit();
// Verify they have different signs
try std.testing.expectEqual(Temporal.Duration.Sign.negative, d1.sign());
try std.testing.expectEqual(Temporal.Duration.Sign.positive, d2.sign());
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/negated/basic.js (zero handling)
test "Temporal.Duration negated zero duration" {
const zero = try Temporal.Duration.init(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer zero.deinit();
const negated = zero.negated();
defer negated.deinit();
try std.testing.expectEqual(Temporal.Duration.Sign.zero, negated.sign());
}
// Mirrors test262 test/built-ins/Temporal/Duration/from/string-with-skipped-units.js
test "Temporal.Duration from string with skipped units" {
const d1 = try Temporal.Duration.from("P1Y3M");
defer d1.deinit();
try std.testing.expectEqual(@as(i64, 1), d1.years());
try std.testing.expectEqual(@as(i64, 3), d1.months());
try std.testing.expectEqual(@as(i64, 0), d1.weeks());
try std.testing.expectEqual(@as(i64, 0), d1.days());
const d2 = try Temporal.Duration.from("PT2H30M");
defer d2.deinit();
try std.testing.expectEqual(@as(i64, 2), d2.hours());
try std.testing.expectEqual(@as(i64, 30), d2.minutes());
try std.testing.expectEqual(@as(i64, 0), d2.seconds());
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/add/blank-duration.js
test "Temporal.Duration add blank duration" {
if (true) return error.SkipZigTest;
const d1 = try Temporal.Duration.init(1, 2, 3, 4, 5, 6, 7, 8, 0, 0);
defer d1.deinit();
const blank = try Temporal.Duration.init(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer blank.deinit();
const result = try d1.add(blank);
defer result.deinit();
try std.testing.expectEqual(@as(i64, 1), result.years());
try std.testing.expectEqual(@as(i64, 5), result.hours());
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/subtract/blank-duration.js
test "Temporal.Duration subtract blank duration" {
if (true) return error.SkipZigTest;
const d1 = try Temporal.Duration.init(1, 2, 3, 4, 5, 6, 7, 8, 0, 0);
defer d1.deinit();
const blank = try Temporal.Duration.init(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer blank.deinit();
const result = try d1.subtract(blank);
defer result.deinit();
try std.testing.expectEqual(@as(i64, 1), result.years());
try std.testing.expectEqual(@as(i64, 5), result.hours());
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/toString/blank-duration-precision.js
test "Temporal.Duration toString blank with precision" {
const allocator = std.testing.allocator;
const blank = try Temporal.Duration.init(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer blank.deinit();
const str = try blank.toString(allocator, .{});
defer allocator.free(str);
try std.testing.expectEqualSlices(u8, "PT0S", str);
}
// Mirrors test262 test/built-ins/Temporal/Duration/prototype/toJSON/blank-duration.js (approx)
test "Temporal.Duration toJSON blank" {
const allocator = std.testing.allocator;
const blank = try Temporal.Duration.init(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
defer blank.deinit();
const json = try blank.toJSON(allocator);
defer allocator.free(json);
try std.testing.expectEqualSlices(u8, "PT0S", json);
}
// Mirrors test262 test/built-ins/Temporal/Duration/prop-desc.js
test "Temporal.Duration property descriptor" {
// JS-specific test for property descriptors (writable, enumerable, configurable)
// Not directly applicable to Zig bindings
if (true) return error.SkipZigTest;
}
// Mirrors test262 test/built-ins/Temporal/Duration/subclass.js
test "Temporal.Duration subclassing" {
// JS-specific test for subclassing Temporal.Duration
// Zig does not support class inheritance in the same way
if (true) return error.SkipZigTest;
}
// Mirrors test262 test/built-ins/Temporal/Duration/get-prototype-from-constructor-throws.js
test "Temporal.Duration get prototype from constructor throws" {
// JS-specific test for OrdinaryCreateFromConstructor prototype handling
// Not applicable to Zig bindings
if (true) return error.SkipZigTest;
}

683
test/test262/polyfill.js Normal file
View file

@ -0,0 +1,683 @@
// This polyfill is executed in a vm.Script context by test262-runner,
// so it must be synchronous and self-contained.
// The wasm bytes are injected as global.__TEMPORALZ_WASM_BYTES__ by the runner.
(function () {
if (!globalThis.__TEMPORALZ_WASM_BYTES__) {
throw new Error("WASM bytes not injected into test context");
}
const wasmBinary = globalThis.__TEMPORALZ_WASM_BYTES__;
const wasmModule = new WebAssembly.Module(wasmBinary);
const wasmInstance = new WebAssembly.Instance(wasmModule, {
env: {
console(ptr, len) {
const bytes = new Uint8Array(memory.buffer, ptr, len);
globalThis.console.log(decoder.decode(bytes));
},
},
});
const exports = wasmInstance.exports;
const memory = exports.memory;
const encoder = {
encode(str) {
const buf = [];
for (let i = 0; i < str.length; i++) {
const code = str.charCodeAt(i);
if (code < 0x80) {
buf.push(code);
} else if (code < 0x800) {
buf.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
} else if (code < 0xd800 || code >= 0xe000) {
buf.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
} else {
const code2 = str.charCodeAt(++i);
const codePoint = 0x10000 + (((code & 0x3ff) << 10) | (code2 & 0x3ff));
buf.push(
0xf0 | (codePoint >> 18),
0x80 | ((codePoint >> 12) & 0x3f),
0x80 | ((codePoint >> 6) & 0x3f),
0x80 | (codePoint & 0x3f)
);
}
}
return new Uint8Array(buf);
},
};
const decoder = {
decode(bytes) {
let str = "";
for (let i = 0; i < bytes.length; i++) {
const byte = bytes[i];
if (byte < 0x80) {
str += String.fromCharCode(byte);
} else if ((byte & 0xe0) === 0xc0) {
str += String.fromCharCode(((byte & 0x1f) << 6) | (bytes[++i] & 0x3f));
} else if ((byte & 0xf0) === 0xe0) {
str += String.fromCharCode(
((byte & 0x0f) << 12) | ((bytes[++i] & 0x3f) << 6) | (bytes[++i] & 0x3f)
);
} else if ((byte & 0xf8) === 0xf0) {
const codePoint =
((byte & 0x07) << 18) |
((bytes[++i] & 0x3f) << 12) |
((bytes[++i] & 0x3f) << 6) |
(bytes[++i] & 0x3f);
const high = ((codePoint - 0x10000) >> 10) + 0xd800;
const low = ((codePoint - 0x10000) & 0x3ff) + 0xdc00;
str += String.fromCharCode(high, low);
}
}
return str;
},
};
function readString(ptr, len) {
return decoder.decode(new Uint8Array(memory.buffer, ptr, len));
}
function lastError() {
const ptr = exports.temporalz_last_error_ptr();
const len = exports.temporalz_last_error_len();
if (!ptr || !len) return new Error("temporalz error");
const msg = readString(ptr, len);
exports.temporalz_last_error_clear();
if (msg.includes("Range")) return new RangeError(msg);
if (msg.includes("Type")) return new TypeError(msg);
return new Error(msg);
}
function requireHandle(handle) {
if (!handle) throw lastError();
return handle;
}
function allocString(text) {
const bytes = encoder.encode(text);
const ptr = exports.temporalz_alloc(bytes.length);
if (!ptr) throw lastError();
new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes);
return { ptr, len: bytes.length };
}
function takeString(packed) {
if (!packed) throw lastError();
const value = BigInt(packed);
const ptr = Number(value >> 32n);
const len = Number(value & 0xffffffffn);
const text = readString(ptr, len);
exports.temporalz_string_free(ptr, len);
return text;
}
function splitI128(value) {
const v = typeof value === "bigint" ? value : BigInt(value);
const mask = (1n << 64n) - 1n;
return { hi: v >> 64n, lo: v & mask };
}
function joinI128(hi, lo) {
const mask = (1n << 64n) - 1n;
return (BigInt(hi) << 64n) | (BigInt(lo) & mask);
}
const unitCodes = {
nanosecond: 1,
microsecond: 2,
millisecond: 3,
second: 4,
minute: 5,
hour: 6,
day: 7,
week: 8,
month: 9,
year: 10,
auto: 11,
};
const roundingModeCodes = {
ceil: 1,
floor: 2,
expand: 3,
trunc: 4,
halfCeil: 5,
halfFloor: 6,
halfExpand: 7,
halfTrunc: 8,
halfEven: 9,
};
function toUnitCode(value, name) {
if (value === undefined || value === null) return 255;
const code = unitCodes[value];
if (!code) throw new RangeError(`Invalid ${name}`);
return code;
}
function toRoundingModeCode(value) {
if (value === undefined || value === null) return 255;
const code = roundingModeCodes[value];
if (!code) throw new RangeError("Invalid roundingMode");
return code;
}
function toIntegerBigInt(value, name) {
if (typeof value === "bigint") return value;
const number = Number(value);
if (!isFiniteNumber(number)) throw new RangeError(`${name} must be finite`);
if (!Number.isInteger(number)) throw new RangeError(`${name} must be an integer`);
return BigInt(number);
}
function toNumber(value, name) {
const number = Number(value);
if (!isFiniteNumber(number)) throw new RangeError(`${name} must be finite`);
return number;
}
function toInteger(value, name) {
const number = Number(value);
if (!isFiniteNumber(number)) throw new RangeError(`${name} must be finite`);
if (!Number.isInteger(number)) throw new RangeError(`${name} must be an integer`);
return number;
}
function isFiniteNumber(value) {
return value === value && value !== Infinity && value !== -Infinity;
}
function unimplemented(name) {
return function () {
throw new Error(`${name} is not implemented yet`);
};
}
class Instant {
constructor(handle) {
this._handle = handle;
}
static _fromHandle(handle) {
return new Instant(handle);
}
static fromEpochMilliseconds(epochMs) {
const number = Number(epochMs);
if (!isFiniteNumber(number)) throw new RangeError("epochMilliseconds must be finite");
const handle = exports.temporalz_instant_from_epoch_milliseconds(number);
return Instant._fromHandle(requireHandle(handle));
}
static fromEpochNanoseconds(epochNs) {
const parts = splitI128(epochNs);
const handle = exports.temporalz_instant_from_epoch_nanoseconds_parts(
parts.hi,
parts.lo
);
return Instant._fromHandle(requireHandle(handle));
}
static from(value) {
if (value instanceof Instant) return value;
if (typeof value === "string") {
const text = allocString(value);
const handle = exports.temporalz_instant_from_utf8(text.ptr, text.len);
exports.temporalz_free(text.ptr, text.len);
return Instant._fromHandle(requireHandle(handle));
}
if (value && typeof value === "object") {
if (value.epochNanoseconds !== undefined) {
return Instant.fromEpochNanoseconds(value.epochNanoseconds);
}
if (value.epochMilliseconds !== undefined) {
return Instant.fromEpochMilliseconds(value.epochMilliseconds);
}
}
throw new TypeError("Instant.from expects a string or object");
}
get epochMilliseconds() {
return exports.temporalz_instant_epoch_milliseconds(this._handle);
}
get epochNanoseconds() {
const hi = exports.temporalz_instant_epoch_nanoseconds_hi(this._handle);
const lo = exports.temporalz_instant_epoch_nanoseconds_lo(this._handle);
return joinI128(hi, lo);
}
toString() {
return takeString(exports.temporalz_instant_to_string(this._handle));
}
toJSON() {
return this.toString();
}
add(durationLike) {
const dur = Duration.from(durationLike);
const handle = exports.temporalz_instant_add(this._handle, dur._handle);
return Instant._fromHandle(requireHandle(handle));
}
subtract(durationLike) {
const dur = Duration.from(durationLike);
const handle = exports.temporalz_instant_subtract(this._handle, dur._handle);
return Instant._fromHandle(requireHandle(handle));
}
round(options) {
if (!options || typeof options !== "object") {
throw new TypeError("round options must be an object");
}
const smallestUnit = toUnitCode(options.smallestUnit, "smallestUnit");
if (smallestUnit === 255) throw new RangeError("smallestUnit is required");
const roundingMode = toRoundingModeCode(options.roundingMode);
let roundingIncrement = 0;
if (options.roundingIncrement !== undefined) {
const inc = Number(options.roundingIncrement);
if (!isFiniteNumber(inc) || !Number.isInteger(inc) || inc <= 0) {
throw new RangeError("Invalid roundingIncrement");
}
roundingIncrement = inc;
}
const handle = exports.temporalz_instant_round(
this._handle,
smallestUnit,
roundingMode,
roundingIncrement
);
return Instant._fromHandle(requireHandle(handle));
}
equals(other) {
const rhs = Instant.from(other);
return exports.temporalz_instant_equals(this._handle, rhs._handle) === 1;
}
toLocaleString() {
return this.toString();
}
valueOf() {
throw new TypeError("Cannot convert Temporal.Instant to a number");
}
static compare(a, b) {
const left = Instant.from(a);
const right = Instant.from(b);
return Number(exports.temporalz_instant_compare(left._handle, right._handle));
}
}
const plainDateHandleToken = Symbol("PlainDateHandle");
class PlainDate {
constructor(year, month, day) {
if (year === plainDateHandleToken) {
this._handle = month;
return;
}
const yearValue = toInteger(year ?? 0, "year");
const monthValue = toInteger(month ?? 0, "month");
const dayValue = toInteger(day ?? 0, "day");
const handle = exports.temporalz_plain_date_init(yearValue, monthValue, dayValue);
this._handle = requireHandle(handle);
}
static _fromHandle(handle) {
return new PlainDate(plainDateHandleToken, handle);
}
static from(value) {
if (value instanceof PlainDate) return value;
if (typeof value === "string") {
const text = allocString(value);
const handle = exports.temporalz_plain_date_from_utf8(text.ptr, text.len);
exports.temporalz_free(text.ptr, text.len);
return PlainDate._fromHandle(requireHandle(handle));
}
if (value === null || typeof value !== "object") {
throw new TypeError("PlainDate.from expects a string or object");
}
const year = toInteger(value.year, "year");
const month = toInteger(value.month, "month");
const day = toInteger(value.day, "day");
const handle = exports.temporalz_plain_date_init(year, month, day);
return PlainDate._fromHandle(requireHandle(handle));
}
toString() {
return takeString(exports.temporalz_plain_date_to_string(this._handle));
}
}
const durationHandleToken = Symbol("DurationHandle");
class Duration {
constructor(
years,
months,
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds
) {
if (years === durationHandleToken) {
this._handle = months;
return;
}
const yearsValue = toIntegerBigInt(years ?? 0, "years");
const monthsValue = toIntegerBigInt(months ?? 0, "months");
const weeksValue = toIntegerBigInt(weeks ?? 0, "weeks");
const daysValue = toIntegerBigInt(days ?? 0, "days");
const hoursValue = toIntegerBigInt(hours ?? 0, "hours");
const minutesValue = toIntegerBigInt(minutes ?? 0, "minutes");
const secondsValue = toIntegerBigInt(seconds ?? 0, "seconds");
const millisecondsValue = toIntegerBigInt(milliseconds ?? 0, "milliseconds");
const microsecondsValue = toNumber(microseconds ?? 0, "microseconds");
const nanosecondsValue = toNumber(nanoseconds ?? 0, "nanoseconds");
const created = exports.temporalz_duration_init(
yearsValue,
monthsValue,
weeksValue,
daysValue,
hoursValue,
minutesValue,
secondsValue,
millisecondsValue,
microsecondsValue,
nanosecondsValue
);
this._handle = requireHandle(created);
}
static _fromHandle(handle) {
return new Duration(durationHandleToken, handle);
}
static from(value) {
if (value instanceof Duration) return value;
if (typeof value === "string") {
const text = allocString(value);
const handle = exports.temporalz_duration_from_utf8(text.ptr, text.len);
exports.temporalz_free(text.ptr, text.len);
return Duration._fromHandle(requireHandle(handle));
}
if (value === null || typeof value !== "object") {
throw new TypeError("Duration.from expects a string or object");
}
const fields = [
"years",
"months",
"weeks",
"days",
"hours",
"minutes",
"seconds",
"milliseconds",
"microseconds",
"nanoseconds",
];
let mask = 0;
const values = [0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0, 0];
fields.forEach((field, index) => {
if (value[field] !== undefined) {
mask |= 1 << index;
if (field === "microseconds" || field === "nanoseconds") {
values[index] = toNumber(value[field], field);
} else {
values[index] = toIntegerBigInt(value[field], field);
}
}
});
const handle = exports.temporalz_duration_from_parts(
mask,
values[0],
values[1],
values[2],
values[3],
values[4],
values[5],
values[6],
values[7],
values[8],
values[9]
);
return Duration._fromHandle(requireHandle(handle));
}
static compare(a, b, options) {
const left = Duration.from(a);
const right = Duration.from(b);
return Duration.compareWithOptions(left, right, options);
}
static compareWithOptions(left, right, options) {
if (options !== undefined && (options === null || typeof options !== "object")) {
throw new TypeError("options must be an object");
}
const relativeTo = toRelativeTo(options);
if (!relativeTo && (hasYearMonthWeek(left) || hasYearMonthWeek(right))) {
throw new RangeError("relativeTo is required for calendar units");
}
if (relativeTo) {
return Number(
exports.temporalz_duration_compare_plain_date(
left._handle,
right._handle,
relativeTo._handle
)
);
}
return Number(exports.temporalz_duration_compare(left._handle, right._handle));
}
add(other) {
const rhs = Duration.from(other);
const handle = exports.temporalz_duration_add(this._handle, rhs._handle);
return Duration._fromHandle(requireHandle(handle));
}
subtract(other) {
const rhs = Duration.from(other);
const handle = exports.temporalz_duration_subtract(this._handle, rhs._handle);
return Duration._fromHandle(requireHandle(handle));
}
abs() {
const handle = exports.temporalz_duration_abs(this._handle);
return Duration._fromHandle(requireHandle(handle));
}
negated() {
const handle = exports.temporalz_duration_negated(this._handle);
return Duration._fromHandle(requireHandle(handle));
}
round(options = {}) {
if (options === undefined) options = {};
if (options === null || typeof options !== "object") {
throw new TypeError("round options must be an object");
}
const relativeTo = toRelativeTo(options);
if (!relativeTo && hasCalendarUnits(this)) {
throw new RangeError("relativeTo is required for calendar units");
}
const smallestUnit = toUnitCode(options.smallestUnit, "smallestUnit");
const largestUnit = toUnitCode(options.largestUnit, "largestUnit");
const roundingMode = toRoundingModeCode(options.roundingMode);
let roundingIncrement = 0;
if (options.roundingIncrement !== undefined) {
const inc = Number(options.roundingIncrement);
if (!Number.isFinite(inc) || !Number.isInteger(inc) || inc <= 0) {
throw new RangeError("Invalid roundingIncrement");
}
roundingIncrement = inc;
}
const handle = relativeTo
? exports.temporalz_duration_round_plain_date(
this._handle,
smallestUnit,
largestUnit,
roundingMode,
roundingIncrement,
relativeTo._handle
)
: exports.temporalz_duration_round(
this._handle,
smallestUnit,
largestUnit,
roundingMode,
roundingIncrement
);
return Duration._fromHandle(requireHandle(handle));
}
total(options) {
if (!options || typeof options !== "object") {
throw new TypeError("total options must be an object");
}
const relativeTo = toRelativeTo(options);
if (!relativeTo && hasCalendarUnits(this)) {
throw new RangeError("relativeTo is required for calendar units");
}
const unit = toUnitCode(options.unit, "unit");
if (unit === 255) throw new RangeError("Invalid unit");
const result = relativeTo
? exports.temporalz_duration_total_plain_date(
this._handle,
unit,
relativeTo._handle
)
: exports.temporalz_duration_total(this._handle, unit);
if (!Number.isFinite(result)) throw lastError();
return result;
}
get sign() {
return Number(exports.temporalz_duration_sign(this._handle));
}
get blank() {
return exports.temporalz_duration_blank(this._handle) === 1;
}
toString() {
return takeString(exports.temporalz_duration_to_string(this._handle));
}
toJSON() {
return this.toString();
}
toLocaleString() {
return this.toString();
}
valueOf() {
throw new TypeError("Cannot convert Temporal.Duration to a number");
}
get years() {
return Number(exports.temporalz_duration_years(this._handle));
}
get months() {
return Number(exports.temporalz_duration_months(this._handle));
}
get weeks() {
return Number(exports.temporalz_duration_weeks(this._handle));
}
get days() {
return Number(exports.temporalz_duration_days(this._handle));
}
get hours() {
return Number(exports.temporalz_duration_hours(this._handle));
}
get minutes() {
return Number(exports.temporalz_duration_minutes(this._handle));
}
get seconds() {
return Number(exports.temporalz_duration_seconds(this._handle));
}
get milliseconds() {
return Number(exports.temporalz_duration_milliseconds(this._handle));
}
get microseconds() {
return Number(exports.temporalz_duration_microseconds(this._handle));
}
get nanoseconds() {
return Number(exports.temporalz_duration_nanoseconds(this._handle));
}
}
function hasCalendarUnits(duration) {
return (
duration.years !== 0 ||
duration.months !== 0 ||
duration.weeks !== 0 ||
duration.days !== 0
);
}
function hasYearMonthWeek(duration) {
return duration.years !== 0 || duration.months !== 0 || duration.weeks !== 0;
}
function toRelativeTo(options) {
if (!options || options.relativeTo === undefined) return null;
const rel = options.relativeTo;
if (rel instanceof PlainDate) return rel;
if (typeof rel === "string") return PlainDate.from(rel);
if (rel && typeof rel === "object") return PlainDate.from(rel);
throw new TypeError("Invalid relativeTo");
}
const Temporal = {
Instant,
Duration,
Now: {
instant: unimplemented("Temporal.Now.instant"),
plainDateISO: unimplemented("Temporal.Now.plainDateISO"),
plainDateTimeISO: unimplemented("Temporal.Now.plainDateTimeISO"),
plainTimeISO: unimplemented("Temporal.Now.plainTimeISO"),
timeZoneId: unimplemented("Temporal.Now.timeZoneId"),
zonedDateTimeISO: unimplemented("Temporal.Now.zonedDateTimeISO"),
},
PlainDate,
PlainTime: unimplemented("Temporal.PlainTime"),
PlainDateTime: unimplemented("Temporal.PlainDateTime"),
PlainYearMonth: unimplemented("Temporal.PlainYearMonth"),
PlainMonthDay: unimplemented("Temporal.PlainMonthDay"),
ZonedDateTime: unimplemented("Temporal.ZonedDateTime"),
};
globalThis.Temporal = Temporal;
})();

View file

@ -1,3 +0,0 @@
test {
_ = @import("duration.zig");
}

35
test/test262/runner.mjs Normal file
View file

@ -0,0 +1,35 @@
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 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),
});
fs.unlinkSync(tempPolyfillPath);
process.exit(result ? 0 : 1);