temporalz/src/PlainMonthDay.zig
2026-02-11 17:27:24 +06:00

310 lines
12 KiB
Zig

const std = @import("std");
const abi = @import("abi.zig");
const t = @import("temporal.zig");
const PlainDate = @import("PlainDate.zig");
/// # Temporal.PlainMonthDay
///
/// The `Temporal.PlainMonthDay` object represents a month and day in a calendar, with no year or time.
///
/// - [MDN Temporal.PlainMonthDay](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainMonthDay)
const PlainMonthDay = @This();
_inner: *abi.c.PlainMonthDay,
// 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 = (try abi.extractResult(result)) orelse return abi.TemporalError.Generic;
return .{ ._inner = ptr };
}
/// Creates a new PlainMonthDay from the given month, day, and optional calendar.
/// Equivalent to the Temporal.PlainMonthDay constructor.
/// See [MDN Temporal.PlainMonthDay() constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainMonthDay/PlainMonthDay)
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 try abi.extractResult(cal_result);
} 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,
));
}
/// Parses a PlainMonthDay from a string.
/// See [MDN Temporal.PlainMonthDay.from()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainMonthDay/from)
pub fn from(s: []const u8) !PlainMonthDay {
return fromUtf8(s);
}
fn fromUtf8(utf8: []const u8) !PlainMonthDay {
const view = abi.toDiplomatStringView(utf8);
return wrapPlainMonthDay(abi.c.temporal_rs_PlainMonthDay_from_utf8(view));
}
fn fromUtf16(utf16: []const u16) !PlainMonthDay {
const view = abi.toDiplomatString16View(utf16);
return wrapPlainMonthDay(abi.c.temporal_rs_PlainMonthDay_from_utf16(view));
}
// Comparison
/// Returns true if this PlainMonthDay is equal to another (same date and calendar).
/// See [MDN Temporal.PlainMonthDay.prototype.equals()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainMonthDay/equals)
pub fn equals(self: PlainMonthDay, other: PlainMonthDay) bool {
return abi.c.temporal_rs_PlainMonthDay_equals(self._inner, other._inner);
}
// Property accessors
/// Returns the calendar identifier used to interpret the internal ISO 8601 date.
/// See [MDN Temporal.PlainMonthDay.prototype.calendarId](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainMonthDay/calendarId)
pub fn calendarId(self: PlainMonthDay, allocator: std.mem.Allocator) ![]u8 {
const calendar_ptr = abi.c.temporal_rs_PlainMonthDay_calendar(self._inner) orelse return error.TemporalError;
const cal_id_view = abi.c.temporal_rs_Calendar_identifier(calendar_ptr);
return try allocator.dupe(u8, cal_id_view.data[0..cal_id_view.len]);
}
/// Returns the 1-based day index in the month of this date.
/// See [MDN Temporal.PlainMonthDay.prototype.day](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainMonthDay/day)
pub fn day(self: PlainMonthDay) u8 {
return abi.c.temporal_rs_PlainMonthDay_day(self._inner);
}
/// Returns the calendar-specific string representing the month of this date.
/// See [MDN Temporal.PlainMonthDay.prototype.monthCode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainMonthDay/monthCode)
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
/// Returns a new PlainMonthDay with some fields replaced by new values.
/// See [MDN Temporal.PlainMonthDay.prototype.with()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainMonthDay/with)
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
/// Returns a PlainDate representing this month-day and a supplied year in the same calendar system.
/// See [MDN Temporal.PlainMonthDay.prototype.toPlainDate()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainMonthDay/toPlainDate)
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 = (try abi.extractResult(result)) orelse return abi.TemporalError.Generic;
return PlainDate{ ._inner = ptr };
}
// String conversions
/// Returns a string representing this month-day in RFC 9557 format.
/// See [MDN Temporal.PlainMonthDay.prototype.toString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainMonthDay/toString)
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();
}
/// Returns a string representing this month-day in RFC 9557 format (ISO 8601).
/// See [MDN Temporal.PlainMonthDay.prototype.toJSON()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainMonthDay/toJSON)
pub fn toJSON(self: PlainMonthDay, allocator: std.mem.Allocator) ![]u8 {
return toString(self, allocator);
}
/// Returns a language-sensitive string representation of this month-day.
/// See [MDN Temporal.PlainMonthDay.prototype.toLocaleString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainMonthDay/toLocaleString)
pub fn toLocaleString(self: PlainMonthDay, allocator: std.mem.Allocator) ![]u8 {
return toString(self, allocator);
}
/// Throws an error; valueOf() is not supported for PlainMonthDay.
/// See [MDN Temporal.PlainMonthDay.prototype.valueOf()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainMonthDay/valueOf)
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 {
const md = try from("12-25");
try std.testing.expectEqual(@as(u8, 25), md.day());
}
test equals {
{
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 {
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 {
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 {
// 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);
}
test calendarId {
const md = try init(12, 25, null);
const cal_id = try md.calendarId(std.testing.allocator);
defer std.testing.allocator.free(cal_id);
try std.testing.expectEqualStrings("iso8601", cal_id);
}
test day {
const md = try init(12, 25, null);
try std.testing.expectEqual(@as(u8, 25), md.day());
}
test monthCode {
const md = try init(12, 25, null);
const code = try md.monthCode(std.testing.allocator);
defer std.testing.allocator.free(code);
try std.testing.expectEqualStrings("M12", code);
}
test valueOf {
const md = try init(12, 25, null);
try std.testing.expectError(error.ValueError, md.valueOf());
}