Skip to content

Commit

Permalink
Value range checks for temporal types
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lutovich committed Jun 21, 2018
1 parent dd1187d commit cfcee42
Show file tree
Hide file tree
Showing 4 changed files with 325 additions and 26 deletions.
110 changes: 110 additions & 0 deletions src/v1/internal/temporal-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

import {int, isInt} from '../integer';
import {Date, LocalDateTime, LocalTime} from '../temporal-types';
import {assertNumberOrInteger} from './util';
import {newError} from '../error';

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

class ValueRange {

constructor(min, max) {
this._minNumber = min;
this._maxNumber = max;
this._minInteger = int(min);
this._maxInteger = int(max);
}

contains(value) {
if (isInt(value)) {
return value.greaterThanOrEqual(this._minInteger) && value.lessThanOrEqual(this._maxInteger);
} else {
return value >= this._minNumber && value <= this._maxNumber;
}
}

toString() {
return `[${this._minNumber}, ${this._maxNumber}]`;
}
}

const YEAR_RANGE = new ValueRange(-999999999, 999999999);
const MONTH_OF_YEAR_RANGE = new ValueRange(1, 12);
const DAY_OF_MONTH_RANGE = new ValueRange(1, 31);
const HOUR_OF_DAY_RANGE = new ValueRange(0, 23);
const MINUTE_OF_HOUR_RANGE = new ValueRange(0, 59);
const SECOND_OF_MINUTE_RANGE = new ValueRange(0, 59);
const NANOSECOND_OF_SECOND_RANGE = new ValueRange(0, 999999999);

const MINUTES_PER_HOUR = 60;
const SECONDS_PER_MINUTE = 60;
const SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR;
Expand Down Expand Up @@ -286,6 +318,84 @@ export function timeZoneOffsetInSeconds(standardDate) {
return standardDate.getTimezoneOffset() * SECONDS_PER_MINUTE;
}

/**
* Assert that the year value is valid.
* @param {Integer|number} year the value to check.
* @return {Integer|number} the value of the year if it is valid. Exception is thrown otherwise.
*/
export function assertValidYear(year) {
return assertValidTemporalValue(year, YEAR_RANGE, 'Year');
}

/**
* Assert that the month value is valid.
* @param {Integer|number} month the value to check.
* @return {Integer|number} the value of the month if it is valid. Exception is thrown otherwise.
*/
export function assertValidMonth(month) {
return assertValidTemporalValue(month, MONTH_OF_YEAR_RANGE, 'Month');
}

/**
* Assert that the day value is valid.
* @param {Integer|number} day the value to check.
* @return {Integer|number} the value of the day if it is valid. Exception is thrown otherwise.
*/
export function assertValidDay(day) {
return assertValidTemporalValue(day, DAY_OF_MONTH_RANGE, 'Day');
}

/**
* Assert that the hour value is valid.
* @param {Integer|number} hour the value to check.
* @return {Integer|number} the value of the hour if it is valid. Exception is thrown otherwise.
*/
export function assertValidHour(hour) {
return assertValidTemporalValue(hour, HOUR_OF_DAY_RANGE, 'Hour');
}

/**
* Assert that the minute value is valid.
* @param {Integer|number} minute the value to check.
* @return {Integer|number} the value of the minute if it is valid. Exception is thrown otherwise.
*/
export function assertValidMinute(minute) {
return assertValidTemporalValue(minute, MINUTE_OF_HOUR_RANGE, 'Minute');
}

/**
* Assert that the second value is valid.
* @param {Integer|number} second the value to check.
* @return {Integer|number} the value of the second if it is valid. Exception is thrown otherwise.
*/
export function assertValidSecond(second) {
return assertValidTemporalValue(second, SECOND_OF_MINUTE_RANGE, 'Second');
}

/**
* Assert that the nanosecond value is valid.
* @param {Integer|number} nanosecond the value to check.
* @return {Integer|number} the value of the nanosecond if it is valid. Exception is thrown otherwise.
*/
export function assertValidNanosecond(nanosecond) {
return assertValidTemporalValue(nanosecond, NANOSECOND_OF_SECOND_RANGE, 'Nanosecond');
}

/**
* Check if the given value is of expected type and is in the expected range.
* @param {Integer|number} value the value to check.
* @param {ValueRange} range the range.
* @param {string} name the name of the value.
* @return {Integer|number} the value if valid. Exception is thrown otherwise.
*/
function assertValidTemporalValue(value, range, name) {
assertNumberOrInteger(value, name);
if (!range.contains(value)) {
throw newError(`${name} is expected to be in range ${range} but was: ${value}`);
}
return value;
}

