diff --git a/example/src/root.zig b/example/src/root.zig index 3f3551d..440e10f 100644 --- a/example/src/root.zig +++ b/example/src/root.zig @@ -479,7 +479,7 @@ pub fn run(allocator: std.mem.Allocator, io_optional: ?std.Io) !void { \\ - blank: {} \\ - microseconds: {d} \\ - toJSON(): {s} - \\ - toLocaleString implemented: {} + \\ - toLocaleString supported: {} \\ \\ , .{ @@ -522,16 +522,17 @@ pub fn run(allocator: std.mem.Allocator, io_optional: ?std.Io) !void { try instant_zdt.toString(allocator, .{}), }); - const now_zdt_supported = Temporal.Now.zonedDateTimeISO() != error.TemporalNotImplemented; + const now_zdt = try Temporal.Now.zonedDateTimeISO(); + defer now_zdt.deinit(); std.log.info( \\Now Coverage \\ - timeZoneId(): {s} - \\ - zonedDateTimeISO implemented: {} + \\ - zonedDateTimeISO(): {s} \\ \\ , .{ Temporal.Now.timeZoneId(), - now_zdt_supported, + try now_zdt.toString(allocator, .{}), }); const date_cal = try Temporal.PlainDate.calInit(2024, 2, 2, "iso8601"); @@ -558,7 +559,7 @@ pub fn run(allocator: std.mem.Allocator, io_optional: ?std.Io) !void { \\ - toPlainYearMonth: {s} \\ - toZonedDateTime: {s} \\ - toJSON(): {s} - \\ - toLocaleString implemented: {} + \\ - toLocaleString supported: {} \\ - valueOf supported: {} \\ - with year/month/day: {s} \\ @@ -736,7 +737,8 @@ pub fn run(allocator: std.mem.Allocator, io_optional: ?std.Io) !void { const zdt_plain_datetime = try zdt.toPlainDateTime(); const zdt_plain_time = try zdt.toPlainTime(); const zdt_valueof_supported = zdt.valueOf() != error.ValueOfNotSupported; - const zdt_with_supported = zdt.with(allocator, .{ .year = 2025 }) != error.TemporalNoteImplemented; + const zdt_with_coverage = try zdt.with(allocator, .{ .year = 2025 }); + defer zdt_with_coverage.deinit(); std.log.info( \\ZonedDateTime Coverage \\ - init: {s} @@ -786,7 +788,7 @@ pub fn run(allocator: std.mem.Allocator, io_optional: ?std.Io) !void { \\ - offset/offsetNanoseconds: {s}/{} \\ - weekOfYear/yearOfWeek: {?}/{?} \\ - valueOf supported: {} - \\ - with implemented: {} + \\ - with year=2025: {s} \\ \\ , .{ @@ -804,7 +806,7 @@ pub fn run(allocator: std.mem.Allocator, io_optional: ?std.Io) !void { zdt.weekOfYear(), zdt.yearOfWeek(), zdt_valueof_supported, - zdt_with_supported, + try zdt_with_coverage.toString(allocator, .{}), }); } diff --git a/src/Duration.zig b/src/Duration.zig index 7bb5048..8fba621 100644 --- a/src/Duration.zig +++ b/src/Duration.zig @@ -320,7 +320,8 @@ pub fn toJSON(self: Duration, allocator: std.mem.Allocator) ![]u8 { return self.toString(allocator, .{}); } -/// Returns a string with a language-sensitive representation of this duration. Not implemented. +/// Returns a string with a language-sensitive representation of this duration. +/// Not implemented. /// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/toLocaleString pub fn toLocaleString(self: Duration, allocator: std.mem.Allocator) ![]u8 { _ = self; diff --git a/src/Now.zig b/src/Now.zig index 5745779..17297f3 100644 --- a/src/Now.zig +++ b/src/Now.zig @@ -87,7 +87,8 @@ pub fn timeZoneId() []const u8 { /// /// See: [MDN Temporal.Now.zonedDateTimeISO](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Now/zonedDateTimeISO) pub fn zonedDateTimeISO() !ZonedDateTime { - return error.TemporalNotImplemented; + const time_zone = try ZonedDateTime.TimeZone.init(timeZoneId()); + return ZonedDateTime.fromEpochNanoseconds(currentEpochNanoseconds(), time_zone); } const CurrentParts = struct { @@ -132,6 +133,18 @@ fn currentParts(io: std.Io) CurrentParts { }; } +fn currentEpochNanoseconds() i128 { + var timespec: std.posix.timespec = undefined; + switch (std.posix.errno(std.posix.system.clock_gettime(.REALTIME, ×pec))) { + .SUCCESS => {}, + else => return 0, + } + + const seconds: i128 = @intCast(timespec.sec); + const nanos: i128 = @intCast(timespec.nsec); + return seconds * std.time.ns_per_s + nanos; +} + // ---------- Tests --------------------- test instant { const io = std.testing.io; @@ -168,5 +181,11 @@ test timeZoneId { try std.testing.expectEqualStrings("UTC", tz); } test zonedDateTimeISO { - try std.testing.expectError(error.TemporalNotImplemented, zonedDateTimeISO()); + const zdt = try zonedDateTimeISO(); + defer zdt.deinit(); + + try std.testing.expect(zdt.epochNanoseconds() > 0); + const tz = try zdt.timeZoneId(std.testing.allocator); + defer std.testing.allocator.free(tz); + try std.testing.expectEqualStrings("UTC", tz); } diff --git a/src/ZonedDateTime.zig b/src/ZonedDateTime.zig index 3177417..ee3bd7c 100644 --- a/src/ZonedDateTime.zig +++ b/src/ZonedDateTime.zig @@ -99,6 +99,24 @@ pub const ToStringOptions = struct { time_zone_name: DisplayTimeZone = .auto, }; +/// Options for `with()` method. +pub const WithOptions = struct { + year: ?i32 = null, + month: ?u8 = null, + month_code: ?[]const u8 = null, + day: ?u8 = null, + era: ?[]const u8 = null, + era_year: ?i32 = null, + hour: ?u8 = null, + minute: ?u8 = null, + second: ?u8 = null, + millisecond: ?u16 = null, + microsecond: ?u16 = null, + nanosecond: ?u16 = null, + offset: ?[]const u8 = null, + time_zone: ?TimeZone = null, +}; + /// Helper function to wrap a C API result into a ZonedDateTime fn wrapZonedDateTime(result: anytype) !ZonedDateTime { const ptr = (try abi.extractResult(result)) orelse return abi.TemporalError.Generic; @@ -273,9 +291,21 @@ pub fn valueOf(_: ZonedDateTime) !void { /// See [MDN Temporal.ZonedDateTime.prototype.with()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/ZonedDateTime/with) pub fn with(self: ZonedDateTime, allocator: std.mem.Allocator, fields: anytype) !ZonedDateTime { _ = allocator; - _ = fields; - _ = self; - return error.TemporalNoteImplemented; // Need PartialZonedDateTime mapping + const partial = abi.toPartialZonedDateTime(fields); + const disambiguation = abi.toOption(abi.c.Disambiguation_option, abi.to.toDisambiguation(Disambiguation.compatible)); + const offset_option = abi.toOption(abi.c.OffsetDisambiguation_option, abi.to.toOffsetDisambiguation(OffsetDisambiguation.prefer_offset)); + const overflow = abi.c.ArithmeticOverflow_option{ + .is_ok = true, + .unnamed_0 = .{ .ok = abi.c.ArithmeticOverflow_Constrain }, + }; + + return wrapZonedDateTime(abi.c.temporal_rs_ZonedDateTime_with( + self._inner, + partial, + disambiguation, + offset_option, + overflow, + )); } /// Returns a new ZonedDateTime interpreted in the new calendar system. @@ -760,7 +790,14 @@ test valueOf { test with { const zdt = try from("2021-01-01T12:00:00+00:00[UTC]", null, .compatible, .reject); defer zdt.deinit(); - try std.testing.expectError(error.TemporalNoteImplemented, zdt.with(std.testing.allocator, .{})); + + const result = try zdt.with(std.testing.allocator, .{ .year = 2022, .month = 2, .day = 3, .hour = 4 }); + defer result.deinit(); + + try std.testing.expectEqual(@as(i32, 2022), result.year()); + try std.testing.expectEqual(@as(u8, 2), result.month()); + try std.testing.expectEqual(@as(u8, 3), result.day()); + try std.testing.expectEqual(@as(u8, 4), result.hour()); } test withCalendar { diff --git a/src/abi.zig b/src/abi.zig index 5ea375f..a32505b 100644 --- a/src/abi.zig +++ b/src/abi.zig @@ -90,6 +90,85 @@ pub fn toTimeZoneOption(maybe_value: ?c.TimeZone) c.TimeZone_option { return toOption(c.TimeZone_option, maybe_value); } +pub fn toPartialZonedDateTime(fields: anytype) c.PartialZonedDateTime { + return .{ + .date = .{ + .year = toI32OptionField(fields, "year"), + .month = toU8OptionField(fields, "month"), + .month_code = toStringViewField(fields, "month_code"), + .day = toU8OptionField(fields, "day"), + .era = toStringViewField(fields, "era"), + .era_year = toI32OptionField(fields, "era_year"), + .calendar = c.AnyCalendarKind_Iso, + }, + .time = .{ + .hour = toU8OptionField(fields, "hour"), + .minute = toU8OptionField(fields, "minute"), + .second = toU8OptionField(fields, "second"), + .millisecond = toU16OptionField(fields, "millisecond"), + .microsecond = toU16OptionField(fields, "microsecond"), + .nanosecond = toU16OptionField(fields, "nanosecond"), + }, + .offset = toOptionStringViewField(fields, "offset"), + .timezone = toTimeZoneOptionField(fields, "time_zone"), + }; +} + +fn toI32OptionField(fields: anytype, comptime name: []const u8) c.OptionI32 { + return toOption(c.OptionI32, intField(i32, fields, name)); +} + +fn toU8OptionField(fields: anytype, comptime name: []const u8) c.OptionU8 { + return toOption(c.OptionU8, intField(u8, fields, name)); +} + +fn toU16OptionField(fields: anytype, comptime name: []const u8) c.OptionU16 { + return toOption(c.OptionU16, intField(u16, fields, name)); +} + +fn intField(comptime Int: type, fields: anytype, comptime name: []const u8) ?Int { + if (!@hasField(@TypeOf(fields), name)) return null; + + const value = @field(fields, name); + return switch (@typeInfo(@TypeOf(value))) { + .optional => if (value) |payload| @as(Int, @intCast(payload)) else null, + else => @as(Int, @intCast(value)), + }; +} + +fn toStringViewField(fields: anytype, comptime name: []const u8) c.DiplomatStringView { + if (stringField(fields, name)) |value| return toDiplomatStringView(value); + return .{ .data = null, .len = 0 }; +} + +fn toOptionStringViewField(fields: anytype, comptime name: []const u8) c.OptionStringView { + if (stringField(fields, name)) |value| { + return .{ .is_ok = true, .unnamed_0 = .{ .ok = toDiplomatStringView(value) } }; + } + return .{ .is_ok = false }; +} + +fn stringField(fields: anytype, comptime name: []const u8) ?[]const u8 { + if (!@hasField(@TypeOf(fields), name)) return null; + + const value = @field(fields, name); + return switch (@typeInfo(@TypeOf(value))) { + .optional => value, + else => value, + }; +} + +fn toTimeZoneOptionField(fields: anytype, comptime name: []const u8) c.TimeZone_option { + if (!@hasField(@TypeOf(fields), name)) return .{ .is_ok = false }; + + const value = @field(fields, name); + const maybe_time_zone = switch (@typeInfo(@TypeOf(value))) { + .optional => if (value) |payload| payload._inner else null, + else => value._inner, + }; + return toOption(c.TimeZone_option, maybe_time_zone); +} + /// Convert a Zig `?Unit` to a Rust `Option`. pub fn toUnitOption(maybe_value: ?c.Unit) c.Unit_option { return toOption(c.Unit_option, maybe_value); diff --git a/src/root.zig b/src/root.zig index 1daf15d..ef11512 100644 --- a/src/root.zig +++ b/src/root.zig @@ -502,6 +502,7 @@ test ZonedDateTime { "DisplayOffset", "DisplayTimeZone", "ToStringOptions", + "WithOptions", }; try assertDecls(ZonedDateTime, checks);