Skip to content

Commit cfcee42

Browse files
committed
Value range checks for temporal types
Added checks for all temporal types to assert that values used to create them are in expected numeric ranges. This should disallow negative months, days more than 31 and things like that.
1 parent dd1187d commit cfcee42

File tree

4 files changed

+325
-26
lines changed

4 files changed

+325
-26
lines changed

src/v1/internal/temporal-util.js

+110
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
import {int, isInt} from '../integer';
2121
import {Date, LocalDateTime, LocalTime} from '../temporal-types';
22+
import {assertNumberOrInteger} from './util';
23+
import {newError} from '../error';
2224

2325
/*
2426
Code in this util should be compatible with code in the database that uses JSR-310 java.time APIs.
@@ -31,6 +33,36 @@ import {Date, LocalDateTime, LocalTime} from '../temporal-types';
3133
conversion functions.
3234
*/
3335

36+
class ValueRange {
37+
38+
constructor(min, max) {
39+
this._minNumber = min;
40+
this._maxNumber = max;
41+
this._minInteger = int(min);
42+
this._maxInteger = int(max);
43+
}
44+
45+
contains(value) {
46+
if (isInt(value)) {
47+
return value.greaterThanOrEqual(this._minInteger) && value.lessThanOrEqual(this._maxInteger);
48+
} else {
49+
return value >= this._minNumber && value <= this._maxNumber;
50+
}
51+
}
52+
53+
toString() {
54+
return `[${this._minNumber}, ${this._maxNumber}]`;
55+
}
56+
}
57+
58+
const YEAR_RANGE = new ValueRange(-999999999, 999999999);
59+
const MONTH_OF_YEAR_RANGE = new ValueRange(1, 12);
60+
const DAY_OF_MONTH_RANGE = new ValueRange(1, 31);
61+
const HOUR_OF_DAY_RANGE = new ValueRange(0, 23);
62+
const MINUTE_OF_HOUR_RANGE = new ValueRange(0, 59);
63+
const SECOND_OF_MINUTE_RANGE = new ValueRange(0, 59);
64+
const NANOSECOND_OF_SECOND_RANGE = new ValueRange(0, 999999999);
65+
3466
const MINUTES_PER_HOUR = 60;
3567
const SECONDS_PER_MINUTE = 60;
3668
const SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR;
@@ -286,6 +318,84 @@ export function timeZoneOffsetInSeconds(standardDate) {
286318
return standardDate.getTimezoneOffset() * SECONDS_PER_MINUTE;
287319
}
288320

