Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 44 additions & 149 deletions Jint.Tests.Test262/Test262Harness.settings.json

Large diffs are not rendered by default.

42 changes: 19 additions & 23 deletions Jint/Native/Temporal/NonIsoCalendars.cs
Original file line number Diff line number Diff line change
Expand Up @@ -408,13 +408,15 @@ internal static DurationRecord CalendarDateUntil(string calendar, in IsoDate one
{
return calendar switch
{
// Single-era calendars: always use the primary era
"hebrew" => "am",
"persian" => "ap",
"chinese" or "dangi" => null, // no eras
"coptic" => calDate.Year >= 1 ? "am" : "bd",
"ethiopic" => calDate.Year >= 1 ? "am" : "aa",
"coptic" => "am",
"ethioaa" => "aa",
"indian" => "shaka",
"chinese" or "dangi" => null, // no eras
// Multi-era calendars: era depends on year
"ethiopic" => calDate.Year >= 1 ? "am" : "aa",
"islamic-umalqura" or "islamic-civil" or "islamic-tbla" => calDate.Year >= 1 ? "ah" : "bh",
_ => null
};
Expand All @@ -427,13 +429,15 @@ internal static DurationRecord CalendarDateUntil(string calendar, in IsoDate one
{
return calendar switch
{
// Single-era calendars: eraYear = year directly
"hebrew" => calDate.Year,
"persian" => calDate.Year,
"chinese" or "dangi" => null, // no eras
"coptic" => calDate.Year >= 1 ? calDate.Year : 1 - calDate.Year,
"ethiopic" => calDate.Year >= 1 ? calDate.Year : calDate.Year + 5500,
"coptic" => calDate.Year,
"ethioaa" => calDate.Year,
"indian" => calDate.Year,
"chinese" or "dangi" => null, // no eras
// Multi-era calendars: eraYear depends on year
"ethiopic" => calDate.Year >= 1 ? calDate.Year : calDate.Year + 5500,
"islamic-umalqura" or "islamic-civil" or "islamic-tbla" => calDate.Year >= 1 ? calDate.Year : 1 - calDate.Year,
_ => null
};
Expand All @@ -450,34 +454,26 @@ internal static int MonthCodeToOrdinal(string calendar, Calendar? cal, int year,

if (calendar is "persian" or "indian" or "islamic-umalqura" or "islamic-civil" or "islamic-tbla")
{
// These calendars have no leap months
if (isLeap)
// These calendars have 12 months and no leap months
// Invalid monthCode (M13+, M00, or any leap month) is always rejected
if (isLeap || displayMonth < 1 || displayMonth > 12)
{
if (string.Equals(overflow, "reject", StringComparison.Ordinal))
{
throw new InvalidOperationException("reject");
}

// Constrain: use the base month
throw new InvalidOperationException("reject");
}

return Clamp(displayMonth, 1, 12);
return displayMonth;
}

if (calendar is "coptic" or "ethiopic" or "ethioaa")
{
// 13-month calendars with no leap months
if (isLeap)
// Invalid monthCode (M14+, M00, or any leap month) is always rejected
if (isLeap || displayMonth < 1 || displayMonth > 13)
{
if (string.Equals(overflow, "reject", StringComparison.Ordinal))
{
throw new InvalidOperationException("reject");
}

// Constrain: use the base month
throw new InvalidOperationException("reject");
}

return Clamp(displayMonth, 1, 13);
return displayMonth;
}

var leapOrdinal = GetLeapMonthOrdinal(calendar, cal, year);
Expand Down
2 changes: 2 additions & 0 deletions Jint/Native/Temporal/PlainDate/PlainDateConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ private JsPlainDate ToTemporalDateFromObjectWithOptions(ObjectInstance obj, JsVa
calendar = TemporalHelpers.ToTemporalCalendarIdentifier(_realm, calendarValue);
}

TemporalHelpers.RejectTemporalUnsupportedCalendar(_realm, calendar);

// 2. day - read and convert immediately
var dayValue = obj.Get("day");
if (dayValue.IsUndefined())
Expand Down
46 changes: 10 additions & 36 deletions Jint/Native/Temporal/PlainDate/PlainDatePrototype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,11 @@ private JsNumber GetDay(JsValue thisObject, JsCallArguments arguments)
return JsNumber.Create(TemporalHelpers.CalendarDay(pd.Calendar, pd.IsoDate));
}
private JsNumber GetDayOfWeek(JsValue thisObject, JsCallArguments arguments) => JsNumber.Create(ValidatePlainDate(thisObject).IsoDate.DayOfWeek());
private JsNumber GetDayOfYear(JsValue thisObject, JsCallArguments arguments) => JsNumber.Create(ValidatePlainDate(thisObject).IsoDate.DayOfYear());
private JsNumber GetDayOfYear(JsValue thisObject, JsCallArguments arguments)
{
var pd = ValidatePlainDate(thisObject);
return JsNumber.Create(TemporalHelpers.CalendarDayOfYear(pd.Calendar, pd.IsoDate));
}
private JsValue GetWeekOfYear(JsValue thisObject, JsCallArguments arguments)
{
var pd = ValidatePlainDate(thisObject);
Expand Down Expand Up @@ -338,20 +342,10 @@ private JsPlainDate With(JsValue thisObject, JsCallArguments arguments)
var calMonth = month ?? plainDate.IsoDate.Month;
var calDay = day ?? plainDate.IsoDate.Day;

if (monthCode is not null)
var mc = TemporalHelpers.ValidateMonthCodeForNonLeapCalendar(_realm, monthCode, month);
if (mc.HasValue)
{
var mc = TemporalHelpers.ParseMonthCode(_realm, monthCode);
if (monthCode.Length == 4 && monthCode[3] == 'L')
{
Throw.RangeError(_realm, $"Leap months are not valid for calendar: {monthCode}");
}

if (month.HasValue && month.Value != mc)
{
Throw.RangeError(_realm, "month and monthCode must match");
}

calMonth = mc;
calMonth = mc.Value;
}

var date2 = TemporalHelpers.CalendarDateToISO(_realm, plainDate.Calendar, calYear, calMonth, calDay, overflow);
Expand All @@ -368,28 +362,8 @@ private JsPlainDate With(JsValue thisObject, JsCallArguments arguments)
var finalMonth = month ?? plainDate.IsoDate.Month;
var finalDayVal = day ?? plainDate.IsoDate.Day;

// Handle monthCode - if provided, validate and use it
int? parsedMonthCode = null;
if (monthCode is not null)
{
parsedMonthCode = TemporalHelpers.ParseMonthCode(_realm, monthCode);

if (monthCode.Length == 4 && monthCode[3] == 'L')
{
Throw.RangeError(_realm, $"Leap months are not valid for ISO 8601 calendar: {monthCode}");
}

if (parsedMonthCode.Value < 1 || parsedMonthCode.Value > 12)
{
Throw.RangeError(_realm, $"Month {parsedMonthCode.Value} is not valid for ISO 8601 calendar");
}
}

if (month.HasValue && parsedMonthCode.HasValue && month.Value != parsedMonthCode.Value)
{
Throw.RangeError(_realm, "month and monthCode must match");
}

// Handle monthCode - validate and use it
var parsedMonthCode = TemporalHelpers.ValidateMonthCodeForNonLeapCalendar(_realm, monthCode, month);
if (parsedMonthCode.HasValue)
{
finalMonth = parsedMonthCode.Value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ private JsPlainDateTime ToTemporalDateTimeFromFields(ObjectInstance obj, JsValue
calendar = TemporalHelpers.ToTemporalCalendarIdentifier(_realm, calendarProp);
}

TemporalHelpers.RejectTemporalUnsupportedCalendar(_realm, calendar);

// 2. day - read and convert immediately
var dayValue = obj.Get("day");
if (dayValue.IsUndefined())
Expand Down
57 changes: 27 additions & 30 deletions Jint/Native/Temporal/PlainDateTime/PlainDateTimePrototype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,11 @@ private JsNumber GetDay(JsValue thisObject, JsCallArguments arguments)
private JsNumber GetMicrosecond(JsValue thisObject, JsCallArguments arguments) => JsNumber.Create(ValidatePlainDateTime(thisObject).IsoDateTime.Microsecond);
private JsNumber GetNanosecond(JsValue thisObject, JsCallArguments arguments) => JsNumber.Create(ValidatePlainDateTime(thisObject).IsoDateTime.Nanosecond);
private JsNumber GetDayOfWeek(JsValue thisObject, JsCallArguments arguments) => JsNumber.Create(ValidatePlainDateTime(thisObject).IsoDateTime.Date.DayOfWeek());
private JsNumber GetDayOfYear(JsValue thisObject, JsCallArguments arguments) => JsNumber.Create(ValidatePlainDateTime(thisObject).IsoDateTime.Date.DayOfYear());
private JsNumber GetDayOfYear(JsValue thisObject, JsCallArguments arguments)
{
var pdt = ValidatePlainDateTime(thisObject);
return JsNumber.Create(TemporalHelpers.CalendarDayOfYear(pdt.Calendar, pdt.IsoDateTime.Date));
}
private JsValue GetWeekOfYear(JsValue thisObject, JsCallArguments arguments)
{
var pdt = ValidatePlainDateTime(thisObject);
Expand Down Expand Up @@ -221,6 +225,8 @@ private JsPlainDateTime With(JsValue thisObject, JsCallArguments arguments)
var any = false;
JsValue v;

var isNonIso8601 = plainDateTime.Calendar is not "iso8601" and not "gregory";

v = obj.Get("day");
int day;
if (!v.IsUndefined())
Expand All @@ -230,7 +236,7 @@ private JsPlainDateTime With(JsValue thisObject, JsCallArguments arguments)
}
else
{
day = NonIsoCalendars.IsNonIsoCalendar(plainDateTime.Calendar)
day = isNonIso8601
? TemporalHelpers.CalendarDay(plainDateTime.Calendar, plainDateTime.IsoDateTime.Date)
: plainDateTime.IsoDateTime.Day;
}
Expand Down Expand Up @@ -282,7 +288,7 @@ private JsPlainDateTime With(JsValue thisObject, JsCallArguments arguments)
}
else
{
month = NonIsoCalendars.IsNonIsoCalendar(plainDateTime.Calendar)
month = isNonIso8601
? TemporalHelpers.CalendarMonth(plainDateTime.Calendar, plainDateTime.IsoDateTime.Date)
: plainDateTime.IsoDateTime.Month;
}
Expand All @@ -294,7 +300,7 @@ private JsPlainDateTime With(JsValue thisObject, JsCallArguments arguments)
any = true;
monthCode = TypeConverter.ToString(v);
}
else if (NonIsoCalendars.IsNonIsoCalendar(plainDateTime.Calendar) && !monthExplicit)
else if (isNonIso8601 && !monthExplicit)
{
monthCode = TemporalHelpers.CalendarMonthCode(plainDateTime.Calendar, plainDateTime.IsoDateTime.Date);
}
Expand Down Expand Up @@ -326,7 +332,7 @@ private JsPlainDateTime With(JsValue thisObject, JsCallArguments arguments)
}
else
{
year = NonIsoCalendars.IsNonIsoCalendar(plainDateTime.Calendar)
year = isNonIso8601
? TemporalHelpers.CalendarYear(plainDateTime.Calendar, plainDateTime.IsoDateTime.Date)
: plainDateTime.IsoDateTime.Year;
}
Expand Down Expand Up @@ -365,41 +371,32 @@ private JsPlainDateTime With(JsValue thisObject, JsCallArguments arguments)

// Process monthCode (after reading options, before algorithmic validation)
int? monthFromCode = null;
if (monthCode is not null)
if (!isNonIso8601 || TemporalHelpers.IsGregorianBasedCalendar(plainDateTime.Calendar))
{
// Use ParseMonthCode which properly handles "L" suffix for leap months
monthFromCode = TemporalHelpers.ParseMonthCode(_realm, monthCode);
// ISO/Gregorian-based: validate no leap months, month/monthCode consistency
monthFromCode = TemporalHelpers.ValidateMonthCodeForNonLeapCalendar(
_realm, monthCode, monthExplicit ? month : null);

// ISO/Gregorian calendars don't support leap months
if (!NonIsoCalendars.IsNonIsoCalendar(plainDateTime.Calendar))
if (monthFromCode.HasValue)
{
if (monthCode.Length == 4 && monthCode[3] == 'L')
{
Throw.RangeError(_realm, $"Leap months are not valid for calendar: {monthCode}");
}

if (monthFromCode.Value < 1 || monthFromCode.Value > 12)
{
Throw.RangeError(_realm, $"Month {monthFromCode.Value} is not valid for calendar");
}
month = monthFromCode.Value;
}
}

// Validate month/monthCode consistency (ISO calendars only - non-ISO ordinal ≠ display)
if (!NonIsoCalendars.IsNonIsoCalendar(plainDateTime.Calendar) &&
monthFromCode.HasValue && month != plainDateTime.IsoDateTime.Month && month != monthFromCode.Value)
else if (monthCode is not null)
{
Throw.RangeError(_realm, "month and monthCode must match");
}
// Non-ISO: just parse for well-formedness, let CalendarDateToISO handle validation
monthFromCode = TemporalHelpers.ParseMonthCode(_realm, monthCode);

// Use monthCode if provided (for ISO calendars)
if (monthFromCode.HasValue && !NonIsoCalendars.IsNonIsoCalendar(plainDateTime.Calendar))
{
month = monthFromCode.Value;
// When monthCode is explicitly provided but month is not, signal CalendarDateToIso
// to use monthCode by passing month=0 (avoids month/monthCode mismatch rejection)
if (!monthExplicit)
{
month = 0;
}
}

IsoDate? date;
if (NonIsoCalendars.IsNonIsoCalendar(plainDateTime.Calendar))
if (isNonIso8601)
{
date = TemporalHelpers.CalendarDateToISO(_realm, plainDateTime.Calendar, year, month, day, overflow, monthCode);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ private JsPlainMonthDay ToTemporalMonthDayFromFields(ObjectInstance obj, JsValue
calendar = TemporalHelpers.ToTemporalCalendarIdentifier(_realm, calendarValue);
}

TemporalHelpers.RejectTemporalUnsupportedCalendar(_realm, calendar);

// 2. day - read and convert immediately
var dayValue = obj.Get("day");
if (dayValue.IsUndefined())
Expand Down
31 changes: 8 additions & 23 deletions Jint/Native/Temporal/PlainMonthDay/PlainMonthDayPrototype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,35 +182,20 @@ private JsPlainMonthDay With(JsValue thisObject, JsCallArguments arguments)

// NOW validate monthCode (after options are read)
int? monthFromCode = null;
if (monthCode is not null)
if (!isNonIso)
{
monthFromCode = TemporalHelpers.ParseMonthCode(_realm, monthCode);
// ISO/Gregorian: validate no leap months, month/monthCode consistency
monthFromCode = TemporalHelpers.ValidateMonthCodeForNonLeapCalendar(
_realm, monthCode, !monthProp.IsUndefined() ? month : null);

// For ISO/Gregorian calendars: validate monthCode is valid (01-12, no leap months)
if (!isNonIso)
if (monthFromCode.HasValue)
{
if (monthCode.Length == 4 && monthCode[3] == 'L')
{
Throw.RangeError(_realm, $"Leap months are not valid for calendar: {monthCode}");
}

if (monthFromCode.Value < 1 || monthFromCode.Value > 12)
{
Throw.RangeError(_realm, $"Month {monthFromCode.Value} is not valid for calendar");
}
month = monthFromCode.Value;
}
}

// Validate month/monthCode consistency (ISO only)
if (!isNonIso && monthFromCode.HasValue && !monthProp.IsUndefined() && month != monthFromCode.Value)
else if (monthCode is not null)
{
Throw.RangeError(_realm, "month and monthCode must match");
}

// Use monthCode if provided (ISO only - non-ISO handled by CalendarDateToISO)
if (!isNonIso && monthFromCode.HasValue)
{
month = monthFromCode.Value;
monthFromCode = TemporalHelpers.ParseMonthCode(_realm, monthCode);
}

if (isNonIso)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ private JsPlainYearMonth ToTemporalYearMonthFromObjectWithOptions(ObjectInstance
calendar = TemporalHelpers.ToTemporalCalendarIdentifier(_realm, calendarValue);
}

TemporalHelpers.RejectTemporalUnsupportedCalendar(_realm, calendar);

// 2. era/eraYear - read for era-supporting calendars (alphabetically between calendar and month)
var eraYear = TemporalHelpers.ReadEraFields(_realm, obj, calendar);

Expand Down
Loading