517 lines
11 KiB
Zig
517 lines
11 KiB
Zig
pub const Duration = @import("Duration.zig");
|
|
pub const Instant = @import("Instant.zig");
|
|
pub const Now = @import("Now.zig");
|
|
pub const PlainDate = @import("PlainDate.zig");
|
|
pub const PlainDateTime = @import("PlainDateTime.zig");
|
|
pub const PlainMonthDay = @import("PlainMonthDay.zig");
|
|
pub const PlainTime = @import("PlainTime.zig");
|
|
pub const PlainYearMonth = @import("PlainYearMonth.zig");
|
|
pub const ZonedDateTime = @import("ZonedDateTime.zig");
|
|
|
|
const Temporal = @This();
|
|
|
|
test Temporal {
|
|
const std = @import("std");
|
|
_ = @import("abi.zig");
|
|
|
|
const expected_scopes = .{
|
|
"Duration",
|
|
"Instant",
|
|
"Now",
|
|
"PlainDate",
|
|
"PlainDateTime",
|
|
"PlainMonthDay",
|
|
"PlainTime",
|
|
"PlainYearMonth",
|
|
"ZonedDateTime",
|
|
};
|
|
|
|
inline for (expected_scopes) |scope| {
|
|
const has = @hasDecl(Temporal, scope);
|
|
if (!has) std.log.err("Missing Temporal scope: {s}", .{scope});
|
|
try std.testing.expect(has);
|
|
}
|
|
}
|
|
|
|
test Duration {
|
|
const checks = .{
|
|
// Constructor
|
|
"init", // Temporal.Duration()
|
|
// Static methods
|
|
"compare",
|
|
"from",
|
|
|
|
// Instance methods
|
|
"abs",
|
|
"add",
|
|
"negated",
|
|
"round",
|
|
"subtract",
|
|
"toJSON",
|
|
"toLocaleString",
|
|
"toString",
|
|
"total",
|
|
"valueOf",
|
|
// "with",
|
|
|
|
// Properties
|
|
"blank",
|
|
"days",
|
|
"hours",
|
|
"microseconds",
|
|
"milliseconds",
|
|
"minutes",
|
|
"months",
|
|
"nanoseconds",
|
|
"seconds",
|
|
"sign",
|
|
"weeks",
|
|
"years",
|
|
|
|
// Public types
|
|
"ToStringOptions",
|
|
"PartialDuration",
|
|
"RelativeTo",
|
|
"Unit",
|
|
"RoundingMode",
|
|
"RoundingOptions",
|
|
"ToStringRoundingOptions",
|
|
"Sign",
|
|
"TotalOptions",
|
|
};
|
|
|
|
try assertDecls(Duration, checks);
|
|
}
|
|
|
|
test Instant {
|
|
const checks = .{
|
|
// Constructor
|
|
"init", // Temporal.Instant()
|
|
|
|
// Static methods
|
|
"compare",
|
|
"from",
|
|
"fromEpochMilliseconds",
|
|
"fromEpochNanoseconds",
|
|
|
|
// Instance methods
|
|
"add",
|
|
"equals",
|
|
"round",
|
|
"since",
|
|
"subtract",
|
|
"toJSON",
|
|
"toLocaleString",
|
|
"toString",
|
|
"toZonedDateTimeISO",
|
|
"until",
|
|
"valueOf",
|
|
|
|
// Properties
|
|
"epochMilliseconds",
|
|
"epochNanoseconds",
|
|
|
|
// Public types
|
|
"ToStringOptions",
|
|
"Unit",
|
|
"RoundingMode",
|
|
"Sign",
|
|
"RoundingOptions",
|
|
"DifferenceSettings",
|
|
};
|
|
|
|
try assertDecls(Instant, checks);
|
|
}
|
|
|
|
test Now {
|
|
const checks = .{
|
|
// Static methods
|
|
"instant",
|
|
"plainDateISO",
|
|
"plainDateTimeISO",
|
|
"plainTimeISO",
|
|
"timeZoneId",
|
|
"zonedDateTimeISO",
|
|
};
|
|
|
|
try assertDecls(Now, checks);
|
|
}
|
|
|
|
test PlainDate {
|
|
const checks = .{
|
|
// Constructor
|
|
"init", // Temporal.PlainDate()
|
|
|
|
// Static methods
|
|
"compare",
|
|
"from",
|
|
|
|
// Instance methods
|
|
"add",
|
|
"equals",
|
|
"since",
|
|
"subtract",
|
|
"toJSON",
|
|
"toLocaleString",
|
|
"toPlainDateTime",
|
|
"toPlainMonthDay",
|
|
"toPlainYearMonth",
|
|
"toString",
|
|
"toZonedDateTime",
|
|
"until",
|
|
"valueOf",
|
|
"with",
|
|
"withCalendar",
|
|
|
|
// Properties
|
|
"calendarId",
|
|
"day",
|
|
"dayOfWeek",
|
|
"dayOfYear",
|
|
"daysInMonth",
|
|
"daysInWeek",
|
|
"daysInYear",
|
|
"era",
|
|
"eraYear",
|
|
"inLeapYear",
|
|
"month",
|
|
"monthCode",
|
|
"monthsInYear",
|
|
"weekOfYear",
|
|
"year",
|
|
"yearOfWeek",
|
|
};
|
|
|
|
try assertDecls(PlainDate, checks);
|
|
}
|
|
|
|
test PlainDateTime {
|
|
const checks = .{
|
|
// Constructor
|
|
"init", // Temporal.PlainDateTime()
|
|
|
|
// Static methods
|
|
"compare",
|
|
"from",
|
|
|
|
// Instance methods
|
|
"add",
|
|
"equals",
|
|
"round",
|
|
"since",
|
|
"subtract",
|
|
"toJSON",
|
|
"toLocaleString",
|
|
"toPlainDate",
|
|
"toPlainTime",
|
|
"toString",
|
|
"toZonedDateTime",
|
|
"until",
|
|
"valueOf",
|
|
"with",
|
|
"withCalendar",
|
|
"withPlainTime",
|
|
|
|
// Properties
|
|
"calendarId",
|
|
"day",
|
|
"dayOfWeek",
|
|
"dayOfYear",
|
|
"daysInMonth",
|
|
"daysInWeek",
|
|
"daysInYear",
|
|
"era",
|
|
"eraYear",
|
|
"hour",
|
|
"inLeapYear",
|
|
"microsecond",
|
|
"millisecond",
|
|
"minute",
|
|
"month",
|
|
"monthCode",
|
|
"monthsInYear",
|
|
"nanosecond",
|
|
"second",
|
|
"weekOfYear",
|
|
"year",
|
|
"yearOfWeek",
|
|
};
|
|
|
|
try assertDecls(PlainDateTime, checks);
|
|
}
|
|
|
|
test PlainMonthDay {
|
|
const checks = .{
|
|
// Constructor
|
|
"init", // Temporal.PlainMonthDay()
|
|
|
|
// Static methods
|
|
"from",
|
|
|
|
// Instance methods
|
|
"equals",
|
|
"toJSON",
|
|
"toLocaleString",
|
|
"toPlainDate",
|
|
"toString",
|
|
"valueOf",
|
|
"with",
|
|
|
|
// Properties
|
|
"calendarId",
|
|
"day",
|
|
"monthCode",
|
|
};
|
|
|
|
try assertDecls(PlainMonthDay, checks);
|
|
}
|
|
|
|
test PlainTime {
|
|
const checks = .{
|
|
// Constructor
|
|
"init", // Temporal.PlainTime()
|
|
|
|
// Static methods
|
|
"compare",
|
|
"from",
|
|
|
|
// Instance methods
|
|
"add",
|
|
"equals",
|
|
"round",
|
|
"since",
|
|
"subtract",
|
|
"toJSON",
|
|
"toLocaleString",
|
|
"toString",
|
|
"until",
|
|
"valueOf",
|
|
"with",
|
|
|
|
// Properties
|
|
"hour",
|
|
"microsecond",
|
|
"millisecond",
|
|
"minute",
|
|
"nanosecond",
|
|
"second",
|
|
};
|
|
|
|
try assertDecls(PlainTime, checks);
|
|
}
|
|
|
|
test PlainYearMonth {
|
|
const checks = .{
|
|
// Constructor
|
|
"init", // Temporal.PlainYearMonth()
|
|
|
|
// Static methods
|
|
"compare",
|
|
"from",
|
|
|
|
// Instance methods
|
|
"add",
|
|
"equals",
|
|
"since",
|
|
"subtract",
|
|
"toJSON",
|
|
"toLocaleString",
|
|
"toPlainDate",
|
|
"toString",
|
|
"until",
|
|
"valueOf",
|
|
"with",
|
|
|
|
// Properties
|
|
"calendarId",
|
|
"daysInMonth",
|
|
"daysInYear",
|
|
"era",
|
|
"eraYear",
|
|
"inLeapYear",
|
|
"month",
|
|
"monthCode",
|
|
"monthsInYear",
|
|
"year",
|
|
};
|
|
|
|
try assertDecls(PlainYearMonth, checks);
|
|
}
|
|
|
|
test ZonedDateTime {
|
|
const checks = .{
|
|
// Constructor
|
|
"init", // Temporal.ZonedDateTime()
|
|
|
|
// Static methods
|
|
"compare",
|
|
"from",
|
|
|
|
// Instance methods
|
|
"add",
|
|
"equals",
|
|
"getTimeZoneTransition",
|
|
"round",
|
|
"since",
|
|
"startOfDay",
|
|
"subtract",
|
|
"toInstant",
|
|
"toJSON",
|
|
"toLocaleString",
|
|
"toPlainDate",
|
|
"toPlainDateTime",
|
|
"toPlainTime",
|
|
"toString",
|
|
"until",
|
|
"valueOf",
|
|
"with",
|
|
"withCalendar",
|
|
"withPlainTime",
|
|
"withTimeZone",
|
|
|
|
// Properties
|
|
"calendarId",
|
|
"day",
|
|
"dayOfWeek",
|
|
"dayOfYear",
|
|
"daysInMonth",
|
|
"daysInWeek",
|
|
"daysInYear",
|
|
"epochMilliseconds",
|
|
"epochNanoseconds",
|
|
"era",
|
|
"eraYear",
|
|
"hour",
|
|
"hoursInDay",
|
|
"inLeapYear",
|
|
"microsecond",
|
|
"millisecond",
|
|
"minute",
|
|
"month",
|
|
"monthCode",
|
|
"monthsInYear",
|
|
"nanosecond",
|
|
"offset",
|
|
"offsetNanoseconds",
|
|
"second",
|
|
"timeZoneId",
|
|
"weekOfYear",
|
|
"year",
|
|
"yearOfWeek",
|
|
};
|
|
|
|
try assertDecls(ZonedDateTime, checks);
|
|
}
|
|
|
|
fn assertDecls(comptime T: type, checks: anytype) !void {
|
|
@setEvalBranchQuota(5000); // Increase branch quota for large check lists
|
|
const std = @import("std");
|
|
const typeInfo = @typeInfo(T);
|
|
|
|
// Check: all items in checks exist (either as decls or as fields)
|
|
inline for (checks) |check| {
|
|
const should_ignore =
|
|
std.mem.eql(u8, check, "deinit") or
|
|
std.mem.eql(u8, check, "valueOf");
|
|
|
|
if (!should_ignore) {
|
|
const hasDecl = @hasDecl(T, check);
|
|
|
|
// Also check if it's a field (property)
|
|
var hasField = false;
|
|
if (typeInfo == .@"struct") {
|
|
const struct_info = typeInfo.@"struct";
|
|
inline for (struct_info.fields) |field| {
|
|
// Check both camelCase and snake_case
|
|
if (std.mem.eql(u8, field.name, check) or
|
|
std.mem.eql(u8, field.name, camelToSnakeCase(check)))
|
|
{
|
|
hasField = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const has = hasDecl or hasField;
|
|
if (!has) std.log.err("Missing {s} decl or field: {s}", .{ @typeName(T), check });
|
|
try std.testing.expect(has);
|
|
}
|
|
}
|
|
|
|
// Check: no extraneous declarations or fields beyond checks
|
|
if (typeInfo == .@"struct") {
|
|
const struct_info = typeInfo.@"struct";
|
|
|
|
// Check declarations
|
|
inline for (struct_info.decls) |decl| {
|
|
// Allow deinit as extraneous
|
|
if (comptime std.mem.eql(u8, decl.name, "deinit")) continue;
|
|
|
|
var found = false;
|
|
inline for (checks) |check| {
|
|
if (std.mem.eql(u8, decl.name, check)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
std.log.err("Extraneous {s} decl: {s}", .{ @typeName(T), decl.name });
|
|
try std.testing.expect(false);
|
|
}
|
|
}
|
|
|
|
// Check fields (properties)
|
|
inline for (struct_info.fields) |field| {
|
|
// Allow internal fields (starting with underscore)
|
|
if (comptime std.mem.startsWith(u8, field.name, "_")) continue;
|
|
|
|
var found = false;
|
|
inline for (checks) |check| {
|
|
if (std.mem.eql(u8, field.name, camelToSnakeCase(check))) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
std.log.err("Extraneous {s} field: {s}", .{ @typeName(T), field.name });
|
|
try std.testing.expect(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn camelToSnakeCase(comptime input: []const u8) []const u8 {
|
|
comptime var len: usize = undefined;
|
|
comptime {
|
|
var llen: usize = input.len;
|
|
for (input, 0..) |c, i| {
|
|
if (c >= 'A' and c <= 'Z' and i != 0) {
|
|
llen += 1;
|
|
}
|
|
}
|
|
len = llen;
|
|
}
|
|
|
|
comptime var result: [len]u8 = undefined;
|
|
comptime var write_index: usize = 0;
|
|
|
|
comptime {
|
|
for (input, 0..) |c, i| {
|
|
if (c >= 'A' and c <= 'Z') {
|
|
if (i != 0) {
|
|
result[write_index] = '_';
|
|
write_index += 1;
|
|
}
|
|
result[write_index] = c + 32; // Convert to lowercase
|
|
} else {
|
|
result[write_index] = c;
|
|
}
|
|
write_index += 1;
|
|
}
|
|
}
|
|
|
|
const final = result;
|
|
return &final;
|
|
}
|