321+
/**
322+
* Assert that the year value is valid.
323+
* @param {Integer|number} year the value to check.
324+
* @return {Integer|number} the value of the year if it is valid. Exception is thrown otherwise.
325+
*/
326+
export function assertValidYear(year) {
327+
return assertValidTemporalValue(year, YEAR_RANGE, 'Year');
328+
}
329+
330+
/**
331+
* Assert that the month value is valid.
332+
* @param {Integer|number} month the value to check.
333+
* @return {Integer|number} the value of the month if it is valid. Exception is thrown otherwise.
334+
*/
335+
export function assertValidMonth(month) {
336+
return assertValidTemporalValue(month, MONTH_OF_YEAR_RANGE, 'Month');
337+
}
338+
339+
/**
340+
* Assert that the day value is valid.
341+
* @param {Integer|number} day the value to check.
342+
* @return {Integer|number} the value of the day if it is valid. Exception is thrown otherwise.
343+
*/
344+
export function assertValidDay(day) {
345+
return assertValidTemporalValue(day, DAY_OF_MONTH_RANGE, 'Day');
346+
}
347+
348+
/**
349+
* Assert that the hour value is valid.
350+
* @param {Integer|number} hour the value to check.
351+
* @return {Integer|number} the value of the hour if it is valid. Exception is thrown otherwise.
352+
*/
353+
export function assertValidHour(hour) {
354+
return assertValidTemporalValue(hour, HOUR_OF_DAY_RANGE, 'Hour');
355+
}
356+
357+
/**
358+
* Assert that the minute value is valid.
359+
* @param {Integer|number} minute the value to check.
360+
* @return {Integer|number} the value of the minute if it is valid. Exception is thrown otherwise.
361+
*/
362+
export function assertValidMinute(minute) {
363+
return assertValidTemporalValue(minute, MINUTE_OF_HOUR_RANGE, 'Minute');
364+
}
365+
366+
/**
367+
* Assert that the second value is valid.
368+
* @param {Integer|number} second the value to check.
369+
* @return {Integer|number} the value of the second if it is valid. Exception is thrown otherwise.
370+
*/
371+
export function assertValidSecond(second) {
372+
return assertValidTemporalValue(second, SECOND_OF_MINUTE_RANGE, 'Second');
373+
}
374+
375+
/**
376+
* Assert that the nanosecond value is valid.
377+
* @param {Integer|number} nanosecond the value to check.
378+
* @return {Integer|number} the value of the nanosecond if it is valid. Exception is thrown otherwise.
379+
*/
380+
export function assertValidNanosecond(nanosecond) {
381+
return assertValidTemporalValue(nanosecond, NANOSECOND_OF_SECOND_RANGE, 'Nanosecond');
382+
}
383+
384+
/**
385+
* Check if the given value is of expected type and is in the expected range.
386+
* @param {Integer|number} value the value to check.
387+
* @param {ValueRange} range the range.
388+
* @param {string} name the name of the value.
389+
* @return {Integer|number} the value if valid. Exception is thrown otherwise.
390+
*/
391+
function assertValidTemporalValue(value, range, name) {
392+
assertNumberOrInteger(value, name);
393+
if (!range.contains(value)) {
394+
throw newError(`${name} is expected to be in range ${range} but was: ${value}`);
395+
}
396+
return value;
397+
}
398+
289399
/**
290400
* Converts given local time into a single integer representing this same time in seconds of the day. Nanoseconds are skipped.
291401
* @param {Integer|number|string} hour the hour of the local time.

src/v1/temporal-types.js

+25-25
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,10 @@ export class LocalTime {
8787
* @param {Integer|number} nanosecond the nanosecond for the new local time.
8888
*/
8989
constructor(hour, minute, second, nanosecond) {
90-
this.hour = assertNumberOrInteger(hour, 'Hour');
91-
this.minute = assertNumberOrInteger(minute, 'Minute');
92-
this.second = assertNumberOrInteger(second, 'Second');
93-
this.nanosecond = assertNumberOrInteger(nanosecond, 'Nanosecond');
90+
this.hour = util.assertValidHour(hour);
91+
this.minute = util.assertValidMinute(minute);
92+
this.second = util.assertValidSecond(second);
93+
this.nanosecond = util.assertValidNanosecond(nanosecond);
9494
Object.freeze(this);
9595
}
9696

