const std = @import("std"); const abi = @import("abi.zig"); const temporal = @import("temporal.zig"); const Duration = @This(); _inner: *abi.c.Duration, /// Construct a Duration from years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, and nanoseconds. /// Equivalent to `Temporal.Duration.from()` or the constructor. pub fn init( years_val: i64, months_val: i64, weeks_val: i64, days_val: i64, hours_val: i64, minutes_val: i64, seconds_val: i64, milliseconds_val: i64, microseconds_val: f64, nanoseconds_val: f64, ) !Duration { return wrapDuration(abi.c.temporal_rs_Duration_try_new( years_val, months_val, weeks_val, days_val, hours_val, minutes_val, seconds_val, milliseconds_val, microseconds_val, nanoseconds_val, )); } /// Parse an ISO 8601 duration string (Temporal.Duration.from). pub fn from(text: []const u8) !Duration { const view = abi.toDiplomatStringView(text); return wrapDuration(abi.c.temporal_rs_Duration_from_utf8(view)); } /// Parse an ISO 8601 UTF-16 duration string. fn fromUtf16(text: []const u16) !Duration { const view = abi.toDiplomatString16View(text); return wrapDuration(abi.c.temporal_rs_Duration_from_utf16(view)); } /// Create a Duration from a partial duration (where some fields may be omitted). fn fromPartialDuration(partial: PartialDuration) !Duration { return wrapDuration(abi.c.temporal_rs_Duration_from_partial_duration(partial)); } /// Check if the time portion of the duration is within valid ranges. fn isTimeWithinRange(self: Duration) bool { return abi.c.temporal_rs_Duration_is_time_within_range(self._inner); } /// Get the years component of the duration. pub fn years(self: Duration) i64 { return abi.c.temporal_rs_Duration_years(self._inner); } /// Get the months component of the duration. pub fn months(self: Duration) i64 { return abi.c.temporal_rs_Duration_months(self._inner); } /// Get the weeks component of the duration. pub fn weeks(self: Duration) i64 { return abi.c.temporal_rs_Duration_weeks(self._inner); } /// Get the days component of the duration. pub fn days(self: Duration) i64 { return abi.c.temporal_rs_Duration_days(self._inner); } /// Get the hours component of the duration. pub fn hours(self: Duration) i64 { return abi.c.temporal_rs_Duration_hours(self._inner); } /// Get the minutes component of the duration. pub fn minutes(self: Duration) i64 { return abi.c.temporal_rs_Duration_minutes(self._inner); } /// Get the seconds component of the duration. pub fn seconds(self: Duration) i64 { return abi.c.temporal_rs_Duration_seconds(self._inner); } /// Get the milliseconds component of the duration. pub fn milliseconds(self: Duration) i64 { return abi.c.temporal_rs_Duration_milliseconds(self._inner); } /// Get the microseconds component of the duration. pub fn microseconds(self: Duration) f64 { return abi.c.temporal_rs_Duration_microseconds(self._inner); } /// Get the nanoseconds component of the duration. pub fn nanoseconds(self: Duration) f64 { return abi.c.temporal_rs_Duration_nanoseconds(self._inner); } /// Get the sign of the duration: positive (1), zero (0), or negative (-1). pub fn sign(self: Duration) Sign { return Sign.fromCApi(abi.c.temporal_rs_Duration_sign(self._inner)); } /// Check if the duration is zero (all fields are zero). pub fn blank(self: Duration) bool { return abi.c.temporal_rs_Duration_is_zero(self._inner); } /// Returns a new Duration with the absolute value (all components positive). pub fn abs(self: Duration) Duration { const ptr: *abi.c.Duration = abi.c.temporal_rs_Duration_abs(self._inner) orelse unreachable; return .{ ._inner = ptr }; } /// Returns a new Duration with all components negated. pub fn negated(self: Duration) Duration { const ptr: *abi.c.Duration = abi.c.temporal_rs_Duration_negated(self._inner) orelse unreachable; return .{ ._inner = ptr }; } /// Add two durations together (Temporal.Duration.prototype.add). pub fn add(self: Duration, other: Duration) !Duration { return wrapDuration(abi.c.temporal_rs_Duration_add(self._inner, other._inner)); } /// Subtract another duration from this one (Temporal.Duration.prototype.subtract). pub fn subtract(self: Duration, other: Duration) !Duration { return wrapDuration(abi.c.temporal_rs_Duration_subtract(self._inner, other._inner)); } /// 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.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.toCApi(), relative_to, provider)); } /// Compare two durations (Temporal.Duration.compare). pub fn compare(self: Duration, other: Duration, relative_to: RelativeTo) !i8 { const res = abi.c.temporal_rs_Duration_compare(self._inner, other._inner, relative_to); return abi.success(res) orelse return error.TemporalError; } /// Compare two durations with an explicit provider. fn compareWithProvider(self: Duration, other: Duration, relative_to: RelativeTo, provider: *const abi.c.Provider) !i8 { const res = abi.c.temporal_rs_Duration_compare_with_provider(self._inner, other._inner, relative_to, provider); return abi.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 = abi.c.temporal_rs_Duration_total(self._inner, unit.toCApi(), relative_to); return abi.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 abi.c.Provider) !f64 { const res = abi.c.temporal_rs_Duration_total_with_provider(self._inner, unit.toCApi(), relative_to, provider); return abi.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 { var write = abi.DiplomatWrite.init(allocator); defer write.deinit(); const res = abi.c.temporal_rs_Duration_to_string(self._inner, options.toCApi(), &write.inner); try handleVoidResult(res); return try write.toOwnedSlice(); } pub fn toJSON(self: Duration, allocator: std.mem.Allocator) ![]u8 { return self.toString(allocator, .{}); } pub fn toLocaleString(self: Duration, allocator: std.mem.Allocator) ![]u8 { _ = self; _ = allocator; return error.TemporalNotImplemented; } /// Clone the underlying duration. fn clone(self: Duration) Duration { const ptr: *abi.c.Duration = abi.c.temporal_rs_Duration_clone(self._inner) orelse unreachable; return .{ ._inner = ptr }; } pub fn deinit(self: Duration) void { abi.c.temporal_rs_Duration_destroy(self._inner); } // --- Helpers ----------------------------------------------------------------- fn handleVoidResult(res: anytype) !void { _ = abi.success(res) orelse return error.TemporalError; } fn wrapDuration(res: anytype) !Duration { const ptr = (abi.success(res) orelse return error.TemporalError) orelse return error.TemporalError; return .{ ._inner = ptr }; } // --- Public helper types ----------------------------------------------------- pub const PartialDuration = abi.c.PartialDuration; /// Relative-to context for duration operations. pub const RelativeTo = extern struct { plain_date: ?*abi.c.PlainDate, zoned_date_time: ?*abi.c.ZonedDateTime, }; // --- Public type aliases and enums ------------------------------------------ const OptionU8 = abi.c.OptionU8; const OptionU32 = abi.c.OptionU32; const OptionI64 = abi.c.OptionI64; const OptionF64 = abi.c.OptionF64; const Precision = abi.c.Precision; const Unit_option = abi.c.Unit_option; const RoundingMode_option = abi.c.RoundingMode_option; pub const RoundingOptions = temporal.RoundingOptions; pub const ToStringOptions = temporal.ToStringRoundingOptions; pub const ToStringRoundingOptions = temporal.ToStringRoundingOptions; pub const Unit = temporal.Unit; pub const RoundingMode = temporal.RoundingMode; pub const Sign = temporal.Sign; // --- Tests ------------------------------------------------------------------- test init { // Create a simple duration: 1 hour, 30 minutes const dur = try Duration.init(0, 0, 0, 0, 1, 30, 0, 0, 0, 0); defer dur.deinit(); try std.testing.expectEqual(@as(i64, 1), dur.hours()); try std.testing.expectEqual(@as(i64, 30), dur.minutes()); try std.testing.expectEqual(@as(i64, 0), dur.seconds()); } test from { // P1Y2M3DT4H5M6.789S = 1 year, 2 months, 3 days, 4 hours, 5 minutes, 6.789 seconds const dur = try Duration.from("P1Y2M3DT4H5M6.789S"); defer dur.deinit(); try std.testing.expectEqual(@as(i64, 1), dur.years()); try std.testing.expectEqual(@as(i64, 2), dur.months()); try std.testing.expectEqual(@as(i64, 3), dur.days()); try std.testing.expectEqual(@as(i64, 4), dur.hours()); try std.testing.expectEqual(@as(i64, 5), dur.minutes()); try std.testing.expectEqual(@as(i64, 6), dur.seconds()); // from-time // PT2H30M = 2 hours, 30 minutes const dur_time = try Duration.from("PT2H30M"); defer dur_time.deinit(); try std.testing.expectEqual(@as(i64, 0), dur_time.years()); try std.testing.expectEqual(@as(i64, 2), dur_time.hours()); try std.testing.expectEqual(@as(i64, 30), dur_time.minutes()); // from-negative const dur_negative = try Duration.from("-P1D"); defer dur_negative.deinit(); try std.testing.expectEqual(@as(i64, -1), dur_negative.days()); try std.testing.expectEqual(Sign.negative, dur_negative.sign()); } test blank { const dur = try Duration.init(0, 0, 0, 0, 0, 0, 0, 0, 0, 0); defer dur.deinit(); try std.testing.expect(dur.blank()); try std.testing.expectEqual(Sign.zero, dur.sign()); } test add { const dur1 = try Duration.from("PT1H"); defer dur1.deinit(); const dur2 = try Duration.from("PT30M"); defer dur2.deinit(); const result = try dur1.add(dur2); defer result.deinit(); try std.testing.expectEqual(@as(i64, 1), result.hours()); try std.testing.expectEqual(@as(i64, 30), result.minutes()); } test subtract { const dur1 = try Duration.from("PT2H"); defer dur1.deinit(); const dur2 = try Duration.from("PT30M"); defer dur2.deinit(); const result = try dur1.subtract(dur2); defer result.deinit(); try std.testing.expectEqual(@as(i64, 1), result.hours()); try std.testing.expectEqual(@as(i64, 30), result.minutes()); } test abs { const dur = try Duration.from("-PT1H30M"); defer dur.deinit(); const abs_dur = dur.abs(); defer abs_dur.deinit(); try std.testing.expectEqual(@as(i64, 1), abs_dur.hours()); try std.testing.expectEqual(@as(i64, 30), abs_dur.minutes()); try std.testing.expectEqual(Sign.positive, abs_dur.sign()); } test negated { const dur = try Duration.from("PT1H"); defer dur.deinit(); const neg = dur.negated(); defer neg.deinit(); try std.testing.expectEqual(@as(i64, -1), neg.hours()); try std.testing.expectEqual(Sign.negative, neg.sign()); } test toString { const dur = try Duration.from("P1Y2M3DT4H5M6S"); defer dur.deinit(); const allocator = std.testing.allocator; const str = try dur.toString(allocator, .{}); defer allocator.free(str); // Should output ISO 8601 duration format try std.testing.expect(str.len > 0); } test fromPartialDuration { const empty_i64 = abi.toOption(abi.c.OptionI64, null); const empty_f64 = abi.toOption(abi.c.OptionF64, null); const partial = abi.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); defer dur.deinit(); try std.testing.expectEqual(@as(i64, 3), dur.hours()); try std.testing.expectEqual(@as(i64, 45), dur.minutes()); try std.testing.expectEqual(@as(i64, 0), dur.days()); } test isTimeWithinRange { const dur = try Duration.from("PT23H59M59S"); defer dur.deinit(); try std.testing.expect(dur.isTimeWithinRange()); } test clone { const dur = try Duration.from("PT1H"); defer dur.deinit(); const cloned = dur.clone(); defer cloned.deinit(); try std.testing.expectEqual(dur.hours(), cloned.hours()); }