temporalz/src/PlainYearMonth.zig
2026-07-15 17:23:58 +06:00

557 lines
21 KiB
Zig

const std = @import("std");
const abi = @import("abi.zig");
const t = @import("temporal.zig");
const PlainDate = @import("PlainDate.zig");
const Duration = @import("Duration.zig");
/// # Temporal.PlainYearMonth
///
/// The `Temporal.PlainYearMonth` object represents a particular month in a specific year, with no day or time.
///
/// See: [Temporal.PlainYearMonth](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth)
const PlainYearMonth = @This();
_inner: *abi.c.PlainYearMonth,
// Import types from temporal.zig
pub const Unit = t.Unit;
pub const RoundingMode = t.RoundingMode;
pub const DifferenceSettings = t.DifferenceSettings;
pub const RoundOptions = t.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 = (try abi.extractResult(result)) orelse return abi.TemporalError.Generic;
return .{ ._inner = ptr };
}
/// Creates a new PlainYearMonth from the given year, month, and optional calendar.
/// Equivalent to the Temporal.PlainYearMonth constructor.
///
/// See: [Temporal.PlainYearMonth](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/PlainYearMonth)
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 try abi.extractResult(cal_result);
} 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,
));
}
/// Parses a PlainYearMonth from a string.
///
/// See: [Temporal.PlainYearMonth.from](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/from)
pub fn from(s: []const u8) !PlainYearMonth {
return fromUtf8(s);
}
fn fromUtf8(utf8: []const u8) !PlainYearMonth {
const view = abi.toDiplomatStringView(utf8);
return wrapPlainYearMonth(abi.c.temporal_rs_PlainYearMonth_from_utf8(view));
}
fn fromUtf16(utf16: []const u16) !PlainYearMonth {
const view = abi.toDiplomatString16View(utf16);
return wrapPlainYearMonth(abi.c.temporal_rs_PlainYearMonth_from_utf16(view));
}
// Comparison
/// Compares two PlainYearMonth instances by their ISO date values.
/// Returns -1, 0, or 1 if the first is before, equal, or after the second.
///
/// See: [Temporal.PlainYearMonth.compare](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/compare)
pub fn compare(a: PlainYearMonth, b: PlainYearMonth) i8 {
return abi.c.temporal_rs_PlainYearMonth_compare(a._inner, b._inner);
}
/// Returns true if this PlainYearMonth is equal to another (same ISO date and calendar).
///
/// See: [Temporal.PlainYearMonth.equals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/equals)
pub fn equals(self: PlainYearMonth, other: PlainYearMonth) bool {
return abi.c.temporal_rs_PlainYearMonth_equals(self._inner, other._inner);
}
// Arithmetic
/// Returns a new PlainYearMonth moved forward by the given duration.
///
/// See: [Temporal.PlainYearMonth.add](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/add)
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));
}
/// Returns a new PlainYearMonth moved backward by the given duration.
///
/// See: [Temporal.PlainYearMonth.subtract](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/subtract)
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));
}
/// Returns the duration from this PlainYearMonth to another.
///
/// See: [Temporal.PlainYearMonth.until](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/until)
pub fn until(self: PlainYearMonth, other: PlainYearMonth, options: DifferenceSettings) !Duration {
const settings = abi.to.diffsettings(options);
const result = abi.c.temporal_rs_PlainYearMonth_until(self._inner, other._inner, settings);
const ptr = (try abi.extractResult(result)) orelse return abi.TemporalError.Generic;
return .{ ._inner = ptr };
}
/// Returns the duration from another PlainYearMonth to this one.
///
/// See: [Temporal.PlainYearMonth.since](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/since)
pub fn since(self: PlainYearMonth, other: PlainYearMonth, options: DifferenceSettings) !Duration {
const settings = abi.to.diffsettings(options);
const result = abi.c.temporal_rs_PlainYearMonth_since(self._inner, other._inner, settings);
const ptr = (try abi.extractResult(result)) orelse return abi.TemporalError.Generic;
return .{ ._inner = ptr };
}
// Property accessors
/// Returns the calendar identifier used to interpret the internal ISO 8601 date.
///
/// See: [Temporal.PlainYearMonth.calendarId](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/calendarId)
pub fn calendarId(self: PlainYearMonth, allocator: std.mem.Allocator) ![]u8 {
const calendar_ptr = abi.c.temporal_rs_PlainYearMonth_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 year of this year-month relative to the calendar's epoch.
///
/// See: [Temporal.PlainYearMonth.year](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/year)
pub fn year(self: PlainYearMonth) i32 {
return abi.c.temporal_rs_PlainYearMonth_year(self._inner);
}
/// Returns the 1-based month index in the year of this year-month.
///
/// See: [Temporal.PlainYearMonth.month](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/month)
pub fn month(self: PlainYearMonth) u8 {
return abi.c.temporal_rs_PlainYearMonth_month(self._inner);
}
/// Returns the calendar-specific string representing the month of this year-month.
///
/// See: [Temporal.PlainYearMonth.monthCode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/monthCode)
pub fn monthCode(self: PlainYearMonth, allocator: std.mem.Allocator) ![]const u8 {
var write = abi.DiplomatWrite.init(allocator);
defer write.deinit();
abi.c.temporal_rs_PlainYearMonth_month_code(self._inner, &write.inner);
return try write.toOwnedSlice();
}
/// Returns the number of days in the month of this year-month.
///
/// See: [Temporal.PlainYearMonth.daysInMonth](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/daysInMonth)
pub fn daysInMonth(self: PlainYearMonth) u16 {
return abi.c.temporal_rs_PlainYearMonth_days_in_month(self._inner);
}
/// Returns the number of days in the year of this year-month.
///
/// See: [Temporal.PlainYearMonth.daysInYear](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/daysInYear)
pub fn daysInYear(self: PlainYearMonth) u16 {
return abi.c.temporal_rs_PlainYearMonth_days_in_year(self._inner);
}
/// Returns the number of months in the year of this year-month.
///
/// See: [Temporal.PlainYearMonth.monthsInYear](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/monthsInYear)
pub fn monthsInYear(self: PlainYearMonth) u16 {
return abi.c.temporal_rs_PlainYearMonth_months_in_year(self._inner);
}
/// Returns true if this year-month is in a leap year.
///
/// See: [Temporal.PlainYearMonth.inLeapYear](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/inLeapYear)
pub fn inLeapYear(self: PlainYearMonth) bool {
return abi.c.temporal_rs_PlainYearMonth_in_leap_year(self._inner);
}
/// Returns the calendar-specific era of this year-month, or null if not applicable.
///
/// See: [Temporal.PlainYearMonth.era](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/era)
pub fn era(self: PlainYearMonth, allocator: std.mem.Allocator) !?[]const u8 {
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;
}
/// Returns the year of this year-month within the era, or null if not applicable.
///
/// See: [Temporal.PlainYearMonth.eraYear](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/eraYear)
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
/// Returns a new PlainYearMonth with some fields replaced by new values.
///
/// See: [Temporal.PlainYearMonth.with](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/with)
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
/// Returns a PlainDate representing this year-month and a supplied day in the same calendar system.
///
/// See: [Temporal.PlainYearMonth.toPlainDate](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/toPlainDate)
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 = (try abi.extractResult(result)) orelse return abi.TemporalError.Generic;
return PlainDate{ ._inner = ptr };
}
// String conversions
/// Returns a string representing this year-month in RFC 9557 format.
///
/// See: [Temporal.PlainYearMonth.toString](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/toString)
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();
}
/// Returns a JSON string representing this year-month.
///
/// See: [Temporal.PlainYearMonth.toJSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/toJSON)
pub fn toJSON(self: PlainYearMonth, allocator: std.mem.Allocator) ![]u8 {
return toString(self, allocator);
}
/// Returns a locale-sensitive string representing this year-month.
///
/// See: [Temporal.PlainYearMonth.toLocaleString](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/toLocaleString)
pub fn toLocaleString(self: PlainYearMonth, allocator: std.mem.Allocator) ![]u8 {
return toString(self, allocator);
}
/// Throws a TypeError, as PlainYearMonth objects cannot be converted to primitive values.
///
/// See: [Temporal.PlainYearMonth.valueOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainYearMonth/valueOf)
pub fn valueOf(self: PlainYearMonth) !void {
_ = self;
return error.ValueError;
}
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 {
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 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 {
{
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 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 {
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 daysInMonth {
const ym = try init(2024, 2, null);
try std.testing.expectEqual(@as(u16, 29), ym.daysInMonth()); // 2024 is a leap year
}
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 {
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 {
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 {
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 calendarId {
const ym = try from("2021-07-01[u-ca=gregory]");
const cal_id = try ym.calendarId(std.testing.allocator);
defer std.testing.allocator.free(cal_id);
try std.testing.expectEqualStrings("gregory", cal_id);
}
test year {
const ym = try from("2021-07");
try std.testing.expectEqual(@as(i32, 2021), ym.year());
}
test month {
const ym = try from("2021-07");
try std.testing.expectEqual(@as(u8, 7), ym.month());
}
test monthCode {
const ym = try from("2021-07");
const month_code = try ym.monthCode(std.testing.allocator);
defer std.testing.allocator.free(month_code);
try std.testing.expectEqualStrings("M07", month_code);
}
test era {
const ym = try from("2021-07-01[u-ca=gregory]");
const maybe_era = try ym.era(std.testing.allocator);
const era_value = maybe_era orelse return error.TestUnexpectedResult;
defer std.testing.allocator.free(era_value);
try std.testing.expectEqualStrings("ce", era_value);
}
test eraYear {
const ym = try from("2021-07-01[u-ca=gregory]");
try std.testing.expectEqual(@as(?i32, 2021), ym.eraYear());
}
test with {
// 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);
}
test "props" {
const ym = try from("2021-07-01[u-ca=gregory]");
const cal_id = try ym.calendarId(std.testing.allocator);
defer std.testing.allocator.free(cal_id);
try std.testing.expectEqualStrings("gregory", cal_id);
try std.testing.expectEqual(@as(i32, 2021), ym.year());
try std.testing.expectEqual(@as(u8, 7), ym.month());
const month_code = try ym.monthCode(std.testing.allocator);
defer std.testing.allocator.free(month_code);
try std.testing.expectEqualStrings("M07", month_code);
try std.testing.expectEqual(@as(u16, 31), ym.daysInMonth());
try std.testing.expectEqual(@as(u16, 365), ym.daysInYear());
try std.testing.expectEqual(@as(u16, 12), ym.monthsInYear());
try std.testing.expect(ym.inLeapYear() == false);
const eraa = try ym.era(std.testing.allocator);
if (eraa) |e| {
defer std.testing.allocator.free(e);
try std.testing.expectEqualStrings("ce", e);
} else {
try std.testing.expect(false);
}
const era_year = ym.eraYear();
if (era_year) |ey| {
try std.testing.expectEqual(@as(i32, 2021), ey);
} else {
try std.testing.expect(false);
}
}
test valueOf {
const ym = try init(2024, 12, null);
try std.testing.expectError(error.ValueError, ym.valueOf());
}