@@ -142,10 +142,10 @@ export class Time {
142142
* @param {Integer|number} timeZoneOffsetSeconds the time zone offset in seconds.
143143
*/
144144
constructor(hour, minute, second, nanosecond, timeZoneOffsetSeconds) {
145-
this.hour = assertNumberOrInteger(hour, 'Hour');
146-
this.minute = assertNumberOrInteger(minute, 'Minute');
147-
this.second = assertNumberOrInteger(second, 'Second');
148-
this.nanosecond = assertNumberOrInteger(nanosecond, 'Nanosecond');
145+
this.hour = util.assertValidHour(hour);
146+
this.minute = util.assertValidMinute(minute);
147+
this.second = util.assertValidSecond(second);
148+
this.nanosecond = util.assertValidNanosecond(nanosecond);
149149
this.timeZoneOffsetSeconds = assertNumberOrInteger(timeZoneOffsetSeconds, 'Time zone offset in seconds');
150150
Object.freeze(this);
151151
}
@@ -197,9 +197,9 @@ export class Date {
197197
* @param {Integer|number} day the day for the new local date.
198198
*/
199199
constructor(year, month, day) {
200-
this.year = assertNumberOrInteger(year, 'Year');
201-
this.month = assertNumberOrInteger(month, 'Month');
202-
this.day = assertNumberOrInteger(day, 'Day');
200+
this.year = util.assertValidYear(year);
201+
this.month = util.assertValidMonth(month);
202+
this.day = util.assertValidDay(day);
203203
Object.freeze(this);
204204
}
205205

@@ -251,13 +251,13 @@ export class LocalDateTime {
251251
* @param {Integer|number} nanosecond the nanosecond for the new local time.
252252
*/
253253
constructor(year, month, day, hour, minute, second, nanosecond) {
254-
this.year = assertNumberOrInteger(year, 'Year');
255-
this.month = assertNumberOrInteger(month, 'Month');
256-
this.day = assertNumberOrInteger(day, 'Day');
257-
this.hour = assertNumberOrInteger(hour, 'Hour');
258-
this.minute = assertNumberOrInteger(minute, 'Minute');
259-
this.second = assertNumberOrInteger(second, 'Second');
260-
this.nanosecond = assertNumberOrInteger(nanosecond, 'Nanosecond');
254+
this.year = util.assertValidYear(year);
255+
this.month = util.assertValidMonth(month);
256+
this.day = util.assertValidDay(day);
257+
this.hour = util.assertValidHour(hour);
258+
this.minute = util.assertValidMinute(minute);
259+
this.second = util.assertValidSecond(second);
260+
this.nanosecond = util.assertValidNanosecond(nanosecond);
261261
Object.freeze(this);
262262
}
263263

@@ -316,13 +316,13 @@ export class DateTime {
316316
* @param {string|null} timeZoneId the time zone id for the new date-time. Either this argument or <code>timeZoneOffsetSeconds</code> should be defined.
317317
*/
318318
constructor(year, month, day, hour, minute, second, nanosecond, timeZoneOffsetSeconds, timeZoneId) {
319-
this.year = assertNumberOrInteger(year, 'Year');
320-
this.month = assertNumberOrInteger(month, 'Month');
321-
this.day = assertNumberOrInteger(day, 'Day');
322-
this.hour = assertNumberOrInteger(hour, 'Hour');
323-
this.minute = assertNumberOrInteger(minute, 'Minute');
324-
this.second = assertNumberOrInteger(second, 'Second');
325-
this.nanosecond = assertNumberOrInteger(nanosecond, 'Nanosecond');
319+
this.year = util.assertValidYear(year);
320+
this.month = util.assertValidMonth(month);
321+
this.day = util.assertValidDay(day);
322+
this.hour = util.assertValidHour(hour);
323+
this.minute = util.assertValidMinute(minute);
324+
this.second = util.assertValidSecond(second);
325+
this.nanosecond = util.assertValidNanosecond(nanosecond);
326326

327327
const [offset, id] = verifyTimeZoneArguments(timeZoneOffsetSeconds, timeZoneId);
328328
this.timeZoneOffsetSeconds = offset;

test/internal/temporal-util.test.js

+122
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,128 @@ describe('temporal-util', () => {
195195
expect(util.timeZoneOffsetInSeconds(fakeStandardDateWithOffset(101))).toEqual(6060);
196196
});
197197

198+
it('should verify year', () => {
199+
expect(util.assertValidYear(-1)).toEqual(-1);
200+
expect(util.assertValidYear(-2010)).toEqual(-2010);
201+
expect(util.assertValidYear(int(-42))).toEqual(int(-42));
202+
expect(util.assertValidYear(int(-2019))).toEqual(int(-2019));
203+
204+
expect(util.assertValidYear(0)).toEqual(0);
205+
expect(util.assertValidYear(1)).toEqual(1);
206+
expect(util.assertValidYear(int(2015))).toEqual(int(2015));
207+
expect(util.assertValidYear(int(99999))).toEqual(int(99999));
208+
209+
expect(() => util.assertValidYear(1000000000)).toThrow();
210+
expect(() => util.assertValidYear(1999999999)).toThrow();
211+
expect(() => util.assertValidYear(int(2000000000))).toThrow();
212+
expect(() => util.assertValidYear(int(3999999999))).toThrow();
213+
214+
expect(() => util.assertValidYear(-1000000001)).toThrow();
215+
expect(() => util.assertValidYear(-1888888888)).toThrow();
216+
expect(() => util.assertValidYear(int(-2000000001))).toThrow();
217+
expect(() => util.assertValidYear(int(-3888888888))).toThrow();
218+
});
219+
220+
it('should verify month', () => {
221+
for (let i = 1; i <= 12; i++) {
222+
expect(util.assertValidMonth(i)).toEqual(i);
223+
expect(util.assertValidMonth(int(i))).toEqual(int(i));
224+
}
225+
226+
expect(() => util.assertValidMonth(0)).toThrow();
227+
expect(() => util.assertValidMonth(int(0))).toThrow();
228+
expect(() => util.assertValidMonth(-1)).toThrow();
229+
expect(() => util.assertValidMonth(int(-1))).toThrow();
230+
expect(() => util.assertValidMonth(-42)).toThrow();
231+
expect(() => util.assertValidMonth(int(-42))).toThrow();
232+
expect(() => util.assertValidMonth(13)).toThrow();
233+
expect(() => util.assertValidMonth(int(13))).toThrow();
234+
expect(() => util.assertValidMonth(42)).toThrow();
235+
expect(() => util.assertValidMonth(int(42))).toThrow();
236+
});
237+
238+
it('should verify day', () => {
239+
for (let i = 1; i <= 31; i++) {
240+
expect(util.assertValidDay(i)).toEqual(i);
241+
expect(util.assertValidDay(int(i))).toEqual(int(i));
242+
}
243+
244+
expect(() => util.assertValidDay(0)).toThrow();
245+
expect(() => util.assertValidDay(int(0))).toThrow();
246+
expect(() => util.assertValidDay(-1)).toThrow();
247+
expect(() => util.assertValidDay(int(-1))).toThrow();
248+
expect(() => util.assertValidDay(-42)).toThrow();
249+
expect(() => util.assertValidDay(int(-42))).toThrow();
250+
expect(() => util.assertValidDay(42)).toThrow();
251+
expect(() => util.assertValidDay(int(42))).toThrow();
252+
});
253+
254+
it('should verify hour', () => {
255+
for (let i = 0; i <= 23; i++) {
256+
expect(util.assertValidHour(i)).toEqual(i);
257+
expect(util.assertValidHour(int(i))).toEqual(int(i));
258+
}
259+
260+
expect(() => util.assertValidHour(-1)).toThrow();
261+
expect(() => util.assertValidHour(int(-1))).toThrow();
262+
expect(() => util.assertValidHour(-42)).toThrow();
263+
expect(() => util.assertValidHour(int(-42))).toThrow();
264+
expect(() => util.assertValidHour(24)).toThrow();
265+
expect(() => util.assertValidHour(int(24))).toThrow();
266+
expect(() => util.assertValidHour(42)).toThrow();
267+
expect(() => util.assertValidHour(int(42))).toThrow();
268+
});
269+
270+
it('should verify minute', () => {
271+
for (let i = 0; i <= 59; i++) {
272+
expect(util.assertValidMinute(i)).toEqual(i);
273+
expect(util.assertValidMinute(int(i))).toEqual(int(i));
274+
}
275+
276+
expect(() => util.assertValidMinute(-1)).toThrow();
277+
expect(() => util.assertValidMinute(int(-1))).toThrow();
278+
expect(() => util.assertValidMinute(-42)).toThrow();
279+
expect(() => util.assertValidMinute(int(-42))).toThrow();
280+
expect(() => util.assertValidMinute(60)).toThrow();
281+
expect(() => util.assertValidMinute(int(60))).toThrow();
282+
expect(() => util.assertValidMinute(91023)).toThrow();
283+
expect(() => util.assertValidMinute(int(1234))).toThrow();
284+
});
285+
286+
it('should verify second', () => {
287+
for (let i = 0; i <= 59; i++) {
288+
expect(util.assertValidSecond(i)).toEqual(i);
289+
expect(util.assertValidSecond(int(i))).toEqual(int(i));
290+
}
291+
292+
expect(() => util.assertValidSecond(-1)).toThrow();
293+
expect(() => util.assertValidSecond(int(-1))).toThrow();
294+
expect(() => util.assertValidSecond(-42)).toThrow();
295+
expect(() => util.assertValidSecond(int(-42))).toThrow();
296+
expect(() => util.assertValidSecond(60)).toThrow();
297+
expect(() => util.assertValidSecond(int(60))).toThrow();
298+
expect(() => util.assertValidSecond(123)).toThrow();
299+
expect(() => util.assertValidSecond(int(321))).toThrow();
300+
});
301+
302+
it('should verify nanosecond', () => {
303+
expect(util.assertValidNanosecond(0)).toEqual(0);
304+
expect(util.assertValidNanosecond(1)).toEqual(1);
305+
expect(util.assertValidNanosecond(42)).toEqual(42);
306+
expect(util.assertValidNanosecond(999)).toEqual(999);
307+
expect(util.assertValidNanosecond(123456789)).toEqual(123456789);
308+
expect(util.assertValidNanosecond(999999999)).toEqual(999999999);
309+
310+
expect(() => util.assertValidNanosecond(-1)).toThrow();
311+
expect(() => util.assertValidNanosecond(int(-1))).toThrow();
312+
expect(() => util.assertValidNanosecond(-42)).toThrow();
313+
expect(() => util.assertValidNanosecond(int(-42))).toThrow();
314+
expect(() => util.assertValidNanosecond(1000000000)).toThrow();
315+
expect(() => util.assertValidNanosecond(int(1000000000))).toThrow();
316+
expect(() => util.assertValidNanosecond(1999999999)).toThrow();
317+
expect(() => util.assertValidNanosecond(int(1222222222))).toThrow();
318+
});
319+
198320
});
199321

200322
function date(year, month, day) {

0 commit comments

Comments
 (0)