diff --git a/src/Duration.zig b/src/Duration.zig index 40f87e6..b4e5b8b 100644 --- a/src/Duration.zig +++ b/src/Duration.zig @@ -2,139 +2,6 @@ const std = @import("std"); const temporal_rs = @import("cabi.zig"); const c = temporal_rs.c; -/// The Zig implementation of `Temporal.Duration`. -/// -/// Represents a span of time such as "2 hours and 30 minutes" or "3 years, 2 months". -/// Unlike `Instant` which represents a specific moment in time, Duration represents -/// the amount of time between two moments. -/// -/// A Duration consists of two categories of components: -/// - Date components: years, months, weeks, and days -/// - Time components: hours, minutes, seconds, and subsecond units (nanosecond precision) -/// -/// Note that date arithmetic can be complex. For example, adding "1 month" to January 31st -/// could result in February 28th (non-leap year), February 29th (leap year), or March 3rd -/// (if you overflow February), depending on the calendar system and overflow handling. -/// -/// ## Examples -/// -/// ### Creating durations -/// -/// ```zig -/// const Duration = @import("Duration.zig"); -/// -/// // Create a duration with specific components -/// // 2 weeks and 3 days, no time components -/// const vacation_duration = try Duration.init( -/// 0, 0, 2, 3, // years, months, weeks, days -/// 0, 0, 0, // hours, minutes, seconds -/// 0, 0, 0 // milliseconds, microseconds, nanoseconds -/// ); -/// defer vacation_duration.deinit(); -/// -/// try std.testing.expectEqual(@as(i64, 2), vacation_duration.weeks()); -/// try std.testing.expectEqual(@as(i64, 3), vacation_duration.days()); -/// ``` -/// -/// ### Parsing ISO 8601 duration strings -/// -/// ```zig -/// const Duration = @import("Duration.zig"); -/// -/// // Complex duration with multiple components -/// const complex = try Duration.from("P1Y2M3DT4H5M6.789S"); -/// defer complex.deinit(); -/// try std.testing.expectEqual(@as(i64, 1), complex.years()); -/// try std.testing.expectEqual(@as(i64, 2), complex.months()); -/// try std.testing.expectEqual(@as(i64, 3), complex.days()); -/// try std.testing.expectEqual(@as(i64, 4), complex.hours()); -/// try std.testing.expectEqual(@as(i64, 5), complex.minutes()); -/// try std.testing.expectEqual(@as(i64, 6), complex.seconds()); -/// -/// // Time-only duration -/// const movie_length = try Duration.from("PT2H30M"); -/// defer movie_length.deinit(); -/// try std.testing.expectEqual(@as(i64, 2), movie_length.hours()); -/// try std.testing.expectEqual(@as(i64, 30), movie_length.minutes()); -/// -/// // Negative durations -/// const negative = try Duration.from("-P1D"); -/// defer negative.deinit(); -/// try std.testing.expectEqual(@as(i64, -1), negative.days()); -/// ``` -/// -/// ### Duration arithmetic -/// -/// ```zig -/// const Duration = @import("Duration.zig"); -/// -/// const commute_time = try Duration.from("PT45M"); -/// defer commute_time.deinit(); -/// const lunch_break = try Duration.from("PT1H"); -/// defer lunch_break.deinit(); -/// -/// // Add durations together -/// const total_time = try commute_time.add(lunch_break); -/// defer total_time.deinit(); -/// try std.testing.expectEqual(@as(i64, 1), total_time.hours()); // Results in 1 hour 45 minutes -/// try std.testing.expectEqual(@as(i64, 45), total_time.minutes()); -/// -/// // Subtract duration components -/// const shortened = try lunch_break.subtract(try Duration.from("PT15M")); -/// defer shortened.deinit(); -/// try std.testing.expectEqual(@as(i64, 45), shortened.minutes()); -/// ``` -/// -/// ### Working with partial durations -/// -/// ```zig -/// const Duration = @import("Duration.zig"); -/// -/// var partial = Duration.PartialDuration.empty(); -/// partial = partial.withHours(3); -/// partial = partial.withMinutes(45); -/// -/// const duration = try Duration.fromPartialDuration(partial); -/// defer duration.deinit(); -/// try std.testing.expectEqual(@as(i64, 3), duration.hours()); -/// try std.testing.expectEqual(@as(i64, 45), duration.minutes()); -/// try std.testing.expectEqual(@as(i64, 0), duration.days()); // other fields default to 0 -/// ``` -/// -/// ### Duration properties -/// -/// ```zig -/// const Duration = @import("Duration.zig"); -/// -/// const duration = try Duration.from("P1Y2M3D"); -/// defer duration.deinit(); -/// -/// // Check if duration is zero -/// try std.testing.expect(!duration.isZero()); -/// -/// // Get the sign of the duration -/// const sign_val = duration.sign(); -/// -/// // Get absolute value -/// const abs_duration = duration.abs(); -/// defer abs_duration.deinit(); -/// try std.testing.expectEqual(@as(i64, 1), abs_duration.years()); -/// try std.testing.expectEqual(@as(i64, 2), abs_duration.months()); -/// try std.testing.expectEqual(@as(i64, 3), abs_duration.days()); -/// -/// // Negate duration -/// const negated_dur = duration.negated(); -/// defer negated_dur.deinit(); -/// try std.testing.expectEqual(@as(i64, -1), negated_dur.years()); -/// try std.testing.expectEqual(@as(i64, -2), negated_dur.months()); -/// try std.testing.expectEqual(@as(i64, -3), negated_dur.days()); -/// ``` -/// -/// ## Reference -/// -/// For more information, see the [MDN documentation][mdn-duration]. -/// -/// [mdn-duration]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration const Duration = @This(); _inner: *c.Duration, @@ -284,45 +151,36 @@ fn roundWithProvider(self: Duration, options: RoundingOptions, relative_to: Rela /// Compare two durations (Temporal.Duration.compare). pub fn compare(self: Duration, other: Duration, relative_to: RelativeTo) !i8 { const res = c.temporal_rs_Duration_compare(self._inner, other._inner, relative_to); - if (!res.is_ok) return error.TemporalError; - return res.unnamed_0.ok; + return temporal_rs.success(res) orelse return error.TemporalError; } /// Compare two durations with an explicit provider. fn compareWithProvider(self: Duration, other: Duration, relative_to: RelativeTo, provider: *const c.Provider) !i8 { const res = c.temporal_rs_Duration_compare_with_provider(self._inner, other._inner, relative_to, provider); - if (!res.is_ok) return error.TemporalError; - return res.unnamed_0.ok; + return temporal_rs.success(res) orelse return error.TemporalError; } /// 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 { const res = c.temporal_rs_Duration_total(self._inner, @intFromEnum(unit), relative_to); - if (!res.is_ok) return error.TemporalError; - return res.unnamed_0.ok; + return temporal_rs.success(res) orelse return error.TemporalError; } /// Get the total value of the duration with an explicit provider. fn totalWithProvider(self: Duration, unit: Unit, relative_to: RelativeTo, provider: *const c.Provider) !f64 { const res = c.temporal_rs_Duration_total_with_provider(self._inner, @intFromEnum(unit), relative_to, provider); - if (!res.is_ok) return error.TemporalError; - return res.unnamed_0.ok; + return temporal_rs.success(res) orelse return error.TemporalError; } /// Convert to string (Temporal.Duration.prototype.toString); caller owns returned slice. pub fn toString(self: Duration, allocator: std.mem.Allocator, options: ToStringRoundingOptions) ![]u8 { - const writer = c.diplomat_buffer_write_create(128); - defer c.diplomat_buffer_write_destroy(writer); + var write = temporal_rs.DiplomatWrite.init(allocator); + defer write.deinit(); - const res = c.temporal_rs_Duration_to_string(self._inner, options, writer); + const res = c.temporal_rs_Duration_to_string(self._inner, options, &write.inner); try handleVoidResult(res); - const len = c.diplomat_buffer_write_len(writer); - const source = c.diplomat_buffer_write_get_bytes(writer)[0..len]; - - const out = try allocator.alloc(u8, len); - std.mem.copyForwards(u8, out, source); - return out; + return try write.toOwnedSlice(); } pub fn toJSON(self: Duration, allocator: std.mem.Allocator) ![]u8 { @@ -348,14 +206,11 @@ pub fn deinit(self: Duration) void { // --- Helpers ----------------------------------------------------------------- fn handleVoidResult(res: anytype) !void { - if (!res.is_ok) return error.TemporalError; + _ = temporal_rs.success(res) 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.?; + const ptr = (temporal_rs.success(res) orelse return error.TemporalError) orelse return error.TemporalError; return .{ ._inner = ptr }; } diff --git a/src/Instant.zig b/src/Instant.zig index 89f048e..b849be6 100644 --- a/src/Instant.zig +++ b/src/Instant.zig @@ -124,18 +124,13 @@ pub fn toString(self: Instant, allocator: std.mem.Allocator, opts: ToStringOptio const zone_opt = temporal_rs.toTimeZoneOption(opts.time_zone); const rounding = optsToRounding(opts); - const writer = c.diplomat_buffer_write_create(128); - defer c.diplomat_buffer_write_destroy(writer); + var write = temporal_rs.DiplomatWrite.init(allocator); + defer write.deinit(); - const res = c.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, &write.inner); try handleVoidResult(res); - const len = c.diplomat_buffer_write_len(writer); - const source = c.diplomat_buffer_write_get_bytes(writer)[0..len]; - - const out = try allocator.alloc(u8, len); - std.mem.copyForwards(u8, out, source); - return out; + return try write.toOwnedSlice(); } /// Convert to string using an explicit provider. @@ -143,18 +138,13 @@ fn toStringWithProvider(self: Instant, allocator: std.mem.Allocator, provider: * const zone_opt = temporal_rs.toTimeZoneOption(opts.time_zone); const rounding = optsToRounding(opts); - const writer = c.diplomat_buffer_write_create(128); - defer c.diplomat_buffer_write_destroy(writer); + var write = temporal_rs.DiplomatWrite.init(allocator); + defer write.deinit(); - const res = c.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, &write.inner); try handleVoidResult(res); - const len = c.diplomat_buffer_write_len(writer); - const source = c.diplomat_buffer_write_get_bytes(writer)[0..len]; - - const out = try allocator.alloc(u8, len); - std.mem.copyForwards(u8, out, source); - return out; + return try write.toOwnedSlice(); } pub fn toJSON(self: Instant, allocator: std.mem.Allocator) ![]u8 { @@ -190,43 +180,26 @@ pub fn deinit(self: Instant) void { // --- Helpers ----------------------------------------------------------------- fn wrapInstant(res: anytype) !Instant { - if (!res.is_ok) return error.TemporalError; - const maybe_ptr = res.unnamed_0.ok; - 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)) }; + const ptr = (temporal_rs.success(res) orelse return error.TemporalError) orelse return error.TemporalError; + 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: anytype) !DurationHandle { - if (!res.is_ok) return error.TemporalError; - return .{ .ptr = res.unnamed_0.ok orelse return error.TemporalError }; + const ptr = (temporal_rs.success(res) orelse return error.TemporalError) orelse return error.TemporalError; + return .{ .ptr = ptr }; } fn wrapZonedDateTime(res: anytype) !ZonedDateTimeHandle { - if (!res.is_ok) return error.TemporalError; - return .{ .ptr = res.unnamed_0.ok orelse return error.TemporalError }; + const ptr = (temporal_rs.success(res) orelse return error.TemporalError) orelse return error.TemporalError; + return .{ .ptr = ptr }; } fn handleVoidResult(res: anytype) !void { - if (!res.is_ok) return error.TemporalError; -} - -fn i128ToParts(value: i128) temporal_rs.c.I128Nanoseconds { - const is_neg = value < 0; - const mag: u128 = if (is_neg) @intCast(@as(u128, @intCast(-value))) else @intCast(value); - const mask: u64 = 1 << 63; - var high: u64 = @intCast(mag >> 64); - const low: u64 = @intCast(mag & 0xffff_ffff_ffff_ffff); - if (is_neg) high |= mask; - return .{ .high = high, .low = low }; -} - -fn partsToI128(value: temporal_rs.c.I128Nanoseconds) i128 { - const mask: u64 = 1 << 63; - const is_neg = (value.high & mask) != 0; - const mag: u128 = ((@as(u128, value.high & ~mask)) << 64) | value.low; - if (is_neg) return -@as(i128, @intCast(mag)); - return @as(i128, @intCast(mag)); + _ = temporal_rs.success(res) orelse return error.TemporalError; } fn defaultPrecision() Precision {