From: Fabrice Bellard Date: Mon, 9 Nov 2026 16:36:13 +0000 (+0100) Subject: added missing bound checks to dates in temporal objects and simplified constructor... X-Git-Url: https://git.kaiwu.me/$%7BISSUES%7D/static/gitweb.js?a=commitdiff_plain;h=f2e4461d4df507a09961dc9be10c44ff55005a01;p=quickjs.git added missing bound checks to dates in temporal objects and simplified constructor invocation (#610) --- diff --git a/temporal/duration.c b/temporal/duration.c index 4322709..1e0e96c 100644 --- a/temporal/duration.c +++ b/temporal/duration.c @@ -54,7 +54,7 @@ static TIUnit ti_larger_of_two_units(TIUnit a, TIUnit b) return a < b ? a : b; } -JSValue js_create_duration(JSContext *ctx, const JSDurationData *d) +static JSValue js_create_duration2(JSContext *ctx, JSValueConst new_target, const JSDurationData *d) { JSDurationData *s; JSValue obj; @@ -64,7 +64,7 @@ JSValue js_create_duration(JSContext *ctx, const JSDurationData *d) *s = *d; /* Round fields to float64-representable integers to match spec behavior */ duration_round_fields_to_float64(s); - obj = JS_NewObjectClass(ctx, (int)js_duration_class_id); + obj = JS_CreateFromConstructor(ctx, new_target, js_duration_class_id); if (JS_IsException(obj)) { js_free(ctx, s); return JS_EXCEPTION; @@ -73,6 +73,11 @@ JSValue js_create_duration(JSContext *ctx, const JSDurationData *d) return obj; } +JSValue js_create_duration(JSContext *ctx, const JSDurationData *d) +{ + return js_create_duration2(ctx, JS_UNDEFINED, d); +} + static int to_integer_if_integral(JSContext *ctx, JSValueConst val, int64_t *pres) { double d; @@ -625,10 +630,6 @@ static JSValue js_duration_constructor(JSContext *ctx, JSValueConst new_target, { JSDurationData d = {0}; js_int128_t tmp_microseconds = 0, tmp_nanoseconds = 0; - JSValue obj; - if (JS_IsUndefined(new_target)) { - return JS_ThrowTypeError(ctx, "Temporal.Duration constructor cannot be called as a function"); - } if (argc > 0 && to_integer_if_integral(ctx, argv[0], &d.years) < 0) return JS_EXCEPTION; if (argc > 1 && to_integer_if_integral(ctx, argv[1], &d.months) < 0) @@ -655,18 +656,7 @@ static JSValue js_duration_constructor(JSContext *ctx, JSValueConst new_target, return JS_ThrowRangeError(ctx, "duration out of range"); if (duration_is_out_of_range(&d)) return JS_ThrowRangeError(ctx, "duration out of range"); - { - JSDurationData *s = js_mallocz(ctx, sizeof(*s)); - if (!s) - return JS_EXCEPTION; - *s = d; - obj = js_create_temporal_object(ctx, new_target, js_duration_class_id, s); - if (JS_IsException(obj)) { - js_free(ctx, s); - return JS_EXCEPTION; - } - return obj; - } + return js_create_duration2(ctx, new_target, &d); } /* Temporal.Duration.from */ diff --git a/temporal/instant.c b/temporal/instant.c index c163710..c97ae02 100644 --- a/temporal/instant.c +++ b/temporal/instant.c @@ -25,15 +25,17 @@ static const JSClassDef js_instant_class = { .finalizer = js_instant_finalizer, }; -JSValue js_create_instant(JSContext *ctx, const JSTimeSpec *ts) +static JSValue js_create_instant2(JSContext *ctx, JSValueConst new_target, const JSTimeSpec *ts) { JSInstantData *s; JSValue obj; + if (!is_valid_epoch_timespec(ts)) + return JS_ThrowRangeError(ctx, "epochNanoseconds out of range"); s = js_mallocz(ctx, sizeof(*s)); if (!s) return JS_EXCEPTION; s->ts = *ts; - obj = JS_NewObjectClass(ctx, (int)js_instant_class_id); + obj = JS_CreateFromConstructor(ctx, new_target, js_instant_class_id); if (JS_IsException(obj)) { js_free(ctx, s); return JS_EXCEPTION; @@ -42,6 +44,11 @@ JSValue js_create_instant(JSContext *ctx, const JSTimeSpec *ts) return obj; } +JSValue js_create_instant(JSContext *ctx, const JSTimeSpec *ts) +{ + return js_create_instant2(ctx, JS_UNDEFINED, ts); +} + /* --- Date math helpers --- */ static int ti_is_leap_year(int64_t y) @@ -342,32 +349,13 @@ static JSValue ti_to_temporal_instant(JSContext *ctx, JSValueConst item) static JSValue js_instant_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { - (void)argc; - JSValue obj = JS_UNDEFINED; JSTimeSpec ts; - if (JS_IsUndefined(new_target)) { - return JS_ThrowTypeError(ctx, "Temporal.Instant constructor cannot be called as a function"); - } if (JS_IsUndefined(argv[0])) { return JS_ThrowTypeError(ctx, "Temporal.Instant requires an epochNanoseconds argument"); } if (ti_bigint_to_timespec(ctx, argv[0], &ts) < 0) return JS_EXCEPTION; - if (!is_valid_epoch_timespec(&ts)) { - return JS_ThrowRangeError(ctx, "epochNanoseconds out of range"); - } - { - JSInstantData *s = js_mallocz(ctx, sizeof(*s)); - if (!s) - return JS_EXCEPTION; - s->ts = ts; - obj = js_create_temporal_object(ctx, new_target, js_instant_class_id, s); - if (JS_IsException(obj)) { - js_free(ctx, s); - return JS_EXCEPTION; - } - return obj; - } + return js_create_instant2(ctx, new_target, &ts); } /* Temporal.Instant.fromEpochMilliseconds */ @@ -408,8 +396,6 @@ static JSValue js_instant_fromEpochMilliseconds(JSContext *ctx, JSValueConst thi if (js_number_to_int64(ctx, val, &ms) < 0) return JS_EXCEPTION; ms_to_timespec(ms, &ts); - if (!is_valid_epoch_timespec(&ts)) - return JS_ThrowRangeError(ctx, "epochMilliseconds out of range"); return js_create_instant(ctx, &ts); } @@ -422,8 +408,6 @@ static JSValue js_instant_fromEpochNanoseconds(JSContext *ctx, JSValueConst this JSTimeSpec ts; if (ti_bigint_to_timespec(ctx, argv[0], &ts) < 0) return JS_EXCEPTION; - if (!is_valid_epoch_timespec(&ts)) - return JS_ThrowRangeError(ctx, "epochNanoseconds out of range"); return js_create_instant(ctx, &ts); } @@ -1051,8 +1035,6 @@ static JSValue js_instant_round(JSContext *ctx, JSValueConst this_val, /* Convert back to JSTimeSpec */ ti_ns_to_timespec(rounded_ns, &result); - if (!is_valid_epoch_timespec(&result)) - return JS_ThrowRangeError(ctx, "rounded value out of range"); return js_create_instant(ctx, &result); } @@ -1377,11 +1359,6 @@ static JSValue js_instant_add_subtract(JSContext *ctx, JSValueConst this_val, total_ns = -total_ns; total_ns += ti_timespec_to_ns(&s->ts); - - if (total_ns < (js_int128_t)(-INSTANT_MAX_SECS) * 1000000000 || - total_ns > (js_int128_t)INSTANT_MAX_SECS * 1000000000) - return JS_ThrowRangeError(ctx, "instant arithmetic overflow"); - ti_ns_to_timespec(total_ns, &result); return js_create_instant(ctx, &result); } diff --git a/temporal/plaindate.c b/temporal/plaindate.c index ef6341e..8ce1479 100644 --- a/temporal/plaindate.c +++ b/temporal/plaindate.c @@ -23,15 +23,10 @@ static const JSClassDef js_plaindate_class = { .finalizer = js_plaindate_finalizer, }; -JSValue js_create_plaindate_with_calendar(JSContext *ctx, int year, int month, int day, CalendarType calendar) +static JSValue js_create_plaindate_with_calendar2(JSContext *ctx, JSValueConst new_target, int year, int month, int day, CalendarType calendar) { - if (calendar == CAL_ISO8601 || calendar == CAL_GREGORY) { - /* ISO date range: -271821-04-19 to +275760-09-13 */ - if (year < -271821 || year > 275760 || - (year == -271821 && (month < 4 || (month == 4 && day < 19))) || - (year == 275760 && (month > 9 || (month == 9 && day > 13)))) - return JS_ThrowRangeError(ctx, "PlainDate out of range"); - } + if (!ti_is_date_within_iso_limits(year, month, day)) + return JS_ThrowRangeError(ctx, "PlainDate out of range"); { JSPlainDateData *s; JSValue obj; @@ -42,7 +37,7 @@ JSValue js_create_plaindate_with_calendar(JSContext *ctx, int year, int month, i s->month = month; s->day = day; s->calendar = calendar; - obj = JS_NewObjectClass(ctx, (int)js_plaindate_class_id); + obj = JS_CreateFromConstructor(ctx, new_target, js_plaindate_class_id); if (JS_IsException(obj)) { js_free(ctx, s); return JS_EXCEPTION; @@ -52,6 +47,11 @@ JSValue js_create_plaindate_with_calendar(JSContext *ctx, int year, int month, i } } +JSValue js_create_plaindate_with_calendar(JSContext *ctx, int year, int month, int day, CalendarType calendar) +{ + return js_create_plaindate_with_calendar2(ctx, JS_UNDEFINED, year, month, day, calendar); +} + JSValue js_create_plaindate(JSContext *ctx, int year, int month, int day) { return js_create_plaindate_with_calendar(ctx, year, month, day, CAL_ISO8601); @@ -60,20 +60,12 @@ JSValue js_create_plaindate(JSContext *ctx, int year, int month, int day) JSValue ti_create_plaindate_from_fields(JSContext *ctx, TIPreparedFields *fields, int overflow_constrain) { - int year, month, day; - JSValue obj; + int year, month, day, iso_y, iso_m, iso_d; if (ti_resolve_date_from_fields(ctx, fields, overflow_constrain, &year, &month, &day, 1) < 0) return JS_EXCEPTION; - - { - int iso_y = year, iso_m = month, iso_d = day; - if (fields->calendar != CAL_ISO8601 && fields->calendar != CAL_GREGORY) { - ti_calendar_date_to_iso(fields->calendar, year, month, day, &iso_y, &iso_m, &iso_d); - } - obj = js_create_plaindate_with_calendar(ctx, iso_y, iso_m, iso_d, fields->calendar); - } - return obj; + ti_calendar_date_to_iso(fields->calendar, year, month, day, &iso_y, &iso_m, &iso_d); + return js_create_plaindate_with_calendar(ctx, iso_y, iso_m, iso_d, fields->calendar); } int pd_is_leap_year(int year) @@ -84,6 +76,8 @@ int pd_is_leap_year(int year) int pd_days_in_month(int year, int month) { static const int days[] = {31,28,31,30,31,30,31,31,30,31,30,31}; + if (month < 1 || month > 12) + return 30; /* fail safe */ if (month == 2 && pd_is_leap_year(year)) return 29; return days[month - 1]; @@ -139,9 +133,7 @@ static JSValue js_plaindate_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { int year, month, day; - if (JS_IsUndefined(new_target)) { - return JS_ThrowTypeError(ctx, "Temporal.PlainDate constructor cannot be called as a function"); - } + CalendarType calendar = CAL_ISO8601; if (pd_to_integer_with_truncation(ctx, argv[0], &year) < 0) return JS_EXCEPTION; if (pd_to_integer_with_truncation(ctx, argv[1], &month) < 0) @@ -150,33 +142,12 @@ static JSValue js_plaindate_constructor(JSContext *ctx, JSValueConst new_target, return JS_EXCEPTION; if (!pd_is_valid_date(year, month, day)) return JS_ThrowRangeError(ctx, "invalid PlainDate"); - if (year < -271821 || year > 275760 || - (year == -271821 && (month < 4 || (month == 4 && day < 19))) || - (year == 275760 && (month > 9 || (month == 9 && day > 13)))) - return JS_ThrowRangeError(ctx, "PlainDate out of range"); - { - JSPlainDateData *s = js_mallocz(ctx, sizeof(*s)); - JSValue obj; - if (!s) - return JS_EXCEPTION; - s->year = year; - s->month = month; - s->day = day; - s->calendar = CAL_ISO8601; - if (argc > 3) { - int cal_res = ti_validate_constructor_calendar(ctx, argv[3], &s->calendar); - if (cal_res < 0) { - js_free(ctx, s); - return JS_EXCEPTION; - } - } - obj = js_create_temporal_object(ctx, new_target, js_plaindate_class_id, s); - if (JS_IsException(obj)) { - js_free(ctx, s); + calendar = CAL_ISO8601; + if (argc > 3) { + if (ti_validate_constructor_calendar(ctx, argv[3], &calendar) < 0) return JS_EXCEPTION; - } - return obj; } + return js_create_plaindate_with_calendar2(ctx, new_target, year, month, day, calendar); } /* Getters */ @@ -758,17 +729,7 @@ static JSValue js_plaindate_add_subtract(JSContext *ctx, JSValueConst this_val, ti_calendar_date_to_iso(s->calendar, year, month, day, &iso_y, &iso_m, &iso_d); year = iso_y; month = iso_m; day = iso_d; } - if (year < -271821 || year > 275760) - return JS_ThrowRangeError(ctx, "invalid PlainDate"); - /* Check against ISO date bounds */ - if (year == -271821 && (month < 4 || (month == 4 && day < 19))) - return JS_ThrowRangeError(ctx, "invalid PlainDate"); - if (year == 275760 && (month > 9 || (month == 9 && day > 13))) - return JS_ThrowRangeError(ctx, "invalid PlainDate"); - { - JSValue obj = js_create_plaindate_with_calendar(ctx, year, month, day, s->calendar); - return obj; - } + return js_create_plaindate_with_calendar(ctx, year, month, day, s->calendar); } static JSValue js_plaindate_difference(JSContext *ctx, JSValueConst this_val, @@ -1085,8 +1046,6 @@ static JSValue js_plaindate_toZonedDateTime(JSContext *ctx, JSValueConst this_va } } } - if (!is_valid_epoch_timespec(&ts)) - return JS_ThrowRangeError(ctx, "epoch nanoseconds outside valid limits"); return js_create_zoneddatetime(ctx, &ts, timeZone, s->calendar); } diff --git a/temporal/plaindatetime.c b/temporal/plaindatetime.c index 38b3646..51f2b71 100644 --- a/temporal/plaindatetime.c +++ b/temporal/plaindatetime.c @@ -40,17 +40,17 @@ JSValue js_create_plaindatetime(JSContext *ctx, int year, int month, int day, millisecond, microsecond, nanosecond, CAL_ISO8601); } -JSValue js_create_plaindatetime_with_calendar(JSContext *ctx, int year, int month, int day, - int hour, int minute, int second, - int millisecond, int microsecond, int nanosecond, - CalendarType calendar) +static JSValue js_create_plaindatetime_with_calendar2(JSContext *ctx, JSValueConst new_target, int year, int month, int day, + int hour, int minute, int second, + int millisecond, int microsecond, int nanosecond, + CalendarType calendar) { JSPlainDateTimeData *s; JSValue obj; - if ((calendar == CAL_ISO8601 || calendar == CAL_GREGORY) && - !pdt_is_within_limits(year, month, day, hour, minute, second, - millisecond, microsecond, nanosecond)) + if (!pdt_is_within_limits(year, month, day, hour, minute, second, + millisecond, microsecond, nanosecond)) { return JS_ThrowRangeError(ctx, "PlainDateTime outside valid ISO range"); + } s = js_mallocz(ctx, sizeof(*s)); if (!s) return JS_EXCEPTION; @@ -64,7 +64,7 @@ JSValue js_create_plaindatetime_with_calendar(JSContext *ctx, int year, int mont s->microsecond = microsecond; s->nanosecond = nanosecond; s->calendar = calendar; - obj = JS_NewObjectClass(ctx, (int)js_plaindatetime_class_id); + obj = JS_CreateFromConstructor(ctx, new_target, js_plaindatetime_class_id); if (JS_IsException(obj)) { js_free(ctx, s); return JS_EXCEPTION; @@ -73,6 +73,15 @@ JSValue js_create_plaindatetime_with_calendar(JSContext *ctx, int year, int mont return obj; } +JSValue js_create_plaindatetime_with_calendar(JSContext *ctx, int year, int month, int day, + int hour, int minute, int second, + int millisecond, int microsecond, int nanosecond, + CalendarType calendar) +{ + return js_create_plaindatetime_with_calendar2(ctx, JS_UNDEFINED, year, month, day, hour, minute, second, + millisecond, microsecond, nanosecond, calendar); +} + static JSValue ti_create_plaindatetime_from_fields(JSContext *ctx, TIPreparedFields *fields, int overflow_constrain) { @@ -110,7 +119,7 @@ static JSValue ti_create_plaindatetime_from_fields(JSContext *ctx, TIPreparedFie if (ns > 999) ns = 999; else if (ns < 0) ns = 0; } - if (fields->calendar != CAL_ISO8601 && fields->calendar != CAL_GREGORY) { + { int iso_y, iso_m, iso_d; ti_calendar_date_to_iso(fields->calendar, year, month, day, &iso_y, &iso_m, &iso_d); year = iso_y; month = iso_m; day = iso_d; @@ -219,9 +228,7 @@ static JSValue js_plaindatetime_constructor(JSContext *ctx, JSValueConst new_tar { int year, month, day, hour = 0, minute = 0, second = 0; int millisecond = 0, microsecond = 0, nanosecond = 0; - if (JS_IsUndefined(new_target)) { - return JS_ThrowTypeError(ctx, "Temporal.PlainDateTime constructor cannot be called as a function"); - } + CalendarType calendar = CAL_ISO8601; if (JS_IsUndefined(argv[0])) { JS_ThrowRangeError(ctx, "value must be an integer"); return JS_EXCEPTION; @@ -256,40 +263,13 @@ static JSValue js_plaindatetime_constructor(JSContext *ctx, JSValueConst new_tar return JS_ThrowRangeError(ctx, "invalid PlainDateTime date"); if (!pdt_is_valid_time(hour, minute, second, millisecond, microsecond, nanosecond)) return JS_ThrowRangeError(ctx, "invalid PlainDateTime time"); - { - CalendarType calendar = CAL_ISO8601; - if (argc > 9) { - int cal_res = ti_validate_constructor_calendar(ctx, argv[9], &calendar); - if (cal_res < 0) - return JS_EXCEPTION; - } - if ((calendar == CAL_ISO8601 || calendar == CAL_GREGORY) && - !pdt_is_within_limits(year, month, day, hour, minute, second, - millisecond, microsecond, nanosecond)) - return JS_ThrowRangeError(ctx, "PlainDateTime outside valid range"); - { - JSPlainDateTimeData *s = js_mallocz(ctx, sizeof(*s)); - JSValue obj; - if (!s) - return JS_EXCEPTION; - s->year = year; - s->month = month; - s->day = day; - s->hour = hour; - s->minute = minute; - s->second = second; - s->millisecond = millisecond; - s->microsecond = microsecond; - s->nanosecond = nanosecond; - s->calendar = calendar; - obj = js_create_temporal_object(ctx, new_target, js_plaindatetime_class_id, s); - if (JS_IsException(obj)) { - js_free(ctx, s); + if (argc > 9) { + int cal_res = ti_validate_constructor_calendar(ctx, argv[9], &calendar); + if (cal_res < 0) return JS_EXCEPTION; - } - return obj; - } } + return js_create_plaindatetime_with_calendar2(ctx, new_target, year, month, day, hour, minute, second, + millisecond, microsecond, nanosecond, calendar); } /* Getters */ @@ -963,9 +943,6 @@ static JSValue js_plaindatetime_with(JSContext *ctx, JSValueConst this_val, ti_calendar_date_to_iso(s->calendar, year, month, day, &iso_y, &iso_m, &iso_d); year = iso_y; month = iso_m; day = iso_d; } - if (!pdt_is_within_limits(year, month, day, hour, minute, second, - millisecond, microsecond, nanosecond)) - return JS_ThrowRangeError(ctx, "PlainDateTime outside valid range"); return js_create_plaindatetime_with_calendar(ctx, year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, s->calendar); } @@ -1579,8 +1556,6 @@ static JSValue js_plaindatetime_toZonedDateTime(JSContext *ctx, JSValueConst thi } } } - if (!is_valid_epoch_timespec(&ts)) - return JS_ThrowRangeError(ctx, "epoch nanoseconds outside valid limits"); return js_create_zoneddatetime(ctx, &ts, timeZone, s->calendar); } } diff --git a/temporal/plainmonthday.c b/temporal/plainmonthday.c index 9fbb4b9..cc5b51e 100644 --- a/temporal/plainmonthday.c +++ b/temporal/plainmonthday.c @@ -24,12 +24,11 @@ static const JSClassDef js_plainmonthday_class = { .finalizer = js_plainmonthday_finalizer, }; -JSValue js_create_plainmonthday_with_calendar(JSContext *ctx, int iso_year, int iso_month, int iso_day, CalendarType calendar) +static JSValue js_create_plainmonthday_with_calendar2(JSContext *ctx, JSValueConst new_target, int iso_year, int iso_month, int iso_day, CalendarType calendar) { JSPlainMonthDayData *s; JSValue obj; - /* Validate ISO year range (ISO date limits: -271821 to 275760) */ - if (iso_year < -271821 || iso_year > 275760) + if (!ti_is_date_within_iso_limits(iso_year, iso_month, iso_day)) return JS_ThrowRangeError(ctx, "PlainMonthDay out of range"); s = js_mallocz(ctx, sizeof(*s)); if (!s) @@ -38,7 +37,7 @@ JSValue js_create_plainmonthday_with_calendar(JSContext *ctx, int iso_year, int s->iso_month = iso_month; s->iso_day = iso_day; s->calendar = calendar; - obj = JS_NewObjectClass(ctx, (int)js_plainmonthday_class_id); + obj = JS_CreateFromConstructor(ctx, new_target, js_plainmonthday_class_id); if (JS_IsException(obj)) { js_free(ctx, s); return JS_EXCEPTION; @@ -47,6 +46,11 @@ JSValue js_create_plainmonthday_with_calendar(JSContext *ctx, int iso_year, int return obj; } +JSValue js_create_plainmonthday_with_calendar(JSContext *ctx, int iso_year, int iso_month, int iso_day, CalendarType calendar) +{ + return js_create_plainmonthday_with_calendar2(ctx, JS_UNDEFINED, iso_year, iso_month, iso_day, calendar); +} + static int pmd_to_integer_with_truncation(JSContext *ctx, JSValueConst val, int *pres) { double d; @@ -73,9 +77,6 @@ static JSValue js_plainmonthday_constructor(JSContext *ctx, JSValueConst new_tar int argc, JSValueConst *argv) { int month, day, year = 1972; - if (JS_IsUndefined(new_target)) { - return JS_ThrowTypeError(ctx, "Temporal.PlainMonthDay constructor cannot be called as a function"); - } if (pmd_to_integer_with_truncation(ctx, argv[0], &month) < 0) return JS_EXCEPTION; if (pmd_to_integer_with_truncation(ctx, argv[1], &day) < 0) @@ -95,27 +96,7 @@ static JSValue js_plainmonthday_constructor(JSContext *ctx, JSValueConst new_tar } if (month < 1 || month > 12 || day < 1 || day > pd_days_in_month(year, month)) return JS_ThrowRangeError(ctx, "invalid PlainMonthDay"); - /* ISODateWithinLimits check for CreateTemporalMonthDay */ - if (year < -271821 || (year == -271821 && (month < 4 || (month == 4 && day < 19)))) - return JS_ThrowRangeError(ctx, "date outside supported range"); - if (year > 275760 || (year == 275760 && (month > 9 || (month == 9 && day > 13)))) - return JS_ThrowRangeError(ctx, "date outside supported range"); - { - JSPlainMonthDayData *s = js_mallocz(ctx, sizeof(*s)); - JSValue obj; - if (!s) - return JS_EXCEPTION; - s->iso_year = year; - s->iso_month = month; - s->iso_day = day; - s->calendar = calendar; - obj = js_create_temporal_object(ctx, new_target, js_plainmonthday_class_id, s); - if (JS_IsException(obj)) { - js_free(ctx, s); - return JS_EXCEPTION; - } - return obj; - } + return js_create_plainmonthday_with_calendar2(ctx, new_target, year, month, day, calendar); } } @@ -409,6 +390,17 @@ static JSValue js_plainmonthday_with(JSContext *ctx, JSValueConst this_val, return JS_ThrowRangeError(ctx, "month and monthCode must agree"); { int ref_year = has_year ? year_for_overflow : orig_cal_y; + int max_month = ti_calendar_months_in_year(s->calendar, ref_year); + + if (month < 1 || month > max_month) { + if (!overflow_constrain) + return JS_ThrowRangeError(ctx, "invalid PlainMonthDay"); + if (month > max_month) + month = max_month; + else if (month < 1) + month = 1; + } + int dim = ti_calendar_days_in_month(s->calendar, ref_year, month); if (day > dim || day < 1) { if (!overflow_constrain) diff --git a/temporal/plaintime.c b/temporal/plaintime.c index 33d4613..109bcbd 100644 --- a/temporal/plaintime.c +++ b/temporal/plaintime.c @@ -23,7 +23,7 @@ static const JSClassDef js_plaintime_class = { .finalizer = js_plaintime_finalizer, }; -JSValue js_create_plaintime(JSContext *ctx, const JSPlainTimeData *t) +static JSValue js_create_plaintime2(JSContext *ctx, JSValueConst new_target, const JSPlainTimeData *t) { JSPlainTimeData *s; JSValue obj; @@ -31,7 +31,7 @@ JSValue js_create_plaintime(JSContext *ctx, const JSPlainTimeData *t) if (!s) return JS_EXCEPTION; *s = *t; - obj = JS_NewObjectClass(ctx, (int)js_plaintime_class_id); + obj = JS_CreateFromConstructor(ctx, new_target, js_plaintime_class_id); if (JS_IsException(obj)) { js_free(ctx, s); return JS_EXCEPTION; @@ -40,6 +40,11 @@ JSValue js_create_plaintime(JSContext *ctx, const JSPlainTimeData *t) return obj; } +JSValue js_create_plaintime(JSContext *ctx, const JSPlainTimeData *t) +{ + return js_create_plaintime2(ctx, JS_UNDEFINED, t); +} + /* Forward declaration */ JSValue js_plaintime_from(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); @@ -89,10 +94,6 @@ static JSValue js_plaintime_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { JSPlainTimeData t = {0}; - JSValue obj; - if (JS_IsUndefined(new_target)) { - return JS_ThrowTypeError(ctx, "Temporal.PlainTime constructor cannot be called as a function"); - } if (argc > 0 && pt_to_integer_with_truncation(ctx, argv[0], &t.hour) < 0) return JS_EXCEPTION; if (argc > 1 && pt_to_integer_with_truncation(ctx, argv[1], &t.minute) < 0) @@ -107,18 +108,7 @@ static JSValue js_plaintime_constructor(JSContext *ctx, JSValueConst new_target, return JS_EXCEPTION; if (!pt_is_valid_time(t.hour, t.minute, t.second, t.millisecond, t.microsecond, t.nanosecond)) return JS_ThrowRangeError(ctx, "invalid PlainTime"); - { - JSPlainTimeData *s = js_mallocz(ctx, sizeof(*s)); - if (!s) - return JS_EXCEPTION; - *s = t; - obj = js_create_temporal_object(ctx, new_target, js_plaintime_class_id, s); - if (JS_IsException(obj)) { - js_free(ctx, s); - return JS_EXCEPTION; - } - return obj; - } + return js_create_plaintime2(ctx, new_target, &t); } /* Getters */ diff --git a/temporal/plainyearmonth.c b/temporal/plainyearmonth.c index 18285df..520244b 100644 --- a/temporal/plainyearmonth.c +++ b/temporal/plainyearmonth.c @@ -23,15 +23,20 @@ static const JSClassDef js_plainyearmonth_class = { .finalizer = js_plainyearmonth_finalizer, }; -static JSValue js_create_plainyearmonth(JSContext *ctx, int year, int month, int referenceISODay) +static int pym_is_valid_year_month(int year, int month) { - return js_create_plainyearmonth_with_calendar(ctx, year, month, referenceISODay, CAL_ISO8601); + return month >= 1 && month <= 12 && + ((year > -271821 || (year == -271821 && month >= 4)) && + (year < 275760 || (year == 275760 && month <= 9))); } -JSValue js_create_plainyearmonth_with_calendar(JSContext *ctx, int year, int month, int referenceISODay, CalendarType calendar) +static JSValue js_create_plainyearmonth_with_calendar2(JSContext *ctx, JSValueConst new_target, int year, int month, int referenceISODay, CalendarType calendar) { JSPlainYearMonthData *s; JSValue obj; + /* XXX: test the day too ? */ + if (!pym_is_valid_year_month(year, month)) + return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); s = js_mallocz(ctx, sizeof(*s)); if (!s) return JS_EXCEPTION; @@ -39,7 +44,7 @@ JSValue js_create_plainyearmonth_with_calendar(JSContext *ctx, int year, int mon s->iso_month = month; s->iso_day = referenceISODay; s->calendar = calendar; - obj = JS_NewObjectClass(ctx, (int)js_plainyearmonth_class_id); + obj = JS_CreateFromConstructor(ctx, new_target, js_plainyearmonth_class_id); if (JS_IsException(obj)) { js_free(ctx, s); return JS_EXCEPTION; @@ -48,12 +53,15 @@ JSValue js_create_plainyearmonth_with_calendar(JSContext *ctx, int year, int mon return obj; } +JSValue js_create_plainyearmonth_with_calendar(JSContext *ctx, int year, int month, int referenceISODay, CalendarType calendar) +{ + return js_create_plainyearmonth_with_calendar2(ctx, JS_UNDEFINED, year, month, referenceISODay, calendar); +} + static JSValue ti_create_plainyearmonth_from_fields(JSContext *ctx, TIPreparedFields *fields, int overflow_constrain) { int year, month, dummy_day = 1; - JSValue obj; - JSPlainYearMonthData *s; if (ti_resolve_date_from_fields(ctx, fields, overflow_constrain, &year, &month, &dummy_day, 0) < 0) return JS_EXCEPTION; @@ -72,46 +80,14 @@ static JSValue ti_create_plainyearmonth_from_fields(JSContext *ctx, TIPreparedFi } } - if (fields->calendar == CAL_ISO8601 || fields->calendar == CAL_GREGORY) { - int is_valid = month >= 1 && month <= 12 && - ((year > -271821 || (year == -271821 && month >= 4)) && - (year < 275760 || (year == 275760 && month <= 9))); - if (!is_valid) - return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); - } - { - int iso_d = 1; + int iso_y, iso_m, iso_d = 1; /* Convert calendar date to ISO for all non-ISO/non-Gregory calendars */ - if (fields->calendar != CAL_ISO8601 && fields->calendar != CAL_GREGORY) { - int iso_y, iso_m; - ti_calendar_date_to_iso(fields->calendar, year, month, 1, &iso_y, &iso_m, &iso_d); - year = iso_y; - month = iso_m; - } - { - int is_valid = month >= 1 && month <= 12 && - ((year > -271821 || (year == -271821 && month >= 4)) && - (year < 275760 || (year == 275760 && month <= 9))); - if (!is_valid) - return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); - } - obj = js_create_plainyearmonth(ctx, year, month, iso_d); - } - if (!JS_IsException(obj)) { - s = JS_GetOpaque(obj, js_plainyearmonth_class_id); - if (s) { - s->calendar = fields->calendar; - } + ti_calendar_date_to_iso(fields->calendar, year, month, 1, &iso_y, &iso_m, &iso_d); + year = iso_y; + month = iso_m; + return js_create_plainyearmonth_with_calendar(ctx, year, month, iso_d, fields->calendar); } - return obj; -} - -static int pym_is_valid_year_month(int year, int month) -{ - return month >= 1 && month <= 12 && - ((year > -271821 || (year == -271821 && month >= 4)) && - (year < 275760 || (year == 275760 && month <= 9))); } static int pym_to_integer_with_truncation(JSContext *ctx, JSValueConst val, int *pres) @@ -136,19 +112,12 @@ static JSValue js_plainyearmonth_constructor(JSContext *ctx, JSValueConst new_ta int argc, JSValueConst *argv) { int year, month, day = 1; - if (JS_IsUndefined(new_target)) { - return JS_ThrowTypeError(ctx, "Temporal.PlainYearMonth constructor cannot be called as a function"); - } if (pym_to_integer_with_truncation(ctx, argv[0], &year) < 0) return JS_EXCEPTION; if (pym_to_integer_with_truncation(ctx, argv[1], &month) < 0) return JS_EXCEPTION; if (!pym_is_valid_year_month(year, month)) return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); - if (year < -271821 || (year == -271821 && month < 4)) - return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); - if (year > 275760 || (year == 275760 && month > 9)) - return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); { CalendarType calendar = CAL_ISO8601; if (argc > 2) { @@ -164,22 +133,7 @@ static JSValue js_plainyearmonth_constructor(JSContext *ctx, JSValueConst new_ta return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); if (argc <= 3 || JS_IsUndefined(argv[3])) day = 1; - { - JSPlainYearMonthData *s = js_mallocz(ctx, sizeof(*s)); - JSValue obj; - if (!s) - return JS_EXCEPTION; - s->iso_year = year; - s->iso_month = month; - s->iso_day = day; - s->calendar = calendar; - obj = js_create_temporal_object(ctx, new_target, js_plainyearmonth_class_id, s); - if (JS_IsException(obj)) { - js_free(ctx, s); - return JS_EXCEPTION; - } - return obj; - } + return js_create_plainyearmonth_with_calendar2(ctx, new_target, year, month, day, calendar); } } @@ -672,9 +626,7 @@ static JSValue js_plainyearmonth_add_subtract(JSContext *ctx, JSValueConst this_ /* Convert ISO to calendar space for arithmetic */ pym_get_calendar_date(s, &cal_year, &cal_month, &cal_day); /* Check that base date is within ISO limits */ - if (s->iso_year < -271821 || (s->iso_year == -271821 && (s->iso_month < 4 || (s->iso_month == 4 && s->iso_day < 19)))) - return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); - if (s->iso_year > 275760 || (s->iso_year == 275760 && (s->iso_month > 9 || (s->iso_month == 9 && s->iso_day > 13)))) + if (!ti_is_date_within_iso_limits(s->iso_year, s->iso_month, s->iso_day)) return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); { int orig_mc = 0, orig_has_l = 0; @@ -705,10 +657,6 @@ static JSValue js_plainyearmonth_add_subtract(JSContext *ctx, JSValueConst this_ { int iso_y, iso_m, iso_d; ti_calendar_date_to_iso(s->calendar, cal_year, cal_month, cal_day, &iso_y, &iso_m, &iso_d); - if (iso_y < -271821 || (iso_y == -271821 && iso_m < 4)) - return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); - if (iso_y > 275760 || (iso_y == 275760 && iso_m > 9)) - return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); return js_create_plainyearmonth_with_calendar(ctx, iso_y, iso_m, iso_d, s->calendar); } } diff --git a/temporal/temporal.c b/temporal/temporal.c index f1ced5d..e6bd521 100644 --- a/temporal/temporal.c +++ b/temporal/temporal.c @@ -3880,16 +3880,7 @@ int ti_validate_timezone_identifier(JSContext *ctx, JSValueConst v, const char * *out_str = str; return 1; } -JSValue js_create_temporal_object(JSContext *ctx, JSValueConst new_target, - JSClassID class_id, void *opaque) -{ - - JSValue obj = JS_CreateFromConstructor(ctx, new_target, class_id); - if (JS_IsException(obj)) - return JS_EXCEPTION; - JS_SetOpaque(obj, opaque); - return obj; -} + int is_valid_epoch_timespec(const JSTimeSpec *ts) { if (ts->secs > INSTANT_MAX_SECS || ts->secs < -INSTANT_MAX_SECS) diff --git a/temporal/temporal_private.h b/temporal/temporal_private.h index c8ba297..53801cc 100644 --- a/temporal/temporal_private.h +++ b/temporal/temporal_private.h @@ -209,7 +209,6 @@ void ti_append_calendar_annotation(char *buf, int buf_size, CalendarType cal, in int ti_canonicalize_timezone_identifier(const char *str, int64_t offset_ns, char *out, size_t out_size); int ti_validate_constructor_calendar(JSContext *ctx, JSValueConst v, CalendarType *out); int ti_validate_timezone_identifier(JSContext *ctx, JSValueConst v, const char **out_str); -JSValue js_create_temporal_object(JSContext *ctx, JSValueConst new_target, JSClassID class_id, void *opaque); int is_valid_epoch_timespec(const JSTimeSpec *ts); static inline js_int128_t ti_sec_nsec_to_ns(int64_t secs, uint32_t nsecs) diff --git a/temporal/zoneddatetime.c b/temporal/zoneddatetime.c index e5c0e02..2c3d791 100644 --- a/temporal/zoneddatetime.c +++ b/temporal/zoneddatetime.c @@ -36,18 +36,20 @@ static const JSClassDef js_zoneddatetime_class = { .finalizer = js_zoneddatetime_finalizer, }; -JSValue js_create_zoneddatetime(JSContext *ctx, const JSTimeSpec *ts, +static JSValue js_create_zoneddatetime2(JSContext *ctx, JSValueConst new_target, const JSTimeSpec *ts, const char *timeZone, CalendarType calendar) { JSZonedDateTimeData *s; JSValue obj; + if (!is_valid_epoch_timespec(ts)) + return JS_ThrowRangeError(ctx, "epoch nanoseconds outside valid limits"); s = js_mallocz(ctx, sizeof(*s)); if (!s) return JS_EXCEPTION; s->ts = *ts; pstrcpy(s->timeZone, sizeof(s->timeZone), timeZone); s->calendar = calendar; - obj = JS_NewObjectClass(ctx, (int)js_zoneddatetime_class_id); + obj = JS_CreateFromConstructor(ctx, new_target, js_zoneddatetime_class_id); if (JS_IsException(obj)) { js_free(ctx, s); return JS_EXCEPTION; @@ -56,6 +58,12 @@ JSValue js_create_zoneddatetime(JSContext *ctx, const JSTimeSpec *ts, return obj; } +JSValue js_create_zoneddatetime(JSContext *ctx, const JSTimeSpec *ts, + const char *timeZone, CalendarType calendar) +{ + return js_create_zoneddatetime2(ctx, JS_UNDEFINED, ts, timeZone, calendar); +} + /* Apply timezone offset to UTC epoch seconds to get local wall-clock time */ /* Create a ZonedDateTime from prepared fields */ JSValue ti_create_zoneddatetime_from_fields(JSContext *ctx, TIPreparedFields *fields, @@ -234,9 +242,6 @@ JSValue ti_create_zoneddatetime_from_fields(JSContext *ctx, TIPreparedFields *fi } } - if (!is_valid_epoch_timespec(&ts)) - return JS_ThrowRangeError(ctx, "epoch nanoseconds outside valid limits"); - return js_create_zoneddatetime(ctx, &ts, timeZone, fields->calendar); } static void zdt_apply_timezone_offset(JSContext *ctx, JSZonedDateTimeData *s, @@ -271,15 +276,11 @@ static void zdt_apply_timezone_offset(JSContext *ctx, JSZonedDateTimeData *s, static JSValue js_zoneddatetime_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { - JSZonedDateTimeData *s; JSValue obj; JSTimeSpec ts; const char *timeZone = "UTC"; CalendarType calendar = CAL_ISO8601; int tz_allocated = 0; - if (JS_IsUndefined(new_target)) { - return JS_ThrowTypeError(ctx, "Temporal.ZonedDateTime constructor cannot be called as a function"); - } if (ti_bigint_to_timespec(ctx, argv[0], &ts) < 0) return JS_EXCEPTION; if (!is_valid_epoch_timespec(&ts)) @@ -298,20 +299,7 @@ static JSValue js_zoneddatetime_constructor(JSContext *ctx, JSValueConst new_tar return JS_EXCEPTION; } } - s = js_mallocz(ctx, sizeof(*s)); - if (!s) { - if (tz_allocated) JS_FreeCString(ctx, timeZone); - return JS_EXCEPTION; - } - s->ts = ts; - pstrcpy(s->timeZone, sizeof(s->timeZone), timeZone); - s->calendar = calendar; - obj = js_create_temporal_object(ctx, new_target, js_zoneddatetime_class_id, s); - if (JS_IsException(obj)) { - js_free(ctx, s); - if (tz_allocated) JS_FreeCString(ctx, timeZone); - return JS_EXCEPTION; - } + obj = js_create_zoneddatetime2(ctx, new_target, &ts, timeZone, calendar); if (tz_allocated) JS_FreeCString(ctx, timeZone); return obj; } @@ -902,13 +890,6 @@ static JSValue js_zoneddatetime_startOfDay(JSContext *ctx, JSValueConst this_val } else { ts.secs -= offset_sec; } - /* Validate epoch nanoseconds are within Instant limits */ - { - js_int128_t epoch_ns = ti_timespec_to_ns(&ts); - js_int128_t limit = (js_int128_t)8640000000000LL * 1000000000LL; - if (epoch_ns < -limit || epoch_ns > limit) - return JS_ThrowRangeError(ctx, "epoch nanoseconds outside valid limits"); - } return js_create_zoneddatetime(ctx, &ts, s->timeZone, s->calendar); } @@ -1657,8 +1638,6 @@ static JSValue js_zoneddatetime_round(JSContext *ctx, JSValueConst this_val, ti_ns_to_timespec(rounded_ns, &result); - if (!is_valid_epoch_timespec(&result)) - return JS_ThrowRangeError(ctx, "outside representable range"); return js_create_zoneddatetime(ctx, &result, s->timeZone, s->calendar); } @@ -2040,8 +2019,6 @@ static JSValue js_zoneddatetime_with(JSContext *ctx, JSValueConst this_val, } } } - if (!is_valid_epoch_timespec(&ts)) - return JS_ThrowRangeError(ctx, "epoch nanoseconds outside valid limits"); return js_create_zoneddatetime(ctx, &ts, s->timeZone, s->calendar); } @@ -2134,8 +2111,6 @@ static JSValue js_zoneddatetime_withPlainTime(JSContext *ctx, JSValueConst this_ } } JS_FreeValue(ctx, pt_val); - if (!is_valid_epoch_timespec(&ts)) - return JS_ThrowRangeError(ctx, "invalid epoch nanoseconds"); return js_create_zoneddatetime(ctx, &ts, s->timeZone, s->calendar); } @@ -2755,12 +2730,6 @@ static JSValue js_zoneddatetime_add_subtract(JSContext *ctx, JSValueConst this_v ti_ns_to_timespec(total_ns, &ts); } } - { - js_int128_t epoch_ns = ti_timespec_to_ns(&ts); - js_int128_t limit = (js_int128_t)8640000000000LL * 1000000000LL; - if (epoch_ns < -limit || epoch_ns > limit) - return JS_ThrowRangeError(ctx, "epoch nanoseconds outside valid limits"); - } return js_create_zoneddatetime(ctx, &ts, s->timeZone, s->calendar); } @@ -3398,9 +3367,6 @@ JSValue js_zoneddatetime_from(JSContext *ctx, JSValueConst this_val, ts.nsecs = (uint32_t)new_nsec; } } - /* Validate epoch nanoseconds are within valid range */ - if (!is_valid_epoch_timespec(&ts)) - return JS_ThrowRangeError(ctx, "invalid ZonedDateTime string"); { CalendarType cal = r.has_calendar ? r.calendar : CAL_ISO8601; if (cal == CAL_UNKNOWN)