refactor: use temporal_rs as pkg

This commit is contained in:
Nurul Huda (Apon) 2026-01-26 11:34:26 +06:00
parent 518998fcd8
commit 46eb39ae26
21 changed files with 1227 additions and 771 deletions

10
.gitignore vendored
View file

@ -1,11 +1,9 @@
zig-out zig-out
.zig-cache .zig-cache
target
# Ignore Rust build target directory lib
vendor/temporal/target !lib/*/libtemporal_capi.a
!lib/*/temporal_capi.lib
# But track the specific compiled libraries
!vendor/temporal/target/**/libtemporal.a
!vendor/temporal/target/**/temporal.lib
tmp tmp

477
Cargo.lock generated Normal file
View file

@ -0,0 +1,477 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bytes"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
[[package]]
name = "calendrical_calculations"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a0b39595c6ee54a8d0900204ba4c401d0ab4eb45adaf07178e8d017541529e7"
dependencies = [
"core_maths",
"displaydoc",
]
[[package]]
name = "combine"
version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
dependencies = [
"bytes",
"memchr",
]
[[package]]
name = "core_maths"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30"
dependencies = [
"libm",
]
[[package]]
name = "diplomat"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9adb46b05e2f53dcf6a7dfc242e4ce9eb60c369b6b6eb10826a01e93167f59c6"
dependencies = [
"diplomat_core",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "diplomat-runtime"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0569bd3caaf13829da7ee4e83dbf9197a0e1ecd72772da6d08f0b4c9285c8d29"
[[package]]
name = "diplomat_core"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51731530ed7f2d4495019abc7df3744f53338e69e2863a6a64ae91821c763df1"
dependencies = [
"proc-macro2",
"quote",
"serde",
"smallvec",
"strck",
"syn",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "icu_calendar"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f0e52e009b6b16ba9c0693578796f2dd4aaa59a7f8f920423706714a89ac4e"
dependencies = [
"calendrical_calculations",
"displaydoc",
"icu_calendar_data",
"icu_locale",
"icu_locale_core",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_calendar_data"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527f04223b17edfe0bd43baf14a0cb1b017830db65f3950dc00224860a9a446d"
[[package]]
name = "icu_collections"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
dependencies = [
"displaydoc",
"potential_utf",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locale"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "532b11722e350ab6bf916ba6eb0efe3ee54b932666afec989465f9243fe6dd60"
dependencies = [
"icu_collections",
"icu_locale_core",
"icu_locale_data",
"icu_provider",
"potential_utf",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_locale_core"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
dependencies = [
"displaydoc",
"litemap",
"serde",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_locale_data"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c5f1d16b4c3a2642d3a719f18f6b06070ab0aef246a6418130c955ae08aa831"
[[package]]
name = "icu_provider"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
dependencies = [
"displaydoc",
"icu_locale_core",
"serde",
"stable_deref_trait",
"writeable",
"yoke",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]]
name = "ixdtf"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84de9d95a6d2547d9b77ee3f25fa0ee32e3c3a6484d47a55adebc0439c077992"
[[package]]
name = "jiff-tzdb"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2"
[[package]]
name = "libm"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "litemap"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "potential_utf"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
dependencies = [
"serde_core",
"writeable",
"zerovec",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "strck"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42316e70da376f3d113a68d138a60d8a9883c604fe97942721ec2068dab13a9f"
dependencies = [
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "temporal_capi"
version = "0.1.2"
dependencies = [
"temporal_capi 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"temporal_rs",
]
[[package]]
name = "temporal_capi"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a151e402c2bdb6a3a2a2f3f225eddaead2e7ce7dd5d3fa2090deb11b17aa4ed8"
dependencies = [
"diplomat",
"diplomat-runtime",
"icu_calendar",
"icu_locale",
"num-traits",
"temporal_rs",
"timezone_provider",
"writeable",
]
[[package]]
name = "temporal_rs"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88afde3bd75d2fc68d77a914bece426aa08aa7649ffd0cdd4a11c3d4d33474d1"
dependencies = [
"core_maths",
"icu_calendar",
"icu_locale",
"ixdtf",
"num-traits",
"timezone_provider",
"tinystr",
"writeable",
]
[[package]]
name = "timezone_provider"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df9ba0000e9e73862f3e7ca1ff159e2ddf915c9d8bb11e38a7874760f445d993"
dependencies = [
"combine",
"jiff-tzdb",
"tinystr",
"tzif",
"zerotrie",
"zerovec",
]
[[package]]
name = "tinystr"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
dependencies = [
"displaydoc",
"serde_core",
"zerovec",
]
[[package]]
name = "tzif"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0376dfa52cce372f3b095010fd064fb850a5d8fbfd5be8b0ffa3d64eeab5a5d"
dependencies = [
"combine",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "writeable"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "yoke"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
dependencies = [
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerotrie"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
dependencies = [
"displaydoc",
]
[[package]]
name = "zerovec"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
dependencies = [
"serde",
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

21
Cargo.toml Normal file
View file

@ -0,0 +1,21 @@
[package]
name = "temporal_capi"
version = "0.1.2"
edition = "2024"
[dependencies.temporal_capi]
version = "0.1.2"
default-features = false
features = ["compiled_data"]
[dependencies.temporal_rs]
version = "*"
default-features = false
features = ["float64_representable_durations"]
[lib]
crate-type = ["staticlib"]
[profile.release]
strip = true
lto = true

248
build.zig
View file

@ -1,22 +1,85 @@
const std = @import("std"); const std = @import("std");
const build_crab = @import("build_crab");
pub fn build(b: *std.Build) void { pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
// --- Zig Module: temporalz --- //
const mod = b.addModule("temporalz", .{ const mod = b.addModule("temporalz", .{
.root_source_file = b.path("src/root.zig"), .root_source_file = b.path("src/root.zig"),
.target = target, .target = target,
.optimize = optimize,
}); });
mod.addObjectFile(b.path(getTemporalRsPath(target)));
mod.link_libc = true; // --- Rust C ABI: temporal_capi --- //
if (target.result.os.tag == .linux) mod.linkSystemLibrary("unwind", .{}); const temporal_rs = b.dependency("temporal_rs", .{ .target = target, .optimize = optimize });
if (target.result.os.tag == .windows) { mod.addIncludePath(temporal_rs.path("temporal_capi/bindings/c"));
mod.linkSystemLibrary("ws2_32", .{});
mod.linkSystemLibrary("bcrypt", .{}); // --- Rust Crate: temporal_rs --- //
mod.linkSystemLibrary("advapi32", .{}); {
mod.linkSystemLibrary("userenv", .{}); // Determine target triple string for pre-built library lookup
const arch_str = @tagName(target.result.cpu.arch);
const os_str = @tagName(target.result.os.tag);
const target_triple = b.fmt("{s}-{s}", .{ arch_str, os_str });
const lib_name = if (target.result.os.tag == .windows) "temporal_capi.lib" else "libtemporal_capi.a";
// Check if pre-built library exists in lib/<target>/
const prebuilt_lib_path = b.fmt("lib/{s}/{s}", .{ target_triple, lib_name });
const prebuilt_lib_file = b.path(prebuilt_lib_path);
// Try to use pre-built library if it exists by checking the path object
const use_prebuilt = blk: {
// Use the LazyPath to check if the library exists
const lib_full_path = prebuilt_lib_file.getPath(b);
const lib_check_file = std.fs.openFileAbsolute(lib_full_path, .{}) catch break :blk false;
lib_check_file.close();
break :blk true;
};
if (use_prebuilt) {
// Use pre-built library (no Rust compiler needed)
// std.debug.print("Using pre-built temporal_capi library: {s}\n", .{prebuilt_lib_path});
mod.addObjectFile(prebuilt_lib_file);
} else {
// Build from source using Cargo
const build_dir = build_crab.addCargoBuild(
b,
.{
.manifest_path = b.path("Cargo.toml"),
.cargo_args = if (optimize == .Debug) &.{} else &.{"--release"},
},
.{
.target = target,
.optimize = .ReleaseSafe,
},
);
// Install .a/.lib to lib/<target>/libtemporal_capi.a/temporal_capi.lib
const install_lib = b.addInstallDirectory(.{
.source_dir = build_dir,
.install_dir = .{ .custom = "../lib" },
.install_subdir = target_triple,
});
b.getInstallStep().dependOn(&install_lib.step);
mod.addObjectFile(build_dir.path(b, lib_name));
} }
// --- Rust Misc Deps --- //
if (target.result.os.tag == .windows) mod.linkSystemLibrary("userenv", .{});
const unwind_stubs = b.addLibrary(.{
.linkage = .static,
.name = "unwind_stubs",
.root_module = b.createModule(.{
.root_source_file = b.path("src/unwind_stubs.zig"),
.target = target,
.optimize = optimize,
}),
});
mod.linkLibrary(unwind_stubs);
}
// --- Zig Executable: temporalz --- //
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "temporalz", .name = "temporalz",
.root_module = b.createModule(.{ .root_module = b.createModule(.{
@ -30,20 +93,21 @@ pub fn build(b: *std.Build) void {
}); });
b.installArtifact(exe); b.installArtifact(exe);
// Run command // --- Steps: Run --- //
{
const run_cmd = b.addRunArtifact(exe); const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep()); run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| run_cmd.addArgs(args); if (b.args) |args| run_cmd.addArgs(args);
const run_step = b.step("run", "Run the app"); const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}
// Docs // --- Steps: Docs --- //
{ {
const docs_step = b.step("docs", "Build the temporalz docs"); const docs_step = b.step("docs", "Build the temporalz docs");
const docs_obj = b.addObject(.{ const docs_obj = b.addObject(.{ .name = "temporalz", .root_module = mod });
.name = "temporalz",
.root_module = mod,
});
const docs = docs_obj.getEmittedDocs(); const docs = docs_obj.getEmittedDocs();
docs_step.dependOn(&b.addInstallDirectory(.{ docs_step.dependOn(&b.addInstallDirectory(.{
.source_dir = docs, .source_dir = docs,
.install_dir = .prefix, .install_dir = .prefix,
@ -51,9 +115,8 @@ pub fn build(b: *std.Build) void {
}).step); }).step);
} }
// Tests // --- Steps: Test --- //
{ {
run_step.dependOn(&run_cmd.step);
const test_step = b.step("test", "Run tests"); const test_step = b.step("test", "Run tests");
const mod_tests = b.addTest(.{ const mod_tests = b.addTest(.{
.root_module = mod, .root_module = mod,
@ -63,141 +126,46 @@ pub fn build(b: *std.Build) void {
}, },
}); });
mod_tests.linkLibC(); mod_tests.linkLibC();
if (target.result.os.tag == .linux) mod_tests.linkSystemLibrary("unwind");
if (target.result.os.tag == .windows) {
mod_tests.linkSystemLibrary("ws2_32");
mod_tests.linkSystemLibrary("bcrypt");
mod_tests.linkSystemLibrary("advapi32");
}
test_step.dependOn(&b.addRunArtifact(mod_tests).step); test_step.dependOn(&b.addRunArtifact(mod_tests).step);
const exe_tests = b.addTest(.{ .root_module = exe.root_module }); const exe_tests = b.addTest(.{ .root_module = exe.root_module });
test_step.dependOn(&b.addRunArtifact(exe_tests).step); test_step.dependOn(&b.addRunArtifact(exe_tests).step);
} }
// Rust cross-compilation prebuild // --- Steps: Build all platforms --- //
{ {
const rust_prebuild_step = b.step("temporal-rs", "Build and vendor Rust staticlibs for supported targets"); const build_lib_step = b.step("lib", "Build libraries for all common platforms");
var rustup_args: [3 + rust_targets.len][]const u8 = undefined;
rustup_args[0] = "rustup";
rustup_args[1] = "target";
rustup_args[2] = "add";
for (rust_targets, 0..) |t, i| {
rustup_args[3 + i] = t.triple;
}
const rustup_add = b.addSystemCommand(&rustup_args);
rust_prebuild_step.dependOn(&rustup_add.step);
for (rust_targets) |t| { inline for (platforms) |p| {
const cargo_build = b.addSystemCommand(&.{ const query = try std.Build.parseTargetQuery(.{ .arch_os_abi = p });
"cargo", const platform_target = b.resolveTargetQuery(query);
"build",
"--release",
"--manifest-path",
"vendor/temporal/Cargo.toml",
"--target",
t.triple,
});
cargo_build.step.dependOn(&rustup_add.step);
rust_prebuild_step.dependOn(&cargo_build.step);
}
}
// Release builds for all platforms const build_dir = build_crab.addCargoBuild(
{ b,
const release_targets = [_]struct { .{
name: []const u8, .manifest_path = b.path("Cargo.toml"),
target: std.Target.Query, .cargo_args = &.{"--release"},
}{ },
.{ .name = "linux-x64", .target = .{ .cpu_arch = .x86_64, .os_tag = .linux } }, .{
.{ .name = "linux-aarch64", .target = .{ .cpu_arch = .aarch64, .os_tag = .linux } }, .target = platform_target,
.{ .name = "macos-x64", .target = .{ .cpu_arch = .x86_64, .os_tag = .macos } }, .optimize = .ReleaseSafe,
.{ .name = "macos-aarch64", .target = .{ .cpu_arch = .aarch64, .os_tag = .macos } },
.{ .name = "windows-x64", .target = .{ .cpu_arch = .x86_64, .os_tag = .windows, .abi = .msvc } },
.{ .name = "windows-aarch64", .target = .{ .cpu_arch = .aarch64, .os_tag = .windows, .abi = .msvc } },
};
const release_step = b.step("release", "Build release binaries for all targets");
for (release_targets) |release_target| {
const resolved_target = b.resolveTargetQuery(release_target.target);
const release_mod = b.addModule(b.fmt("temporalz-{s}", .{release_target.name}), .{
.root_source_file = b.path("src/root.zig"),
.target = resolved_target,
});
release_mod.addObjectFile(b.path(getTemporalRsPath(resolved_target)));
release_mod.link_libc = true;
if (resolved_target.result.os.tag == .linux) release_mod.linkSystemLibrary("unwind", .{});
if (resolved_target.result.os.tag == .windows) {
release_mod.linkSystemLibrary("ws2_32", .{});
release_mod.linkSystemLibrary("bcrypt", .{});
release_mod.linkSystemLibrary("advapi32", .{});
release_mod.linkSystemLibrary("userenv", .{});
}
const release_exe = b.addExecutable(.{
.name = "temporalz",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = resolved_target,
.optimize = .ReleaseSmall,
.imports = &.{
.{ .name = "temporalz", .module = release_mod },
}, },
}),
});
release_exe.linkLibC();
if (resolved_target.result.os.tag == .windows) {
release_exe.linkSystemLibrary("ws2_32");
release_exe.linkSystemLibrary("bcrypt");
release_exe.linkSystemLibrary("advapi32");
release_exe.linkSystemLibrary("userenv");
}
const exe_ext = if (resolved_target.result.os.tag == .windows) ".exe" else "";
const install_release = b.addInstallArtifact(release_exe, .{
.dest_sub_path = b.fmt("release/temporalz-{s}{s}", .{ release_target.name, exe_ext }),
});
const target_step = b.step(
b.fmt("release-{s}", .{release_target.name}),
b.fmt("Build release binary for {s}", .{release_target.name}),
); );
target_step.dependOn(&install_release.step);
release_step.dependOn(&install_release.step); const install_lib = b.addInstallDirectory(.{
.source_dir = build_dir,
.install_dir = .{ .custom = "../lib" },
.install_subdir = p,
});
build_lib_step.dependOn(&install_lib.step);
} }
} }
} }
// Supported cross-compilation targets const platforms = [_][]const u8{
const rust_targets = [_]struct { triple: []const u8 }{ "aarch64-macos",
.{ .triple = "aarch64-apple-darwin" }, "x86_64-macos",
.{ .triple = "x86_64-apple-darwin" }, "aarch64-linux-gnu",
.{ .triple = "x86_64-unknown-linux-gnu" }, "x86_64-linux-gnu",
.{ .triple = "aarch64-unknown-linux-gnu" }, "x86_64-windows-gnu",
.{ .triple = "x86_64-pc-windows-msvc" }, "aarch64-windows-gnu",
.{ .triple = "aarch64-pc-windows-msvc" },
}; };
// Platform-specific Rust library path resolution
fn getTemporalRsPath(target: std.Build.ResolvedTarget) []const u8 {
const arch_tag = target.result.cpu.arch;
return switch (target.result.os.tag) {
.macos => switch (arch_tag) {
.aarch64 => "vendor/temporal/target/aarch64-apple-darwin/release/libtemporal.a",
.x86_64 => "vendor/temporal/target/x86_64-apple-darwin/release/libtemporal.a",
else => @panic("unsupported macOS architecture"),
},
.linux => switch (arch_tag) {
.x86_64 => "vendor/temporal/target/x86_64-unknown-linux-gnu/release/libtemporal.a",
.aarch64 => "vendor/temporal/target/aarch64-unknown-linux-gnu/release/libtemporal.a",
else => @panic("unsupported Linux architecture"),
},
.windows => switch (arch_tag) {
.x86_64 => "vendor/temporal/target/x86_64-pc-windows-msvc/release/temporal.lib",
.aarch64 => "vendor/temporal/target/aarch64-pc-windows-msvc/release/temporal.lib",
else => @panic("unsupported Windows architecture"),
},
else => @panic("unsupported OS"),
};
}

View file

@ -3,11 +3,23 @@
.version = "0.0.0", .version = "0.0.0",
.fingerprint = 0xd8d79d59acc4faae, .fingerprint = 0xd8d79d59acc4faae,
.minimum_zig_version = "0.15.2", .minimum_zig_version = "0.15.2",
.dependencies = .{}, .dependencies = .{
// .temporal_rs = .{
// .path = "./pkg/temporal_rs",
// },
.temporal_rs = .{
.url = "git+https://github.com/boa-dev/temporal#v0.1.2",
.hash = "N-V-__8AANlkNAB77Z946lqp-lgp2qSsOBDADValZC86PhB-",
},
.build_crab = .{
.url = "git+https://github.com/linusg/build.crab#7c8ace3739023b88f1fb95151df4f5b65bd7a311",
.hash = "build_crab-0.2.1-U0id_33BAAD3l81W7CBvgLU3OJSw7WI6FVLBSEHTnjTX",
},
},
.paths = .{ .paths = .{
"build.zig", "build.zig",
"build.zig.zon", "build.zig.zon",
"src", "src",
"vendor", "lib",
}, },
} }

27
example/build.zig Normal file
View file

@ -0,0 +1,27 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const temporalz_dep = b.dependency("temporalz", .{ .target = target, .optimize = optimize });
const exe = b.addExecutable(.{
.name = "example",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "temporalz", .module = temporalz_dep.module("temporalz") },
},
}),
});
b.installArtifact(exe);
const run_step = b.step("run", "Run the app");
const run_cmd = b.addRunArtifact(exe);
run_step.dependOn(&run_cmd.step);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| run_cmd.addArgs(args);
}