/**
* Converts given local time into a single integer representing this same time in seconds of the day. Nanoseconds are skipped.
* @param {Integer|number|string} hour the hour of the local time.
Expand Down
50 changes: 25 additions & 25 deletions src/v1/temporal-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ export class LocalTime {
* @param {Integer|number} nanosecond the nanosecond for the new local time.
*/
constructor(hour, minute, second, nanosecond) {
this.hour = assertNumberOrInteger(hour, 'Hour');
this.minute = assertNumberOrInteger(minute, 'Minute');
this.second = assertNumberOrInteger(second, 'Second');
this.nanosecond = assertNumberOrInteger(nanosecond, 'Nanosecond');
this.hour = util.assertValidHour(hour);
this.minute = util.assertValidMinute(minute);
this.second = util.assertValidSecond(second);
this.nanosecond = util.assertValidNanosecond(nanosecond);
Object.freeze(this);
}

Expand Down Expand Up @@ -142,10 +142,10 @@ export class Time {
* @param {Integer|number} timeZoneOffsetSeconds the time zone offset in seconds.
*/
constructor(hour, minute, second, nanosecond, timeZoneOffsetSeconds) {
this.hour = assertNumberOrInteger(hour, 'Hour');
this.minute = assertNumberOrInteger(minute, 'Minute');
this.second = assertNumberOrInteger(second, 'Second');
this.nanosecond = assertNumberOrInteger(nanosecond, 'Nanosecond');
this.hour = util.assertValidHour(hour);
this.minute = util.assertValidMinute(minute);
this.second = util.assertValidSecond(second);
this.nanosecond = util.assertValidNanosecond(nanosecond);
this.timeZoneOffsetSeconds = assertNumberOrInteger(timeZoneOffsetSeconds, 'Time zone offset in seconds');
Object.freeze(this);
}
Expand Down Expand Up @@ -197,9 +197,9 @@ export class Date {
* @param {Integer|number} day the day for the new local date.
*/
constructor(year, month, day) {
this.year = assertNumberOrInteger(year, 'Year');
this.month = assertNumberOrInteger(month, 'Month');
this.day = assertNumberOrInteger(day, 'Day');
this.year = util.assertValidYear(year);
this.month = util.assertValidMonth(month);
this.day = util.assertValidDay(day);
Object.freeze(this);
}

Expand Down Expand Up @@ -251,13 +251,13 @@ export class LocalDateTime {
* @param {Integer|number} nanosecond the nanosecond for the new local time.
*/
constructor(year, month, day, hour, minute, second, nanosecond) {
this.year = assertNumberOrInteger(year, 'Year');
this.month = assertNumberOrInteger(month, 'Month');
this.day = assertNumberOrInteger(day, 'Day');
this.hour = assertNumberOrInteger(hour, 'Hour');
this.minute = assertNumberOrInteger(minute, 'Minute');
this.second = assertNumberOrInteger(second, 'Second');
this.nanosecond = assertNumberOrInteger(nanosecond, 'Nanosecond');
this.year = util.assertValidYear(year);
this.month = util.assertValidMonth(month);
this.day = util.assertValidDay(day);
this.hour = util.assertValidHour(hour);
this.minute = util.assertValidMinute(minute);
this.second = util.assertValidSecond(second);
this.nanosecond = util.assertValidNanosecond(nanosecond);
Object.freeze(this);
}

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

const [offset, id] = verifyTimeZoneArguments(timeZoneOffsetSeconds, timeZoneId);
this.timeZoneOffsetSeconds = offset;
Expand Down
122 changes: 122 additions & 0 deletions test/internal/temporal-util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,128 @@ describe('temporal-util', () => {
expect(util.timeZoneOffsetInSeconds(fakeStandardDateWithOffset(101))).toEqual(6060);
});

it('should verify year', () => {
expect(util.assertValidYear(-1)).toEqual(-1);
expect(util.assertValidYear(-2010)).toEqual(-2010);
expect(util.assertValidYear(int(-42))).toEqual(int(-42));
expect(util.assertValidYear(int(-2019))).toEqual(int(-2019));

expect(util.assertValidYear(0)).toEqual(0);
expect(util.assertValidYear(1)).toEqual(1);
expect(util.assertValidYear(int(2015))).toEqual(int(2015));
expect(util.assertValidYear(int(99999))).toEqual(int(99999));

expect(() => util.assertValidYear(1000000000)).toThrow();
expect(() => util.assertValidYear(1999999999)).toThrow();
expect(() => util.assertValidYear(int(2000000000))).toThrow();
expect(() => util.assertValidYear(int(3999999999))).toThrow();

expect(() => util.assertValidYear(-1000000001)).toThrow();
expect(() => util.assertValidYear(-1888888888)).toThrow();
expect(() => util.assertValidYear(int(-2000000001))).toThrow();
expect(() => util.assertValidYear(int(-3888888888))).toThrow();
});

it('should verify month', () => {
for (let i = 1; i <= 12; i++) {
expect(util.assertValidMonth(i)).toEqual(i);
expect(util.assertValidMonth(int(i))).toEqual(int(i));
}

expect(() => util.assertValidMonth(0)).toThrow();
expect(() => util.assertValidMonth(int(0))).toThrow();
expect(() => util.assertValidMonth(-1)).toThrow();
expect(() => util.assertValidMonth(int(-1))).toThrow();
expect(() => util.assertValidMonth(-42)).toThrow();
expect(() => util.assertValidMonth(int(-42))).toThrow();
expect(() => util.assertValidMonth(13)).toThrow();
expect(() => util.assertValidMonth(int(13))).toThrow();
expect(() => util.assertValidMonth(42)).toThrow();
expect(() => util.assertValidMonth(int(42))).toThrow();
});

it('should verify day', () => {
for (let i = 1; i <= 31; i++) {
expect(util.assertValidDay(i)).toEqual(i);
expect(util.assertValidDay(int(i))).toEqual(int(i));
}

expect(() => util.assertValidDay(0)).toThrow();
expect(() => util.assertValidDay(int(0))).toThrow();
expect(() => util.assertValidDay(-1)).toThrow();
expect(() => util.assertValidDay(int(-1))).toThrow();
expect(() => util.assertValidDay(-42)).toThrow();
expect(() => util.assertValidDay(int(-42))).toThrow();
expect(() => util.assertValidDay(42)).toThrow();
expect(() => util.assertValidDay(int(42))).toThrow();
});

it('should verify hour', () => {
for (let i = 0; i <= 23; i++) {
expect(util.assertValidHour(i)).toEqual(i);
expect(util.assertValidHour(int(i))).toEqual(int(i));
}

expect(() => util.assertValidHour(-1)).toThrow();
expect(() => util.assertValidHour(int(-1))).toThrow();
expect(() => util.assertValidHour(-42)).toThrow();
expect(() => util.assertValidHour(int(-42))).toThrow();
expect(() => util.assertValidHour(24)).toThrow();
expect(() => util.assertValidHour(int(24))).toThrow();
expect(() => util.assertValidHour(42)).toThrow();
expect(() => util.assertValidHour(int(42))).toThrow();
});

it('should verify minute', () => {
for (let i = 0; i <= 59; i++) {
expect(util.assertValidMinute(i)).toEqual(i);
expect(util.assertValidMinute(int(i))).toEqual(int(i));
}

expect(() => util.assertValidMinute(-1)).toThrow();
expect(() => util.assertValidMinute(int(-1))).toThrow();
expect(() => util.assertValidMinute(-42)).toThrow();
expect(() => util.assertValidMinute(int(-42))).toThrow();
expect(() => util.assertValidMinute(60)).toThrow();
expect(() => util.assertValidMinute(int(60))).toThrow();
expect(() => util.assertValidMinute(91023)).toThrow();
expect(() => util.assertValidMinute(int(1234))).toThrow();
});

it('should verify second', () => {
for (let i = 0; i <= 59; i++) {
expect(util.assertValidSecond(i)).toEqual(i);
expect(util.assertValidSecond(int(i))).toEqual(int(i));
}

expect(() => util.assertValidSecond(-1)).toThrow();
expect(() => util.assertValidSecond(int(-1))).toThrow();
expect(() => util.assertValidSecond(-42)).toThrow();
expect(() => util.assertValidSecond(int(-42))).toThrow();
expect(() => util.assertValidSecond(60)).toThrow();
expect(() => util.assertValidSecond(int(60))).toThrow();
expect(() => util.assertValidSecond(123)).toThrow();
expect(() => util.assertValidSecond(int(321))).toThrow();
});

it('should verify nanosecond', () => {
expect(util.assertValidNanosecond(0)).toEqual(0);
expect(util.assertValidNanosecond(1)).toEqual(1);
expect(util.assertValidNanosecond(42)).toEqual(42);
expect(util.assertValidNanosecond(999)).toEqual(999);
expect(util.assertValidNanosecond(123456789)).toEqual(123456789);
expect(util.assertValidNanosecond(999999999)).toEqual(999999999);

expect(() => util.assertValidNanosecond(-1)).toThrow();
expect(() => util.assertValidNanosecond(int(-1))).toThrow();
expect(() => util.assertValidNanosecond(-42)).toThrow();
expect(() => util.assertValidNanosecond(int(-42))).toThrow();
expect(() => util.assertValidNanosecond(1000000000)).toThrow();
expect(() => util.assertValidNanosecond(int(1000000000))).toThrow();
expect(() => util.assertValidNanosecond(1999999999)).toThrow();
expect(() => util.assertValidNanosecond(int(1222222222))).toThrow();
});

});

function date(year, month, day) {
Expand Down
Loading

0 comments on commit cfcee42

Please sign in to comment.