Skip to content

Value range checks for temporal types #393

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 28, 2018
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
134 changes: 133 additions & 1 deletion src/v1/internal/temporal-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
* limitations under the License.
*/

import {int} from '../integer';
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,10 +33,41 @@ 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;
const NANOS_PER_SECOND = 1000000000;
const NANOS_PER_MILLISECOND = 1000000;
const NANOS_PER_MINUTE = NANOS_PER_SECOND * SECONDS_PER_MINUTE;
const NANOS_PER_HOUR = NANOS_PER_MINUTE * MINUTES_PER_HOUR;
const DAYS_0000_TO_1970 = 719528;
Expand Down Expand Up @@ -264,6 +297,105 @@ export function dateToIsoString(year, month, day) {
return `${yearString}-${monthString}-${dayString}`;
}

/**
* Get the total number of nanoseconds from the milliseconds of the given standard JavaScript date and optional nanosecond part.
* @param {global.Date} standardDate the standard JavaScript date.
* @param {Integer|number|undefined} nanoseconds the optional number of nanoseconds.
* @return {Integer|number} the total amount of nanoseconds.
*/
export function totalNanoseconds(standardDate, nanoseconds) {
nanoseconds = (nanoseconds || 0);
const nanosFromMillis = standardDate.getMilliseconds() * NANOS_PER_MILLISECOND;
return isInt(nanoseconds) ? nanoseconds.add(nanosFromMillis) : nanoseconds + nanosFromMillis;
}

/**
* Get the time zone offset in seconds from the given standard JavaScript date.
* @param {global.Date} standardDate the standard JavaScript date.
* @return {number} the time zone offset in seconds.
*/
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
11 changes: 11 additions & 0 deletions src/v1/internal/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ function assertNumberOrInteger(obj, objName) {
return obj;
}

function assertValidDate(obj, objName) {
if (Object.prototype.toString.call(obj) !== '[object Date]') {
throw new TypeError(objName + ' expected to be a standard JavaScript Date but was: ' + JSON.stringify(obj));
}
if (Number.isNaN(obj.getTime())) {
throw new TypeError(objName + ' expected to be valid JavaScript Date but its time was NaN: ' + JSON.stringify(obj));
}
return obj;
}

function assertCypherStatement(obj) {
assertString(obj, 'Cypher statement');
if (obj.trim().length === 0) {
Expand All @@ -112,6 +122,7 @@ export {
assertString,
assertNumber,
assertNumberOrInteger,
assertValidDate,
validateStatementAndParameters,
ENCRYPTION_ON,
ENCRYPTION_OFF
Expand Down
Loading