From 11c0de116645eb1244a5868462a6d81fbe67f846 Mon Sep 17 00:00:00 2001 From: "Nurul Huda (Apon)" Date: Mon, 26 Jan 2026 14:23:31 +0600 Subject: [PATCH] feat: more native zig types --- build.zig.zon | 2 ++ src/Duration.zig | 7 ++-- src/Instant.zig | 86 +++++++++++++++++++++++++++++++++--------------- src/root.zig | 2 ++ src/temporal.zig | 42 +++++++++++++++++++++++ 5 files changed, 108 insertions(+), 31 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index c1baf58..0bfe0c9 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -16,6 +16,8 @@ .paths = .{ "build.zig", "build.zig.zon", + "Cargo.toml", + "Cargo.lock", "src", "lib", }, diff --git a/src/Duration.zig b/src/Duration.zig index abfc826..821e49a 100644 --- a/src/Duration.zig +++ b/src/Duration.zig @@ -140,12 +140,12 @@ pub fn subtract(self: Duration, other: Duration) !Duration { /// Round the duration according to the specified options (Temporal.Duration.prototype.round). pub fn round(self: Duration, options: RoundingOptions, relative_to: RelativeTo) !Duration { - return wrapDuration(abi.c.temporal_rs_Duration_round(self._inner, options, relative_to)); + return wrapDuration(abi.c.temporal_rs_Duration_round(self._inner, options.toCApi(), relative_to)); } /// Round the duration with an explicit provider. fn roundWithProvider(self: Duration, options: RoundingOptions, relative_to: RelativeTo, provider: *const abi.c.Provider) !Duration { - return wrapDuration(abi.c.temporal_rs_Duration_round_with_provider(self._inner, options, relative_to, provider)); + return wrapDuration(abi.c.temporal_rs_Duration_round_with_provider(self._inner, options.toCApi(), relative_to, provider)); } /// Compare two durations (Temporal.Duration.compare). @@ -252,9 +252,8 @@ const Precision = abi.c.Precision; const Unit_option = abi.c.Unit_option; const RoundingMode_option = abi.c.RoundingMode_option; -pub const RoundingOptions = abi.c.RoundingOptions; +pub const RoundingOptions = temporal.RoundingOptions; pub const ToStringRoundingOptions = abi.c.ToStringRoundingOptions; - pub const Unit = temporal.Unit; pub const RoundingMode = temporal.RoundingMode; pub const Sign = temporal.Sign; diff --git a/src/Instant.zig b/src/Instant.zig index 3f99304..3d07582 100644 --- a/src/Instant.zig +++ b/src/Instant.zig @@ -12,6 +12,8 @@ epoch_nanoseconds: i128, pub const Unit = temporal.Unit; pub const RoundingMode = temporal.RoundingMode; pub const Sign = temporal.Sign; +pub const RoundingOptions = temporal.RoundingOptions; +pub const DifferenceSettings = temporal.DifferenceSettings; /// Construct from epoch nanoseconds (Temporal.Instant.fromEpochNanoseconds). pub fn init(epoch_ns: i128) !Instant { @@ -29,18 +31,41 @@ pub fn fromEpochNanoseconds(epoch_ns: i128) !Instant { return wrapInstant(abi.c.temporal_rs_Instant_try_new(parts)); } -/// Parse an ISO 8601 string (Temporal.Instant.from). -pub fn from(text: []const u8) !Instant { - const view = abi.toDiplomatStringView(text); - return wrapInstant(abi.c.temporal_rs_Instant_from_utf8(view)); +/// Parse an ISO 8601 string (Temporal.Instant.from) or from another Temporal.Instant. +pub fn from(info: anytype) !Instant { + const T = @TypeOf(info); + + if (T == Instant) return info.clone(); + + // Handle string types (both literals and slices) + const type_info = @typeInfo(T); + switch (type_info) { + .pointer => { + const ptr = type_info.pointer; + const ChildType = switch (@typeInfo(ptr.child)) { + .array => |arr| arr.child, + else => ptr.child, + }; + + if (ChildType == u8) return fromUtf8(info); + if (ChildType == u16) return fromUtf16(info); + }, + else => @compileError("from() expects an Instant, []const u8, or []const u16"), + } } /// Parse an ISO 8601 UTF-16 string (Temporal.Instant.from). -fn fromUtf16(text: []const u16) !Instant { +inline fn fromUtf16(text: []const u16) !Instant { const view = abi.toDiplomatString16View(text); return wrapInstant(abi.c.temporal_rs_Instant_from_utf16(view)); } +/// Parse an ISO 8601 UTF-16 string (Temporal.Instant.from). +inline fn fromUtf8(text: []const u8) !Instant { + const view = abi.toDiplomatStringView(text); + return wrapInstant(abi.c.temporal_rs_Instant_from_utf8(view)); +} + /// Add a Duration to this instant (Temporal.Instant.prototype.add). pub fn add(self: Instant, duration: *Duration) !Instant { return wrapInstant(abi.c.temporal_rs_Instant_add(self._inner, duration._inner)); @@ -52,18 +77,18 @@ pub fn subtract(self: Instant, duration: *Duration) !Instant { } /// Difference until another instant (Temporal.Instant.prototype.until). -pub fn until(self: Instant, other: Instant, settings: abi.c.DifferenceSettings) !DurationHandle { - return wrapDuration(abi.c.temporal_rs_Instant_until(self._inner, other._inner, settings)); +pub fn until(self: Instant, other: Instant, settings: DifferenceSettings) !DurationHandle { + return wrapDuration(abi.c.temporal_rs_Instant_until(self._inner, other._inner, settings.toCApi())); } /// Difference since another instant (Temporal.Instant.prototype.since). -pub fn since(self: Instant, other: Instant, settings: abi.c.DifferenceSettings) !DurationHandle { - return wrapDuration(abi.c.temporal_rs_Instant_since(self._inner, other._inner, settings)); +pub fn since(self: Instant, other: Instant, settings: DifferenceSettings) !DurationHandle { + return wrapDuration(abi.c.temporal_rs_Instant_since(self._inner, other._inner, settings.toCApi())); } /// Round this instant (Temporal.Instant.prototype.round). -pub fn round(self: Instant, options: abi.c.RoundingOptions) !Instant { - return wrapInstant(abi.c.temporal_rs_Instant_round(self._inner, options)); +pub fn round(self: Instant, options: RoundingOptions) !Instant { + return wrapInstant(abi.c.temporal_rs_Instant_round(self._inner, options.toCApi())); } /// Compare two instants (Temporal.Instant.compare). @@ -258,10 +283,17 @@ test fromEpochNanoseconds { } test from { + // Test parsing from UTF-8 string const inst = try Instant.from("2024-03-15T14:30:45.123Z"); defer inst.deinit(); - try std.testing.expectEqual(@as(i64, 1_710_513_045_123), inst.epoch_milliseconds); + + // Test creating from another Instant + const inst2 = try Instant.from(inst); + defer inst2.deinit(); + try std.testing.expectEqual(inst.epoch_milliseconds, inst2.epoch_milliseconds); + try std.testing.expectEqual(inst.epoch_nanoseconds, inst2.epoch_nanoseconds); + try std.testing.expect(Instant.equals(inst, inst2)); } test fromUtf16 { @@ -326,11 +358,11 @@ test until { const later = try Instant.fromEpochMilliseconds(3_600_000); defer later.deinit(); - const settings = abi.c.DifferenceSettings{ - .largest_unit = abi.toUnitOption(Unit.hour.toCApi()), - .smallest_unit = abi.toUnitOption(Unit.second.toCApi()), - .rounding_mode = abi.toRoundingModeOption(RoundingMode.trunc.toCApi()), - .increment = abi.toOption(abi.c.OptionU32, null), + const settings = DifferenceSettings{ + .largest_unit = .hour, + .smallest_unit = .second, + .rounding_mode = .trunc, + .increment = null, }; var until_handle = try earlier.until(later, settings); @@ -350,11 +382,11 @@ test since { const later = try Instant.fromEpochMilliseconds(3_600_000); defer later.deinit(); - const settings = abi.c.DifferenceSettings{ - .largest_unit = abi.toUnitOption(Unit.hour.toCApi()), - .smallest_unit = abi.toUnitOption(Unit.second.toCApi()), - .rounding_mode = abi.toRoundingModeOption(RoundingMode.trunc.toCApi()), - .increment = abi.toOption(abi.c.OptionU32, null), + const settings = DifferenceSettings{ + .largest_unit = .hour, + .smallest_unit = .second, + .rounding_mode = .trunc, + .increment = null, }; var until_handle = try earlier.until(later, settings); @@ -372,11 +404,11 @@ test round { const inst = try Instant.fromEpochNanoseconds(1_609_459_245_123_456_789); defer inst.deinit(); - const opts = abi.c.RoundingOptions{ - .largest_unit = abi.toUnitOption(null), - .smallest_unit = abi.toUnitOption(Unit.second.toCApi()), - .rounding_mode = abi.toRoundingModeOption(RoundingMode.half_expand.toCApi()), - .increment = abi.toOption(abi.c.OptionU32, null), + const opts = RoundingOptions{ + .largest_unit = null, + .smallest_unit = .second, + .rounding_mode = .half_expand, + .increment = null, }; const rounded = try inst.round(opts); diff --git a/src/root.zig b/src/root.zig index 55b6ae0..01bc520 100644 --- a/src/root.zig +++ b/src/root.zig @@ -114,6 +114,8 @@ test Instant { "Unit", "RoundingMode", "Sign", + "RoundingOptions", + "DifferenceSettings", }; try assertDecls(Instant, checks); diff --git a/src/temporal.zig b/src/temporal.zig index 9088f44..38506b4 100644 --- a/src/temporal.zig +++ b/src/temporal.zig @@ -93,3 +93,45 @@ pub const Sign = enum { }; } }; + +/// Options for rounding operations (Instant.round, Duration.round, etc.). +pub const RoundingOptions = struct { + largest_unit: ?Unit = null, + smallest_unit: ?Unit = null, + rounding_mode: ?RoundingMode = null, + increment: ?u32 = null, + + pub fn toCApi(self: RoundingOptions) c.RoundingOptions { + return .{ + .largest_unit = abi.toUnitOption(toCUnit(self.largest_unit)), + .smallest_unit = abi.toUnitOption(toCUnit(self.smallest_unit)), + .rounding_mode = abi.toRoundingModeOption(toCRoundingMode(self.rounding_mode)), + .increment = abi.toOption(c.OptionU32, self.increment), + }; + } +}; + +/// Options for computing differences between instants. +pub const DifferenceSettings = struct { + largest_unit: ?Unit = null, + smallest_unit: ?Unit = null, + rounding_mode: ?RoundingMode = null, + increment: ?u32 = null, + + pub fn toCApi(self: DifferenceSettings) c.DifferenceSettings { + return .{ + .largest_unit = abi.toUnitOption(toCUnit(self.largest_unit)), + .smallest_unit = abi.toUnitOption(toCUnit(self.smallest_unit)), + .rounding_mode = abi.toRoundingModeOption(toCRoundingMode(self.rounding_mode)), + .increment = abi.toOption(c.OptionU32, self.increment), + }; + } +}; + +fn toCUnit(opt: ?Unit) ?c.Unit { + return if (opt) |u| @as(c.Unit, @intCast(u.toCApi())) else null; +} + +fn toCRoundingMode(opt: ?RoundingMode) ?c.RoundingMode { + return if (opt) |m| @as(c.RoundingMode, @intCast(m.toCApi())) else null; +}