feat(plain): add initial wrapper

This commit is contained in:
Nurul Huda (Apon) 2026-01-26 21:50:48 +06:00
parent 387fbc577a
commit f88236cf31
11 changed files with 2687 additions and 547 deletions

View file

@ -7,18 +7,18 @@ Temporalz provides Zig bindings to the Rust-based [temporal_rs](https://github.c
## Installation
### Prerequisites
#### Prerequisites
- Zig 0.15.2
- Rust toolchain (only required if [prebuilt binaries](#prebuilt) are not available for your platform)
### Add as a Dependency
#### Add as a Dependency
```bash
zig fetch --save=temporalz https://github.com/nurulhudaapon/temporalz/archive/refs/tags/v0.1.2.tar.gz
zig fetch --save https://github.com/nurulhudaapon/temporalz/archive/refs/tags/v0.1.2.tar.gz
```
### Use in build.zig
#### Use in build.zig
Add temporalz to your executable in `build.zig`:
@ -57,20 +57,20 @@ For other platforms, the library will build from source; you need the Rust toolc
## Development
### Clone the Repository
#### Clone the Repository
```bash
git clone https://github.com/nurulhudaapon/temporalz.git
cd temporalz
```
### Build and Run
#### Build and Run
```bash
zig build run
```
### Run Tests
#### Run Tests
```bash
zig build test

View file

@ -10,16 +10,56 @@ pub const ToStringRoundingOptions = temporal.ToStringRoundingOptions;
pub const Unit = temporal.Unit;
pub const RoundingMode = temporal.RoundingMode;
pub const Sign = temporal.Sign;
pub const PartialDuration = abi.c.PartialDuration;
/// Partial duration specification for creating Duration objects.
/// This is a wrapper around the C API type to avoid exposing C types directly.
pub const PartialDuration = struct {
years: ?i64 = null,
months: ?i64 = null,
weeks: ?i64 = null,
days: ?i64 = null,
hours: ?i64 = null,
minutes: ?i64 = null,
seconds: ?i64 = null,
milliseconds: ?i64 = null,
microseconds: ?f64 = null,
nanoseconds: ?f64 = null,
fn toCApi(self: PartialDuration) abi.c.PartialDuration {
return .{
.years = abi.toOption(abi.c.OptionI64, self.years),
.months = abi.toOption(abi.c.OptionI64, self.months),
.weeks = abi.toOption(abi.c.OptionI64, self.weeks),
.days = abi.toOption(abi.c.OptionI64, self.days),
.hours = abi.toOption(abi.c.OptionI64, self.hours),
.minutes = abi.toOption(abi.c.OptionI64, self.minutes),
.seconds = abi.toOption(abi.c.OptionI64, self.seconds),
.milliseconds = abi.toOption(abi.c.OptionI64, self.milliseconds),
.microseconds = abi.toOption(abi.c.OptionF64, self.microseconds),
.nanoseconds = abi.toOption(abi.c.OptionF64, self.nanoseconds),
};
}
};
/// Wrapper for PlainDate reference in RelativeTo
const PlainDateRef = struct {
_inner: *abi.c.PlainDate,
};
/// Wrapper for ZonedDateTime reference in RelativeTo
const ZonedDateTimeRef = struct {
_inner: *abi.c.ZonedDateTime,
};
/// Relative-to context for duration operations.
pub const RelativeTo = extern struct {
plain_date: ?*abi.c.PlainDate,
zoned_date_time: ?*abi.c.ZonedDateTime,
pub const RelativeTo = struct {
plain_date: ?PlainDateRef = null,
zoned_date_time: ?ZonedDateTimeRef = null,
fn toCApi(self: RelativeTo) abi.c.RelativeTo {
return .{
.date = self.plain_date,
.zoned = self.zoned_date_time,
.date = if (self.plain_date) |pd| pd._inner else null,
.zoned = if (self.zoned_date_time) |zdt| zdt._inner else null,
};
}
};
@ -73,7 +113,7 @@ fn fromUtf16(text: []const u16) !Duration {
/// 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));
return wrapDuration(abi.c.temporal_rs_Duration_from_partial_duration(partial.toCApi()));
}
/// Check if the time portion of the duration is within valid ranges.
@ -362,20 +402,9 @@ test toString {
}
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 partial = PartialDuration{
.hours = 3,
.minutes = 45,
};
const dur = try Duration.fromPartialDuration(partial);

View file

@ -11,6 +11,21 @@ pub const RoundingMode = temporal.RoundingMode;
pub const Sign = temporal.Sign;
pub const RoundingOptions = temporal.RoundingOptions;
pub const DifferenceSettings = temporal.DifferenceSettings;
/// Time zone identifier for Temporal operations
pub const TimeZone = struct {
_inner: abi.c.TimeZone,
pub fn init(id: []const u8) TimeZone {
const view = abi.toDiplomatStringView(id);
return .{ ._inner = .{ .id = view } };
}
fn toCApi(self: TimeZone) abi.c.TimeZone {
return self._inner;
}
};
/// Options for Instant.toString()
/// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant/toString
pub const ToStringOptions = struct {
@ -29,8 +44,7 @@ pub const ToStringOptions = struct {
smallest_unit: ?Unit = null,
/// Time zone to use. Either a time zone identifier string or null for UTC.
/// Note: In the Zig API, this must be pre-resolved to a TimeZone struct.
time_zone: ?abi.c.TimeZone = null,
time_zone: ?TimeZone = null,
};
_inner: *abi.c.Instant,
@ -99,13 +113,15 @@ pub fn subtract(self: Instant, duration: *Duration) !Instant {
}
/// Difference until another instant (Temporal.Instant.prototype.until).
pub fn until(self: Instant, other: Instant, settings: DifferenceSettings) !DurationHandle {
return wrapDuration(abi.c.temporal_rs_Instant_until(self._inner, other._inner, settings.toCApi()));
pub fn until(self: Instant, other: Instant, settings: DifferenceSettings) !Duration {
const ptr = (abi.success(abi.c.temporal_rs_Instant_until(self._inner, other._inner, settings.toCApi())) orelse return error.TemporalError) orelse return error.TemporalError;
return .{ ._inner = ptr };
}
/// Difference since another instant (Temporal.Instant.prototype.since).
pub fn since(self: Instant, other: Instant, settings: DifferenceSettings) !DurationHandle {
return wrapDuration(abi.c.temporal_rs_Instant_since(self._inner, other._inner, settings.toCApi()));
pub fn since(self: Instant, other: Instant, settings: DifferenceSettings) !Duration {
const ptr = (abi.success(abi.c.temporal_rs_Instant_since(self._inner, other._inner, settings.toCApi())) orelse return error.TemporalError) orelse return error.TemporalError;
return .{ ._inner = ptr };
}
/// Round this instant (Temporal.Instant.prototype.round).
@ -125,7 +141,7 @@ pub fn equals(a: Instant, b: Instant) bool {
/// Convert to string using compiled TZ data; caller owns returned slice.
pub fn toString(self: Instant, allocator: std.mem.Allocator, opts: ToStringOptions) ![]u8 {
const zone_opt = abi.toTimeZoneOption(opts.time_zone);
const zone_opt = if (opts.time_zone) |tz| abi.toTimeZoneOption(tz._inner) else abi.toTimeZoneOption(null);
const rounding = optsToRounding(opts);
var write = abi.DiplomatWrite.init(allocator);
@ -139,7 +155,7 @@ pub fn toString(self: Instant, allocator: std.mem.Allocator, opts: ToStringOptio
/// Convert to string using an explicit provider.
fn toStringWithProvider(self: Instant, allocator: std.mem.Allocator, provider: *const abi.c.Provider, opts: ToStringOptions) ![]u8 {
const zone_opt = abi.toTimeZoneOption(opts.time_zone);
const zone_opt = if (opts.time_zone) |tz| abi.toTimeZoneOption(tz._inner) else abi.toTimeZoneOption(null);
const rounding = optsToRounding(opts);
var write = abi.DiplomatWrite.init(allocator);
@ -155,20 +171,23 @@ pub fn toJSON(self: Instant, allocator: std.mem.Allocator) ![]u8 {
return self.toString(allocator, .{});
}
/// Convert to a locale string representation.
/// Per Temporal spec, toLocaleString returns a formatted string without taking locale/options parameters.
/// This uses auto precision for fractional seconds.
pub fn toLocaleString(self: Instant, allocator: std.mem.Allocator) ![]u8 {
_ = self;
_ = allocator;
return error.TemporalNotImplemented;
return self.toString(allocator, .{});
}
/// Convert to ZonedDateTime using built-in provider (Temporal.Instant.prototype.toZonedDateTimeISO).
pub fn toZonedDateTimeISO(self: Instant, zone: abi.c.TimeZone) !ZonedDateTimeHandle {
return wrapZonedDateTime(abi.c.temporal_rs_Instant_to_zoned_date_time_iso(self._inner, zone));
pub fn toZonedDateTimeISO(self: Instant, zone: TimeZone) !ZonedDateTime {
const ptr = (abi.success(abi.c.temporal_rs_Instant_to_zoned_date_time_iso(self._inner, zone._inner)) orelse return error.TemporalError) orelse return error.TemporalError;
return .{ ._inner = ptr };
}
/// Convert to ZonedDateTime using an explicit provider.
fn toZonedDateTimeIsoWithProvider(self: Instant, zone: abi.c.TimeZone, provider: *const abi.c.Provider) !ZonedDateTimeHandle {
return wrapZonedDateTime(abi.c.temporal_rs_Instant_to_zoned_date_time_iso_with_provider(self._inner, zone, provider));
fn toZonedDateTimeIsoWithProvider(self: Instant, zone: TimeZone, provider: *const abi.c.Provider) !ZonedDateTime {
const ptr = (abi.success(abi.c.temporal_rs_Instant_to_zoned_date_time_iso_with_provider(self._inner, zone._inner, provider)) orelse return error.TemporalError) orelse return error.TemporalError;
return .{ ._inner = ptr };
}
/// Clone the underlying instant.
@ -192,16 +211,6 @@ fn wrapInstant(res: anytype) !Instant {
};
}
fn wrapDuration(res: anytype) !DurationHandle {
const ptr = (abi.success(res) orelse return error.TemporalError) orelse return error.TemporalError;
return .{ .ptr = ptr };
}
fn wrapZonedDateTime(res: anytype) !ZonedDateTimeHandle {
const ptr = (abi.success(res) orelse return error.TemporalError) orelse return error.TemporalError;
return .{ .ptr = ptr };
}
fn handleVoidResult(res: anytype) !void {
_ = abi.success(res) orelse return error.TemporalError;
}
@ -232,26 +241,13 @@ fn optsToRounding(opts: ToStringOptions) abi.c.ToStringRoundingOptions {
};
}
fn parseDuration(text: []const u8) !DurationHandle {
const view = abi.toDiplomatStringView(text);
return wrapDuration(abi.c.temporal_rs_Duration_from_utf8(view));
}
// --- Forward declarations -----------------------------------------------------
// --- Helper types -----------------------------------------------------
const ZonedDateTime = struct {
_inner: *abi.c.ZonedDateTime,
const DurationHandle = struct {
ptr: *abi.c.Duration,
pub fn deinit(self: DurationHandle) void {
abi.c.temporal_rs_Duration_destroy(self.ptr);
}
};
const ZonedDateTimeHandle = struct {
ptr: *abi.c.ZonedDateTime,
pub fn deinit(self: ZonedDateTimeHandle) void {
abi.c.temporal_rs_ZonedDateTime_destroy(self.ptr);
pub fn deinit(self: ZonedDateTime) void {
abi.c.temporal_rs_ZonedDateTime_destroy(self._inner);
}
};
@ -365,15 +361,15 @@ test until {
.rounding_increment = null,
};
var until_handle = try earlier.until(later, settings);
defer until_handle.deinit();
try std.testing.expectEqual(Sign.positive, Sign.fromCApi(abi.c.temporal_rs_Duration_sign(until_handle.ptr)));
try std.testing.expectEqual(@as(i64, 1), abi.c.temporal_rs_Duration_hours(until_handle.ptr));
const until_dur = try earlier.until(later, settings);
defer until_dur.deinit();
try std.testing.expectEqual(Sign.positive, until_dur.sign());
try std.testing.expectEqual(@as(i64, 1), until_dur.hours());
var since_handle = try later.since(earlier, settings);
defer since_handle.deinit();
try std.testing.expectEqual(Sign.positive, Sign.fromCApi(abi.c.temporal_rs_Duration_sign(since_handle.ptr)));
try std.testing.expectEqual(@as(i64, 1), abi.c.temporal_rs_Duration_hours(since_handle.ptr));
const since_dur = try later.since(earlier, settings);
defer since_dur.deinit();
try std.testing.expectEqual(Sign.positive, since_dur.sign());
try std.testing.expectEqual(@as(i64, 1), since_dur.hours());
}
test since {
@ -389,15 +385,15 @@ test since {
.rounding_increment = null,
};
var until_handle = try earlier.until(later, settings);
defer until_handle.deinit();
try std.testing.expectEqual(Sign.positive, Sign.fromCApi(abi.c.temporal_rs_Duration_sign(until_handle.ptr)));
try std.testing.expectEqual(@as(i64, 1), abi.c.temporal_rs_Duration_hours(until_handle.ptr));
const until_dur = try earlier.until(later, settings);
defer until_dur.deinit();
try std.testing.expectEqual(Sign.positive, until_dur.sign());
try std.testing.expectEqual(@as(i64, 1), until_dur.hours());
var since_handle = try later.since(earlier, settings);
defer since_handle.deinit();
try std.testing.expectEqual(Sign.positive, Sign.fromCApi(abi.c.temporal_rs_Duration_sign(since_handle.ptr)));
try std.testing.expectEqual(@as(i64, 1), abi.c.temporal_rs_Duration_hours(since_handle.ptr));
const since_dur = try later.since(earlier, settings);
defer since_dur.deinit();
try std.testing.expectEqual(Sign.positive, since_dur.sign());
try std.testing.expectEqual(@as(i64, 1), since_dur.hours());
}
test round {
@ -450,3 +446,25 @@ test toString {
defer allocator.free(with_unit);
// Output should truncate to seconds
}
test toLocaleString {
const epoch_ns: i128 = 1704067200000000000; // 2024-01-01 00:00:00 UTC
const inst = try Instant.init(epoch_ns);
defer inst.deinit();
const allocator = std.testing.allocator;
// toLocaleString should return a formatted string (same as toString with defaults)
const locale_str = try inst.toLocaleString(allocator);
defer allocator.free(locale_str);
try std.testing.expectEqualStrings(locale_str, "2024-01-01T00:00:00Z");
// Test with a different instant with fractional seconds
const inst2 = try Instant.fromEpochNanoseconds(1704067200123456789);
defer inst2.deinit();
const locale_str2 = try inst2.toLocaleString(allocator);
defer allocator.free(locale_str2);
// Auto precision should include fractional seconds
try std.testing.expect(std.mem.containsAtLeast(u8, locale_str2, 1, "2024-01-01T00:00:00"));
}

View file

@ -1,3 +1,4 @@
const std = @import("std");
const Instant = @import("Instant.zig");
const PlainDate = @import("PlainDate.zig");
const PlainDateTime = @import("PlainDateTime.zig");
@ -6,47 +7,124 @@ const ZonedDateTime = @import("ZonedDateTime.zig");
const Now = @This();
pub fn instant() error{Todo}!Instant {
return error.Todo;
pub fn instant() !Instant {
const ns: i128 = std.time.nanoTimestamp();
return Instant.fromEpochNanoseconds(ns);
}
pub fn plainDateISO() error{Todo}!PlainDate {
return error.Todo;
pub fn plainDateISO() !PlainDate {
const now = currentParts();
return PlainDate.init(now.year, now.month, now.day);
}
pub fn plainDateTimeISO() error{Todo}!PlainDateTime {
return error.Todo;
pub fn plainDateTimeISO() !PlainDateTime {
const now = currentParts();
return PlainDateTime.init(
now.year,
now.month,
now.day,
now.hour,
now.minute,
now.second,
now.millisecond,
now.microsecond,
now.nanosecond,
);
}
pub fn plainTimeISO() error{Todo}!PlainTime {
return error.Todo;
pub fn plainTimeISO() !PlainTime {
const now = currentParts();
return PlainTime.init(
now.hour,
now.minute,
now.second,
now.millisecond,
now.microsecond,
now.nanosecond,
);
}
pub fn timeZoneId() error{Todo}![]const u8 {
return error.Todo;
pub fn timeZoneId() []const u8 {
return "UTC";
}
pub fn zonedDateTimeISO() error{Todo}!ZonedDateTime {
return error.Todo;
pub fn zonedDateTimeISO() !ZonedDateTime {
return error.TemporalNotImplemented;
}
const CurrentParts = struct {
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
millisecond: u16,
microsecond: u16,
nanosecond: u16,
};
fn currentParts() CurrentParts {
const ns: i128 = std.time.nanoTimestamp();
const seconds: i64 = @intCast(@divTrunc(ns, 1_000_000_000));
const subsec_nanos_u64: u64 = @intCast(@rem(ns, 1_000_000_000));
const days_since_epoch: u64 = @intCast(@divTrunc(seconds, std.time.epoch.secs_per_day));
const secs_of_day: u64 = @intCast(@mod(seconds, std.time.epoch.secs_per_day));
const epoch_day = std.time.epoch.EpochDay{ .day = @intCast(days_since_epoch) };
const year_day = epoch_day.calculateYearDay();
const month_day = year_day.calculateMonthDay();
const day_seconds = std.time.epoch.DaySeconds{ .secs = @intCast(secs_of_day) };
const millisecond: u16 = @intCast(subsec_nanos_u64 / 1_000_000);
const microsecond: u16 = @intCast((subsec_nanos_u64 / 1_000) % 1_000);
const nanosecond: u16 = @intCast(subsec_nanos_u64 % 1_000);
return .{
.year = @intCast(year_day.year),
.month = @intCast(month_day.month.numeric()),
.day = @intCast(month_day.day_index + 1),
.hour = @intCast(day_seconds.getHoursIntoDay()),
.minute = @intCast(day_seconds.getMinutesIntoHour()),
.second = @intCast(day_seconds.getSecondsIntoMinute()),
.millisecond = millisecond,
.microsecond = microsecond,
.nanosecond = nanosecond,
};
}
// ---------- Tests ---------------------
test instant {
if (true) return error.Todo;
const inst = try instant();
defer inst.deinit();
try std.testing.expect(inst.epoch_nanoseconds > 0);
}
test plainDateISO {
if (true) return error.Todo;
const date = try plainDateISO();
try std.testing.expect(date.year() >= 1970);
try std.testing.expect(date.month() >= 1 and date.month() <= 12);
try std.testing.expect(date.day() >= 1 and date.day() <= 31);
}
test plainDateTimeISO {
if (true) return error.Todo;
const dt = try plainDateTimeISO();
try std.testing.expect(dt.year() >= 1970);
try std.testing.expect(dt.month() >= 1 and dt.month() <= 12);
try std.testing.expect(dt.day() >= 1 and dt.day() <= 31);
try std.testing.expect(dt.hour() < 24);
try std.testing.expect(dt.minute() < 60);
}
test plainTimeISO {
if (true) return error.Todo;
const t = try plainTimeISO();
try std.testing.expect(t.hour() < 24);
try std.testing.expect(t.minute() < 60);
try std.testing.expect(t.second() < 60);
}
test timeZoneId {
if (true) return error.Todo;
const tz = timeZoneId();
try std.testing.expectEqualStrings("UTC", tz);
}
test zonedDateTimeISO {
if (true) return error.Todo;
try std.testing.expectError(error.TemporalNotImplemented, zonedDateTimeISO());
}

View file

@ -1,148 +1,497 @@
const std = @import("std");
const abi = @import("abi.zig");
const temporal = @import("temporal.zig");
const PlainDateTime = @import("PlainDateTime.zig");
const PlainMonthDay = @import("PlainMonthDay.zig");
const PlainYearMonth = @import("PlainYearMonth.zig");
const ZonedDateTime = @import("ZonedDateTime.zig");
const PlainTime = @import("PlainTime.zig");
const Duration = @import("Duration.zig");
const PlainDate = @This();
pub const Unit = temporal.Unit;
pub const RoundingMode = temporal.RoundingMode;
pub const Sign = temporal.Sign;
pub const DifferenceSettings = temporal.DifferenceSettings;
pub const ToStringOptions = struct {
calendar_id: ?CalendarDisplay = null,
};
pub const CalendarDisplay = enum {
auto,
always,
never,
critical,
fn toCApi(self: CalendarDisplay) abi.c.ShowCalendar {
return switch (self) {
.auto => .Auto,
.always => .Always,
.never => .Never,
.critical => .Critical,
};
}
};
pub const ToZonedDateTimeOptions = struct {
time_zone: []const u8,
plain_time: ?PlainTime = null,
};
_inner: *abi.c.PlainDate,
calendar_id: []const u8,
day: i64 = 0,
day_of_week: i64,
day_of_year: i64,
days_in_month: i64,
days_in_week: i64,
days_in_year: i64,
era: []const u8,
era_year: i64,
in_leap_year: bool,
month: i64,
month_code: []const u8,
months_in_year: i64,
week_of_year: i64,
year: i64,
year_of_week: i64,
pub fn init() error{Todo}!PlainDate {
return error.Todo;
pub fn init(year_val: i32, month_val: u8, day_val: u8) !PlainDate {
return initWithCalendar(year_val, month_val, day_val, "iso8601");
}
pub fn compare() error{Todo}!i8 {
return error.Todo;
pub fn initWithCalendar(year_val: i32, month_val: u8, day_val: u8, calendar: []const u8) !PlainDate {
const cal_view = abi.toDiplomatStringView(calendar);
const cal_result = abi.c.temporal_rs_AnyCalendarKind_parse_temporal_calendar_string(cal_view);
const cal_kind = abi.success(cal_result) orelse return error.TemporalError;
return wrapPlainDate(abi.c.temporal_rs_PlainDate_try_new(year_val, month_val, day_val, cal_kind));
}
pub fn from() error{Todo}!PlainDate {
return error.Todo;
pub fn from(info: anytype) !PlainDate {
const T = @TypeOf(info);
if (T == PlainDate) return info.clone();
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 a PlainDate, []const u8, or []const u16"),
}
}
pub fn add() error{Todo}!PlainDate {
return error.Todo;
inline fn fromUtf8(text: []const u8) !PlainDate {
const view = abi.toDiplomatStringView(text);
return wrapPlainDate(abi.c.temporal_rs_PlainDate_from_utf8(view));
}
pub fn equals() error{Todo}!bool {
return error.Todo;
inline fn fromUtf16(text: []const u16) !PlainDate {
const view = abi.toDiplomatString16View(text);
return wrapPlainDate(abi.c.temporal_rs_PlainDate_from_utf16(view));
}
pub fn since() error{Todo}!Duration {
return error.Todo;
pub fn compare(a: PlainDate, b: PlainDate) i8 {
return abi.c.temporal_rs_PlainDate_compare(a._inner, b._inner);
}
pub fn subtract() error{Todo}!PlainDate {
return error.Todo;
pub fn equals(self: PlainDate, other: PlainDate) bool {
return abi.c.temporal_rs_PlainDate_equals(self._inner, other._inner);
}
pub fn toJSON() error{Todo}![]const u8 {
return error.Todo;
pub fn add(self: PlainDate, duration: Duration) !PlainDate {
const overflow_opt = abi.c.ArithmeticOverflow_option{ .is_ok = true, .unnamed_0 = .{ .ok = abi.c.ArithmeticOverflow_Constrain } };
return wrapPlainDate(abi.c.temporal_rs_PlainDate_add(self._inner, duration._inner, overflow_opt));
}
pub fn toLocaleString() error{Todo}![]const u8 {
return error.Todo;
pub fn subtract(self: PlainDate, duration: Duration) !PlainDate {
const overflow_opt = abi.c.ArithmeticOverflow_option{ .is_ok = true, .unnamed_0 = .{ .ok = abi.c.ArithmeticOverflow_Constrain } };
return wrapPlainDate(abi.c.temporal_rs_PlainDate_subtract(self._inner, duration._inner, overflow_opt));
}
pub fn toPlainDateTime() error{Todo}!PlainDateTime {
return error.Todo;
pub fn until(self: PlainDate, other: PlainDate, settings: DifferenceSettings) !Duration {
const ptr = (abi.success(abi.c.temporal_rs_PlainDate_until(self._inner, other._inner, settings.toCApi())) orelse return error.TemporalError) orelse return error.TemporalError;
return .{ ._inner = ptr };
}
pub fn toPlainMonthDay() error{Todo}!PlainMonthDay {
return error.Todo;
pub fn since(self: PlainDate, other: PlainDate, settings: DifferenceSettings) !Duration {
const ptr = (abi.success(abi.c.temporal_rs_PlainDate_since(self._inner, other._inner, settings.toCApi())) orelse return error.TemporalError) orelse return error.TemporalError;
return .{ ._inner = ptr };
}
pub fn toPlainYearMonth() error{Todo}!PlainYearMonth {
return error.Todo;
pub fn with(self: PlainDate, fields: anytype) !PlainDate {
_ = self;
_ = fields;
return error.TemporalNotImplemented;
}
pub fn toString() error{Todo}![]const u8 {
return error.Todo;
pub fn withCalendar(self: PlainDate, calendar: []const u8) !PlainDate {
const cal_view = abi.toDiplomatStringView(calendar);
const cal_result = abi.c.temporal_rs_AnyCalendarKind_parse_temporal_calendar_string(cal_view);
const cal_kind = abi.success(cal_result) orelse return error.TemporalError;
const ptr = abi.c.temporal_rs_PlainDate_with_calendar(self._inner, cal_kind) orelse return error.TemporalError;
const calendar_ptr = abi.c.temporal_rs_PlainDate_calendar(ptr) orelse return error.TemporalError;
const cal_id_view = abi.c.temporal_rs_Calendar_identifier(calendar_ptr);
const allocator = std.heap.page_allocator;
const cal_id = allocator.dupe(u8, cal_id_view.data[0..cal_id_view.len]) catch "iso8601";
return .{ ._inner = ptr, .calendar_id = cal_id };
}
pub fn toZonedDateTime() error{Todo}!ZonedDateTime {
return error.Todo;
pub fn toPlainDateTime(self: PlainDate, time: ?PlainTime) !PlainDateTime {
const time_ptr = if (time) |t| t._inner else null;
const ptr = (abi.success(abi.c.temporal_rs_PlainDate_to_plain_date_time(self._inner, time_ptr)) orelse return error.TemporalError) orelse return error.TemporalError;
return .{ ._inner = ptr, .calendar_id = self.calendar_id };
}
pub fn until() error{Todo}!Duration {
return error.Todo;
pub fn toPlainMonthDay(self: PlainDate) !PlainMonthDay {
const ptr = (abi.success(abi.c.temporal_rs_PlainDate_to_plain_month_day(self._inner)) orelse return error.TemporalError) orelse return error.TemporalError;
return .{ ._inner = ptr, .calendar_id = self.calendar_id };
}
pub fn valueOf() error{Todo}!void {
return error.Todo;
pub fn toPlainYearMonth(self: PlainDate) !PlainYearMonth {
const ptr = (abi.success(abi.c.temporal_rs_PlainDate_to_plain_year_month(self._inner)) orelse return error.TemporalError) orelse return error.TemporalError;
return .{ ._inner = ptr, .calendar_id = self.calendar_id };
}
pub fn with() error{Todo}!PlainDate {
return error.Todo;
pub fn toZonedDateTime(self: PlainDate, options: ToZonedDateTimeOptions) !ZonedDateTime {
const tz_view = abi.toDiplomatStringView(options.time_zone);
const tz_result = abi.c.temporal_rs_TimeZone_try_from_str(tz_view);
const time_zone = abi.success(tz_result) orelse return error.TemporalError;
const time_ptr = if (options.plain_time) |t| t._inner else null;
const ptr = (abi.success(abi.c.temporal_rs_PlainDate_to_zoned_date_time(self._inner, time_zone, time_ptr)) orelse return error.TemporalError) orelse return error.TemporalError;
// Get time zone identifier for ZonedDateTime
const allocator = std.heap.page_allocator;
var write = abi.DiplomatWrite.init(allocator);
defer write.deinit();
abi.c.temporal_rs_TimeZone_identifier(time_zone, &write.inner);
const tz_id = try write.toOwnedSlice();
return .{ ._inner = ptr, .time_zone_id = tz_id, .calendar_id = self.calendar_id };
}
pub fn withCalendar() error{Todo}!PlainDate {
return error.Todo;
pub fn toString(self: PlainDate, allocator: std.mem.Allocator, options: ToStringOptions) ![]u8 {
var write = abi.DiplomatWrite.init(allocator);
defer write.deinit();
const display_cal: abi.c.DisplayCalendar = if (options.calendar_id) |cal| switch (cal) {
.auto => @intCast(abi.c.DisplayCalendar_Auto),
.always => @intCast(abi.c.DisplayCalendar_Always),
.never => @intCast(abi.c.DisplayCalendar_Never),
.critical => @intCast(abi.c.DisplayCalendar_Critical),
} else @intCast(abi.c.DisplayCalendar_Auto);
abi.c.temporal_rs_PlainDate_to_ixdtf_string(self._inner, display_cal, &write.inner);
return try write.toOwnedSlice();
}
// ---------- Tests ---------------------
test compare {
if (true) return error.Todo;
pub fn toJSON(self: PlainDate, allocator: std.mem.Allocator) ![]u8 {
return self.toString(allocator, .{});
}
pub fn toLocaleString(self: PlainDate, allocator: std.mem.Allocator) ![]u8 {
_ = self;
_ = allocator;
return error.TemporalNotImplemented;
}
pub fn valueOf(self: PlainDate) !void {
_ = self;
return error.TemporalValueOfNotSupported;
}
pub fn day(self: PlainDate) u8 {
return abi.c.temporal_rs_PlainDate_day(self._inner);
}
pub fn dayOfWeek(self: PlainDate) u16 {
return abi.c.temporal_rs_PlainDate_day_of_week(self._inner);
}
pub fn dayOfYear(self: PlainDate) u16 {
return abi.c.temporal_rs_PlainDate_day_of_year(self._inner);
}
pub fn daysInMonth(self: PlainDate) u16 {
return abi.c.temporal_rs_PlainDate_days_in_month(self._inner);
}
pub fn daysInWeek(self: PlainDate) u16 {
return abi.c.temporal_rs_PlainDate_days_in_week(self._inner);
}
pub fn daysInYear(self: PlainDate) u16 {
return abi.c.temporal_rs_PlainDate_days_in_year(self._inner);
}
pub fn monthCode(self: PlainDate, allocator: std.mem.Allocator) ![]u8 {
var write = abi.DiplomatWrite.init(allocator);
defer write.deinit();
abi.c.temporal_rs_PlainDate_month_code(self._inner, &write.inner);
return try write.toOwnedSlice();
}
pub fn month(self: PlainDate) u8 {
return abi.c.temporal_rs_PlainDate_month(self._inner);
}
pub fn monthsInYear(self: PlainDate) u16 {
return abi.c.temporal_rs_PlainDate_months_in_year(self._inner);
}
pub fn year(self: PlainDate) i32 {
return abi.c.temporal_rs_PlainDate_year(self._inner);
}
pub fn inLeapYear(self: PlainDate) bool {
return abi.c.temporal_rs_PlainDate_in_leap_year(self._inner);
}
pub fn era(self: PlainDate, allocator: std.mem.Allocator) !?[]u8 {
var write = abi.DiplomatWrite.init(allocator);
defer write.deinit();
const has_era = abi.c.temporal_rs_PlainDate_era(self._inner, &write.inner);
if (!has_era) return null;
return try write.toOwnedSlice();
}
pub fn eraYear(self: PlainDate) ?u32 {
const result = abi.c.temporal_rs_PlainDate_era_year(self._inner);
if (!result.is_ok) return null;
return result.unnamed_0.ok;
}
pub fn weekOfYear(self: PlainDate) ?u8 {
const result = abi.c.temporal_rs_PlainDate_week_of_year(self._inner);
if (!result.is_ok) return null;
return result.unnamed_0.ok;
}
pub fn yearOfWeek(self: PlainDate) ?i32 {
const result = abi.c.temporal_rs_PlainDate_year_of_week(self._inner);
if (!result.is_ok) return null;
return result.unnamed_0.ok;
}
fn clone(self: PlainDate) PlainDate {
const ptr = abi.c.temporal_rs_PlainDate_clone(self._inner) orelse unreachable;
return .{ ._inner = ptr, .calendar_id = self.calendar_id };
}
pub fn deinit(self: PlainDate) void {
abi.c.temporal_rs_PlainDate_destroy(self._inner);
}
fn wrapPlainDate(res: anytype) !PlainDate {
const ptr = (abi.success(res) orelse return error.TemporalError) orelse return error.TemporalError;
const calendar_ptr = abi.c.temporal_rs_PlainDate_calendar(ptr) orelse return error.TemporalError;
const cal_id_view = abi.c.temporal_rs_Calendar_identifier(calendar_ptr);
const allocator = std.heap.page_allocator;
const cal_id = allocator.dupe(u8, cal_id_view.data[0..cal_id_view.len]) catch "iso8601";
return .{ ._inner = ptr, .calendar_id = cal_id };
}
fn handleVoidResult(res: anytype) !void {
_ = abi.success(res) orelse return error.TemporalError;
}
test init {
const date = try PlainDate.init(2024, 3, 15);
defer date.deinit();
try std.testing.expectEqual(@as(i32, 2024), date.year());
try std.testing.expectEqual(@as(u8, 3), date.month());
try std.testing.expectEqual(@as(u8, 15), date.day());
}
test from {
if (true) return error.Todo;
const date = try PlainDate.from("2024-03-15");
defer date.deinit();
try std.testing.expectEqual(@as(i32, 2024), date.year());
try std.testing.expectEqual(@as(u8, 3), date.month());
try std.testing.expectEqual(@as(u8, 15), date.day());
const date2 = try PlainDate.from(date);
defer date2.deinit();
try std.testing.expect(PlainDate.equals(date, date2));
}
test add {
if (true) return error.Todo;
test compare {
const date1 = try PlainDate.init(2024, 1, 1);
defer date1.deinit();
const date2 = try PlainDate.init(2024, 1, 1);
defer date2.deinit();
const date3 = try PlainDate.init(2024, 12, 31);
defer date3.deinit();
try std.testing.expectEqual(@as(i8, 0), PlainDate.compare(date1, date2));
try std.testing.expectEqual(@as(i8, -1), PlainDate.compare(date1, date3));
try std.testing.expectEqual(@as(i8, 1), PlainDate.compare(date3, date1));
}
test equals {
if (true) return error.Todo;
const date1 = try PlainDate.init(2024, 3, 15);
defer date1.deinit();
const date2 = try PlainDate.init(2024, 3, 15);
defer date2.deinit();
const date3 = try PlainDate.init(2024, 3, 16);
defer date3.deinit();
try std.testing.expect(PlainDate.equals(date1, date2));
try std.testing.expect(!PlainDate.equals(date1, date3));
}
test since {
if (true) return error.Todo;
test add {
const date = try PlainDate.init(2024, 1, 1);
defer date.deinit();
var duration = try Duration.from("P1M");
defer duration.deinit();
const result = try date.add(duration);
defer result.deinit();
try std.testing.expectEqual(@as(i32, 2024), result.year());
try std.testing.expectEqual(@as(u8, 2), result.month());
try std.testing.expectEqual(@as(u8, 1), result.day());
}
test subtract {
if (true) return error.Todo;
}
test toJSON {
if (true) return error.Todo;
}
test toLocaleString {
if (true) return error.Todo;
}
test toPlainDateTime {
if (true) return error.Todo;
}
test toPlainMonthDay {
if (true) return error.Todo;
}
test toPlainYearMonth {
if (true) return error.Todo;
}
test toString {
if (true) return error.Todo;
}
test toZonedDateTime {
if (true) return error.Todo;
const date = try PlainDate.init(2024, 3, 15);
defer date.deinit();
var duration = try Duration.from("P10D");
defer duration.deinit();
const result = try date.subtract(duration);
defer result.deinit();
try std.testing.expectEqual(@as(i32, 2024), result.year());
try std.testing.expectEqual(@as(u8, 3), result.month());
try std.testing.expectEqual(@as(u8, 5), result.day());
}
test until {
if (true) return error.Todo;
const start = try PlainDate.init(2024, 1, 1);
defer start.deinit();
const end = try PlainDate.init(2024, 2, 1);
defer end.deinit();
const settings = DifferenceSettings{
.largest_unit = .month,
.smallest_unit = .day,
.rounding_mode = .trunc,
.rounding_increment = null,
};
const duration = try start.until(end, settings);
defer duration.deinit();
try std.testing.expectEqual(@as(i64, 1), duration.months());
}
test since {
const end = try PlainDate.init(2024, 2, 1);
defer end.deinit();
const start = try PlainDate.init(2024, 1, 1);
defer start.deinit();
const settings = DifferenceSettings{
.largest_unit = .month,
.smallest_unit = .day,
.rounding_mode = .trunc,
.rounding_increment = null,
};
const duration = try end.since(start, settings);
defer duration.deinit();
try std.testing.expectEqual(@as(i64, 1), duration.months());
}
test toString {
const date = try PlainDate.init(2024, 3, 15);
defer date.deinit();
const allocator = std.testing.allocator;
const str = try date.toString(allocator, .{});
defer allocator.free(str);
try std.testing.expect(std.mem.indexOf(u8, str, "2024-03-15") != null);
}
test toJSON {
const date = try PlainDate.init(2024, 3, 15);
defer date.deinit();
const allocator = std.testing.allocator;
const str = try date.toJSON(allocator);
defer allocator.free(str);
try std.testing.expect(std.mem.indexOf(u8, str, "2024-03-15") != null);
}
test toLocaleString {
const date = try PlainDate.init(2024, 3, 15);
defer date.deinit();
const allocator = std.testing.allocator;
try std.testing.expectError(error.TemporalNotImplemented, date.toLocaleString(allocator));
}
test toPlainDateTime {
const date = try PlainDate.init(2024, 1, 15);
const datetime = try date.toPlainDateTime(null);
try std.testing.expectEqual(@as(i32, 2024), datetime.year());
try std.testing.expectEqual(@as(u8, 1), datetime.month());
try std.testing.expectEqual(@as(u8, 15), datetime.day());
try std.testing.expectEqual(@as(u8, 0), datetime.hour());
try std.testing.expectEqual(@as(u8, 0), datetime.minute());
}
test toPlainMonthDay {
const date = try PlainDate.init(2024, 12, 25);
const md = try date.toPlainMonthDay();
try std.testing.expectEqual(@as(u8, 25), md.day());
const month_code = try md.monthCode(std.testing.allocator);
defer std.testing.allocator.free(month_code);
try std.testing.expect(month_code.len > 0);
}
test toPlainYearMonth {
const date = try PlainDate.init(2024, 12, 25);
const ym = try date.toPlainYearMonth();
try std.testing.expectEqual(@as(i32, 2024), ym.year());
try std.testing.expectEqual(@as(u8, 12), ym.month());
}
test toZonedDateTime {
if (true) return error.SkipZigTest; // ZonedDateTime not fully implemented yet
// const date = try PlainDate.init(2024, 1, 15);
// const zdt = try date.toZonedDateTime(.{ .time_zone = "UTC", .plain_time = null });
// try std.testing.expectEqual(@as(i32, 2024), zdt.year());
// try std.testing.expectEqual(@as(u8, 1), zdt.month());
// try std.testing.expectEqual(@as(u8, 15), zdt.day());
}
test with {
if (true) return error.Todo;
const date = try PlainDate.init(2024, 3, 15);
defer date.deinit();
try std.testing.expectError(error.TemporalNotImplemented, date.with(.{}));
}
test withCalendar {
if (true) return error.Todo;
const date = try PlainDate.init(2024, 3, 15);
defer date.deinit();
const result = try date.withCalendar("iso8601");
defer result.deinit();
try std.testing.expectEqual(@as(i32, 2024), result.year());
}

View file

@ -1,4 +1,6 @@
const std = @import("std");
const abi = @import("abi.zig");
const temporal = @import("temporal.zig");
const PlainDate = @import("PlainDate.zig");
const PlainTime = @import("PlainTime.zig");
@ -7,154 +9,593 @@ const Duration = @import("Duration.zig");
const PlainDateTime = @This();
_inner: *abi.c.PlainDateTime,
calendar_id: []const u8,
day: i64,
day_of_week: i64,
day_of_year: i64,
days_in_month: i64,
days_in_week: i64,
days_in_year: i64,
era: []const u8,
era_year: i64,
hour: i64,
in_leap_year: bool,
microsecond: i64,
millisecond: i64,
minute: i64,
month: i64,
month_code: []const u8,
months_in_year: i64,
nanosecond: i64,
second: i64,
week_of_year: i64,
year: i64,
year_of_week: i64,
pub fn init() error{Todo}!PlainDateTime {
return error.Todo;
// Import types from temporal.zig
pub const Unit = temporal.Unit;
pub const RoundingMode = temporal.RoundingMode;
pub const Sign = temporal.Sign;
pub const DifferenceSettings = temporal.DifferenceSettings;
pub const RoundOptions = temporal.RoundingOptions;
// Type definitions for API compatibility
pub const CalendarDisplay = enum {
auto,
always,
never,
critical,
};
pub const ToStringOptions = struct {
fractional_second_digits: ?u8 = null,
smallest_unit: ?Unit = null,
rounding_mode: ?RoundingMode = null,
calendar_display: ?CalendarDisplay = null,
};
pub const ToZonedDateTimeOptions = struct {
timeZone: []const u8,
disambiguation: ?[]const u8 = null,
};
pub const WithOptions = struct {
year: ?i32 = null,
month: ?u8 = null,
day: ?u8 = null,
hour: ?u8 = null,
minute: ?u8 = null,
second: ?u8 = null,
millisecond: ?u16 = null,
microsecond: ?u16 = null,
nanosecond: ?u16 = null,
};
// Constructor - creates a PlainDateTime with all parameters
pub fn init(
year_val: i32,
month_val: u8,
day_val: u8,
hour_val: u8,
minute_val: u8,
second_val: u8,
millisecond_val: u16,
microsecond_val: u16,
nanosecond_val: u16,
) !PlainDateTime {
return wrapPlainDateTime(abi.c.temporal_rs_PlainDateTime_try_new(
year_val,
month_val,
day_val,
hour_val,
minute_val,
second_val,
millisecond_val,
microsecond_val,
nanosecond_val,
abi.c.AnyCalendarKind_Iso,
));
}
pub fn compare() error{Todo}!i8 {
return error.Todo;
pub fn initWithCalendar(
year_val: i32,
month_val: u8,
day_val: u8,
hour_val: u8,
minute_val: u8,
second_val: u8,
millisecond_val: u16,
microsecond_val: u16,
nanosecond_val: u16,
calendar: []const u8,
) !PlainDateTime {
const cal_view = abi.toDiplomatStringView(calendar);
const cal_result = abi.c.temporal_rs_AnyCalendarKind_parse_temporal_calendar_string(cal_view);
const cal_kind = abi.success(cal_result) orelse return error.TemporalError;
return wrapPlainDateTime(abi.c.temporal_rs_PlainDateTime_try_new(
year_val,
month_val,
day_val,
hour_val,
minute_val,
second_val,
millisecond_val,
microsecond_val,
nanosecond_val,
cal_kind,
));
}
pub fn from() error{Todo}!PlainDateTime {
return error.Todo;
// Parse from string
pub fn from(s: []const u8) !PlainDateTime {
return fromUtf8(s);
}
pub fn add() error{Todo}!PlainDateTime {
return error.Todo;
fn fromUtf8(utf8: []const u8) !PlainDateTime {
const view = abi.toDiplomatStringView(utf8);
const parsed = abi.c.temporal_rs_ParsedDateTime_from_utf8(view);
const ptr = abi.success(parsed) orelse return error.TemporalError;
return wrapPlainDateTime(abi.c.temporal_rs_PlainDateTime_from_parsed(ptr));
}
pub fn equals() error{Todo}!bool {
return error.Todo;
fn fromUtf16(utf16: []const u16) !PlainDateTime {
const view = abi.toDiplomatString16View(utf16);
const parsed = abi.c.temporal_rs_ParsedDateTime_from_utf16(view);
const ptr = abi.success(parsed) orelse return error.TemporalError;
return wrapPlainDateTime(abi.c.temporal_rs_PlainDateTime_from_parsed(ptr));
}
pub fn round() error{Todo}!PlainDateTime {
return error.Todo;
// Comparison
pub fn compare(a: PlainDateTime, b: PlainDateTime) i8 {
return abi.c.temporal_rs_PlainDateTime_compare(a._inner, b._inner);
}
pub fn since() error{Todo}!Duration {
return error.Todo;
pub fn equals(self: PlainDateTime, other: PlainDateTime) bool {
if (compare(self, other) != 0) return false;
return std.mem.eql(u8, self.calendar_id, other.calendar_id);
}
pub fn subtract() error{Todo}!PlainDateTime {
return error.Todo;
// Arithmetic
pub fn add(self: PlainDateTime, duration: Duration) !PlainDateTime {
const overflow_opt = abi.c.ArithmeticOverflow_option{
.is_ok = true,
.unnamed_0 = .{ .ok = abi.c.ArithmeticOverflow_Constrain },
};
return wrapPlainDateTime(abi.c.temporal_rs_PlainDateTime_add(self._inner, duration._inner, overflow_opt));
}
pub fn toJSON() error{Todo}![]const u8 {
return error.Todo;
pub fn subtract(self: PlainDateTime, duration: Duration) !PlainDateTime {
const overflow_opt = abi.c.ArithmeticOverflow_option{
.is_ok = true,
.unnamed_0 = .{ .ok = abi.c.ArithmeticOverflow_Constrain },
};
return wrapPlainDateTime(abi.c.temporal_rs_PlainDateTime_subtract(self._inner, duration._inner, overflow_opt));
}
pub fn toLocaleString() error{Todo}![]const u8 {
return error.Todo;
pub fn until(self: PlainDateTime, other: PlainDateTime, options: DifferenceSettings) !Duration {
const result = abi.c.temporal_rs_PlainDateTime_until(self._inner, other._inner, options.toCApi());
const ptr = (abi.success(result) orelse return error.TemporalError) orelse return error.TemporalError;
return .{ ._inner = ptr };
}
pub fn toPlainDate() error{Todo}!PlainDate {
return error.Todo;
pub fn since(self: PlainDateTime, other: PlainDateTime, options: DifferenceSettings) !Duration {
const result = abi.c.temporal_rs_PlainDateTime_since(self._inner, other._inner, options.toCApi());
const ptr = (abi.success(result) orelse return error.TemporalError) orelse return error.TemporalError;
return .{ ._inner = ptr };
}
pub fn toPlainTime() error{Todo}!PlainTime {
return error.Todo;
// Rounding
pub fn round(self: PlainDateTime, options: RoundOptions) !PlainDateTime {
return wrapPlainDateTime(abi.c.temporal_rs_PlainDateTime_round(self._inner, options.toCApi()));
}
pub fn toString() error{Todo}![]const u8 {
return error.Todo;
// Property accessors - Date fields
pub fn calendarId(self: PlainDateTime) []const u8 {
return self.calendar_id;
}
pub fn toZonedDateTime() error{Todo}!ZonedDateTime {
return error.Todo;
pub fn day(self: PlainDateTime) u8 {
return abi.c.temporal_rs_PlainDateTime_day(self._inner);
}
pub fn until() error{Todo}!Duration {
return error.Todo;
pub fn dayOfWeek(self: PlainDateTime) u16 {
return abi.c.temporal_rs_PlainDateTime_day_of_week(self._inner);
}
pub fn valueOf() error{Todo}!void {
return error.Todo;
pub fn dayOfYear(self: PlainDateTime) u16 {
return abi.c.temporal_rs_PlainDateTime_day_of_year(self._inner);
}
pub fn with() error{Todo}!PlainDateTime {
return error.Todo;
pub fn daysInMonth(self: PlainDateTime) u16 {
return abi.c.temporal_rs_PlainDateTime_days_in_month(self._inner);
}
pub fn withCalendar() error{Todo}!PlainDateTime {
return error.Todo;
pub fn daysInWeek(self: PlainDateTime) u16 {
return abi.c.temporal_rs_PlainDateTime_days_in_week(self._inner);
}
pub fn withPlainTime() error{Todo}!PlainDateTime {
return error.Todo;
pub fn daysInYear(self: PlainDateTime) u16 {
return abi.c.temporal_rs_PlainDateTime_days_in_year(self._inner);
}
pub fn month(self: PlainDateTime) u8 {
return abi.c.temporal_rs_PlainDateTime_month(self._inner);
}
pub fn monthCode(self: PlainDateTime, allocator: std.mem.Allocator) ![]u8 {
var write = abi.DiplomatWrite.init(allocator);
defer write.deinit();
abi.c.temporal_rs_PlainDateTime_month_code(self._inner, &write.inner);
return try write.toOwnedSlice();
}
pub fn monthsInYear(self: PlainDateTime) u16 {
return abi.c.temporal_rs_PlainDateTime_months_in_year(self._inner);
}
pub fn year(self: PlainDateTime) i32 {
return abi.c.temporal_rs_PlainDateTime_year(self._inner);
}
pub fn inLeapYear(self: PlainDateTime) bool {
return abi.c.temporal_rs_PlainDateTime_in_leap_year(self._inner);
}
pub fn era(self: PlainDateTime, allocator: std.mem.Allocator) !?[]u8 {
const result = abi.c.temporal_rs_PlainDateTime_era(self._inner);
if (!result.is_ok) return null;
var write = abi.DiplomatWrite.init(allocator);
defer write.deinit();
abi.c.temporal_rs_Era_to_string(result.unnamed_0.ok, &write.inner);
return try write.toOwnedSlice();
}
pub fn eraYear(self: PlainDateTime) !?u32 {
const result = abi.c.temporal_rs_PlainDateTime_era_year(self._inner);
if (!result.is_ok) return null;
return result.unnamed_0.ok;
}
pub fn weekOfYear(self: PlainDateTime) !?u16 {
const result = abi.c.temporal_rs_PlainDateTime_week_of_year(self._inner);
if (!result.is_ok) return null;
return result.unnamed_0.ok;
}
pub fn yearOfWeek(self: PlainDateTime) !?i32 {
const result = abi.c.temporal_rs_PlainDateTime_year_of_week(self._inner);
if (!result.is_ok) return null;
return result.unnamed_0.ok;
}
// Property accessors - Time fields
pub fn hour(self: PlainDateTime) u8 {
return abi.c.temporal_rs_PlainDateTime_hour(self._inner);
}
pub fn minute(self: PlainDateTime) u8 {
return abi.c.temporal_rs_PlainDateTime_minute(self._inner);
}
pub fn second(self: PlainDateTime) u8 {
return abi.c.temporal_rs_PlainDateTime_second(self._inner);
}
pub fn millisecond(self: PlainDateTime) u16 {
return abi.c.temporal_rs_PlainDateTime_millisecond(self._inner);
}
pub fn microsecond(self: PlainDateTime) u16 {
return abi.c.temporal_rs_PlainDateTime_microsecond(self._inner);
}
pub fn nanosecond(self: PlainDateTime) u16 {
return abi.c.temporal_rs_PlainDateTime_nanosecond(self._inner);
}
// Modification methods
pub fn with(self: PlainDateTime, partial: WithOptions) !PlainDateTime {
// Extract fields from partial, or use current values as defaults
const new_year = partial.year orelse self.year();
const new_month = partial.month orelse self.month();
const new_day = partial.day orelse self.day();
const new_hour = partial.hour orelse self.hour();
const new_minute = partial.minute orelse self.minute();
const new_second = partial.second orelse self.second();
const new_millisecond = partial.millisecond orelse self.millisecond();
const new_microsecond = partial.microsecond orelse self.microsecond();
const new_nanosecond = partial.nanosecond orelse self.nanosecond();
// Preserve calendar
const cal_view = abi.toDiplomatStringView(self.calendar_id);
const cal_result = abi.c.temporal_rs_AnyCalendarKind_parse_temporal_calendar_string(cal_view);
const cal_kind = abi.success(cal_result) orelse return error.TemporalError;
return wrapPlainDateTime(abi.c.temporal_rs_PlainDateTime_try_new(
new_year,
new_month,
new_day,
new_hour,
new_minute,
new_second,
new_millisecond,
new_microsecond,
new_nanosecond,
cal_kind,
));
}
pub fn withCalendar(self: PlainDateTime, calendar: []const u8) !PlainDateTime {
const cal_view = abi.toDiplomatStringView(calendar);
const cal_result = abi.c.temporal_rs_AnyCalendarKind_parse_temporal_calendar_string(cal_view);
const cal_kind = abi.success(cal_result) orelse return error.TemporalError;
const ptr = abi.c.temporal_rs_PlainDateTime_with_calendar(self._inner, cal_kind) orelse return error.TemporalError;
const calendar_ptr = abi.c.temporal_rs_PlainDateTime_calendar(ptr) orelse return error.TemporalError;
const cal_id_view = abi.c.temporal_rs_Calendar_identifier(calendar_ptr);
const allocator = std.heap.page_allocator;
const cal_id = allocator.dupe(u8, cal_id_view.data[0..cal_id_view.len]) catch "iso8601";
return .{ ._inner = ptr, .calendar_id = cal_id };
}
pub fn withPlainTime(self: PlainDateTime, time: ?PlainTime) !PlainDateTime {
const new_hour: u8 = if (time) |t| t.hour() else 0;
const new_minute: u8 = if (time) |t| t.minute() else 0;
const new_second: u8 = if (time) |t| t.second() else 0;
const new_millisecond: u16 = if (time) |t| t.millisecond() else 0;
const new_microsecond: u16 = if (time) |t| t.microsecond() else 0;
const new_nanosecond: u16 = if (time) |t| t.nanosecond() else 0;
const cal_view = abi.toDiplomatStringView(self.calendar_id);
const cal_result = abi.c.temporal_rs_AnyCalendarKind_parse_temporal_calendar_string(cal_view);
const cal_kind = abi.success(cal_result) orelse return error.TemporalError;
return wrapPlainDateTime(abi.c.temporal_rs_PlainDateTime_try_new(
self.year(),
self.month(),
self.day(),
new_hour,
new_minute,
new_second,
new_millisecond,
new_microsecond,
new_nanosecond,
cal_kind,
));
}
// Conversion methods
pub fn toPlainDate(self: PlainDateTime) !PlainDate {
const ptr = abi.c.temporal_rs_PlainDateTime_to_plain_date(self._inner) orelse return error.TemporalError;
const calendar_ptr = abi.c.temporal_rs_PlainDate_calendar(ptr) orelse return error.TemporalError;
const cal_id_view = abi.c.temporal_rs_Calendar_identifier(calendar_ptr);
const allocator = std.heap.page_allocator;
const cal_id = allocator.dupe(u8, cal_id_view.data[0..cal_id_view.len]) catch "iso8601";
return .{ ._inner = ptr, .calendar_id = cal_id };
}
pub fn toPlainTime(self: PlainDateTime) !PlainTime {
const ptr = abi.c.temporal_rs_PlainDateTime_to_plain_time(self._inner) orelse return error.TemporalError;
return .{ ._inner = ptr };
}
pub fn toZonedDateTime(self: PlainDateTime, options: ToZonedDateTimeOptions) !ZonedDateTime {
const tz_view = abi.toDiplomatStringView(options.timeZone);
const tz_result = abi.c.temporal_rs_TimeZone_try_from_str(tz_view);
const time_zone = abi.success(tz_result) orelse return error.TemporalError;
// Convert to PlainDate and PlainTime
const date = try self.toPlainDate();
const time = try self.toPlainTime();
// Use PlainDate's toZonedDateTime with the time component
const ptr = (abi.success(abi.c.temporal_rs_PlainDate_to_zoned_date_time(date._inner, time_zone, time._inner)) orelse return error.TemporalError) orelse return error.TemporalError;
const calendar_ptr = abi.c.temporal_rs_ZonedDateTime_calendar(ptr);
const cal_id_view = abi.c.temporal_rs_Calendar_identifier(calendar_ptr);
const allocator = std.heap.page_allocator;
const cal_id = allocator.dupe(u8, cal_id_view.data[0..cal_id_view.len]) catch "iso8601";
return .{ ._inner = ptr, .calendar_id = cal_id };
}
pub fn toString(self: PlainDateTime, allocator: std.mem.Allocator, options: ToStringOptions) ![]u8 {
var write = abi.DiplomatWrite.init(allocator);
defer write.deinit();
const rounding_options = std.mem.zeroes(abi.c.ToStringRoundingOptions);
const display_cal: abi.c.DisplayCalendar = if (options.calendar_display) |cal| switch (cal) {
.auto => @intCast(abi.c.DisplayCalendar_Auto),
.always => @intCast(abi.c.DisplayCalendar_Always),
.never => @intCast(abi.c.DisplayCalendar_Never),
.critical => @intCast(abi.c.DisplayCalendar_Critical),
} else @intCast(abi.c.DisplayCalendar_Auto);
const result = abi.c.temporal_rs_PlainDateTime_to_ixdtf_string(
self._inner,
rounding_options,
display_cal,
&write.inner,
);
if (!result.is_ok) return error.TemporalError;
return try write.toOwnedSlice();
}
pub fn toJSON(self: PlainDateTime, allocator: std.mem.Allocator) ![]u8 {
return try self.toString(allocator, .{});
}
pub fn toLocaleString(self: PlainDateTime, allocator: std.mem.Allocator) []const u8 {
const s = self.toString(allocator, .{}) catch return "PlainDateTime.toLocaleString error";
return s;
}
pub fn valueOf(self: PlainDateTime) !void {
_ = self;
return error.ComparisonNotSupported;
}
// Helper functions
fn clone(self: PlainDateTime) PlainDateTime {
return self;
}
pub fn deinit(self: *PlainDateTime) void {
_ = self;
// The C API manages memory
}
fn wrapPlainDateTime(res: anytype) !PlainDateTime {
const ptr = (abi.success(res) orelse return error.TemporalError) orelse return error.TemporalError;
const calendar_ptr = abi.c.temporal_rs_PlainDateTime_calendar(ptr) orelse return error.TemporalError;
const cal_id_view = abi.c.temporal_rs_Calendar_identifier(calendar_ptr);
const allocator = std.heap.page_allocator;
const cal_id = allocator.dupe(u8, cal_id_view.data[0..cal_id_view.len]) catch "iso8601";
return .{ ._inner = ptr, .calendar_id = cal_id };
}
// ---------- Tests ---------------------
test compare {
if (true) return error.Todo;
test init {
const dt = try PlainDateTime.init(2024, 1, 15, 14, 30, 45, 123, 456, 789);
try std.testing.expectEqual(@as(i32, 2024), dt.year());
try std.testing.expectEqual(@as(u8, 1), dt.month());
try std.testing.expectEqual(@as(u8, 15), dt.day());
try std.testing.expectEqual(@as(u8, 14), dt.hour());
try std.testing.expectEqual(@as(u8, 30), dt.minute());
try std.testing.expectEqual(@as(u8, 45), dt.second());
}
test from {
if (true) return error.Todo;
const dt = try PlainDateTime.from("2024-01-15T14:30:45");
try std.testing.expectEqual(@as(i32, 2024), dt.year());
try std.testing.expectEqual(@as(u8, 1), dt.month());
try std.testing.expectEqual(@as(u8, 15), dt.day());
}
test add {
if (true) return error.Todo;
test compare {
const dt1 = try PlainDateTime.init(2024, 1, 15, 14, 30, 0, 0, 0, 0);
const dt2 = try PlainDateTime.init(2024, 1, 15, 14, 30, 0, 0, 0, 0);
const dt3 = try PlainDateTime.init(2024, 1, 16, 14, 30, 0, 0, 0, 0);
try std.testing.expectEqual(@as(i8, 0), PlainDateTime.compare(dt1, dt2));
try std.testing.expectEqual(@as(i8, -1), PlainDateTime.compare(dt1, dt3));
try std.testing.expectEqual(@as(i8, 1), PlainDateTime.compare(dt3, dt1));
}
test equals {
if (true) return error.Todo;
const dt1 = try PlainDateTime.init(2024, 1, 15, 14, 30, 0, 0, 0, 0);
const dt2 = try PlainDateTime.init(2024, 1, 15, 14, 30, 0, 0, 0, 0);
const dt3 = try PlainDateTime.init(2024, 1, 16, 14, 30, 0, 0, 0, 0);
try std.testing.expect(dt1.equals(dt2));
try std.testing.expect(!dt1.equals(dt3));
}
test round {
if (true) return error.Todo;
}
test since {
if (true) return error.Todo;
test add {
const dt = try PlainDateTime.init(2024, 1, 15, 14, 30, 0, 0, 0, 0);
const dur = try Duration.from("P1D");
const result = try dt.add(dur);
try std.testing.expectEqual(@as(u8, 16), result.day());
}
test subtract {
if (true) return error.Todo;
}
test toJSON {
if (true) return error.Todo;
}
test toLocaleString {
if (true) return error.Todo;
}
test toPlainDate {
if (true) return error.Todo;
}
test toPlainTime {
if (true) return error.Todo;
}
test toString {
if (true) return error.Todo;
}
test toZonedDateTime {
if (true) return error.Todo;
const dt = try PlainDateTime.init(2024, 1, 15, 14, 30, 0, 0, 0, 0);
const dur = try Duration.from("P1D");
const result = try dt.subtract(dur);
try std.testing.expectEqual(@as(u8, 14), result.day());
}
test until {
if (true) return error.Todo;
const dt1 = try PlainDateTime.init(2024, 1, 15, 14, 30, 0, 0, 0, 0);
const dt2 = try PlainDateTime.init(2024, 1, 16, 14, 30, 0, 0, 0, 0);
const dur = try dt1.until(dt2, .{});
try std.testing.expectEqual(@as(i64, 1), dur.days());
}
test since {
const dt1 = try PlainDateTime.init(2024, 1, 16, 14, 30, 0, 0, 0, 0);
const dt2 = try PlainDateTime.init(2024, 1, 15, 14, 30, 0, 0, 0, 0);
const dur = try dt1.since(dt2, .{});
try std.testing.expectEqual(@as(i64, 1), dur.days());
}
test round {
const dt = try PlainDateTime.init(2024, 1, 15, 14, 30, 45, 999, 999, 999);
const rounded = try dt.round(.{ .smallest_unit = .second });
try std.testing.expectEqual(@as(u8, 46), rounded.second());
try std.testing.expectEqual(@as(u16, 0), rounded.millisecond());
try std.testing.expectEqual(@as(u16, 0), rounded.microsecond());
try std.testing.expectEqual(@as(u16, 0), rounded.nanosecond());
}
test toString {
const dt = try PlainDateTime.init(2024, 1, 15, 14, 30, 45, 123, 0, 0);
const str = try dt.toString(std.testing.allocator, .{});
defer std.testing.allocator.free(str);
try std.testing.expect(std.mem.indexOf(u8, str, "2024-01-15") != null);
try std.testing.expect(std.mem.indexOf(u8, str, "14:30:45") != null);
}
test toJSON {
const dt = try PlainDateTime.init(2024, 1, 15, 14, 30, 45, 0, 0, 0);
const str = try dt.toJSON(std.testing.allocator);
defer std.testing.allocator.free(str);
try std.testing.expect(std.mem.indexOf(u8, str, "2024-01-15") != null);
}
test toLocaleString {
const dt = try PlainDateTime.init(2024, 1, 15, 14, 30, 45, 0, 0, 0);
const s_const = dt.toLocaleString(std.testing.allocator);
const s = @constCast(s_const);
defer std.testing.allocator.free(s);
try std.testing.expect(std.mem.indexOf(u8, s, "2024-01-15") != null);
}
test toPlainDate {
const dt = try PlainDateTime.init(2024, 1, 15, 14, 30, 0, 0, 0, 0);
const date = try dt.toPlainDate();
try std.testing.expectEqual(@as(i32, 2024), date.year());
try std.testing.expectEqual(@as(u8, 1), date.month());
try std.testing.expectEqual(@as(u8, 15), date.day());
}
test toPlainTime {
const dt = try PlainDateTime.init(2024, 1, 15, 14, 30, 45, 0, 0, 0);
const t = try dt.toPlainTime();
try std.testing.expectEqual(@as(u8, 14), t.hour());
try std.testing.expectEqual(@as(u8, 30), t.minute());
try std.testing.expectEqual(@as(u8, 45), t.second());
}
test toZonedDateTime {
const dt = try PlainDateTime.init(2024, 1, 15, 14, 30, 0, 0, 0, 0);
const zdt = try dt.toZonedDateTime(.{ .timeZone = "UTC" });
const pdt = try zdt.toPlainDateTime();
try std.testing.expectEqual(@as(i32, 2024), pdt.year());
try std.testing.expectEqual(@as(u8, 1), pdt.month());
try std.testing.expectEqual(@as(u8, 15), pdt.day());
}
test with {
if (true) return error.Todo;
const dt = try PlainDateTime.init(2024, 1, 15, 14, 30, 45, 0, 0, 0);
const result = try dt.with(.{ .day = 20, .hour = 16 });
try std.testing.expectEqual(@as(i32, 2024), result.year());
try std.testing.expectEqual(@as(u8, 1), result.month());
try std.testing.expectEqual(@as(u8, 20), result.day());
try std.testing.expectEqual(@as(u8, 16), result.hour());
try std.testing.expectEqual(@as(u8, 30), result.minute());
}
test withCalendar {
if (true) return error.Todo;
const dt = try PlainDateTime.init(2024, 1, 15, 14, 30, 0, 0, 0, 0);
const result = try dt.withCalendar("iso8601");
try std.testing.expectEqual(@as(i32, 2024), result.year());
try std.testing.expectEqualStrings("iso8601", result.calendar_id);
}
test withPlainTime {
if (true) return error.Todo;
const dt = try PlainDateTime.init(2024, 1, 15, 14, 30, 0, 0, 0, 0);
const t = try PlainTime.from("10:15:30");
const result = try dt.withPlainTime(t);
try std.testing.expectEqual(@as(u8, 10), result.hour());
try std.testing.expectEqual(@as(u8, 15), result.minute());
try std.testing.expectEqual(@as(u8, 30), result.second());
}

View file

@ -1,66 +1,268 @@
const std = @import("std");
const abi = @import("abi.zig");
const temporal_types = @import("temporal.zig");
const PlainMonthDay = @This();
const PlainDate = @import("PlainDate.zig");
_inner: *abi.c.PlainMonthDay,
calendar_id: []const u8,
day: i64,
month_code: []const u8,
pub fn init() error{Todo}!PlainMonthDay {
return error.Todo;
// Type definitions for API compatibility
pub const CalendarDisplay = enum {
auto,
always,
never,
critical,
};
pub const ToStringOptions = struct {
calendar_display: ?CalendarDisplay = null,
};
pub const WithOptions = struct {
month_code: ?[]const u8 = null,
day: ?u8 = null,
};
// Helper to wrap PlainMonthDay pointer
fn wrapPlainMonthDay(result: anytype) !PlainMonthDay {
const ptr = (abi.success(result) orelse return error.TemporalError) orelse return error.TemporalError;
const calendar_ptr = abi.c.temporal_rs_PlainMonthDay_calendar(ptr) orelse return error.TemporalError;
const cal_id_view = abi.c.temporal_rs_Calendar_identifier(calendar_ptr);
const allocator = std.heap.page_allocator;
const cal_id = allocator.dupe(u8, cal_id_view.data[0..cal_id_view.len]) catch "iso8601";
return .{ ._inner = ptr, .calendar_id = cal_id };
}
pub fn from() error{Todo}!PlainMonthDay {
return error.Todo;
// Constructor
pub fn init(month_val: u8, day_val: u8, calendar: ?[]const u8) !PlainMonthDay {
const cal_kind = if (calendar) |cal| blk: {
const cal_view = abi.toDiplomatStringView(cal);
const cal_result = abi.c.temporal_rs_AnyCalendarKind_parse_temporal_calendar_string(cal_view);
break :blk abi.success(cal_result) orelse return error.TemporalError;
} else abi.c.AnyCalendarKind_Iso;
const overflow = abi.c.ArithmeticOverflow_Constrain;
const ref_year = abi.c.OptionI32{ .is_ok = false };
return wrapPlainMonthDay(abi.c.temporal_rs_PlainMonthDay_try_new_with_overflow(
month_val,
day_val,
cal_kind,
overflow,
ref_year,
));
}
pub fn equals() error{Todo}!bool {
return error.Todo;
// Parse from string
pub fn from(s: []const u8) !PlainMonthDay {
return fromUtf8(s);
}
pub fn toJSON() error{Todo}![]const u8 {
return error.Todo;
fn fromUtf8(utf8: []const u8) !PlainMonthDay {
const view = abi.toDiplomatStringView(utf8);
return wrapPlainMonthDay(abi.c.temporal_rs_PlainMonthDay_from_utf8(view));
}
pub fn toLocaleString() error{Todo}![]const u8 {
return error.Todo;
fn fromUtf16(utf16: []const u16) !PlainMonthDay {
const view = abi.toDiplomatString16View(utf16);
return wrapPlainMonthDay(abi.c.temporal_rs_PlainMonthDay_from_utf16(view));
}
pub fn toPlainDate() error{Todo}!PlainDate {
return error.Todo;
// Comparison
pub fn equals(self: PlainMonthDay, other: PlainMonthDay) bool {
return abi.c.temporal_rs_PlainMonthDay_equals(self._inner, other._inner);
}
pub fn toString() error{Todo}![]const u8 {
return error.Todo;
// Property accessors
pub fn calendarId(self: PlainMonthDay) []const u8 {
return self.calendar_id;
}
pub fn valueOf() error{Todo}!void {
return error.Todo;
pub fn day(self: PlainMonthDay) u8 {
return abi.c.temporal_rs_PlainMonthDay_day(self._inner);
}
pub fn with() error{Todo}!PlainMonthDay {
return error.Todo;
pub fn monthCode(self: PlainMonthDay, allocator: std.mem.Allocator) ![]const u8 {
var write = abi.DiplomatWrite.init(allocator);
defer write.deinit();
abi.c.temporal_rs_PlainMonthDay_month_code(self._inner, &write.inner);
return try write.toOwnedSlice();
}
// Modification
pub fn with(self: PlainMonthDay, options: WithOptions) !PlainMonthDay {
// Build month_code view
const month_code_view = if (options.month_code) |mc|
abi.toDiplomatStringView(mc)
else
abi.c.DiplomatStringView{ .data = null, .len = 0 };
// Build partial date with only the fields we want to modify
const partial_date = abi.c.PartialDate{
.year = .{ .is_ok = false },
.month = .{ .is_ok = false },
.day = if (options.day) |d| abi.toOption(abi.c.OptionU8, d) else .{ .is_ok = false },
.month_code = month_code_view,
.era = .{ .data = null, .len = 0 },
.era_year = .{ .is_ok = false },
.calendar = abi.c.AnyCalendarKind_Iso,
};
const overflow = abi.c.ArithmeticOverflow_option{
.is_ok = true,
.unnamed_0 = .{ .ok = abi.c.ArithmeticOverflow_Constrain },
};
return wrapPlainMonthDay(abi.c.temporal_rs_PlainMonthDay_with(
self._inner,
partial_date,
overflow,
));
}
// Conversion
pub fn toPlainDate(self: PlainMonthDay, year: i32) !PlainDate {
const partial_date = abi.c.PartialDate_option{
.is_ok = true,
.unnamed_0 = .{
.ok = .{
.year = abi.toOption(abi.c.OptionI32, year),
.month = .{ .is_ok = false },
.day = .{ .is_ok = false },
.month_code = .{ .data = null, .len = 0 },
.era = .{ .data = null, .len = 0 },
.era_year = .{ .is_ok = false },
.calendar = abi.c.AnyCalendarKind_Iso,
},
},
};
const result = abi.c.temporal_rs_PlainMonthDay_to_plain_date(self._inner, partial_date);
const ptr = (abi.success(result) orelse return error.TemporalError) orelse return error.TemporalError;
const calendar_ptr = abi.c.temporal_rs_PlainDate_calendar(ptr) orelse return error.TemporalError;
const cal_id_view = abi.c.temporal_rs_Calendar_identifier(calendar_ptr);
const allocator = std.heap.page_allocator;
const cal_id = allocator.dupe(u8, cal_id_view.data[0..cal_id_view.len]) catch "iso8601";
return PlainDate{ ._inner = ptr, .calendar_id = cal_id };
}
// String conversions
pub fn toString(self: PlainMonthDay, allocator: std.mem.Allocator) ![]u8 {
return toStringWithOptions(self, allocator, .{});
}
fn toStringWithOptions(self: PlainMonthDay, allocator: std.mem.Allocator, options: ToStringOptions) ![]u8 {
_ = options;
var write = abi.DiplomatWrite.init(allocator);
defer write.deinit();
const display = abi.c.DisplayCalendar_Auto;
abi.c.temporal_rs_PlainMonthDay_to_ixdtf_string(self._inner, display, &write.inner);
return try write.toOwnedSlice();
}
pub fn toJSON(self: PlainMonthDay, allocator: std.mem.Allocator) ![]u8 {
return toString(self, allocator);
}
pub fn toLocaleString(self: PlainMonthDay, allocator: std.mem.Allocator) ![]u8 {
return toString(self, allocator);
}
pub fn valueOf(self: PlainMonthDay) !void {
_ = self;
return error.ValueError;
}
// ---------- Tests ---------------------
test init {
const md = try init(12, 25, null);
try std.testing.expectEqual(@as(u8, 25), md.day());
const month_code = try md.monthCode(std.testing.allocator);
defer std.testing.allocator.free(month_code);
try std.testing.expect(month_code.len > 0);
}
test from {
if (true) return error.Todo;
const md = try from("12-25");
try std.testing.expectEqual(@as(u8, 25), md.day());
}
test equals {
if (true) return error.Todo;
}
test toJSON {
if (true) return error.Todo;
}
test toLocaleString {
if (true) return error.Todo;
{
const md1 = try init(12, 25, null);
const md2 = try init(12, 25, null);
try std.testing.expect(md1.equals(md2));
}
{
const md1 = try init(12, 25, null);
const md2 = try init(12, 24, null);
try std.testing.expect(!md1.equals(md2));
}
}
test toPlainDate {
if (true) return error.Todo;
const md = try init(12, 25, null);
const date = try md.toPlainDate(2024);
try std.testing.expectEqual(@as(i32, 2024), date.year());
try std.testing.expectEqual(@as(u8, 25), date.day());
}
test toString {
if (true) return error.Todo;
const md = try init(12, 25, null);
const str = try md.toString(std.testing.allocator);
defer std.testing.allocator.free(str);
try std.testing.expect(str.len > 0);
}
test toJSON {
const md = try init(12, 25, null);
const str = try md.toJSON(std.testing.allocator);
defer std.testing.allocator.free(str);
try std.testing.expect(str.len > 0);
}
test with {
if (true) return error.Todo;
// Test modifying day
const md1 = try init(12, 25, null);
const md2 = try md1.with(.{ .day = 31 });
try std.testing.expectEqual(@as(u8, 31), md2.day());
// Test modifying month_code
const md3 = try init(12, 25, null);
const md4 = try md3.with(.{ .month_code = "M01" });
const month_code = try md4.monthCode(std.testing.allocator);
defer std.testing.allocator.free(month_code);
try std.testing.expectEqualStrings("M01", month_code);
try std.testing.expectEqual(@as(u8, 25), md4.day());
// Test modifying both
const md5 = try init(6, 15, null);
const md6 = try md5.with(.{ .month_code = "M12", .day = 1 });
const month_code2 = try md6.monthCode(std.testing.allocator);
defer std.testing.allocator.free(month_code2);
try std.testing.expectEqualStrings("M12", month_code2);
try std.testing.expectEqual(@as(u8, 1), md6.day());
}
test toLocaleString {
const md = try init(12, 25, null);
const str = try md.toLocaleString(std.testing.allocator);
defer std.testing.allocator.free(str);
try std.testing.expect(str.len > 0);
try std.testing.expect(std.mem.indexOf(u8, str, "12") != null or std.mem.indexOf(u8, str, "25") != null);
}

View file

@ -1,104 +1,359 @@
const std = @import("std");
const abi = @import("abi.zig");
const temporal_types = @import("temporal.zig");
const PlainTime = @This();
const Duration = @import("Duration.zig");
hour: i64,
microsecond: i64,
millisecond: i64,
minute: i64,
nanosecond: i64,
second: i64,
_inner: *abi.c.PlainTime,
pub fn init() error{Todo}!PlainTime {
return error.Todo;
// Import types from temporal.zig
pub const Unit = temporal_types.Unit;
pub const RoundingMode = temporal_types.RoundingMode;
pub const DifferenceSettings = temporal_types.DifferenceSettings;
pub const RoundOptions = temporal_types.RoundingOptions;
pub const WithOptions = struct {
hour: ?u8 = null,
minute: ?u8 = null,
second: ?u8 = null,
millisecond: ?u16 = null,
microsecond: ?u16 = null,
nanosecond: ?u16 = null,
};
// Helper to wrap PlainTime pointer
fn wrapPlainTime(result: anytype) !PlainTime {
const ptr = (abi.success(result) orelse return error.TemporalError) orelse return error.TemporalError;
return PlainTime{ ._inner = ptr };
}
pub fn compare() error{Todo}!i8 {
return error.Todo;
// Constructor - creates a PlainTime with all parameters
pub fn init(
hour_val: u8,
minute_val: u8,
second_val: u8,
millisecond_val: u16,
microsecond_val: u16,
nanosecond_val: u16,
) !PlainTime {
return wrapPlainTime(abi.c.temporal_rs_PlainTime_try_new(
hour_val,
minute_val,
second_val,
millisecond_val,
microsecond_val,
nanosecond_val,
));
}
pub fn from() error{Todo}!PlainTime {
return error.Todo;
// Parse from string
pub fn from(s: []const u8) !PlainTime {
return fromUtf8(s);
}
pub fn add() error{Todo}!PlainTime {
return error.Todo;
fn fromUtf8(utf8: []const u8) !PlainTime {
const view = abi.toDiplomatStringView(utf8);
return wrapPlainTime(abi.c.temporal_rs_PlainTime_from_utf8(view));
}
pub fn equals() error{Todo}!bool {
return error.Todo;
fn fromUtf16(utf16: []const u16) !PlainTime {
const view = abi.toDiplomatString16View(utf16);
return wrapPlainTime(abi.c.temporal_rs_PlainTime_from_utf16(view));
}
pub fn round() error{Todo}!PlainTime {
return error.Todo;
// Comparison
pub fn compare(a: PlainTime, b: PlainTime) i8 {
return abi.c.temporal_rs_PlainTime_compare(a._inner, b._inner);
}
pub fn since() error{Todo}!Duration {
return error.Todo;
pub fn equals(self: PlainTime, other: PlainTime) bool {
return abi.c.temporal_rs_PlainTime_equals(self._inner, other._inner);
}
pub fn subtract() error{Todo}!PlainTime {
return error.Todo;
// Arithmetic
pub fn add(self: PlainTime, duration: Duration) !PlainTime {
return wrapPlainTime(abi.c.temporal_rs_PlainTime_add(self._inner, duration._inner));
}
pub fn toJSON() error{Todo}![]const u8 {
return error.Todo;
pub fn subtract(self: PlainTime, duration: Duration) !PlainTime {
return wrapPlainTime(abi.c.temporal_rs_PlainTime_subtract(self._inner, duration._inner));
}
pub fn toLocaleString() error{Todo}![]const u8 {
return error.Todo;
pub fn until(self: PlainTime, other: PlainTime, options: DifferenceSettings) !Duration {
const settings = options.toCApi();
const result = abi.c.temporal_rs_PlainTime_until(self._inner, other._inner, settings);
const ptr = (abi.success(result) orelse return error.TemporalError) orelse return error.TemporalError;
return .{ ._inner = ptr };
}
pub fn toString() error{Todo}![]const u8 {
return error.Todo;
pub fn since(self: PlainTime, other: PlainTime, options: DifferenceSettings) !Duration {
const settings = options.toCApi();
const result = abi.c.temporal_rs_PlainTime_since(self._inner, other._inner, settings);
const ptr = (abi.success(result) orelse return error.TemporalError) orelse return error.TemporalError;
return .{ ._inner = ptr };
}
pub fn until() error{Todo}!Duration {
return error.Todo;
// Rounding
pub fn round(self: PlainTime, options: RoundOptions) !PlainTime {
return wrapPlainTime(abi.c.temporal_rs_PlainTime_round(self._inner, options.toCApi()));
}
pub fn valueOf() error{Todo}!void {
return error.Todo;
// Property accessors
pub fn hour(self: PlainTime) u8 {
return abi.c.temporal_rs_PlainTime_hour(self._inner);
}
pub fn with() error{Todo}!PlainTime {
return error.Todo;
pub fn minute(self: PlainTime) u8 {
return abi.c.temporal_rs_PlainTime_minute(self._inner);
}
pub fn second(self: PlainTime) u8 {
return abi.c.temporal_rs_PlainTime_second(self._inner);
}
pub fn millisecond(self: PlainTime) u16 {
return abi.c.temporal_rs_PlainTime_millisecond(self._inner);
}
pub fn microsecond(self: PlainTime) u16 {
return abi.c.temporal_rs_PlainTime_microsecond(self._inner);
}
pub fn nanosecond(self: PlainTime) u16 {
return abi.c.temporal_rs_PlainTime_nanosecond(self._inner);
}
// Modification
pub fn with(self: PlainTime, options: WithOptions) !PlainTime {
const partial = abi.c.PartialTime{
.hour = abi.toOption(abi.c.OptionU8, options.hour),
.minute = abi.toOption(abi.c.OptionU8, options.minute),
.second = abi.toOption(abi.c.OptionU8, options.second),
.millisecond = abi.toOption(abi.c.OptionU16, options.millisecond),
.microsecond = abi.toOption(abi.c.OptionU16, options.microsecond),
.nanosecond = abi.toOption(abi.c.OptionU16, options.nanosecond),
};
const overflow_opt = abi.c.ArithmeticOverflow_option{
.is_ok = true,
.unnamed_0 = .{ .ok = abi.c.ArithmeticOverflow_Constrain },
};
return wrapPlainTime(abi.c.temporal_rs_PlainTime_with(self._inner, partial, overflow_opt));
}
// String conversions
pub fn toString(self: PlainTime, allocator: std.mem.Allocator) ![]u8 {
return toStringWithOptions(self, allocator, .{});
}
fn toStringWithOptions(self: PlainTime, allocator: std.mem.Allocator, options: temporal_types.ToStringRoundingOptions) ![]u8 {
const rounding_opts = options.toCApi();
var write = abi.DiplomatWrite.init(allocator);
defer write.deinit();
const result = abi.c.temporal_rs_PlainTime_to_ixdtf_string(
self._inner,
rounding_opts,
&write.inner,
);
if (!result.is_ok) return error.TemporalError;
return try write.toOwnedSlice();
}
pub fn toJSON(self: PlainTime, allocator: std.mem.Allocator) ![]u8 {
return toString(self, allocator);
}
pub fn toLocaleString(self: PlainTime, allocator: std.mem.Allocator) ![]u8 {
// For now, just use toString - locale-specific formatting would require more work
return toString(self, allocator);
}
pub fn valueOf(self: PlainTime) !void {
_ = self;
// PlainTime should not be used in arithmetic/comparison operations implicitly
return error.ValueError;
}
// ---------- Tests ---------------------
test compare {
if (true) return error.Todo;
test init {
{
const time = try init(14, 30, 45, 123, 456, 789);
try std.testing.expectEqual(@as(u8, 14), time.hour());
try std.testing.expectEqual(@as(u8, 30), time.minute());
try std.testing.expectEqual(@as(u8, 45), time.second());
try std.testing.expectEqual(@as(u16, 123), time.millisecond());
try std.testing.expectEqual(@as(u16, 456), time.microsecond());
try std.testing.expectEqual(@as(u16, 789), time.nanosecond());
}
{
const time = try init(0, 0, 0, 0, 0, 0);
try std.testing.expectEqual(@as(u8, 0), time.hour());
try std.testing.expectEqual(@as(u8, 0), time.minute());
try std.testing.expectEqual(@as(u8, 0), time.second());
}
{
const time = try init(23, 59, 59, 999, 999, 999);
try std.testing.expectEqual(@as(u8, 23), time.hour());
try std.testing.expectEqual(@as(u8, 59), time.minute());
try std.testing.expectEqual(@as(u8, 59), time.second());
}
}
test from {
if (true) return error.Todo;
{
const time = try from("14:30:45.123456789");
try std.testing.expectEqual(@as(u8, 14), time.hour());
try std.testing.expectEqual(@as(u8, 30), time.minute());
try std.testing.expectEqual(@as(u8, 45), time.second());
try std.testing.expectEqual(@as(u16, 123), time.millisecond());
try std.testing.expectEqual(@as(u16, 456), time.microsecond());
try std.testing.expectEqual(@as(u16, 789), time.nanosecond());
}
{
const time = try from("14:30");
try std.testing.expectEqual(@as(u8, 14), time.hour());
try std.testing.expectEqual(@as(u8, 30), time.minute());
try std.testing.expectEqual(@as(u8, 0), time.second());
}
{
const time = try from("14:30:45");
try std.testing.expectEqual(@as(u8, 14), time.hour());
try std.testing.expectEqual(@as(u8, 30), time.minute());
try std.testing.expectEqual(@as(u8, 45), time.second());
}
}
test add {
if (true) return error.Todo;
test compare {
{
const time1 = try init(14, 30, 45, 123, 456, 789);
const time2 = try init(14, 30, 45, 123, 456, 789);
try std.testing.expectEqual(@as(i8, 0), compare(time1, time2));
}
{
const time1 = try init(14, 30, 45, 123, 456, 789);
const time2 = try init(15, 30, 45, 123, 456, 789);
try std.testing.expectEqual(@as(i8, -1), compare(time1, time2));
}
{
const time1 = try init(15, 30, 45, 123, 456, 789);
const time2 = try init(14, 30, 45, 123, 456, 789);
try std.testing.expectEqual(@as(i8, 1), compare(time1, time2));
}
}
test equals {
if (true) return error.Todo;
{
const time1 = try init(14, 30, 45, 123, 456, 789);
const time2 = try init(14, 30, 45, 123, 456, 789);
try std.testing.expect(time1.equals(time2));
}
{
const time1 = try init(14, 30, 45, 123, 456, 789);
const time2 = try init(15, 30, 45, 123, 456, 789);
try std.testing.expect(!time1.equals(time2));
}
}
test round {
if (true) return error.Todo;
}
test since {
if (true) return error.Todo;
test add {
{
const time = try init(14, 30, 0, 0, 0, 0);
const duration = try Duration.from("PT1H30M");
const result = try time.add(duration);
try std.testing.expectEqual(@as(u8, 16), result.hour());
try std.testing.expectEqual(@as(u8, 0), result.minute());
}
{
const time = try init(23, 30, 0, 0, 0, 0);
const duration = try Duration.from("PT1H");
const result = try time.add(duration);
try std.testing.expectEqual(@as(u8, 0), result.hour());
try std.testing.expectEqual(@as(u8, 30), result.minute());
}
}
test subtract {
if (true) return error.Todo;
}
test toJSON {
if (true) return error.Todo;
}
test toLocaleString {
if (true) return error.Todo;
}
test toString {
if (true) return error.Todo;
}
test until {
if (true) return error.Todo;
{
const time = try init(16, 0, 0, 0, 0, 0);
const duration = try Duration.from("PT1H30M");
const result = try time.subtract(duration);
try std.testing.expectEqual(@as(u8, 14), result.hour());
try std.testing.expectEqual(@as(u8, 30), result.minute());
}
{
const time = try init(0, 30, 0, 0, 0, 0);
const duration = try Duration.from("PT1H");
const result = try time.subtract(duration);
try std.testing.expectEqual(@as(u8, 23), result.hour());
try std.testing.expectEqual(@as(u8, 30), result.minute());
}
}
test with {
if (true) return error.Todo;
{
const time = try init(14, 30, 45, 123, 456, 789);
const result = try time.with(.{ .hour = 18 });
try std.testing.expectEqual(@as(u8, 18), result.hour());
try std.testing.expectEqual(@as(u8, 30), result.minute());
try std.testing.expectEqual(@as(u8, 45), result.second());
}
{
const time = try init(14, 30, 45, 123, 456, 789);
const result = try time.with(.{ .hour = 18, .minute = 15, .second = 0 });
try std.testing.expectEqual(@as(u8, 18), result.hour());
try std.testing.expectEqual(@as(u8, 15), result.minute());
try std.testing.expectEqual(@as(u8, 0), result.second());
}
}
test toString {
const time = try init(14, 30, 45, 123, 456, 789);
const str = try time.toString(std.testing.allocator);
defer std.testing.allocator.free(str);
// Should be in format: 14:30:45.123456789
try std.testing.expect(str.len > 0);
try std.testing.expect(std.mem.indexOf(u8, str, "14:30:45") != null);
}
test toJSON {
const time = try init(14, 30, 45, 123, 456, 789);
const str = try time.toJSON(std.testing.allocator);
defer std.testing.allocator.free(str);
try std.testing.expect(str.len > 0);
}
test round {
const time = try init(14, 30, 45, 999, 999, 999);
const rounded = try time.round(.{ .smallest_unit = .second });
try std.testing.expectEqual(@as(u8, 14), rounded.hour());
try std.testing.expectEqual(@as(u8, 30), rounded.minute());
try std.testing.expectEqual(@as(u8, 46), rounded.second());
try std.testing.expectEqual(@as(u16, 0), rounded.millisecond());
try std.testing.expectEqual(@as(u16, 0), rounded.microsecond());
try std.testing.expectEqual(@as(u16, 0), rounded.nanosecond());
}
test since {
const time1 = try init(15, 30, 0, 0, 0, 0);
const time2 = try init(14, 0, 0, 0, 0, 0);
const dur = try time1.since(time2, .{});
try std.testing.expectEqual(@as(i64, 1), dur.hours());
try std.testing.expectEqual(@as(i64, 30), dur.minutes());
}
test until {
const time1 = try init(14, 0, 0, 0, 0, 0);
const time2 = try init(15, 30, 0, 0, 0, 0);
const dur = try time1.until(time2, .{});
try std.testing.expectEqual(@as(i64, 1), dur.hours());
try std.testing.expectEqual(@as(i64, 30), dur.minutes());
}

View file

@ -1,109 +1,418 @@
const std = @import("std");
const abi = @import("abi.zig");
const temporal_types = @import("temporal.zig");
const PlainYearMonth = @This();
const PlainDate = @import("PlainDate.zig");
const Duration = @import("Duration.zig");
_inner: *abi.c.PlainYearMonth,
calendar_id: []const u8,
days_in_month: i64,
days_in_year: i64,
era: []const u8,
era_year: i64,
in_leap_year: bool,
month: i64,
month_code: []const u8,
months_in_year: i64,
year: i64,
pub fn init() error{Todo}!PlainYearMonth {
return error.Todo;
// Import types from temporal.zig
pub const Unit = temporal_types.Unit;
pub const RoundingMode = temporal_types.RoundingMode;
pub const DifferenceSettings = temporal_types.DifferenceSettings;
pub const RoundOptions = temporal_types.RoundingOptions;
// Type definitions for API compatibility
pub const CalendarDisplay = enum {
auto,
always,
never,
critical,
};
pub const ToStringOptions = struct {
calendar_display: ?CalendarDisplay = null,
};
pub const WithOptions = struct {
year: ?i32 = null,
month: ?u8 = null,
month_code: ?[]const u8 = null,
};
// Helper to wrap PlainYearMonth pointer
fn wrapPlainYearMonth(result: anytype) !PlainYearMonth {
const ptr = (abi.success(result) orelse return error.TemporalError) orelse return error.TemporalError;
const calendar_ptr = abi.c.temporal_rs_PlainYearMonth_calendar(ptr) orelse return error.TemporalError;
const cal_id_view = abi.c.temporal_rs_Calendar_identifier(calendar_ptr);
const allocator = std.heap.page_allocator;
const cal_id = allocator.dupe(u8, cal_id_view.data[0..cal_id_view.len]) catch "iso8601";
return .{ ._inner = ptr, .calendar_id = cal_id };
}
pub fn compare() error{Todo}!i8 {
return error.Todo;
// Constructor
pub fn init(year_val: i32, month_val: u8, calendar: ?[]const u8) !PlainYearMonth {
const cal_kind = if (calendar) |cal| blk: {
const cal_view = abi.toDiplomatStringView(cal);
const cal_result = abi.c.temporal_rs_AnyCalendarKind_parse_temporal_calendar_string(cal_view);
break :blk abi.success(cal_result) orelse return error.TemporalError;
} else abi.c.AnyCalendarKind_Iso;
const overflow = abi.c.ArithmeticOverflow_Constrain;
const ref_day = abi.c.OptionU8{ .is_ok = false };
return wrapPlainYearMonth(abi.c.temporal_rs_PlainYearMonth_try_new_with_overflow(
year_val,
month_val,
ref_day,
cal_kind,
overflow,
));
}
pub fn from() error{Todo}!PlainYearMonth {
return error.Todo;
// Parse from string
pub fn from(s: []const u8) !PlainYearMonth {
return fromUtf8(s);
}
pub fn add() error{Todo}!PlainYearMonth {
return error.Todo;
fn fromUtf8(utf8: []const u8) !PlainYearMonth {
const view = abi.toDiplomatStringView(utf8);
return wrapPlainYearMonth(abi.c.temporal_rs_PlainYearMonth_from_utf8(view));
}
pub fn equals() error{Todo}!bool {
return error.Todo;
fn fromUtf16(utf16: []const u16) !PlainYearMonth {
const view = abi.toDiplomatString16View(utf16);
return wrapPlainYearMonth(abi.c.temporal_rs_PlainYearMonth_from_utf16(view));
}
pub fn since() error{Todo}!Duration {
return error.Todo;
// Comparison
pub fn compare(a: PlainYearMonth, b: PlainYearMonth) i8 {
return abi.c.temporal_rs_PlainYearMonth_compare(a._inner, b._inner);
}
pub fn subtract() error{Todo}!PlainYearMonth {
return error.Todo;
pub fn equals(self: PlainYearMonth, other: PlainYearMonth) bool {
return abi.c.temporal_rs_PlainYearMonth_equals(self._inner, other._inner);
}
pub fn toJSON() error{Todo}![]const u8 {
return error.Todo;
// Arithmetic
pub fn add(self: PlainYearMonth, duration: Duration) !PlainYearMonth {
const overflow = abi.c.ArithmeticOverflow_Constrain;
return wrapPlainYearMonth(abi.c.temporal_rs_PlainYearMonth_add(self._inner, duration._inner, overflow));
}
pub fn toLocaleString() error{Todo}![]const u8 {
return error.Todo;
pub fn subtract(self: PlainYearMonth, duration: Duration) !PlainYearMonth {
const overflow = abi.c.ArithmeticOverflow_Constrain;
return wrapPlainYearMonth(abi.c.temporal_rs_PlainYearMonth_subtract(self._inner, duration._inner, overflow));
}
pub fn toPlainDate() error{Todo}!PlainDate {
return error.Todo;
pub fn until(self: PlainYearMonth, other: PlainYearMonth, options: DifferenceSettings) !Duration {
const settings = options.toCApi();
const result = abi.c.temporal_rs_PlainYearMonth_until(self._inner, other._inner, settings);
const ptr = (abi.success(result) orelse return error.TemporalError) orelse return error.TemporalError;
return .{ ._inner = ptr };
}
pub fn toString() error{Todo}![]const u8 {
return error.Todo;
pub fn since(self: PlainYearMonth, other: PlainYearMonth, options: DifferenceSettings) !Duration {
const settings = options.toCApi();
const result = abi.c.temporal_rs_PlainYearMonth_since(self._inner, other._inner, settings);
const ptr = (abi.success(result) orelse return error.TemporalError) orelse return error.TemporalError;
return .{ ._inner = ptr };
}
pub fn until() error{Todo}!Duration {
return error.Todo;
// Property accessors
pub fn calendarId(self: PlainYearMonth) []const u8 {
return self.calendar_id;
}
pub fn valueOf() error{Todo}!void {
return error.Todo;
pub fn year(self: PlainYearMonth) i32 {
return abi.c.temporal_rs_PlainYearMonth_year(self._inner);
}
pub fn with() error{Todo}!PlainYearMonth {
return error.Todo;
pub fn month(self: PlainYearMonth) u8 {
return abi.c.temporal_rs_PlainYearMonth_month(self._inner);
}
pub fn monthCode(self: PlainYearMonth) ![]const u8 {
const allocator = std.heap.page_allocator;
var write = abi.DiplomatWrite.init(allocator);
defer write.deinit();
abi.c.temporal_rs_PlainYearMonth_month_code(self._inner, &write.inner);
return try write.toOwnedSlice();
}
pub fn daysInMonth(self: PlainYearMonth) u16 {
return abi.c.temporal_rs_PlainYearMonth_days_in_month(self._inner);
}
pub fn daysInYear(self: PlainYearMonth) u16 {
return abi.c.temporal_rs_PlainYearMonth_days_in_year(self._inner);
}
pub fn monthsInYear(self: PlainYearMonth) u16 {
return abi.c.temporal_rs_PlainYearMonth_months_in_year(self._inner);
}
pub fn inLeapYear(self: PlainYearMonth) bool {
return abi.c.temporal_rs_PlainYearMonth_in_leap_year(self._inner);
}
pub fn era(self: PlainYearMonth) !?[]const u8 {
const allocator = std.heap.page_allocator;
var write = abi.DiplomatWrite.init(allocator);
defer write.deinit();
abi.c.temporal_rs_PlainYearMonth_era(self._inner, &write.inner);
const result = try write.toOwnedSlice();
if (result.len == 0) return null;
return result;
}
pub fn eraYear(self: PlainYearMonth) ?i32 {
const result = abi.c.temporal_rs_PlainYearMonth_era_year(self._inner);
if (!result.is_ok) return null;
return result.unnamed_0.ok;
}
// Modification
pub fn with(self: PlainYearMonth, options: WithOptions) !PlainYearMonth {
// Build month_code view
const month_code_view = if (options.month_code) |mc|
abi.toDiplomatStringView(mc)
else
abi.c.DiplomatStringView{ .data = null, .len = 0 };
// Build partial date with only the fields we want to modify
const partial_date = abi.c.PartialDate{
.year = if (options.year) |y| abi.toOption(abi.c.OptionI32, y) else .{ .is_ok = false },
.month = if (options.month) |m| abi.toOption(abi.c.OptionU8, m) else .{ .is_ok = false },
.day = .{ .is_ok = false },
.month_code = month_code_view,
.era = .{ .data = null, .len = 0 },
.era_year = .{ .is_ok = false },
.calendar = abi.c.AnyCalendarKind_Iso,
};
const overflow = abi.c.ArithmeticOverflow_option{
.is_ok = true,
.unnamed_0 = .{ .ok = abi.c.ArithmeticOverflow_Constrain },
};
return wrapPlainYearMonth(abi.c.temporal_rs_PlainYearMonth_with(
self._inner,
partial_date,
overflow,
));
}
// Conversion
pub fn toPlainDate(self: PlainYearMonth, day: u8) !PlainDate {
const partial_date = abi.c.PartialDate_option{
.is_ok = true,
.unnamed_0 = .{
.ok = .{
.year = .{ .is_ok = false },
.month = .{ .is_ok = false },
.day = abi.toOption(abi.c.OptionU8, day),
.month_code = .{ .data = null, .len = 0 },
.era = .{ .data = null, .len = 0 },
.era_year = .{ .is_ok = false },
.calendar = abi.c.AnyCalendarKind_Iso,
},
},
};
const result = abi.c.temporal_rs_PlainYearMonth_to_plain_date(self._inner, partial_date);
const ptr = (abi.success(result) orelse return error.TemporalError) orelse return error.TemporalError;
const calendar_ptr = abi.c.temporal_rs_PlainDate_calendar(ptr) orelse return error.TemporalError;
const cal_id_view = abi.c.temporal_rs_Calendar_identifier(calendar_ptr);
const allocator = std.heap.page_allocator;
const cal_id = allocator.dupe(u8, cal_id_view.data[0..cal_id_view.len]) catch "iso8601";
return PlainDate{ ._inner = ptr, .calendar_id = cal_id };
}
// String conversions
pub fn toString(self: PlainYearMonth, allocator: std.mem.Allocator) ![]u8 {
return toStringWithOptions(self, allocator, .{});
}
fn toStringWithOptions(self: PlainYearMonth, allocator: std.mem.Allocator, options: ToStringOptions) ![]u8 {
_ = options;
var write = abi.DiplomatWrite.init(allocator);
defer write.deinit();
const display = abi.c.DisplayCalendar_Auto;
abi.c.temporal_rs_PlainYearMonth_to_ixdtf_string(self._inner, display, &write.inner);
return try write.toOwnedSlice();
}
pub fn toJSON(self: PlainYearMonth, allocator: std.mem.Allocator) ![]u8 {
return toString(self, allocator);
}
pub fn toLocaleString(self: PlainYearMonth, allocator: std.mem.Allocator) ![]u8 {
return toString(self, allocator);
}
pub fn valueOf(self: PlainYearMonth) !void {
_ = self;
return error.ValueError;
}
// ---------- Tests ---------------------
test compare {
if (true) return error.Todo;
test init {
const ym = try init(2024, 12, null);
try std.testing.expectEqual(@as(i32, 2024), ym.year());
try std.testing.expectEqual(@as(u8, 12), ym.month());
}
test from {
if (true) return error.Todo;
const ym = try from("2024-12");
try std.testing.expectEqual(@as(i32, 2024), ym.year());
try std.testing.expectEqual(@as(u8, 12), ym.month());
}
test add {
if (true) return error.Todo;
test compare {
{
const ym1 = try init(2024, 12, null);
const ym2 = try init(2024, 12, null);
try std.testing.expectEqual(@as(i8, 0), compare(ym1, ym2));
}
{
const ym1 = try init(2024, 11, null);
const ym2 = try init(2024, 12, null);
try std.testing.expectEqual(@as(i8, -1), compare(ym1, ym2));
}
{
const ym1 = try init(2025, 1, null);
const ym2 = try init(2024, 12, null);
try std.testing.expectEqual(@as(i8, 1), compare(ym1, ym2));
}
{
const ym1 = try init(2024, 12, null);
const ym2 = try init(2024, 12, null);
try std.testing.expect(ym1.equals(ym2));
}
}
test equals {
if (true) return error.Todo;
{
const ym1 = try init(2024, 12, null);
const ym2 = try init(2024, 12, null);
try std.testing.expect(ym1.equals(ym2));
}
{
const ym1 = try init(2024, 12, null);
const ym2 = try init(2024, 11, null);
try std.testing.expect(!ym1.equals(ym2));
}
}
test since {
if (true) return error.Todo;
test add {
const ym = try init(2024, 6, null);
const duration = try Duration.from("P6M");
const result = try ym.add(duration);
try std.testing.expectEqual(@as(i32, 2024), result.year());
try std.testing.expectEqual(@as(u8, 12), result.month());
}
test subtract {
if (true) return error.Todo;
const ym = try init(2024, 12, null);
const duration = try Duration.from("P6M");
const result = try ym.subtract(duration);
try std.testing.expectEqual(@as(i32, 2024), result.year());
try std.testing.expectEqual(@as(u8, 6), result.month());
}
test toJSON {
if (true) return error.Todo;
test daysInMonth {
const ym = try init(2024, 2, null);
try std.testing.expectEqual(@as(u16, 29), ym.daysInMonth()); // 2024 is a leap year
}
test toLocaleString {
if (true) return error.Todo;
test daysInYear {
const ym = try init(2024, 1, null);
try std.testing.expectEqual(@as(u16, 366), ym.daysInYear()); // 2024 is a leap year
}
test monthsInYear {
const ym = try init(2024, 1, null);
try std.testing.expectEqual(@as(u16, 12), ym.monthsInYear());
}
test inLeapYear {
const ym2024 = try init(2024, 1, null);
const ym2023 = try init(2023, 1, null);
try std.testing.expect(ym2024.inLeapYear());
try std.testing.expect(!ym2023.inLeapYear());
}
test toPlainDate {
if (true) return error.Todo;
const ym = try init(2024, 12, null);
const date = try ym.toPlainDate(25);
try std.testing.expectEqual(@as(i32, 2024), date.year());
try std.testing.expectEqual(@as(u8, 12), date.month());
try std.testing.expectEqual(@as(u8, 25), date.day());
}
test toString {
if (true) return error.Todo;
const ym = try init(2024, 12, null);
const str = try ym.toString(std.testing.allocator);
defer std.testing.allocator.free(str);
try std.testing.expect(str.len > 0);
try std.testing.expect(std.mem.indexOf(u8, str, "2024") != null);
}
test toJSON {
const ym = try init(2024, 12, null);
const str = try ym.toJSON(std.testing.allocator);
defer std.testing.allocator.free(str);
try std.testing.expect(str.len > 0);
}
test since {
const ym1 = try init(2024, 12, null);
const ym2 = try init(2024, 6, null);
const dur = try ym1.since(ym2, .{});
try std.testing.expectEqual(@as(i64, 6), dur.months());
}
test until {
if (true) return error.Todo;
const ym1 = try init(2024, 6, null);
const ym2 = try init(2024, 12, null);
const dur = try ym1.until(ym2, .{});
try std.testing.expectEqual(@as(i64, 6), dur.months());
}
test with {
if (true) return error.Todo;
// Test modifying year
const ym1 = try init(2024, 6, null);
const ym2 = try ym1.with(.{ .year = 2025 });
try std.testing.expectEqual(@as(i32, 2025), ym2.year());
try std.testing.expectEqual(@as(u8, 6), ym2.month());
// Test modifying month
const ym3 = try init(2024, 6, null);
const ym4 = try ym3.with(.{ .month = 12 });
try std.testing.expectEqual(@as(i32, 2024), ym4.year());
try std.testing.expectEqual(@as(u8, 12), ym4.month());
// Test modifying both
const ym5 = try init(2024, 6, null);
const ym6 = try ym5.with(.{ .year = 2025, .month = 1 });
try std.testing.expectEqual(@as(i32, 2025), ym6.year());
try std.testing.expectEqual(@as(u8, 1), ym6.month());
}
test toLocaleString {
const ym = try init(2024, 6, null);
const str = try ym.toLocaleString(std.testing.allocator);
defer std.testing.allocator.free(str);
try std.testing.expect(str.len > 0);
try std.testing.expect(std.mem.indexOf(u8, str, "2024") != null);
}

View file

@ -1,4 +1,6 @@
const std = @import("std");
const abi = @import("abi.zig");
const temporal_types = @import("temporal.zig");
const Instant = @import("Instant.zig");
const PlainDate = @import("PlainDate.zig");
@ -8,188 +10,582 @@ const Duration = @import("Duration.zig");
const ZonedDateTime = @This();
_inner: *abi.c.ZonedDateTime,
calendar_id: []const u8,
day: i64,
day_of_week: i64,
day_of_year: i64,
days_in_month: i64,
days_in_week: i64,
days_in_year: i64,
epoch_milliseconds: i64,
epoch_nanoseconds: i128,
era: []const u8,
era_year: i64,
hour: i64,
hours_in_day: i64,
in_leap_year: bool,
microsecond: i64,
millisecond: i64,
minute: i64,
month: i64,
month_code: []const u8,
months_in_year: i64,
nanosecond: i64,
offset: []const u8,
offset_nanoseconds: i64,
second: i64,
time_zone_id: []const u8,
week_of_year: i64,
year: i64,
year_of_week: i64,
pub fn init() error{Todo}!ZonedDateTime {
return error.Todo;
// Import types from temporal.zig
pub const Unit = temporal_types.Unit;
pub const RoundingMode = temporal_types.RoundingMode;
pub const Sign = temporal_types.Sign;
pub const DifferenceSettings = temporal_types.DifferenceSettings;
pub const RoundOptions = temporal_types.RoundingOptions;
pub const TimeZone = struct {
_inner: abi.c.TimeZone,
pub fn init(id: []const u8) !TimeZone {
const view = abi.toDiplomatStringView(id);
const result = abi.c.temporal_rs_TimeZone_try_from_str(view);
const tz = abi.success(result) orelse return error.TemporalError;
return .{ ._inner = tz };
}
fn toCApi(self: TimeZone) abi.c.TimeZone {
return self._inner;
}
};
pub const Disambiguation = enum {
compatible,
earlier,
later,
reject,
fn toCApi(self: Disambiguation) abi.c.Disambiguation {
return switch (self) {
.compatible => abi.c.Disambiguation_Compatible,
.earlier => abi.c.Disambiguation_Earlier,
.later => abi.c.Disambiguation_Later,
.reject => abi.c.Disambiguation_Reject,
};
}
};
pub const OffsetDisambiguation = enum {
use_offset,
prefer_offset,
ignore_offset,
reject,
fn toCApi(self: OffsetDisambiguation) abi.c.OffsetDisambiguation {
return switch (self) {
.use_offset => abi.c.OffsetDisambiguation_Use,
.prefer_offset => abi.c.OffsetDisambiguation_Prefer,
.ignore_offset => abi.c.OffsetDisambiguation_Ignore,
.reject => abi.c.OffsetDisambiguation_Reject,
};
}
};
pub const CalendarDisplay = enum {
auto,
always,
never,
critical,
fn toCApi(self: CalendarDisplay) abi.c.DisplayCalendar {
return switch (self) {
.auto => abi.c.DisplayCalendar_Auto,
.always => abi.c.DisplayCalendar_Always,
.never => abi.c.DisplayCalendar_Never,
.critical => abi.c.DisplayCalendar_Critical,
};
}
};
pub const DisplayOffset = enum {
auto,
never,
fn toCApi(self: DisplayOffset) abi.c.DisplayOffset {
return switch (self) {
.auto => abi.c.DisplayOffset_Auto,
.never => abi.c.DisplayOffset_Never,
};
}
};
pub const DisplayTimeZone = enum {
auto,
never,
critical,
fn toCApi(self: DisplayTimeZone) abi.c.DisplayTimeZone {
return switch (self) {
.auto => abi.c.DisplayTimeZone_Auto,
.never => abi.c.DisplayTimeZone_Never,
.critical => abi.c.DisplayTimeZone_Critical,
};
}
};
pub const ToStringOptions = struct {
fractional_second_digits: ?u8 = null,
smallest_unit: ?Unit = null,
rounding_mode: ?RoundingMode = null,
calendar_display: CalendarDisplay = .auto,
offset_display: DisplayOffset = .auto,
time_zone_name: DisplayTimeZone = .auto,
};
/// Helper function to wrap a C API result into a ZonedDateTime
fn wrapZonedDateTime(result: anytype) !ZonedDateTime {
const ptr = (abi.success(result) orelse return error.TemporalError) orelse return error.TemporalError;
const calendar_ptr = abi.c.temporal_rs_ZonedDateTime_calendar(ptr);
const cal_id_view = abi.c.temporal_rs_Calendar_identifier(calendar_ptr);
const allocator = std.heap.c_allocator;
const cal_id = allocator.dupe(u8, cal_id_view.data[0..cal_id_view.len]) catch "iso8601";
return .{ ._inner = ptr, .calendar_id = cal_id };
}
pub fn compare() error{Todo}!i8 {
return error.Todo;
/// Create a ZonedDateTime from epoch nanoseconds
pub fn init(epoch_ns: i128, time_zone: TimeZone) !ZonedDateTime {
const ns_parts = abi.toI128Nanoseconds(epoch_ns);
return wrapZonedDateTime(abi.c.temporal_rs_ZonedDateTime_from_epoch_nanoseconds(ns_parts, time_zone.toCApi()));
}
pub fn from() error{Todo}!ZonedDateTime {
return error.Todo;
/// Create from epoch milliseconds
pub fn fromEpochMilliseconds(epoch_ms: i64, time_zone: TimeZone) !ZonedDateTime {
return wrapZonedDateTime(abi.c.temporal_rs_ZonedDateTime_from_epoch_milliseconds(epoch_ms, time_zone.toCApi()));
}
pub fn add() error{Todo}!ZonedDateTime {
return error.Todo;
/// Create from epoch nanoseconds
pub fn fromEpochNanoseconds(epoch_ns: i128, time_zone: TimeZone) !ZonedDateTime {
const ns_parts = abi.toI128Nanoseconds(epoch_ns);
return wrapZonedDateTime(abi.c.temporal_rs_ZonedDateTime_from_epoch_nanoseconds(ns_parts, time_zone.toCApi()));
}
pub fn equals() error{Todo}!bool {
return error.Todo;
/// Parse from string
pub fn from(s: []const u8, time_zone: ?TimeZone, disambiguation: Disambiguation, offset_disambiguation: OffsetDisambiguation) !ZonedDateTime {
_ = time_zone; // The time zone is parsed from the string
const view = abi.toDiplomatStringView(s);
return wrapZonedDateTime(abi.c.temporal_rs_ZonedDateTime_from_utf8(view, disambiguation.toCApi(), offset_disambiguation.toCApi()));
}
pub fn getTimeZoneTransition() error{Todo}!?Instant {
return error.Todo;
/// Compare two ZonedDateTime instances
pub fn compare(a: ZonedDateTime, b: ZonedDateTime) i8 {
return abi.c.temporal_rs_ZonedDateTime_compare_instant(a._inner, b._inner);
}
pub fn round() error{Todo}!ZonedDateTime {
return error.Todo;
/// Add a duration
pub fn add(self: ZonedDateTime, duration: Duration) !ZonedDateTime {
const overflow_opt = abi.toOption(abi.c.ArithmeticOverflow_option, null);
return wrapZonedDateTime(abi.c.temporal_rs_ZonedDateTime_add(self._inner, duration._inner, overflow_opt));
}
pub fn since() error{Todo}!Duration {
return error.Todo;
/// Check equality
pub fn equals(self: ZonedDateTime, other: ZonedDateTime) bool {
return abi.c.temporal_rs_ZonedDateTime_equals(self._inner, other._inner);
}
pub fn startOfDay() error{Todo}!ZonedDateTime {
return error.Todo;
/// Get the time zone transition
pub fn getTimeZoneTransition(self: ZonedDateTime, direction: enum { next, previous }) !?ZonedDateTime {
const dir = switch (direction) {
.next => abi.c.TransitionDirection_Next,
.previous => abi.c.TransitionDirection_Previous,
};
const result = abi.c.temporal_rs_ZonedDateTime_get_time_zone_transition(self._inner, dir);
const maybe_ptr = abi.success(result) orelse return error.TemporalError;
if (maybe_ptr) |ptr| {
const calendar_ptr = abi.c.temporal_rs_ZonedDateTime_calendar(ptr);
const cal_id_view = abi.c.temporal_rs_Calendar_identifier(calendar_ptr);
const allocator = std.heap.c_allocator;
const cal_id = allocator.dupe(u8, cal_id_view.data[0..cal_id_view.len]) catch "iso8601";
return .{ ._inner = ptr, .calendar_id = cal_id };
}
return null;
}
pub fn subtract() error{Todo}!ZonedDateTime {
return error.Todo;
/// Round to the given options
pub fn round(self: ZonedDateTime, options: RoundOptions) !ZonedDateTime {
return wrapZonedDateTime(abi.c.temporal_rs_ZonedDateTime_round(self._inner, options.toCApi()));
}
pub fn toInstant() error{Todo}!Instant {
return error.Todo;
/// Calculate duration since another ZonedDateTime
pub fn since(self: ZonedDateTime, other: ZonedDateTime, settings: DifferenceSettings) !Duration {
const ptr = abi.success(abi.c.temporal_rs_ZonedDateTime_since(self._inner, other._inner, settings.toCApi())) orelse return error.TemporalError;
return .{ ._inner = ptr };
}
pub fn toJSON() error{Todo}![]const u8 {
return error.Todo;
/// Get the start of the day
pub fn startOfDay(self: ZonedDateTime) !ZonedDateTime {
return wrapZonedDateTime(abi.c.temporal_rs_ZonedDateTime_start_of_day(self._inner));
}
pub fn toLocaleString() error{Todo}![]const u8 {
return error.Todo;
/// Subtract a duration
pub fn subtract(self: ZonedDateTime, duration: Duration) !ZonedDateTime {
const overflow_opt = abi.toOption(abi.c.ArithmeticOverflow_option, null);
return wrapZonedDateTime(abi.c.temporal_rs_ZonedDateTime_subtract(self._inner, duration._inner, overflow_opt));
}
pub fn toPlainDate() error{Todo}!PlainDate {
return error.Todo;
/// Convert to Instant
pub fn toInstant(self: ZonedDateTime) !Instant {
const instant_ptr = abi.c.temporal_rs_ZonedDateTime_to_instant(self._inner) orelse return error.TemporalError;
const epoch_ms = abi.c.temporal_rs_Instant_epoch_milliseconds(instant_ptr);
const epoch_ns_parts = abi.c.temporal_rs_Instant_epoch_nanoseconds(instant_ptr);
const epoch_ns = abi.fromI128Nanoseconds(epoch_ns_parts);
return .{ ._inner = instant_ptr, .epoch_milliseconds = epoch_ms, .epoch_nanoseconds = epoch_ns };
}
pub fn toPlainDateTime() error{Todo}!PlainDateTime {
return error.Todo;
/// Convert to JSON string (ISO 8601 format)
pub fn toJSON(self: ZonedDateTime, allocator: std.mem.Allocator) ![]u8 {
return self.toString(allocator, .{});
}
pub fn toPlainTime() error{Todo}!PlainTime {
return error.Todo;
/// Convert to locale string (placeholder - returns ISO string)
pub fn toLocaleString(self: ZonedDateTime, allocator: std.mem.Allocator) ![]u8 {
return self.toString(allocator, .{});
}
pub fn toString() error{Todo}![]const u8 {
return error.Todo;
/// Convert to PlainDate
pub fn toPlainDate(self: ZonedDateTime) !PlainDate {
const ptr = abi.c.temporal_rs_ZonedDateTime_to_plain_date(self._inner) orelse return error.TemporalError;
return .{ ._inner = ptr, .calendar_id = self.calendar_id };
}
pub fn until() error{Todo}!Duration {
return error.Todo;
/// Convert to PlainDateTime
pub fn toPlainDateTime(self: ZonedDateTime) !PlainDateTime {
const ptr = abi.c.temporal_rs_ZonedDateTime_to_plain_datetime(self._inner) orelse return error.TemporalError;
return .{ ._inner = ptr, .calendar_id = self.calendar_id };
}
pub fn valueOf() error{Todo}!void {
return error.Todo;
/// Convert to PlainTime
pub fn toPlainTime(self: ZonedDateTime) !PlainTime {
const ptr = abi.c.temporal_rs_ZonedDateTime_to_plain_time(self._inner) orelse return error.TemporalError;
return .{ ._inner = ptr };
}
pub fn with() error{Todo}!ZonedDateTime {
return error.Todo;
/// Convert to string with options
pub fn toString(self: ZonedDateTime, allocator: std.mem.Allocator, opts: ToStringOptions) ![]u8 {
const smallest = if (opts.smallest_unit) |u| abi.toUnitOption(u.toCApi()) else abi.toUnitOption(null);
const rounding = if (opts.rounding_mode) |r| abi.toRoundingModeOption(r.toCApi()) else abi.toRoundingModeOption(null);
const precision: abi.c.Precision = if (opts.fractional_second_digits) |digits|
.{ .is_minute = false, .precision = abi.toOption(abi.c.OptionU8, digits) }
else
.{ .is_minute = true, .precision = abi.toOption(abi.c.OptionU8, null) };
const rounding_opts = abi.c.ToStringRoundingOptions{
.precision = precision,
.smallest_unit = smallest,
.rounding_mode = rounding,
};
var write = abi.DiplomatWrite.init(allocator);
defer write.deinit();
_ = abi.c.temporal_rs_ZonedDateTime_to_ixdtf_string(
self._inner,
opts.offset_display.toCApi(),
opts.time_zone_name.toCApi(),
opts.calendar_display.toCApi(),
rounding_opts,
&write.inner,
);
return try write.toOwnedSlice();
}
pub fn withCalendar() error{Todo}!ZonedDateTime {
return error.Todo;
/// Calculate duration until another ZonedDateTime
pub fn until(self: ZonedDateTime, other: ZonedDateTime, settings: DifferenceSettings) !Duration {
const ptr = abi.success(abi.c.temporal_rs_ZonedDateTime_until(self._inner, other._inner, settings.toCApi())) orelse return error.TemporalError;
return .{ ._inner = ptr };
}
pub fn withPlainTime() error{Todo}!ZonedDateTime {
return error.Todo;
/// valueOf() is not supported for ZonedDateTime
pub fn valueOf(_: ZonedDateTime) !void {
return error.ValueOfNotSupported;
}
pub fn withTimeZone() error{Todo}!ZonedDateTime {
return error.Todo;
/// Create a new ZonedDateTime with some fields changed
pub fn with(self: ZonedDateTime, allocator: std.mem.Allocator, fields: anytype) !ZonedDateTime {
_ = allocator;
_ = fields;
_ = self;
return error.Todo; // Need PartialZonedDateTime mapping
}
/// Create a new ZonedDateTime with a different calendar
pub fn withCalendar(self: ZonedDateTime, calendar: []const u8) !ZonedDateTime {
const cal_view = abi.toDiplomatStringView(calendar);
const cal_result = abi.c.temporal_rs_AnyCalendarKind_parse_temporal_calendar_string(cal_view);
const cal_kind = abi.success(cal_result) orelse return error.TemporalError;
const ptr = abi.c.temporal_rs_ZonedDateTime_with_calendar(self._inner, cal_kind);
return .{ ._inner = ptr, .calendar_id = calendar };
}
/// Create a new ZonedDateTime with a different time
pub fn withPlainTime(self: ZonedDateTime, time: ?PlainTime) !ZonedDateTime {
const time_ptr = if (time) |t| t._inner else null;
return wrapZonedDateTime(abi.c.temporal_rs_ZonedDateTime_with_plain_time(self._inner, time_ptr));
}
/// Create a new ZonedDateTime with a different time zone
pub fn withTimeZone(self: ZonedDateTime, time_zone: TimeZone) !ZonedDateTime {
return wrapZonedDateTime(abi.c.temporal_rs_ZonedDateTime_with_timezone(self._inner, time_zone.toCApi()));
}
// Property accessors
pub fn calendarId(self: ZonedDateTime) []const u8 {
return self.calendar_id;
}
pub fn day(self: ZonedDateTime) u8 {
return abi.c.temporal_rs_ZonedDateTime_day(self._inner);
}
pub fn dayOfWeek(self: ZonedDateTime) u16 {
return abi.c.temporal_rs_ZonedDateTime_day_of_week(self._inner);
}
pub fn dayOfYear(self: ZonedDateTime) u16 {
return abi.c.temporal_rs_ZonedDateTime_day_of_year(self._inner);
}
pub fn daysInMonth(self: ZonedDateTime) u16 {
return abi.c.temporal_rs_ZonedDateTime_days_in_month(self._inner);
}
pub fn daysInWeek(self: ZonedDateTime) u16 {
return abi.c.temporal_rs_ZonedDateTime_days_in_week(self._inner);
}
pub fn daysInYear(self: ZonedDateTime) u16 {
return abi.c.temporal_rs_ZonedDateTime_days_in_year(self._inner);
}
pub fn epochMilliseconds(self: ZonedDateTime) i64 {
return abi.c.temporal_rs_ZonedDateTime_epoch_milliseconds(self._inner);
}
pub fn epochNanoseconds(self: ZonedDateTime) i128 {
const parts = abi.c.temporal_rs_ZonedDateTime_epoch_nanoseconds(self._inner);
return abi.fromI128Nanoseconds(parts);
}
pub fn era(self: ZonedDateTime, allocator: std.mem.Allocator) !?[]u8 {
var write = abi.DiplomatWrite.init(allocator);
defer write.deinit();
abi.c.temporal_rs_ZonedDateTime_era(self._inner, &write.inner);
const result = try write.toOwnedSlice();
if (result.len == 0) {
allocator.free(result);
return null;
}
return result;
}
pub fn eraYear(self: ZonedDateTime) ?i32 {
const result = abi.c.temporal_rs_ZonedDateTime_era_year(self._inner);
return abi.fromOption(result);
}
pub fn hour(self: ZonedDateTime) u8 {
return abi.c.temporal_rs_ZonedDateTime_hour(self._inner);
}
pub fn hoursInDay(self: ZonedDateTime) !f64 {
const result = abi.c.temporal_rs_ZonedDateTime_hours_in_day(self._inner);
return abi.success(result) orelse error.TemporalError;
}
pub fn inLeapYear(self: ZonedDateTime) bool {
return abi.c.temporal_rs_ZonedDateTime_in_leap_year(self._inner);
}
pub fn microsecond(self: ZonedDateTime) u16 {
return abi.c.temporal_rs_ZonedDateTime_microsecond(self._inner);
}
pub fn millisecond(self: ZonedDateTime) u16 {
return abi.c.temporal_rs_ZonedDateTime_millisecond(self._inner);
}
pub fn minute(self: ZonedDateTime) u8 {
return abi.c.temporal_rs_ZonedDateTime_minute(self._inner);
}
pub fn month(self: ZonedDateTime) u8 {
return abi.c.temporal_rs_ZonedDateTime_month(self._inner);
}
pub fn monthCode(self: ZonedDateTime, allocator: std.mem.Allocator) ![]u8 {
var write = abi.DiplomatWrite.init(allocator);
defer write.deinit();
abi.c.temporal_rs_ZonedDateTime_month_code(self._inner, &write.inner);
return try write.toOwnedSlice();
}
pub fn monthsInYear(self: ZonedDateTime) u16 {
return abi.c.temporal_rs_ZonedDateTime_months_in_year(self._inner);
}
pub fn nanosecond(self: ZonedDateTime) u16 {
return abi.c.temporal_rs_ZonedDateTime_nanosecond(self._inner);
}
pub fn offset(self: ZonedDateTime, allocator: std.mem.Allocator) ![]u8 {
var write = abi.DiplomatWrite.init(allocator);
defer write.deinit();
const res = abi.c.temporal_rs_ZonedDateTime_offset(self._inner, &write.inner);
_ = abi.success(res) orelse return error.TemporalError;
return try write.toOwnedSlice();
}
pub fn offsetNanoseconds(self: ZonedDateTime) i64 {
return abi.c.temporal_rs_ZonedDateTime_offset_nanoseconds(self._inner);
}
pub fn second(self: ZonedDateTime) u8 {
return abi.c.temporal_rs_ZonedDateTime_second(self._inner);
}
pub fn timeZoneId(self: ZonedDateTime, allocator: std.mem.Allocator) ![]u8 {
const tz = abi.c.temporal_rs_ZonedDateTime_timezone(self._inner);
const view = abi.fromDiplomatStringView(tz.id);
return try allocator.dupe(u8, view);
}
pub fn weekOfYear(self: ZonedDateTime) ?u8 {
const result = abi.c.temporal_rs_ZonedDateTime_week_of_year(self._inner);
return abi.fromOption(result);
}
pub fn year(self: ZonedDateTime) i32 {
return abi.c.temporal_rs_ZonedDateTime_year(self._inner);
}
pub fn yearOfWeek(self: ZonedDateTime) ?i32 {
const result = abi.c.temporal_rs_ZonedDateTime_year_of_week(self._inner);
return abi.fromOption(result);
}
/// Clone this ZonedDateTime
pub fn clone(self: ZonedDateTime) ZonedDateTime {
const ptr = abi.c.temporal_rs_ZonedDateTime_clone(self._inner);
return .{ ._inner = ptr, .calendar_id = self.calendar_id };
}
/// Free the ZonedDateTime
pub fn deinit(self: ZonedDateTime) void {
abi.c.temporal_rs_ZonedDateTime_destroy(self._inner);
if (self.calendar_id.len > 0) {
std.heap.c_allocator.free(self.calendar_id);
}
}
// ---------- Tests ---------------------
test compare {
if (true) return error.Todo;
test init {
const tz = try TimeZone.init("America/New_York");
const zdt = try init(0, tz);
defer zdt.deinit();
try std.testing.expectEqual(@as(i64, 0), zdt.epochMilliseconds());
}
test fromEpochMilliseconds {
const tz = try TimeZone.init("UTC");
const zdt = try fromEpochMilliseconds(1609459200000, tz); // 2021-01-01T00:00:00Z
defer zdt.deinit();
try std.testing.expectEqual(@as(i64, 1609459200000), zdt.epochMilliseconds());
}
test from {
if (true) return error.Todo;
const zdt = try from("2021-01-01T00:00:00+00:00[UTC]", null, .compatible, .reject);
defer zdt.deinit();
try std.testing.expectEqual(@as(i32, 2021), zdt.year());
try std.testing.expectEqual(@as(u8, 1), zdt.month());
try std.testing.expectEqual(@as(u8, 1), zdt.day());
}
test add {
if (true) return error.Todo;
test compare {
const tz = try TimeZone.init("UTC");
const zdt1 = try fromEpochMilliseconds(1000, tz);
defer zdt1.deinit();
const zdt2 = try fromEpochMilliseconds(2000, tz);
defer zdt2.deinit();
try std.testing.expect(compare(zdt1, zdt2) < 0);
try std.testing.expect(compare(zdt2, zdt1) > 0);
try std.testing.expect(compare(zdt1, zdt1) == 0);
}
test equals {
if (true) return error.Todo;
}
test getTimeZoneTransition {
if (true) return error.Todo;
}
test round {
if (true) return error.Todo;
}
test since {
if (true) return error.Todo;
}
test startOfDay {
if (true) return error.Todo;
}
test subtract {
if (true) return error.Todo;
const tz = try TimeZone.init("UTC");
const zdt1 = try fromEpochMilliseconds(1000, tz);
defer zdt1.deinit();
const zdt2 = try fromEpochMilliseconds(1000, tz);
defer zdt2.deinit();
const zdt3 = try fromEpochMilliseconds(2000, tz);
defer zdt3.deinit();
try std.testing.expect(zdt1.equals(zdt2));
try std.testing.expect(!zdt1.equals(zdt3));
}
test toInstant {
if (true) return error.Todo;
}
test toJSON {
if (true) return error.Todo;
}
test toLocaleString {
if (true) return error.Todo;
const tz = try TimeZone.init("UTC");
const zdt = try fromEpochMilliseconds(1609459200000, tz);
defer zdt.deinit();
const instant = try zdt.toInstant();
defer instant.deinit();
try std.testing.expectEqual(@as(i64, 1609459200000), instant.epoch_milliseconds);
}
test toPlainDate {
if (true) return error.Todo;
const tz = try TimeZone.init("UTC");
const zdt = try fromEpochMilliseconds(1609459200000, tz);
defer zdt.deinit();
const pd = try zdt.toPlainDate();
defer pd.deinit();
try std.testing.expectEqual(@as(i32, 2021), pd.year());
try std.testing.expectEqual(@as(u8, 1), pd.month());
try std.testing.expectEqual(@as(u8, 1), pd.day());
}
test toPlainDateTime {
if (true) return error.Todo;
const tz = try TimeZone.init("UTC");
const zdt = try fromEpochMilliseconds(1609459200000, tz);
defer zdt.deinit();
var pdt = try zdt.toPlainDateTime();
defer pdt.deinit();
try std.testing.expectEqual(@as(i32, 2021), pdt.year());
try std.testing.expectEqual(@as(u8, 1), pdt.month());
try std.testing.expectEqual(@as(u8, 1), pdt.day());
}
test toPlainTime {
if (true) return error.Todo;
const tz = try TimeZone.init("UTC");
const zdt = try fromEpochMilliseconds(1609459200000, tz);
defer zdt.deinit();
const pt = try zdt.toPlainTime();
defer abi.c.temporal_rs_PlainTime_destroy(pt._inner);
try std.testing.expectEqual(@as(u8, 0), pt.hour());
try std.testing.expectEqual(@as(u8, 0), pt.minute());
}
test toString {
if (true) return error.Todo;
const tz = try TimeZone.init("UTC");
const zdt = try fromEpochMilliseconds(1609459200000, tz);
defer zdt.deinit();
const str = try zdt.toString(std.testing.allocator, .{});
defer std.testing.allocator.free(str);
try std.testing.expect(str.len > 0);
}
test until {
if (true) return error.Todo;
}
test with {
if (true) return error.Todo;
}
test withCalendar {
if (true) return error.Todo;
}
test withPlainTime {
if (true) return error.Todo;
}
test withTimeZone {
if (true) return error.Todo;
test "props" {
const tz = try TimeZone.init("UTC");
const zdt = try fromEpochMilliseconds(1609459200000, tz);
defer zdt.deinit();
try std.testing.expectEqual(@as(i32, 2021), zdt.year());
try std.testing.expectEqual(@as(u8, 1), zdt.month());
try std.testing.expectEqual(@as(u8, 1), zdt.day());
try std.testing.expectEqual(@as(u8, 0), zdt.hour());
try std.testing.expectEqual(@as(u8, 0), zdt.minute());
try std.testing.expectEqual(@as(u8, 0), zdt.second());
try std.testing.expectEqual(@as(u16, 0), zdt.millisecond());
}

View file

@ -113,6 +113,7 @@ test Instant {
// Public types
"ToStringOptions",
"TimeZone",
"Unit",
"RoundingMode",
"Sign",
@ -140,7 +141,8 @@ test Now {
test PlainDate {
const checks = .{
// Constructor
"init", // Temporal.PlainDate()
"init",
"initWithCalendar",
// Static methods
"compare",
@ -163,7 +165,7 @@ test PlainDate {
"with",
"withCalendar",
// Properties
// Properties (now as methods)
"calendarId",
"day",
"dayOfWeek",
@ -180,6 +182,15 @@ test PlainDate {
"weekOfYear",
"year",
"yearOfWeek",
// Public types
"ToStringOptions",
"CalendarDisplay",
"ToZonedDateTimeOptions",
"Unit",
"RoundingMode",
"Sign",
"DifferenceSettings",
};
try assertDecls(PlainDate, checks);
@ -189,10 +200,13 @@ test PlainDateTime {
const checks = .{
// Constructor
"init", // Temporal.PlainDateTime()
"initWithCalendar",
// Static methods
"compare",
"from",
// "fromUtf8",
// "fromUtf16",
// Instance methods
"add",
@ -235,6 +249,17 @@ test PlainDateTime {
"weekOfYear",
"year",
"yearOfWeek",
// Public types
"Unit",
"RoundingMode",
"Sign",
"CalendarDisplay",
"DifferenceSettings",
"RoundOptions",
"ToStringOptions",
"ToZonedDateTimeOptions",
"WithOptions",
};
try assertDecls(PlainDateTime, checks);
@ -261,6 +286,11 @@ test PlainMonthDay {
"calendarId",
"day",
"monthCode",
// Public types
"CalendarDisplay",
"ToStringOptions",
"WithOptions",
};
try assertDecls(PlainMonthDay, checks);
@ -295,6 +325,13 @@ test PlainTime {
"minute",
"nanosecond",
"second",
// Public types
"Unit",
"RoundingMode",
"DifferenceSettings",
"RoundOptions",
"WithOptions",
};
try assertDecls(PlainTime, checks);
@ -333,6 +370,15 @@ test PlainYearMonth {
"monthCode",
"monthsInYear",
"year",
// Public types
"Unit",
"RoundingMode",
"CalendarDisplay",
"DifferenceSettings",
"RoundOptions",
"ToStringOptions",
"WithOptions",
};
try assertDecls(PlainYearMonth, checks);
@ -346,9 +392,12 @@ test ZonedDateTime {
// Static methods
"compare",
"from",
"fromEpochMilliseconds",
"fromEpochNanoseconds",
// Instance methods
"add",
"clone",
"equals",
"getTimeZoneTransition",
"round",
@ -398,6 +447,20 @@ test ZonedDateTime {
"weekOfYear",
"year",
"yearOfWeek",
// Public types
"Unit",
"RoundingMode",
"Sign",
"DifferenceSettings",
"RoundOptions",
"TimeZone",
"Disambiguation",
"OffsetDisambiguation",
"CalendarDisplay",
"DisplayOffset",
"DisplayTimeZone",
"ToStringOptions",
};
try assertDecls(ZonedDateTime, checks);