16
example/build.zig.zon Normal file
View file

@ -0,0 +1,16 @@
.{
.name = .temporalz_example,
.version = "0.1.2",
.fingerprint = 0x686c9dfc777f9593,
.minimum_zig_version = "0.15.2",
.dependencies = .{
.temporalz = .{
.path = "../",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}

16
example/src/main.zig Normal file
View file

@ -0,0 +1,16 @@
const std = @import("std");
const Temporal = @import("temporalz");
pub fn main() !void {
const allocator = std.heap.page_allocator;
const result = try Temporal.Instant.init(1_704_067_200_000_000_000); // 2024-01-01 00:00:00 UTC
defer result.deinit();
std.debug.print("Instant.epoch_milliseconds: {}\n", .{result.epoch_milliseconds});
std.debug.print("Instant.epoch_nanoseconds: {}\n", .{result.epoch_nanoseconds});
const instant_str = try result.toString(allocator, .{});
std.debug.print("Instant.toString(): {s}\n", .{instant_str});
const dur = try Temporal.Duration.from("P1Y2M3DT4H5M6S");
defer dur.deinit();
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,4 +1,6 @@
const std = @import("std"); const std = @import("std");
const temporal_rs = @import("cabi.zig");
const c = temporal_rs.c;
/// The Zig implementation of `Temporal.Duration`. /// The Zig implementation of `Temporal.Duration`.
/// ///
@ -135,7 +137,7 @@ const std = @import("std");
/// [mdn-duration]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration /// [mdn-duration]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration
const Duration = @This(); const Duration = @This();
_inner: *CDuration, _inner: *c.Duration,
/// Construct a Duration from years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, and nanoseconds. /// Construct a Duration from years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, and nanoseconds.
/// Equivalent to `Temporal.Duration.from()` or the constructor. /// Equivalent to `Temporal.Duration.from()` or the constructor.
@ -151,7 +153,7 @@ pub fn init(
microseconds_val: f64, microseconds_val: f64,
nanoseconds_val: f64, nanoseconds_val: f64,
) !Duration { ) !Duration {
return wrapDuration(temporal_rs_Duration_try_new( return wrapDuration(c.temporal_rs_Duration_try_new(
years_val, years_val,
months_val, months_val,
weeks_val, weeks_val,
@ -167,156 +169,156 @@ pub fn init(
/// Parse an ISO 8601 duration string (Temporal.Duration.from). /// Parse an ISO 8601 duration string (Temporal.Duration.from).
pub fn from(text: []const u8) !Duration { pub fn from(text: []const u8) !Duration {
const view = DiplomatStringView{ .data = text.ptr, .len = text.len }; const view = temporal_rs.toDiplomatStringView(text);
return wrapDuration(temporal_rs_Duration_from_utf8(view)); return wrapDuration(c.temporal_rs_Duration_from_utf8(view));
} }
/// Parse an ISO 8601 UTF-16 duration string. /// Parse an ISO 8601 UTF-16 duration string.
fn fromUtf16(text: []const u16) !Duration { fn fromUtf16(text: []const u16) !Duration {
const view = DiplomatString16View{ .data = text.ptr, .len = text.len }; const view = temporal_rs.toDiplomatString16View(text);
return wrapDuration(temporal_rs_Duration_from_utf16(view)); return wrapDuration(c.temporal_rs_Duration_from_utf16(view));
} }
/// Create a Duration from a partial duration (where some fields may be omitted). /// Create a Duration from a partial duration (where some fields may be omitted).
fn fromPartialDuration(partial: PartialDuration) !Duration { fn fromPartialDuration(partial: PartialDuration) !Duration {
return wrapDuration(temporal_rs_Duration_from_partial_duration(partial)); return wrapDuration(c.temporal_rs_Duration_from_partial_duration(partial));
} }
/// Check if the time portion of the duration is within valid ranges. /// Check if the time portion of the duration is within valid ranges.
fn isTimeWithinRange(self: Duration) bool { fn isTimeWithinRange(self: Duration) bool {
return temporal_rs_Duration_is_time_within_range(self._inner); return c.temporal_rs_Duration_is_time_within_range(self._inner);
} }
/// Get the years component of the duration. /// Get the years component of the duration.
pub fn years(self: Duration) i64 { pub fn years(self: Duration) i64 {
return temporal_rs_Duration_years(self._inner); return c.temporal_rs_Duration_years(self._inner);
} }
/// Get the months component of the duration. /// Get the months component of the duration.
pub fn months(self: Duration) i64 { pub fn months(self: Duration) i64 {
return temporal_rs_Duration_months(self._inner); return c.temporal_rs_Duration_months(self._inner);
} }
/// Get the weeks component of the duration. /// Get the weeks component of the duration.
pub fn weeks(self: Duration) i64 { pub fn weeks(self: Duration) i64 {
return temporal_rs_Duration_weeks(self._inner); return c.temporal_rs_Duration_weeks(self._inner);
} }
/// Get the days component of the duration. /// Get the days component of the duration.
pub fn days(self: Duration) i64 { pub fn days(self: Duration) i64 {
return temporal_rs_Duration_days(self._inner); return c.temporal_rs_Duration_days(self._inner);
} }
/// Get the hours component of the duration. /// Get the hours component of the duration.
pub fn hours(self: Duration) i64 { pub fn hours(self: Duration) i64 {
return temporal_rs_Duration_hours(self._inner); return c.temporal_rs_Duration_hours(self._inner);
} }
/// Get the minutes component of the duration. /// Get the minutes component of the duration.
pub fn minutes(self: Duration) i64 { pub fn minutes(self: Duration) i64 {
return temporal_rs_Duration_minutes(self._inner); return c.temporal_rs_Duration_minutes(self._inner);
} }
/// Get the seconds component of the duration. /// Get the seconds component of the duration.
pub fn seconds(self: Duration) i64 { pub fn seconds(self: Duration) i64 {
return temporal_rs_Duration_seconds(self._inner); return c.temporal_rs_Duration_seconds(self._inner);
} }
/// Get the milliseconds component of the duration. /// Get the milliseconds component of the duration.
pub fn milliseconds(self: Duration) i64 { pub fn milliseconds(self: Duration) i64 {
return temporal_rs_Duration_milliseconds(self._inner); return c.temporal_rs_Duration_milliseconds(self._inner);
} }
/// Get the microseconds component of the duration. /// Get the microseconds component of the duration.
pub fn microseconds(self: Duration) f64 { pub fn microseconds(self: Duration) f64 {
return temporal_rs_Duration_microseconds(self._inner); return c.temporal_rs_Duration_microseconds(self._inner);
} }
/// Get the nanoseconds component of the duration. /// Get the nanoseconds component of the duration.
pub fn nanoseconds(self: Duration) f64 { pub fn nanoseconds(self: Duration) f64 {
return temporal_rs_Duration_nanoseconds(self._inner); return c.temporal_rs_Duration_nanoseconds(self._inner);
} }
/// Get the sign of the duration: positive (1), zero (0), or negative (-1). /// Get the sign of the duration: positive (1), zero (0), or negative (-1).
pub fn sign(self: Duration) Sign { pub fn sign(self: Duration) Sign {
return temporal_rs_Duration_sign(self._inner); return @as(Sign, @enumFromInt(c.temporal_rs_Duration_sign(self._inner)));
} }
/// Check if the duration is zero (all fields are zero). /// Check if the duration is zero (all fields are zero).
pub fn blank(self: Duration) bool { pub fn blank(self: Duration) bool {
return temporal_rs_Duration_is_zero(self._inner); return c.temporal_rs_Duration_is_zero(self._inner);
} }
/// Returns a new Duration with the absolute value (all components positive). /// Returns a new Duration with the absolute value (all components positive).
pub fn abs(self: Duration) Duration { pub fn abs(self: Duration) Duration {
const ptr = temporal_rs_Duration_abs(self._inner); const ptr: *c.Duration = c.temporal_rs_Duration_abs(self._inner) orelse unreachable;
return .{ ._inner = ptr }; return .{ ._inner = ptr };
} }
/// Returns a new Duration with all components negated. /// Returns a new Duration with all components negated.
pub fn negated(self: Duration) Duration { pub fn negated(self: Duration) Duration {
const ptr = temporal_rs_Duration_negated(self._inner); const ptr: *c.Duration = c.temporal_rs_Duration_negated(self._inner) orelse unreachable;
return .{ ._inner = ptr }; return .{ ._inner = ptr };
} }
/// Add two durations together (Temporal.Duration.prototype.add). /// Add two durations together (Temporal.Duration.prototype.add).
pub fn add(self: Duration, other: Duration) !Duration { pub fn add(self: Duration, other: Duration) !Duration {
return wrapDuration(temporal_rs_Duration_add(self._inner, other._inner)); return wrapDuration(c.temporal_rs_Duration_add(self._inner, other._inner));
} }
/// Subtract another duration from this one (Temporal.Duration.prototype.subtract). /// Subtract another duration from this one (Temporal.Duration.prototype.subtract).
pub fn subtract(self: Duration, other: Duration) !Duration { pub fn subtract(self: Duration, other: Duration) !Duration {
return wrapDuration(temporal_rs_Duration_subtract(self._inner, other._inner)); return wrapDuration(c.temporal_rs_Duration_subtract(self._inner, other._inner));
} }
/// Round the duration according to the specified options (Temporal.Duration.prototype.round). /// Round the duration according to the specified options (Temporal.Duration.prototype.round).
pub fn round(self: Duration, options: RoundingOptions, relative_to: RelativeTo) !Duration { pub fn round(self: Duration, options: RoundingOptions, relative_to: RelativeTo) !Duration {
return wrapDuration(temporal_rs_Duration_round(self._inner, options, relative_to)); return wrapDuration(c.temporal_rs_Duration_round(self._inner, options, relative_to));
} }
/// Round the duration with an explicit provider. /// Round the duration with an explicit provider.
fn roundWithProvider(self: Duration, options: RoundingOptions, relative_to: RelativeTo, provider: *const Provider) !Duration { fn roundWithProvider(self: Duration, options: RoundingOptions, relative_to: RelativeTo, provider: *const c.Provider) !Duration {
return wrapDuration(temporal_rs_Duration_round_with_provider(self._inner, options, relative_to, provider)); return wrapDuration(c.temporal_rs_Duration_round_with_provider(self._inner, options, relative_to, provider));
} }
/// Compare two durations (Temporal.Duration.compare). /// Compare two durations (Temporal.Duration.compare).
pub fn compare(self: Duration, other: Duration, relative_to: RelativeTo) !i8 { pub fn compare(self: Duration, other: Duration, relative_to: RelativeTo) !i8 {
const res = temporal_rs_Duration_compare(self._inner, other._inner, relative_to); const res = c.temporal_rs_Duration_compare(self._inner, other._inner, relative_to);
if (!res.is_ok) return error.TemporalError; if (!res.is_ok) return error.TemporalError;
return res.result.ok; return res.unnamed_0.ok;
} }
/// Compare two durations with an explicit provider. /// Compare two durations with an explicit provider.
fn compareWithProvider(self: Duration, other: Duration, relative_to: RelativeTo, provider: *const Provider) !i8 { fn compareWithProvider(self: Duration, other: Duration, relative_to: RelativeTo, provider: *const c.Provider) !i8 {
const res = temporal_rs_Duration_compare_with_provider(self._inner, other._inner, relative_to, provider); const res = c.temporal_rs_Duration_compare_with_provider(self._inner, other._inner, relative_to, provider);
if (!res.is_ok) return error.TemporalError; if (!res.is_ok) return error.TemporalError;
return res.result.ok; return res.unnamed_0.ok;
} }
/// Get the total value of the duration in the specified unit (Temporal.Duration.prototype.total). /// Get the total value of the duration in the specified unit (Temporal.Duration.prototype.total).
pub fn total(self: Duration, unit: Unit, relative_to: RelativeTo) !f64 { pub fn total(self: Duration, unit: Unit, relative_to: RelativeTo) !f64 {
const res = temporal_rs_Duration_total(self._inner, unit, relative_to); const res = c.temporal_rs_Duration_total(self._inner, @intFromEnum(unit), relative_to);
if (!res.is_ok) return error.TemporalError; if (!res.is_ok) return error.TemporalError;
return res.result.ok; return res.unnamed_0.ok;
} }
/// Get the total value of the duration with an explicit provider. /// Get the total value of the duration with an explicit provider.
fn totalWithProvider(self: Duration, unit: Unit, relative_to: RelativeTo, provider: *const Provider) !f64 { fn totalWithProvider(self: Duration, unit: Unit, relative_to: RelativeTo, provider: *const c.Provider) !f64 {
const res = temporal_rs_Duration_total_with_provider(self._inner, unit, relative_to, provider); const res = c.temporal_rs_Duration_total_with_provider(self._inner, @intFromEnum(unit), relative_to, provider);
if (!res.is_ok) return error.TemporalError; if (!res.is_ok) return error.TemporalError;
return res.result.ok; return res.unnamed_0.ok;
} }
/// Convert to string (Temporal.Duration.prototype.toString); caller owns returned slice. /// Convert to string (Temporal.Duration.prototype.toString); caller owns returned slice.
pub fn toString(self: Duration, allocator: std.mem.Allocator, options: ToStringRoundingOptions) ![]u8 { pub fn toString(self: Duration, allocator: std.mem.Allocator, options: ToStringRoundingOptions) ![]u8 {
const writer = diplomat_buffer_write_create(128); const writer = c.diplomat_buffer_write_create(128);
defer diplomat_buffer_write_destroy(writer); defer c.diplomat_buffer_write_destroy(writer);
const res = temporal_rs_Duration_to_string(self._inner, options, writer); const res = c.temporal_rs_Duration_to_string(self._inner, options, writer);
try handleVoidResult(res); try handleVoidResult(res);
const len = diplomat_buffer_write_len(writer); const len = c.diplomat_buffer_write_len(writer);
const source = diplomat_buffer_write_get_bytes(writer)[0..len]; const source = c.diplomat_buffer_write_get_bytes(writer)[0..len];
const out = try allocator.alloc(u8, len); const out = try allocator.alloc(u8, len);
std.mem.copyForwards(u8, out, source); std.mem.copyForwards(u8, out, source);
@ -335,32 +337,30 @@ pub fn toLocaleString(self: Duration, allocator: std.mem.Allocator) ![]u8 {
/// Clone the underlying duration. /// Clone the underlying duration.
fn clone(self: Duration) Duration { fn clone(self: Duration) Duration {
const ptr = temporal_rs_Duration_clone(self._inner); const ptr: *c.Duration = c.temporal_rs_Duration_clone(self._inner) orelse unreachable;
return .{ ._inner = ptr }; return .{ ._inner = ptr };
} }
pub fn deinit(self: Duration) void { pub fn deinit(self: Duration) void {
temporal_rs_Duration_destroy(self._inner); c.temporal_rs_Duration_destroy(self._inner);
} }
// --- Helpers ----------------------------------------------------------------- // --- Helpers -----------------------------------------------------------------
fn wrapDuration(res: DurationResult) !Duration { fn handleVoidResult(res: anytype) !void {
if (!res.is_ok) return error.TemporalError; if (!res.is_ok) return error.TemporalError;
const ptr = res.result.ok orelse return error.TemporalError; }
fn wrapDuration(res: anytype) !Duration {
if (!res.is_ok) return error.TemporalError;
const maybe_ptr = res.unnamed_0.ok;
if (maybe_ptr == null) return error.TemporalError;
const ptr: *c.Duration = maybe_ptr.?;
return .{ ._inner = ptr }; return .{ ._inner = ptr };
} }
fn handleVoidResult(res: VoidResult) !void {
if (!res.is_ok) return error.TemporalError;
}
fn defaultToStringRoundingOptions() ToStringRoundingOptions { fn defaultToStringRoundingOptions() ToStringRoundingOptions {
return .{ return temporal_rs.to_string_rounding_options_auto;
.precision = .{ .is_minute = false, .precision = OptionU8{ .ok = 0, .is_ok = false } },
.smallest_unit = Unit_option{ .ok = .auto, .is_ok = false },
.rounding_mode = RoundingMode_option{ .ok = .trunc, .is_ok = false },
};
} }
// --- Public helper types ----------------------------------------------------- // --- Public helper types -----------------------------------------------------
@ -379,277 +379,71 @@ pub const ToStringOptions = struct {
smallest_unit: ?Unit = null, smallest_unit: ?Unit = null,
}; };
/// A partial duration where some or all fields may be omitted. pub const PartialDuration = c.PartialDuration;
pub const PartialDuration = extern struct {
years: OptionI64,
months: OptionI64,
weeks: OptionI64,
days: OptionI64,
hours: OptionI64,
minutes: OptionI64,
seconds: OptionI64,
milliseconds: OptionI64,
microseconds: OptionI128,
nanoseconds: OptionI128,
pub fn empty() PartialDuration {
return .{
.years = .{ .ok = 0, .is_ok = false },
.months = .{ .ok = 0, .is_ok = false },
.weeks = .{ .ok = 0, .is_ok = false },
.days = .{ .ok = 0, .is_ok = false },
.hours = .{ .ok = 0, .is_ok = false },
.minutes = .{ .ok = 0, .is_ok = false },
.seconds = .{ .ok = 0, .is_ok = false },
.milliseconds = .{ .ok = 0, .is_ok = false },
.microseconds = .{ .ok = 0, .is_ok = false },
.nanoseconds = .{ .ok = 0, .is_ok = false },
};
}
pub fn withYears(self: PartialDuration, value: i64) PartialDuration {
var result = self;
result.years = .{ .ok = value, .is_ok = true };
return result;
}
pub fn withMonths(self: PartialDuration, value: i64) PartialDuration {
var result = self;
result.months = .{ .ok = value, .is_ok = true };
return result;
}
pub fn withWeeks(self: PartialDuration, value: i64) PartialDuration {
var result = self;
result.weeks = .{ .ok = value, .is_ok = true };
return result;
}
pub fn withDays(self: PartialDuration, value: i64) PartialDuration {
var result = self;
result.days = .{ .ok = value, .is_ok = true };
return result;
}
pub fn withHours(self: PartialDuration, value: i64) PartialDuration {
var result = self;
result.hours = .{ .ok = value, .is_ok = true };
return result;
}
pub fn withMinutes(self: PartialDuration, value: i64) PartialDuration {
var result = self;
result.minutes = .{ .ok = value, .is_ok = true };
return result;
}
pub fn withSeconds(self: PartialDuration, value: i64) PartialDuration {
var result = self;
result.seconds = .{ .ok = value, .is_ok = true };
return result;
}
pub fn withMilliseconds(self: PartialDuration, value: i64) PartialDuration {
var result = self;
result.milliseconds = .{ .ok = value, .is_ok = true };
return result;
}
pub fn withMicroseconds(self: PartialDuration, value: i128) PartialDuration {
var result = self;
result.microseconds = .{ .ok = value, .is_ok = true };
return result;
}
pub fn withNanoseconds(self: PartialDuration, value: i128) PartialDuration {
var result = self;
result.nanoseconds = .{ .ok = value, .is_ok = true };
return result;
}
};
/// Relative-to context for duration operations. /// Relative-to context for duration operations.
pub const RelativeTo = extern struct { pub const RelativeTo = extern struct {
plain_date: ?*PlainDate, plain_date: ?*c.PlainDate,
zoned_date_time: ?*ZonedDateTime, zoned_date_time: ?*c.ZonedDateTime,
}; };
// --- Extern types ------------------------------------------------------------ // --- Public type aliases and enums ------------------------------------------
const CDuration = opaque {}; const OptionU8 = c.OptionU8;
const PlainDate = opaque {}; const OptionU32 = c.OptionU32;
const ZonedDateTime = opaque {}; const OptionI64 = c.OptionI64;
const Provider = opaque {}; const OptionF64 = c.OptionF64;
const Precision = c.Precision;
const DiplomatStringView = extern struct { data: [*c]const u8, len: usize }; const Unit_option = c.Unit_option;
const DiplomatString16View = extern struct { data: [*c]const u16, len: usize }; const RoundingMode_option = c.RoundingMode_option;
const OptionStringView = extern struct { ok: DiplomatStringView, is_ok: bool };
const OptionU8 = extern struct { ok: u8, is_ok: bool };
const OptionU32 = extern struct { ok: u32, is_ok: bool };
const OptionI64 = extern struct { ok: i64, is_ok: bool };
const OptionI128 = extern struct { ok: i128, is_ok: bool };
const DiplomatWrite = extern struct {
context: ?*anyopaque,
buf: [*c]u8,
len: usize,
cap: usize,
grow_failed: bool,
flush: ?*const fn (*DiplomatWrite) void,
grow: ?*const fn (*DiplomatWrite, usize) bool,
};
const Precision = extern struct {
is_minute: bool,
precision: OptionU8,
};
/// Time unit for Temporal operations. /// Time unit for Temporal operations.
/// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal /// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal
pub const Unit = enum(c_int) { pub const Unit = enum(c_uint) {
auto = 0, auto = c.Unit_Auto,
nanosecond = 1, nanosecond = c.Unit_Nanosecond,
microsecond = 2, microsecond = c.Unit_Microsecond,
millisecond = 3, millisecond = c.Unit_Millisecond,
second = 4, second = c.Unit_Second,
minute = 5, minute = c.Unit_Minute,
hour = 6, hour = c.Unit_Hour,
day = 7, day = c.Unit_Day,
week = 8, week = c.Unit_Week,
month = 9, month = c.Unit_Month,
year = 10, year = c.Unit_Year,
}; };
const Unit_option = extern struct { ok: Unit, is_ok: bool };
/// Rounding mode for Temporal operations. /// Rounding mode for Temporal operations.
/// See: https://tc39.es/ecma402/#table-sanctioned-single-unit-identifiers /// See: https://tc39.es/ecma402/#table-sanctioned-single-unit-identifiers
pub const RoundingMode = enum(c_int) { pub const RoundingMode = enum(c_uint) {
/// Round toward positive infinity /// Round toward positive infinity
ceil = 0, ceil = c.RoundingMode_Ceil,
/// Round toward negative infinity /// Round toward negative infinity
floor = 1, floor = c.RoundingMode_Floor,
/// Round away from zero /// Round away from zero
expand = 2, expand = c.RoundingMode_Expand,
/// Round toward zero (truncate) /// Round toward zero (truncate)
trunc = 3, trunc = c.RoundingMode_Trunc,
/// Round half toward positive infinity /// Round half toward positive infinity
half_ceil = 4, half_ceil = c.RoundingMode_HalfCeil,
/// Round half toward negative infinity /// Round half toward negative infinity
half_floor = 5, half_floor = c.RoundingMode_HalfFloor,
/// Round half away from zero /// Round half away from zero
half_expand = 6, half_expand = c.RoundingMode_HalfExpand,
/// Round half toward zero /// Round half toward zero
half_trunc = 7, half_trunc = c.RoundingMode_HalfTrunc,
/// Round half to even (banker's rounding) /// Round half to even (banker's rounding)
half_even = 8, half_even = c.RoundingMode_HalfEven,
};
const RoundingMode_option = extern struct { ok: RoundingMode, is_ok: bool };
pub const RoundingOptions = extern struct {
largest_unit: Unit_option,
smallest_unit: Unit_option,
rounding_mode: RoundingMode_option,
increment: OptionU32,
};
pub const ToStringRoundingOptions = extern struct {
precision: Precision,
smallest_unit: Unit_option,
rounding_mode: RoundingMode_option,
};
const ErrorKind = enum(c_int) {
ErrorKind_Generic = 0,
ErrorKind_Type = 1,
ErrorKind_Range = 2,
ErrorKind_Syntax = 3,
ErrorKind_Assert = 4,
};
const TemporalError = extern struct {
kind: ErrorKind,
msg: OptionStringView,
}; };
pub const Sign = enum(c_int) { pub const Sign = enum(c_int) {
Sign_Positive = 1, positive = c.Sign_Positive,
Sign_Zero = 0, zero = c.Sign_Zero,
Sign_Negative = -1, negative = c.Sign_Negative,
}; };
// --- Result wrappers --------------------------------------------------------- pub const RoundingOptions = c.RoundingOptions;
pub const ToStringRoundingOptions = c.ToStringRoundingOptions;
const DurationResult = extern struct {
result: extern union {
ok: ?*CDuration,
err: TemporalError,
},
is_ok: bool,
};
const I8Result = extern struct {
result: extern union {
ok: i8,
err: TemporalError,
},
is_ok: bool,
};
const F64Result = extern struct {
result: extern union {
ok: f64,
err: TemporalError,
},
is_ok: bool,
};
const VoidResult = extern struct {
result: extern union {
err: TemporalError,
},
is_ok: bool,
};
// --- Extern functions -------------------------------------------------------
extern "c" fn temporal_rs_Duration_try_new(years: i64, months: i64, weeks: i64, days: i64, hours: i64, minutes: i64, seconds: i64, milliseconds: i64, microseconds: f64, nanoseconds: f64) DurationResult;
extern "c" fn temporal_rs_Duration_from_partial_duration(partial: PartialDuration) DurationResult;
extern "c" fn temporal_rs_Duration_from_utf8(s: DiplomatStringView) DurationResult;
extern "c" fn temporal_rs_Duration_from_utf16(s: DiplomatString16View) DurationResult;
extern "c" fn temporal_rs_Duration_is_time_within_range(self: *const CDuration) bool;
extern "c" fn temporal_rs_Duration_years(self: *const CDuration) i64;
extern "c" fn temporal_rs_Duration_months(self: *const CDuration) i64;
extern "c" fn temporal_rs_Duration_weeks(self: *const CDuration) i64;
extern "c" fn temporal_rs_Duration_days(self: *const CDuration) i64;
extern "c" fn temporal_rs_Duration_hours(self: *const CDuration) i64;
extern "c" fn temporal_rs_Duration_minutes(self: *const CDuration) i64;
extern "c" fn temporal_rs_Duration_seconds(self: *const CDuration) i64;
extern "c" fn temporal_rs_Duration_milliseconds(self: *const CDuration) i64;
extern "c" fn temporal_rs_Duration_microseconds(self: *const CDuration) f64;
extern "c" fn temporal_rs_Duration_nanoseconds(self: *const CDuration) f64;
extern "c" fn temporal_rs_Duration_sign(self: *const CDuration) Sign;
extern "c" fn temporal_rs_Duration_is_zero(self: *const CDuration) bool;
extern "c" fn temporal_rs_Duration_abs(self: *const CDuration) *CDuration;
extern "c" fn temporal_rs_Duration_negated(self: *const CDuration) *CDuration;
extern "c" fn temporal_rs_Duration_add(self: *const CDuration, other: *const CDuration) DurationResult;
extern "c" fn temporal_rs_Duration_subtract(self: *const CDuration, other: *const CDuration) DurationResult;
extern "c" fn temporal_rs_Duration_to_string(self: *const CDuration, options: ToStringRoundingOptions, write: *DiplomatWrite) VoidResult;
extern "c" fn temporal_rs_Duration_round(self: *const CDuration, options: RoundingOptions, relative_to: RelativeTo) DurationResult;
extern "c" fn temporal_rs_Duration_round_with_provider(self: *const CDuration, options: RoundingOptions, relative_to: RelativeTo, p: *const Provider) DurationResult;
extern "c" fn temporal_rs_Duration_compare(self: *const CDuration, other: *const CDuration, relative_to: RelativeTo) I8Result;
extern "c" fn temporal_rs_Duration_compare_with_provider(self: *const CDuration, other: *const CDuration, relative_to: RelativeTo, p: *const Provider) I8Result;
extern "c" fn temporal_rs_Duration_total(self: *const CDuration, unit: Unit, relative_to: RelativeTo) F64Result;
extern "c" fn temporal_rs_Duration_total_with_provider(self: *const CDuration, unit: Unit, relative_to: RelativeTo, p: *const Provider) F64Result;
extern "c" fn temporal_rs_Duration_clone(self: *const CDuration) *CDuration;
extern "c" fn temporal_rs_Duration_destroy(self: *CDuration) void;
extern "c" fn diplomat_buffer_write_create(cap: usize) *DiplomatWrite;
extern "c" fn diplomat_buffer_write_get_bytes(write: *DiplomatWrite) [*c]u8;
extern "c" fn diplomat_buffer_write_len(write: *DiplomatWrite) usize;
extern "c" fn diplomat_buffer_write_destroy(write: *DiplomatWrite) void;
// --- Tests ------------------------------------------------------------------- // --- Tests -------------------------------------------------------------------
@ -689,7 +483,7 @@ test from {
defer dur_negative.deinit(); defer dur_negative.deinit();
try std.testing.expectEqual(@as(i64, -1), dur_negative.days()); try std.testing.expectEqual(@as(i64, -1), dur_negative.days());
try std.testing.expectEqual(Sign.Sign_Negative, dur_negative.sign()); try std.testing.expectEqual(Sign.negative, dur_negative.sign());
} }
test blank { test blank {
@ -697,7 +491,7 @@ test blank {
defer dur.deinit(); defer dur.deinit();
try std.testing.expect(dur.blank()); try std.testing.expect(dur.blank());
try std.testing.expectEqual(Sign.Sign_Zero, dur.sign()); try std.testing.expectEqual(Sign.zero, dur.sign());
} }
test add { test add {
@ -735,7 +529,7 @@ test abs {
try std.testing.expectEqual(@as(i64, 1), abs_dur.hours()); try std.testing.expectEqual(@as(i64, 1), abs_dur.hours());
try std.testing.expectEqual(@as(i64, 30), abs_dur.minutes()); try std.testing.expectEqual(@as(i64, 30), abs_dur.minutes());
try std.testing.expectEqual(Sign.Sign_Positive, abs_dur.sign()); try std.testing.expectEqual(Sign.positive, abs_dur.sign());
} }
test negated { test negated {
@ -746,7 +540,7 @@ test negated {
defer neg.deinit(); defer neg.deinit();
try std.testing.expectEqual(@as(i64, -1), neg.hours()); try std.testing.expectEqual(@as(i64, -1), neg.hours());
try std.testing.expectEqual(Sign.Sign_Negative, neg.sign()); try std.testing.expectEqual(Sign.negative, neg.sign());
} }
test toString { test toString {
@ -762,9 +556,21 @@ test toString {
} }
test fromPartialDuration { test fromPartialDuration {
var partial = PartialDuration.empty(); const empty_i64 = temporal_rs.toOption(c.OptionI64, null);
partial = partial.withHours(3); const empty_f64 = temporal_rs.toOption(c.OptionF64, null);
partial = partial.withMinutes(45);
const partial = c.PartialDuration{
.years = empty_i64,
.months = empty_i64,
.weeks = empty_i64,
.days = empty_i64,
.hours = .{ .is_ok = true, .unnamed_0 = .{ .ok = 3 } },
.minutes = .{ .is_ok = true, .unnamed_0 = .{ .ok = 45 } },
.seconds = empty_i64,
.milliseconds = empty_i64,
.microseconds = empty_f64,
.nanoseconds = empty_f64,
};
const dur = try Duration.fromPartialDuration(partial); const dur = try Duration.fromPartialDuration(partial);
defer dur.deinit(); defer dur.deinit();

View file

@ -1,11 +1,61 @@
const std = @import("std"); const std = @import("std");
const temporal_rs = @import("cabi.zig");
const c = temporal_rs.c;
const Instant = @This(); const Instant = @This();
_inner: *CInstant, _inner: *c.Instant,
epoch_milliseconds: i64, epoch_milliseconds: i64,
epoch_nanoseconds: i128, epoch_nanoseconds: i128,
pub const Unit = enum(c_uint) {
auto = c.Unit_Auto,
nanosecond = c.Unit_Nanosecond,
microsecond = c.Unit_Microsecond,
millisecond = c.Unit_Millisecond,
second = c.Unit_Second,
minute = c.Unit_Minute,
hour = c.Unit_Hour,
day = c.Unit_Day,
week = c.Unit_Week,
month = c.Unit_Month,
year = c.Unit_Year,
};
pub const RoundingMode = enum(c_uint) {
ceil = c.RoundingMode_Ceil,
floor = c.RoundingMode_Floor,
expand = c.RoundingMode_Expand,
trunc = c.RoundingMode_Trunc,
half_ceil = c.RoundingMode_HalfCeil,
half_floor = c.RoundingMode_HalfFloor,
half_expand = c.RoundingMode_HalfExpand,
half_trunc = c.RoundingMode_HalfTrunc,
half_even = c.RoundingMode_HalfEven,
};
pub const Sign = enum(c_int) {
positive = c.Sign_Positive,
zero = c.Sign_Zero,
negative = c.Sign_Negative,
};
const TimeZone = c.TimeZone;
const TimeZone_option = c.TimeZone_option;
const Precision = c.Precision;
const Unit_option = c.Unit_option;
const RoundingMode_option = c.RoundingMode_option;
const DifferenceSettings = c.DifferenceSettings;
const RoundingOptions = c.RoundingOptions;
const ToStringRoundingOptions = c.ToStringRoundingOptions;
const OptionU8 = c.OptionU8;
const OptionU32 = c.OptionU32;
const TemporalError = c.TemporalError;
const DiplomatStringView = c.DiplomatStringView;
const DiplomatString16View = c.DiplomatString16View;
const DiplomatWrite = c.DiplomatWrite;
const Duration = c.Duration;
const ZonedDateTime = c.ZonedDateTime;
const Provider = c.Provider;
/// Construct from epoch nanoseconds (Temporal.Instant.fromEpochNanoseconds). /// Construct from epoch nanoseconds (Temporal.Instant.fromEpochNanoseconds).
pub fn init(epoch_ns: i128) !Instant { pub fn init(epoch_ns: i128) !Instant {
return fromEpochNanoseconds(epoch_ns); return fromEpochNanoseconds(epoch_ns);
@ -13,79 +63,75 @@ pub fn init(epoch_ns: i128) !Instant {
/// Construct from epoch milliseconds. /// Construct from epoch milliseconds.
pub fn fromEpochMilliseconds(epoch_ms: i64) !Instant { pub fn fromEpochMilliseconds(epoch_ms: i64) !Instant {
return wrapInstant(temporal_rs_Instant_from_epoch_milliseconds(epoch_ms)); return wrapInstant(c.temporal_rs_Instant_from_epoch_milliseconds(epoch_ms));
} }
/// Construct from epoch nanoseconds (Temporal.Instant.fromEpochNanoseconds). /// Construct from epoch nanoseconds (Temporal.Instant.fromEpochNanoseconds).
pub fn fromEpochNanoseconds(epoch_ns: i128) !Instant { pub fn fromEpochNanoseconds(epoch_ns: i128) !Instant {
const parts = i128ToParts(epoch_ns); const parts = temporal_rs.toI128Nanoseconds(epoch_ns);
return wrapInstant(temporal_rs_Instant_try_new(parts)); return wrapInstant(c.temporal_rs_Instant_try_new(parts));
} }
/// Parse an ISO 8601 string (Temporal.Instant.from). /// Parse an ISO 8601 string (Temporal.Instant.from).
pub fn from(text: []const u8) !Instant { pub fn from(text: []const u8) !Instant {
const view = DiplomatStringView{ .data = text.ptr, .len = text.len }; const view = temporal_rs.toDiplomatStringView(text);
return wrapInstant(temporal_rs_Instant_from_utf8(view)); return wrapInstant(c.temporal_rs_Instant_from_utf8(view));
} }
/// Parse an ISO 8601 UTF-16 string (Temporal.Instant.from). /// Parse an ISO 8601 UTF-16 string (Temporal.Instant.from).
fn fromUtf16(text: []const u16) !Instant { fn fromUtf16(text: []const u16) !Instant {
const view = DiplomatString16View{ .data = text.ptr, .len = text.len }; const view = temporal_rs.toDiplomatString16View(text);
return wrapInstant(temporal_rs_Instant_from_utf16(view)); return wrapInstant(c.temporal_rs_Instant_from_utf16(view));
} }
/// Add a Duration to this instant (Temporal.Instant.prototype.add). /// Add a Duration to this instant (Temporal.Instant.prototype.add).
pub fn add(self: Instant, duration: *const Duration) !Instant { pub fn add(self: Instant, duration: *const Duration) !Instant {
return wrapInstant(temporal_rs_Instant_add(self._inner, duration)); return wrapInstant(c.temporal_rs_Instant_add(self._inner, duration));
} }
/// Subtract a Duration from this instant (Temporal.Instant.prototype.subtract). /// Subtract a Duration from this instant (Temporal.Instant.prototype.subtract).
pub fn subtract(self: Instant, duration: *const Duration) !Instant { pub fn subtract(self: Instant, duration: *const Duration) !Instant {
return wrapInstant(temporal_rs_Instant_subtract(self._inner, duration)); return wrapInstant(c.temporal_rs_Instant_subtract(self._inner, duration));
} }
/// Difference until another instant (Temporal.Instant.prototype.until). /// Difference until another instant (Temporal.Instant.prototype.until).
pub fn until(self: Instant, other: Instant, settings: DifferenceSettings) !DurationHandle { pub fn until(self: Instant, other: Instant, settings: DifferenceSettings) !DurationHandle {
return wrapDuration(temporal_rs_Instant_until(self._inner, other._inner, settings)); return wrapDuration(c.temporal_rs_Instant_until(self._inner, other._inner, settings));
} }
/// Difference since another instant (Temporal.Instant.prototype.since). /// Difference since another instant (Temporal.Instant.prototype.since).
pub fn since(self: Instant, other: Instant, settings: DifferenceSettings) !DurationHandle { pub fn since(self: Instant, other: Instant, settings: DifferenceSettings) !DurationHandle {
return wrapDuration(temporal_rs_Instant_since(self._inner, other._inner, settings)); return wrapDuration(c.temporal_rs_Instant_since(self._inner, other._inner, settings));
} }
/// Round this instant (Temporal.Instant.prototype.round). /// Round this instant (Temporal.Instant.prototype.round).
pub fn round(self: Instant, options: RoundingOptions) !Instant { pub fn round(self: Instant, options: RoundingOptions) !Instant {
return wrapInstant(temporal_rs_Instant_round(self._inner, options)); return wrapInstant(c.temporal_rs_Instant_round(self._inner, options));
} }
/// Compare two instants (Temporal.Instant.compare). /// Compare two instants (Temporal.Instant.compare).
pub fn compare(a: Instant, b: Instant) i8 { pub fn compare(a: Instant, b: Instant) i8 {
return temporal_rs_Instant_compare(a._inner, b._inner); return c.temporal_rs_Instant_compare(a._inner, b._inner);
} }
/// Equality check (Temporal.Instant.prototype.equals). /// Equality check (Temporal.Instant.prototype.equals).
pub fn equals(a: Instant, b: Instant) bool { pub fn equals(a: Instant, b: Instant) bool {
return temporal_rs_Instant_equals(a._inner, b._inner); return c.temporal_rs_Instant_equals(a._inner, b._inner);
} }
/// Convert to string using compiled TZ data; caller owns returned slice. /// Convert to string using compiled TZ data; caller owns returned slice.
pub fn toString(self: Instant, allocator: std.mem.Allocator, opts: ToStringOptions) ![]u8 { pub fn toString(self: Instant, allocator: std.mem.Allocator, opts: ToStringOptions) ![]u8 {
const zone_opt = if (opts.time_zone) |z| const zone_opt = temporal_rs.toTimeZoneOption(opts.time_zone);
TimeZone_option{ .ok = z, .is_ok = true }
else
TimeZone_option{ .ok = undefined, .is_ok = false };
const rounding = optsToRounding(opts); const rounding = optsToRounding(opts);
const writer = diplomat_buffer_write_create(128); const writer = c.diplomat_buffer_write_create(128);
defer diplomat_buffer_write_destroy(writer); defer c.diplomat_buffer_write_destroy(writer);
const res = temporal_rs_Instant_to_ixdtf_string_with_compiled_data(self._inner, zone_opt, rounding, writer); const res = c.temporal_rs_Instant_to_ixdtf_string_with_compiled_data(self._inner, zone_opt, rounding, writer);
try handleVoidResult(res); try handleVoidResult(res);
const len = diplomat_buffer_write_len(writer); const len = c.diplomat_buffer_write_len(writer);
const source = diplomat_buffer_write_get_bytes(writer)[0..len]; const source = c.diplomat_buffer_write_get_bytes(writer)[0..len];
const out = try allocator.alloc(u8, len); const out = try allocator.alloc(u8, len);
std.mem.copyForwards(u8, out, source); std.mem.copyForwards(u8, out, source);
@ -94,21 +140,17 @@ pub fn toString(self: Instant, allocator: std.mem.Allocator, opts: ToStringOptio
/// Convert to string using an explicit provider. /// Convert to string using an explicit provider.
fn toStringWithProvider(self: Instant, allocator: std.mem.Allocator, provider: *const Provider, opts: ToStringOptions) ![]u8 { fn toStringWithProvider(self: Instant, allocator: std.mem.Allocator, provider: *const Provider, opts: ToStringOptions) ![]u8 {
const zone_opt = if (opts.time_zone) |z| const zone_opt = temporal_rs.toTimeZoneOption(opts.time_zone);
TimeZone_option{ .ok = z, .is_ok = true }
else
TimeZone_option{ .ok = undefined, .is_ok = false };
const rounding = optsToRounding(opts); const rounding = optsToRounding(opts);
const writer = diplomat_buffer_write_create(128); const writer = c.diplomat_buffer_write_create(128);
defer diplomat_buffer_write_destroy(writer); defer c.diplomat_buffer_write_destroy(writer);
const res = temporal_rs_Instant_to_ixdtf_string_with_provider(self._inner, zone_opt, rounding, provider, writer); const res = c.temporal_rs_Instant_to_ixdtf_string_with_provider(self._inner, zone_opt, rounding, provider, writer);
try handleVoidResult(res); try handleVoidResult(res);
const len = diplomat_buffer_write_len(writer); const len = c.diplomat_buffer_write_len(writer);
const source = diplomat_buffer_write_get_bytes(writer)[0..len]; const source = c.diplomat_buffer_write_get_bytes(writer)[0..len];
const out = try allocator.alloc(u8, len); const out = try allocator.alloc(u8, len);
std.mem.copyForwards(u8, out, source); std.mem.copyForwards(u8, out, source);
@ -127,49 +169,49 @@ pub fn toLocaleString(self: Instant, allocator: std.mem.Allocator) ![]u8 {
/// Convert to ZonedDateTime using built-in provider (Temporal.Instant.prototype.toZonedDateTimeISO). /// Convert to ZonedDateTime using built-in provider (Temporal.Instant.prototype.toZonedDateTimeISO).
pub fn toZonedDateTimeISO(self: Instant, zone: TimeZone) !ZonedDateTimeHandle { pub fn toZonedDateTimeISO(self: Instant, zone: TimeZone) !ZonedDateTimeHandle {
return wrapZonedDateTime(temporal_rs_Instant_to_zoned_date_time_iso(self._inner, zone)); return wrapZonedDateTime(c.temporal_rs_Instant_to_zoned_date_time_iso(self._inner, zone));
} }
/// Convert to ZonedDateTime using an explicit provider. /// Convert to ZonedDateTime using an explicit provider.
fn toZonedDateTimeIsoWithProvider(self: Instant, zone: TimeZone, provider: *const Provider) !ZonedDateTimeHandle { fn toZonedDateTimeIsoWithProvider(self: Instant, zone: TimeZone, provider: *const Provider) !ZonedDateTimeHandle {
return wrapZonedDateTime(temporal_rs_Instant_to_zoned_date_time_iso_with_provider(self._inner, zone, provider)); return wrapZonedDateTime(c.temporal_rs_Instant_to_zoned_date_time_iso_with_provider(self._inner, zone, provider));
} }
/// Clone the underlying instant. /// Clone the underlying instant.
fn clone(self: Instant) Instant { fn clone(self: Instant) Instant {
const ptr = temporal_rs_Instant_clone(self._inner); const ptr = c.temporal_rs_Instant_clone(self._inner) orelse unreachable;
return .{ ._inner = ptr, .epoch_milliseconds = temporal_rs_Instant_epoch_milliseconds(ptr), .epoch_nanoseconds = partsToI128(temporal_rs_Instant_epoch_nanoseconds(ptr)) }; return .{ ._inner = ptr, .epoch_milliseconds = c.temporal_rs_Instant_epoch_milliseconds(ptr), .epoch_nanoseconds = temporal_rs.fromI128Nanoseconds(c.temporal_rs_Instant_epoch_nanoseconds(ptr)) };
} }
pub fn deinit(self: Instant) void { pub fn deinit(self: Instant) void {
temporal_rs_Instant_destroy(self._inner); c.temporal_rs_Instant_destroy(self._inner);
} }
// --- Helpers ----------------------------------------------------------------- // --- Helpers -----------------------------------------------------------------
fn wrapInstant(res: InstantResult) !Instant { fn wrapInstant(res: anytype) !Instant {
if (!res.is_ok) return error.TemporalError; if (!res.is_ok) return error.TemporalError;
const ptr = res.result.ok orelse return error.TemporalError; const maybe_ptr = res.unnamed_0.ok;
return .{ ._inner = ptr, .epoch_milliseconds = temporal_rs_Instant_epoch_milliseconds(ptr), .epoch_nanoseconds = partsToI128(temporal_rs_Instant_epoch_nanoseconds(ptr)) }; if (maybe_ptr == null) return error.TemporalError;
const ptr: *c.Instant = maybe_ptr.?;
return .{ ._inner = ptr, .epoch_milliseconds = c.temporal_rs_Instant_epoch_milliseconds(ptr), .epoch_nanoseconds = temporal_rs.fromI128Nanoseconds(c.temporal_rs_Instant_epoch_nanoseconds(ptr)) };
} }
fn wrapDuration(res: DurationResult) !DurationHandle { fn wrapDuration(res: anytype) !DurationHandle {
if (!res.is_ok) return error.TemporalError; if (!res.is_ok) return error.TemporalError;
const ptr = res.result.ok orelse return error.TemporalError; return .{ .ptr = res.unnamed_0.ok orelse return error.TemporalError };
return .{ .ptr = ptr };
} }
fn wrapZonedDateTime(res: ZonedDateTimeResult) !ZonedDateTimeHandle { fn wrapZonedDateTime(res: anytype) !ZonedDateTimeHandle {
if (!res.is_ok) return error.TemporalError; if (!res.is_ok) return error.TemporalError;
const ptr = res.result.ok orelse return error.TemporalError; return .{ .ptr = res.unnamed_0.ok orelse return error.TemporalError };
return .{ .ptr = ptr };
} }
fn handleVoidResult(res: VoidResult) !void { fn handleVoidResult(res: anytype) !void {
if (!res.is_ok) return error.TemporalError; if (!res.is_ok) return error.TemporalError;
} }
fn i128ToParts(value: i128) I128Nanoseconds { fn i128ToParts(value: i128) temporal_rs.c.I128Nanoseconds {
const is_neg = value < 0; const is_neg = value < 0;
const mag: u128 = if (is_neg) @intCast(@as(u128, @intCast(-value))) else @intCast(value); const mag: u128 = if (is_neg) @intCast(@as(u128, @intCast(-value))) else @intCast(value);
const mask: u64 = 1 << 63; const mask: u64 = 1 << 63;
@ -179,7 +221,7 @@ fn i128ToParts(value: i128) I128Nanoseconds {
return .{ .high = high, .low = low }; return .{ .high = high, .low = low };
} }
fn partsToI128(value: I128Nanoseconds) i128 { fn partsToI128(value: temporal_rs.c.I128Nanoseconds) i128 {
const mask: u64 = 1 << 63; const mask: u64 = 1 << 63;
const is_neg = (value.high & mask) != 0; const is_neg = (value.high & mask) != 0;
const mag: u128 = ((@as(u128, value.high & ~mask)) << 64) | value.low; const mag: u128 = ((@as(u128, value.high & ~mask)) << 64) | value.low;
@ -188,48 +230,34 @@ fn partsToI128(value: I128Nanoseconds) i128 {
} }
fn defaultPrecision() Precision { fn defaultPrecision() Precision {
return .{ .is_minute = false, .precision = OptionU8{ .ok = 0, .is_ok = false } }; return .{ .is_minute = false, .precision = temporal_rs.toOption(c.OptionU8, null) };
} }
fn defaultToStringRoundingOptions() ToStringRoundingOptions { fn defaultToStringRoundingOptions() ToStringRoundingOptions {
return .{ return temporal_rs.to_string_rounding_options_auto;
.precision = defaultPrecision(),
.smallest_unit = Unit_option{ .ok = .auto, .is_ok = false },
.rounding_mode = RoundingMode_option{ .ok = .trunc, .is_ok = false },
};
} }
/// Convert ToStringOptions to ToStringRoundingOptions for the C API /// Convert ToStringOptions to ToStringRoundingOptions for the C API
fn optsToRounding(opts: ToStringOptions) ToStringRoundingOptions { fn optsToRounding(opts: ToStringOptions) ToStringRoundingOptions {
// If smallest_unit is specified, use it; otherwise use fractional_second_digits for precision // If smallest_unit is specified, use it; otherwise use fractional_second_digits for precision
const smallest_unit_opt = if (opts.smallest_unit) |unit|
Unit_option{ .ok = unit, .is_ok = true }
else
Unit_option{ .ok = .auto, .is_ok = false };
const precision = if (opts.fractional_second_digits) |digits| const precision = if (opts.fractional_second_digits) |digits|
Precision{ .is_minute = false, .precision = OptionU8{ .ok = digits, .is_ok = true } } Precision{ .is_minute = false, .precision = temporal_rs.toOption(c.OptionU8, digits) }
else else
defaultPrecision(); defaultPrecision();
const rounding_mode_opt = if (opts.rounding_mode) |mode| const smallest_unit = if (opts.smallest_unit) |unit| @intFromEnum(unit) else null;
RoundingMode_option{ .ok = mode, .is_ok = true } const rounding_mode = if (opts.rounding_mode) |mode| @intFromEnum(mode) else null;
else
RoundingMode_option{ .ok = .trunc, .is_ok = false };
return .{ return .{
.precision = precision, .precision = precision,
.smallest_unit = smallest_unit_opt, .smallest_unit = temporal_rs.toUnitOption(smallest_unit),
.rounding_mode = rounding_mode_opt, .rounding_mode = temporal_rs.toRoundingModeOption(rounding_mode),
}; };
} }
fn parseDuration(text: []const u8) !DurationHandle { fn parseDuration(text: []const u8) !DurationHandle {
const view = DiplomatStringView{ .data = text.ptr, .len = text.len }; const view = temporal_rs.toDiplomatStringView(text);
const res = temporal_rs_Duration_from_utf8(view); return wrapDuration(c.temporal_rs_Duration_from_utf8(view));
if (!res.is_ok) return error.TemporalError;
const ptr = res.result.ok orelse return error.TemporalError;
return .{ .ptr = ptr };
} }
// --- Public helper types ----------------------------------------------------- // --- Public helper types -----------------------------------------------------
@ -260,7 +288,7 @@ const DurationHandle = struct {
ptr: *Duration, ptr: *Duration,
pub fn deinit(self: DurationHandle) void { pub fn deinit(self: DurationHandle) void {
temporal_rs_Duration_destroy(self.ptr); c.temporal_rs_Duration_destroy(self.ptr);
} }
}; };
@ -268,215 +296,10 @@ const ZonedDateTimeHandle = struct {
ptr: *ZonedDateTime, ptr: *ZonedDateTime,
pub fn deinit(self: ZonedDateTimeHandle) void { pub fn deinit(self: ZonedDateTimeHandle) void {
temporal_rs_ZonedDateTime_destroy(self.ptr); c.temporal_rs_ZonedDateTime_destroy(self.ptr);
} }
}; };
// --- Extern types ------------------------------------------------------------
const CInstant = opaque {};
const Duration = opaque {};
const ZonedDateTime = opaque {};
const Provider = opaque {};
const I128Nanoseconds = extern struct { high: u64, low: u64 };
const I128Nanoseconds_option = extern struct { ok: I128Nanoseconds, is_ok: bool };
const DiplomatStringView = extern struct { data: [*c]const u8, len: usize };
const DiplomatString16View = extern struct { data: [*c]const u16, len: usize };
const OptionStringView = extern struct { ok: DiplomatStringView, is_ok: bool };
const OptionU8 = extern struct { ok: u8, is_ok: bool };
const OptionU32 = extern struct { ok: u32, is_ok: bool };
const DiplomatWrite = extern struct {
context: ?*anyopaque,
buf: [*c]u8,
len: usize,
cap: usize,
grow_failed: bool,
flush: ?*const fn (*DiplomatWrite) void,
grow: ?*const fn (*DiplomatWrite, usize) bool,
};
const TimeZone = extern struct {
offset_minutes: i16,
resolved_id: usize,
normalized_id: usize,
is_iana_id: bool,
};
const TimeZone_option = extern struct {
ok: TimeZone,
is_ok: bool,
};
const Precision = extern struct {
is_minute: bool,
precision: OptionU8,
};
/// Time unit for Temporal operations.
/// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal
pub const Unit = enum(c_int) {
auto = 0,
nanosecond = 1,
microsecond = 2,
millisecond = 3,
second = 4,
minute = 5,
hour = 6,
day = 7,
week = 8,
month = 9,
year = 10,
};
const Unit_option = extern struct { ok: Unit, is_ok: bool };
/// Rounding mode for Temporal operations.
/// See: https://tc39.es/ecma402/#table-sanctioned-single-unit-identifiers
pub const RoundingMode = enum(c_int) {
/// Round toward positive infinity
ceil = 0,
/// Round toward negative infinity
floor = 1,
/// Round away from zero
expand = 2,
/// Round toward zero (truncate)
trunc = 3,
/// Round half toward positive infinity
half_ceil = 4,
/// Round half toward negative infinity
half_floor = 5,
/// Round half away from zero
half_expand = 6,
/// Round half toward zero
half_trunc = 7,
/// Round half to even (banker's rounding)
half_even = 8,
};
const RoundingMode_option = extern struct { ok: RoundingMode, is_ok: bool };
const DifferenceSettings = extern struct {
largest_unit: Unit_option,
smallest_unit: Unit_option,
rounding_mode: RoundingMode_option,
increment: OptionU32,
};
const RoundingOptions = extern struct {
largest_unit: Unit_option,
smallest_unit: Unit_option,
rounding_mode: RoundingMode_option,
increment: OptionU32,
};
const ToStringRoundingOptions = extern struct {
precision: Precision,
smallest_unit: Unit_option,
rounding_mode: RoundingMode_option,
};
const ErrorKind = enum(c_int) {
ErrorKind_Generic = 0,
ErrorKind_Type = 1,
ErrorKind_Range = 2,
ErrorKind_Syntax = 3,
ErrorKind_Assert = 4,
};
const TemporalError = extern struct {
kind: ErrorKind,
msg: OptionStringView,
};
const Sign = enum(c_int) {
Sign_Positive = 1,
Sign_Zero = 0,
Sign_Negative = -1,
};
// --- Result wrappers ---------------------------------------------------------
const InstantResult = extern struct {
result: extern union {
ok: ?*CInstant,
err: TemporalError,
},
is_ok: bool,
};
const DurationResult = extern struct {
result: extern union {
ok: ?*Duration,
err: TemporalError,
},
is_ok: bool,
};
const DurationParseResult = extern struct {
result: extern union {
ok: ?*Duration,
err: TemporalError,
},
is_ok: bool,
};
const ZonedDateTimeResult = extern struct {
result: extern union {
ok: ?*ZonedDateTime,
err: TemporalError,
},
is_ok: bool,
};
const VoidResult = extern struct {
result: extern union {
err: TemporalError,
},
is_ok: bool,
};
// --- Extern functions -------------------------------------------------------
extern "c" fn temporal_rs_Instant_try_new(ns: I128Nanoseconds) InstantResult;
extern "c" fn temporal_rs_Instant_from_epoch_milliseconds(epoch_milliseconds: i64) InstantResult;
extern "c" fn temporal_rs_Instant_from_utf8(s: DiplomatStringView) InstantResult;
extern "c" fn temporal_rs_Instant_from_utf16(s: DiplomatString16View) InstantResult;
extern "c" fn temporal_rs_Instant_add(self: *const CInstant, duration: *const Duration) InstantResult;
extern "c" fn temporal_rs_Instant_subtract(self: *const CInstant, duration: *const Duration) InstantResult;
extern "c" fn temporal_rs_Instant_since(self: *const CInstant, other: *const CInstant, settings: DifferenceSettings) DurationResult;
extern "c" fn temporal_rs_Instant_until(self: *const CInstant, other: *const CInstant, settings: DifferenceSettings) DurationResult;
extern "c" fn temporal_rs_Instant_round(self: *const CInstant, options: RoundingOptions) InstantResult;
extern "c" fn temporal_rs_Instant_compare(self: *const CInstant, other: *const CInstant) i8;
extern "c" fn temporal_rs_Instant_equals(self: *const CInstant, other: *const CInstant) bool;
extern "c" fn temporal_rs_Instant_epoch_milliseconds(self: *const CInstant) i64;
extern "c" fn temporal_rs_Instant_epoch_nanoseconds(self: *const CInstant) I128Nanoseconds;
extern "c" fn temporal_rs_Instant_to_ixdtf_string_with_compiled_data(self: *const CInstant, zone: TimeZone_option, options: ToStringRoundingOptions, write: *DiplomatWrite) VoidResult;
extern "c" fn temporal_rs_Instant_to_ixdtf_string_with_provider(self: *const CInstant, zone: TimeZone_option, options: ToStringRoundingOptions, p: *const Provider, write: *DiplomatWrite) VoidResult;
extern "c" fn temporal_rs_Instant_to_zoned_date_time_iso(self: *const CInstant, zone: TimeZone) ZonedDateTimeResult;
extern "c" fn temporal_rs_Instant_to_zoned_date_time_iso_with_provider(self: *const CInstant, zone: TimeZone, p: *const Provider) ZonedDateTimeResult;
extern "c" fn temporal_rs_Instant_clone(self: *const CInstant) *CInstant;
extern "c" fn temporal_rs_Instant_destroy(self: *CInstant) void;
extern "c" fn temporal_rs_Duration_destroy(self: *Duration) void;
extern "c" fn temporal_rs_Duration_from_utf8(s: DiplomatStringView) DurationParseResult;
extern "c" fn temporal_rs_Duration_hours(self: *const Duration) i64;
extern "c" fn temporal_rs_Duration_minutes(self: *const Duration) i64;
extern "c" fn temporal_rs_Duration_seconds(self: *const Duration) i64;
extern "c" fn temporal_rs_Duration_milliseconds(self: *const Duration) i64;
extern "c" fn temporal_rs_Duration_microseconds(self: *const Duration) f64;
extern "c" fn temporal_rs_Duration_nanoseconds(self: *const Duration) f64;
extern "c" fn temporal_rs_Duration_sign(self: *const Duration) Sign;
extern "c" fn temporal_rs_ZonedDateTime_destroy(self: *ZonedDateTime) void;
extern "c" fn diplomat_buffer_write_create(cap: usize) *DiplomatWrite;
extern "c" fn diplomat_buffer_write_get_bytes(write: *DiplomatWrite) [*c]u8;
extern "c" fn diplomat_buffer_write_len(write: *DiplomatWrite) usize;
extern "c" fn diplomat_buffer_write_destroy(write: *DiplomatWrite) void;
// --- Tests ------------------------------------------------------------------- // --- Tests -------------------------------------------------------------------
test init { test init {
@ -544,13 +367,13 @@ test compare {
defer a.deinit(); defer a.deinit();
const b = try Instant.fromEpochMilliseconds(0); const b = try Instant.fromEpochMilliseconds(0);
defer b.deinit(); defer b.deinit();
const c = try Instant.fromEpochMilliseconds(1_000); const cc = try Instant.fromEpochMilliseconds(1_000);
defer c.deinit(); defer cc.deinit();
try std.testing.expectEqual(@as(i8, 0), Instant.compare(a, b)); try std.testing.expectEqual(@as(i8, 0), Instant.compare(a, b));
try std.testing.expect(Instant.equals(a, b)); try std.testing.expect(Instant.equals(a, b));
try std.testing.expectEqual(@as(i8, -1), Instant.compare(a, c)); try std.testing.expectEqual(@as(i8, -1), Instant.compare(a, cc));
try std.testing.expectEqual(@as(i8, 1), Instant.compare(c, a)); try std.testing.expectEqual(@as(i8, 1), Instant.compare(cc, a));
} }
test equals { test equals {
@ -558,13 +381,13 @@ test equals {
defer a.deinit(); defer a.deinit();
const b = try Instant.fromEpochMilliseconds(0); const b = try Instant.fromEpochMilliseconds(0);
defer b.deinit(); defer b.deinit();
const c = try Instant.fromEpochMilliseconds(1_000); const cc = try Instant.fromEpochMilliseconds(1_000);
defer c.deinit(); defer cc.deinit();
try std.testing.expectEqual(@as(i8, 0), Instant.compare(a, b)); try std.testing.expectEqual(@as(i8, 0), Instant.compare(a, b));
try std.testing.expect(Instant.equals(a, b)); try std.testing.expect(Instant.equals(a, b));
try std.testing.expectEqual(@as(i8, -1), Instant.compare(a, c)); try std.testing.expectEqual(@as(i8, -1), Instant.compare(a, cc));
try std.testing.expectEqual(@as(i8, 1), Instant.compare(c, a)); try std.testing.expectEqual(@as(i8, 1), Instant.compare(cc, a));
} }
test until { test until {
@ -574,21 +397,21 @@ test until {
defer later.deinit(); defer later.deinit();
const settings = DifferenceSettings{ const settings = DifferenceSettings{
.largest_unit = Unit_option{ .ok = .hour, .is_ok = true }, .largest_unit = temporal_rs.toUnitOption(@intFromEnum(Unit.hour)),
.smallest_unit = Unit_option{ .ok = .second, .is_ok = true }, .smallest_unit = temporal_rs.toUnitOption(@intFromEnum(Unit.second)),
.rounding_mode = RoundingMode_option{ .ok = .trunc, .is_ok = true }, .rounding_mode = temporal_rs.toRoundingModeOption(@intFromEnum(RoundingMode.trunc)),
.increment = OptionU32{ .ok = 0, .is_ok = false }, .increment = temporal_rs.toOption(c.OptionU32, null),
}; };
var until_handle = try earlier.until(later, settings); var until_handle = try earlier.until(later, settings);
defer until_handle.deinit(); defer until_handle.deinit();
try std.testing.expectEqual(Sign.Sign_Positive, temporal_rs_Duration_sign(until_handle.ptr)); try std.testing.expectEqual(Sign.positive, @as(Sign, @enumFromInt(temporal_rs.c.temporal_rs_Duration_sign(until_handle.ptr))));
try std.testing.expectEqual(@as(i64, 1), temporal_rs_Duration_hours(until_handle.ptr)); try std.testing.expectEqual(@as(i64, 1), temporal_rs.c.temporal_rs_Duration_hours(until_handle.ptr));
var since_handle = try later.since(earlier, settings); var since_handle = try later.since(earlier, settings);
defer since_handle.deinit(); defer since_handle.deinit();
try std.testing.expectEqual(Sign.Sign_Positive, temporal_rs_Duration_sign(since_handle.ptr)); try std.testing.expectEqual(Sign.positive, @as(Sign, @enumFromInt(temporal_rs.c.temporal_rs_Duration_sign(since_handle.ptr))));
try std.testing.expectEqual(@as(i64, 1), temporal_rs_Duration_hours(since_handle.ptr)); try std.testing.expectEqual(@as(i64, 1), temporal_rs.c.temporal_rs_Duration_hours(since_handle.ptr));
} }
test since { test since {
@ -598,21 +421,21 @@ test since {
defer later.deinit(); defer later.deinit();
const settings = DifferenceSettings{ const settings = DifferenceSettings{
.largest_unit = Unit_option{ .ok = .hour, .is_ok = true }, .largest_unit = temporal_rs.toUnitOption(@intFromEnum(Unit.hour)),
.smallest_unit = Unit_option{ .ok = .second, .is_ok = true }, .smallest_unit = temporal_rs.toUnitOption(@intFromEnum(Unit.second)),
.rounding_mode = RoundingMode_option{ .ok = .trunc, .is_ok = true }, .rounding_mode = temporal_rs.toRoundingModeOption(@intFromEnum(RoundingMode.trunc)),
.increment = OptionU32{ .ok = 0, .is_ok = false }, .increment = temporal_rs.toOption(c.OptionU32, null),
}; };
var until_handle = try earlier.until(later, settings); var until_handle = try earlier.until(later, settings);
defer until_handle.deinit(); defer until_handle.deinit();
try std.testing.expectEqual(Sign.Sign_Positive, temporal_rs_Duration_sign(until_handle.ptr)); try std.testing.expectEqual(Sign.positive, @as(Sign, @enumFromInt(temporal_rs.c.temporal_rs_Duration_sign(until_handle.ptr))));
try std.testing.expectEqual(@as(i64, 1), temporal_rs_Duration_hours(until_handle.ptr)); try std.testing.expectEqual(@as(i64, 1), temporal_rs.c.temporal_rs_Duration_hours(until_handle.ptr));
var since_handle = try later.since(earlier, settings); var since_handle = try later.since(earlier, settings);
defer since_handle.deinit(); defer since_handle.deinit();
try std.testing.expectEqual(Sign.Sign_Positive, temporal_rs_Duration_sign(since_handle.ptr)); try std.testing.expectEqual(Sign.positive, @as(Sign, @enumFromInt(temporal_rs.c.temporal_rs_Duration_sign(since_handle.ptr))));
try std.testing.expectEqual(@as(i64, 1), temporal_rs_Duration_hours(since_handle.ptr)); try std.testing.expectEqual(@as(i64, 1), temporal_rs.c.temporal_rs_Duration_hours(since_handle.ptr));
} }
test round { test round {
@ -620,10 +443,10 @@ test round {
defer inst.deinit(); defer inst.deinit();
const opts = RoundingOptions{ const opts = RoundingOptions{
.largest_unit = Unit_option{ .ok = .auto, .is_ok = false }, .largest_unit = temporal_rs.toUnitOption(null),
.smallest_unit = Unit_option{ .ok = .second, .is_ok = true }, .smallest_unit = temporal_rs.toUnitOption(@intFromEnum(Unit.second)),
.rounding_mode = RoundingMode_option{ .ok = .half_expand, .is_ok = true }, .rounding_mode = temporal_rs.toRoundingModeOption(@intFromEnum(RoundingMode.half_expand)),
.increment = OptionU32{ .ok = 0, .is_ok = false }, .increment = temporal_rs.toOption(c.OptionU32, null),
}; };
const rounded = try inst.round(opts); const rounded = try inst.round(opts);

263
src/cabi.zig Normal file
View file

@ -0,0 +1,263 @@
const std = @import("std");
pub const c = @cImport({
@cInclude("AnyCalendarKind.h");
@cInclude("Calendar.h");
@cInclude("Duration.h");
@cInclude("ErrorKind.h");
@cInclude("I128Nanoseconds.h");
@cInclude("Instant.h");
@cInclude("OwnedRelativeTo.h");
@cInclude("ParsedDate.h");
@cInclude("ParsedDateTime.h");
@cInclude("ParsedZonedDateTime.h");
@cInclude("PlainDate.h");
@cInclude("PlainDateTime.h");
@cInclude("PlainMonthDay.h");
@cInclude("PlainTime.h");
@cInclude("PlainYearMonth.h");
@cInclude("RelativeTo.h");
@cInclude("TimeZone.h");
@cInclude("ZonedDateTime.h");
});
pub const to_string_rounding_options_auto: c.ToStringRoundingOptions = .{
.precision = .{ .is_minute = false, .precision = toOption(c.OptionU8, null) },
.smallest_unit = toUnitOption(null),
.rounding_mode = toRoundingModeOption(null),
};
const u64_high_bit_mask: u64 = 1 << 63;
/// Covert a Rust `I128Nanoseconds` struct to a Zig `i128`.
///
/// Ported from temporal_rs's [`From<ffi::I128Nanoseconds>`](https://github.com/boa-dev/temporal/blob/89bfca1f5b918d00a19354664e1da11da51305ee/temporal_capi/src/instant.rs#L172-L186) trait for `i128`.
pub fn fromI128Nanoseconds(ns: c.I128Nanoseconds) i128 {
const is_neg = (ns.high & u64_high_bit_mask) != 0;
const ns_high: u128 = @intCast((ns.high & ~u64_high_bit_mask));
const total: i128 = @intCast((ns_high << 64) + ns.low);
return if (is_neg) -total else total;
}
/// Covert a Zig `i128` to a Rust `I128Nanoseconds` struct.
///
/// Ported from temporal_rs's [`From<i128>`](https://github.com/boa-dev/temporal/blob/89bfca1f5b918d00a19354664e1da11da51305ee/temporal_capi/src/instant.rs#L188-L207) trait for `ffi::I128Nanoseconds`.
pub fn toI128Nanoseconds(ns: i128) c.I128Nanoseconds {
std.debug.assert(ns != std.math.minInt(i128));
const is_neg = ns < 0;
const ns_abs = @abs(ns);
const high: u64 = @intCast(ns_abs >> 64);
const low: u64 = @truncate(ns_abs);
return .{ .high = if (is_neg) high | u64_high_bit_mask else high, .low = low };
}
/// Convert a Rust `DiplomatStringView` to a Zig slice.
pub fn fromDiplomatStringView(sv: c.DiplomatStringView) []const u8 {
return sv.data[0..sv.len];
}
/// Convert a Zig slice to a Rust `DiplomatStringView`.
pub fn toDiplomatStringView(s: []const u8) c.DiplomatStringView {
return .{ .data = s.ptr, .len = s.len };
}
/// Convert a Zig slice to a Rust `DiplomatString16View`.
pub fn toDiplomatString16View(s: []const u16) c.DiplomatString16View {
return .{ .data = s.ptr, .len = s.len };
}
/// Convert a Rust `Option<T>` to a Zig `?T`.
pub fn fromOption(value: anytype) ?Success(@TypeOf(value)) {
return success(value);
}
/// Convert a Zig `?T` to a Rust `Option<T>`.
pub fn toOption(comptime T: type, maybe_value: ?Success(T)) T {
return if (maybe_value) |value|
.{ .is_ok = true, .unnamed_0 = .{ .ok = value } }
else
.{ .is_ok = false };
}
/// Convert a Zig `?ArithmeticOverflow` to a Rust `Option<ArithmeticOverflow>`.
pub fn toArithmeticOverflowOption(maybe_value: ?c.RoundingMode) c.ArithmeticOverflow_option {
return toOption(c.ArithmeticOverflow_option, maybe_value);
}
/// Convert a Zig `?Disambiguation` to a Rust `Option<Disambiguation>`.
pub fn toDisambiguationOption(maybe_value: ?c.Disambiguation) c.Disambiguation_option {
return toOption(c.Disambiguation_option, maybe_value);
}
/// Convert a Zig `?OffsetDisambiguation` to a Rust `Option<OffsetDisambiguation>`.
pub fn toOffsetDisambiguationOption(maybe_value: ?c.OffsetDisambiguation) c.OffsetDisambiguation_option {
return toOption(c.OffsetDisambiguation_option, maybe_value);
}
/// Convert a Zig `?PartialDate` to a Rust `Option<PartialDate>`.
pub fn toPartialDateOption(maybe_value: ?c.PartialDate) c.PartialDate_option {
return toOption(c.PartialDate_option, maybe_value);
}
/// Convert a Zig `?RoundingMode` to a Rust `Option<RoundingMode>`.
pub fn toRoundingModeOption(maybe_value: ?c.RoundingMode) c.RoundingMode_option {
return toOption(c.RoundingMode_option, maybe_value);
}
/// Convert a Zig `?TimeZone` to a Rust `Option<TimeZone>`.
pub fn toTimeZoneOption(maybe_value: ?c.TimeZone) c.TimeZone_option {
return toOption(c.TimeZone_option, maybe_value);
}
/// Convert a Zig `?Unit` to a Rust `Option<Unit>`.
pub fn toUnitOption(maybe_value: ?c.Unit) c.Unit_option {
return toOption(c.Unit_option, maybe_value);
}
// Wraps values from a `c.RelativeTo` or `c.OwnedRelativeTo`.
pub const RelativeTo = union(enum) {
none,
owned_plain_date: *c.PlainDate,
owned_zoned_date_time: *c.ZonedDateTime,
borrowed_plain_date: *const c.PlainDate,
borrowed_zoned_date_time: *const c.ZonedDateTime,
pub fn fromOwned(owned: c.OwnedRelativeTo) RelativeTo {
if (owned.date) |plain_date| {
return .{ .owned_plain_date = plain_date };
} else if (owned.zoned) |zoned_date_time| {
return .{ .owned_zoned_date_time = zoned_date_time };
} else {
return .none;
}
}
pub fn toRust(self: RelativeTo) c.RelativeTo {
return switch (self) {
.none => .{ .date = null, .zoned = null },
.owned_plain_date => |plain_date| .{ .date = plain_date, .zoned = null },
.owned_zoned_date_time => |zoned_date_time| .{ .date = null, .zoned = zoned_date_time },
.borrowed_plain_date => |plain_date| .{ .date = plain_date, .zoned = null },
.borrowed_zoned_date_time => |zoned_date_time| .{ .date = null, .zoned = zoned_date_time },
};
}
pub fn deinit(self: RelativeTo) void {
switch (self) {
.owned_plain_date => |plain_date| c.temporal_rs_PlainDate_destroy(plain_date),
.owned_zoned_date_time => |zoned_date_time| c.temporal_rs_ZonedDateTime_destroy(zoned_date_time),
else => {},
}
}
};
pub const DiplomatWrite = struct {
gpa: std.mem.Allocator,
array_list: std.ArrayList(u8),
inner: c.DiplomatWrite,
pub fn init(gpa: std.mem.Allocator) DiplomatWrite {
return .{
.gpa = gpa,
.array_list = .empty,
.inner = .{
// NOTE: We use `@fieldParentPtr()` on the `inner` struct field to get to the other
// fields instead of creating a context externally and storing a pointer.
.context = null,
.buf = undefined,
.len = 0,
.cap = 0,
.grow_failed = false,
.flush = flush,
.grow = grow,
},
};
}
pub fn deinit(self: *DiplomatWrite) void {
self.array_list.deinit(self.gpa);
}
pub fn toOwnedSlice(self: *DiplomatWrite) std.mem.Allocator.Error![]u8 {
if (self.inner.grow_failed) return error.OutOfMemory;
self.inner = undefined; // Invalidate the inner struct to prevent further writes
return self.array_list.toOwnedSlice(self.gpa);
}
fn flush(inner: ?*c.DiplomatWrite) callconv(.c) void {
const self: *DiplomatWrite = @fieldParentPtr("inner", inner.?);
self.array_list.items.len = inner.?.len;
}
fn grow(inner: ?*c.DiplomatWrite, size: usize) callconv(.c) bool {
const self: *DiplomatWrite = @fieldParentPtr("inner", inner.?);
self.array_list.ensureTotalCapacity(self.gpa, size) catch return false;
inner.?.buf = self.array_list.items.ptr;
inner.?.cap = self.array_list.capacity;
return true;
}
};
test DiplomatWrite {
const gpa = std.testing.allocator;
var write = DiplomatWrite.init(gpa);
defer write.deinit();
const WriteImpl = struct {
inner: *c.DiplomatWrite,
// https://github.com/rust-diplomat/diplomat/blob/2b903255187976779798fc89df3fee7298641c80/runtime/src/write.rs#L70-L73
pub fn flush(self: @This()) void {
self.inner.flush.?(self.inner);
}
// https://github.com/rust-diplomat/diplomat/blob/2b903255187976779798fc89df3fee7298641c80/runtime/src/write.rs#L76-L94
pub fn writeStr(self: @This(), s: []const u8) void {
if (self.inner.grow_failed) {
return;
}
const needed_len = self.inner.len + s.len;
if (needed_len > self.inner.cap) {
const success_ = self.inner.grow.?(self.inner, needed_len);
if (!success_) {
self.inner.grow_failed = true;
return;
}
}
std.debug.assert(needed_len <= self.inner.cap);
@memcpy(self.inner.buf[self.inner.len..][0..s.len], s);
self.inner.len = needed_len;
}
};
var write_impl: WriteImpl = .{ .inner = &write.inner };
write_impl.writeStr("Hello World");
write_impl.flush();
try std.testing.expectEqual(write.array_list.items.ptr, write.inner.buf);
try std.testing.expectEqual(write.array_list.items.len, write.inner.len);
try std.testing.expectEqual(write.array_list.capacity, write.inner.cap);
try std.testing.expectEqual(false, write.inner.grow_failed);
try std.testing.expectEqualSlices(u8, "Hello World", write.array_list.items);
const slice = try write.toOwnedSlice();
defer gpa.free(slice);
try std.testing.expectEqualSlices(u8, "Hello World", slice);
try std.testing.expectEqualSlices(u8, &.{}, write.array_list.items);
}
/// Converts a "result" value to its "success" type, or returns `null` if the value is an error.
/// This is `inline` to prevent binary bloat, because each instantiation is expected to be called
/// only once.
pub inline fn success(result: anytype) ?Success(@TypeOf(result)) {
if (!result.is_ok) return null;
if (Success(@TypeOf(result)) == void) return;
return result.unnamed_0.ok;
}
/// Given the C API representation of a `Result<T, E>`, returns the type 'T'.
pub fn Success(comptime Result: type) type {
const Union = @FieldType(Result, "unnamed_0");
if (!@hasField(Union, "ok")) return void;
return @FieldType(Union, "ok");
}

1
src/lib.rs Normal file
View file

@ -0,0 +1 @@
extern crate temporal_capi;

View file

@ -13,6 +13,4 @@ pub fn main() !void {
const dur = try Temporal.Duration.from("P1Y2M3DT4H5M6S"); const dur = try Temporal.Duration.from("P1Y2M3DT4H5M6S");
defer dur.deinit(); defer dur.deinit();
// const dur_str = try dur.toString(allocator, .{});
// std.debug.print("Duration String: {s}\n", .{dur_str});
} }

View file

@ -113,6 +113,7 @@ test "Temporal.Instant" {
"ToStringOptions", "ToStringOptions",
"Unit", "Unit",
"RoundingMode", "RoundingMode",
"Sign",
}; };
try assertDecls(Instant, checks); try assertDecls(Instant, checks);

29
src/unwind_stubs.zig Normal file
View file

@ -0,0 +1,29 @@
//! Provides unwind symbols because apparently Rust still might include these when using panic=abort.
//!
//! See:
//! - https://github.com/rust-lang/rust/issues/47493
//! - https://code.videolan.org/videolan/vlc/-/merge_requests/2878
export fn _Unwind_Backtrace() void {}
export fn _Unwind_DeleteException() void {}
export fn _Unwind_GetDataRelBase() void {}
export fn _Unwind_GetIP() void {}
export fn _Unwind_GetIPInfo() void {}
export fn _Unwind_GetLanguageSpecificData() void {}
export fn _Unwind_GetRegionStart() void {}
export fn _Unwind_GetTextRelBase() void {}
export fn _Unwind_RaiseException() void {}
export fn _Unwind_Resume() void {}
export fn _Unwind_SetGR() void {}
export fn _Unwind_SetIP() void {}
// These appear when building for riscv64
export fn _Unwind_FindEnclosingFunction() void {}
export fn _Unwind_GetCFA() void {}
// These appear when building for arm
export fn _Unwind_VRS_Get() void {}
export fn _Unwind_VRS_Set() void {}
export fn __gnu_unwind_frame() void {}
// This appears when building for s390x
export fn _Unwind_GetGR() void {}
// This appears when building for x86_64-windows
export fn _GCC_specific_handler() void {}