From c6c1a087014d5ce977c5de60e5494b6a062b3bb1 Mon Sep 17 00:00:00 2001 From: lutovich Date: Tue, 19 Jun 2018 18:39:05 +0200 Subject: [PATCH 1/4] Allow to create temporal objects from standard JS Date This commit adds `#fromStandardDate()` functions to all temporal types except `Duration`. Such functions allow to create temporal objects from the provided standard JavaScript `Date`. --- src/v1/internal/temporal-util.js | 24 +++- src/v1/internal/util.js | 11 ++ src/v1/temporal-types.js | 100 ++++++++++++++- test/internal/temporal-util.test.js | 32 +++++ test/internal/util.test.js | 25 ++++ test/types/v1/temporal-types.test.ts | 13 ++ test/v1/temporal-types.test.js | 175 +++++++++++++++++++++++++++ types/v1/graph-types.d.ts | 4 +- types/v1/temporal-types.d.ts | 12 +- 9 files changed, 392 insertions(+), 4 deletions(-) diff --git a/src/v1/internal/temporal-util.js b/src/v1/internal/temporal-util.js index ca6a63bfa..f8c44b40a 100644 --- a/src/v1/internal/temporal-util.js +++ b/src/v1/internal/temporal-util.js @@ -17,7 +17,7 @@ * limitations under the License. */ -import {int} from '../integer'; +import {int, isInt} from '../integer'; import {Date, LocalDateTime, LocalTime} from '../temporal-types'; /* @@ -35,6 +35,7 @@ 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; @@ -264,6 +265,27 @@ 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 total number of nanoseconds from the given standard JavaScript date. + * @param {global.Date} standardDate the standard JavaScript date. + * @return {number} the total amount of nanoseconds. + */ +export function timeZoneOffsetInSeconds(standardDate) { + return standardDate.getTimezoneOffset() * SECONDS_PER_MINUTE; +} + /** * 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. diff --git a/src/v1/internal/util.js b/src/v1/internal/util.js index 437be20be..aa858f87c 100644 --- a/src/v1/internal/util.js +++ b/src/v1/internal/util.js @@ -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) { @@ -112,6 +122,7 @@ export { assertString, assertNumber, assertNumberOrInteger, + assertValidDate, validateStatementAndParameters, ENCRYPTION_ON, ENCRYPTION_OFF diff --git a/src/v1/temporal-types.js b/src/v1/temporal-types.js index 0ca11b7df..966747959 100644 --- a/src/v1/temporal-types.js +++ b/src/v1/temporal-types.js @@ -18,7 +18,7 @@ */ import * as util from './internal/temporal-util'; -import {assertNumberOrInteger, assertString} from './internal/util'; +import {assertNumberOrInteger, assertString, assertValidDate} from './internal/util'; import {newError} from './error'; const IDENTIFIER_PROPERTY_ATTRIBUTES = { @@ -94,6 +94,23 @@ export class LocalTime { Object.freeze(this); } + /** + * Create a local time object from the given standard JavaScript Date and optional nanoseconds. + * Year, month, day and time zone offset components of the given date are ignored. + * @param {global.Date} standardDate the standard JavaScript date to convert. + * @param {Integer|number|undefined} nanosecond the optional amount of nanoseconds. + * @return {LocalTime} new local time. + */ + static fromStandardDate(standardDate, nanosecond) { + verifyStandardDateAndNanos(standardDate, nanosecond); + + return new LocalTime( + standardDate.getHours(), + standardDate.getMinutes(), + standardDate.getSeconds(), + util.totalNanoseconds(standardDate, nanosecond)); + } + toString() { return util.timeToIsoString(this.hour, this.minute, this.second, this.nanosecond); } @@ -133,6 +150,24 @@ export class Time { Object.freeze(this); } + /** + * Create a time object from the given standard JavaScript Date and optional nanoseconds. + * Year, month and day components of the given date are ignored. + * @param {global.Date} standardDate the standard JavaScript date to convert. + * @param {Integer|number|undefined} nanosecond the optional amount of nanoseconds. + * @return {Time} new time. + */ + static fromStandardDate(standardDate, nanosecond) { + verifyStandardDateAndNanos(standardDate, nanosecond); + + return new Time( + standardDate.getHours(), + standardDate.getMinutes(), + standardDate.getSeconds(), + util.totalNanoseconds(standardDate, nanosecond), + util.timeZoneOffsetInSeconds(standardDate)); + } + toString() { return util.timeToIsoString(this.hour, this.minute, this.second, this.nanosecond) + util.timeZoneOffsetToIsoString(this.timeZoneOffsetSeconds); } @@ -168,6 +203,21 @@ export class Date { Object.freeze(this); } + /** + * Create a date object from the given standard JavaScript Date. + * Hour, minute, second, millisecond and time zone offset components of the given date are ignored. + * @param {global.Date} standardDate the standard JavaScript date to convert. + * @return {Date} new date. + */ + static fromStandardDate(standardDate) { + verifyStandardDateAndNanos(standardDate, null); + + return new Date( + standardDate.getFullYear(), + standardDate.getMonth(), + standardDate.getDate()); + } + toString() { return util.dateToIsoString(this.year, this.month, this.day); } @@ -211,6 +261,26 @@ export class LocalDateTime { Object.freeze(this); } + /** + * Create a local date-time object from the given standard JavaScript Date and optional nanoseconds. + * Time zone offset component of the given date is ignored. + * @param {global.Date} standardDate the standard JavaScript date to convert. + * @param {Integer|number|undefined} nanosecond the optional amount of nanoseconds. + * @return {LocalDateTime} new local date-time. + */ + static fromStandardDate(standardDate, nanosecond) { + verifyStandardDateAndNanos(standardDate, nanosecond); + + return new LocalDateTime( + standardDate.getFullYear(), + standardDate.getMonth(), + standardDate.getDate(), + standardDate.getHours(), + standardDate.getMinutes(), + standardDate.getSeconds(), + util.totalNanoseconds(standardDate, nanosecond)); + } + toString() { return localDateTimeToString(this.year, this.month, this.day, this.hour, this.minute, this.second, this.nanosecond); } @@ -261,6 +331,27 @@ export class DateTime { Object.freeze(this); } + /** + * Create a date-time object from the given standard JavaScript Date and optional nanoseconds. + * @param {global.Date} standardDate the standard JavaScript date to convert. + * @param {Integer|number|undefined} nanosecond the optional amount of nanoseconds. + * @return {DateTime} new date-time. + */ + static fromStandardDate(standardDate, nanosecond) { + verifyStandardDateAndNanos(standardDate, nanosecond); + + return new DateTime( + standardDate.getFullYear(), + standardDate.getMonth(), + standardDate.getDate(), + standardDate.getHours(), + standardDate.getMinutes(), + standardDate.getSeconds(), + util.totalNanoseconds(standardDate, nanosecond), + util.timeZoneOffsetInSeconds(standardDate), + null /* no time zone id */); + } + toString() { const localDateTimeStr = localDateTimeToString(this.year, this.month, this.day, this.hour, this.minute, this.second, this.nanosecond); const timeZoneStr = this.timeZoneId ? `[${this.timeZoneId}]` : util.timeZoneOffsetToIsoString(this.timeZoneOffsetSeconds); @@ -303,3 +394,10 @@ function verifyTimeZoneArguments(timeZoneOffsetSeconds, timeZoneId) { throw newError(`Unable to create DateTime without either time zone offset or id. Please specify either of them. Given offset: ${timeZoneOffsetSeconds} and id: ${timeZoneId}`); } } + +function verifyStandardDateAndNanos(standardDate, nanosecond) { + assertValidDate(standardDate, 'Standard date'); + if (nanosecond !== null && nanosecond !== undefined) { + assertNumberOrInteger(nanosecond, 'Nanosecond'); + } +} diff --git a/test/internal/temporal-util.test.js b/test/internal/temporal-util.test.js index c9108cee1..f0d05c387 100644 --- a/test/internal/temporal-util.test.js +++ b/test/internal/temporal-util.test.js @@ -169,6 +169,32 @@ describe('temporal-util', () => { expect(util.localTimeToNanoOfDay(12, 51, 17, 808080)).toEqual(int(46277000808080)); }); + it('should get total nanoseconds from standard date', () => { + expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 0))).toEqual(0); + expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 1))).toEqual(1000000); + expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 23))).toEqual(23000000); + expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 999))).toEqual(999000000); + + expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 0), 0)).toEqual(0); + expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 0), 1)).toEqual(1); + expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 0), 999)).toEqual(999); + expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 1), 999)).toEqual(1000999); + expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 999), 111)).toEqual(999000111); + + expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 0), int(0))).toEqual(int(0)); + expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 0), int(1))).toEqual(int(1)); + expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 0), int(999))).toEqual(int(999)); + expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 1), int(999))).toEqual(int(1000999)); + expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 999), int(111))).toEqual(int(999000111)); + }); + + it('should get timezone offset in seconds from standard date', () => { + expect(util.timeZoneOffsetInSeconds(fakeStandardDateWithOffset(0))).toEqual(0); + expect(util.timeZoneOffsetInSeconds(fakeStandardDateWithOffset(2))).toEqual(120); + expect(util.timeZoneOffsetInSeconds(fakeStandardDateWithOffset(10))).toEqual(600); + expect(util.timeZoneOffsetInSeconds(fakeStandardDateWithOffset(101))).toEqual(6060); + }); + }); function date(year, month, day) { @@ -182,3 +208,9 @@ function localTime(hour, minute, second, nanosecond) { function localDateTime(year, month, day, hour, minute, second, nanosecond) { return new types.LocalDateTime(int(year), int(month), int(day), int(hour), int(minute), int(second), int(nanosecond)); } + +function fakeStandardDateWithOffset(offsetMinutes) { + const date = new Date(); + date.getTimezoneOffset = () => offsetMinutes; + return date; +} diff --git a/test/internal/util.test.js b/test/internal/util.test.js index 58cc873d6..6fc420ca9 100644 --- a/test/internal/util.test.js +++ b/test/internal/util.test.js @@ -135,6 +135,23 @@ describe('util', () => { verifyInvalidNumberOrInteger({value: 42}); }); + it('should check dates', () => { + verifyValidDate(new Date()); + verifyValidDate(new Date(0)); + verifyValidDate(new Date(-1)); + verifyValidDate(new Date(2000, 10, 10)); + verifyValidDate(new Date(2000, 10, 10, 10, 10, 10, 10)); + + verifyInvalidDate(new Date('not a valid date')); + verifyInvalidDate(new Date({})); + verifyInvalidDate(new Date([])); + + verifyInvalidDate({}); + verifyInvalidDate([]); + verifyInvalidDate('2007-04-05T12:30-02:00'); + verifyInvalidDate(2019); + }); + function verifyValidString(str) { expect(util.assertString(str, 'Test string')).toBe(str); } @@ -171,4 +188,12 @@ describe('util', () => { expect(() => util.validateStatementAndParameters('RETURN 1', obj)).toThrowError(TypeError); } + function verifyValidDate(obj) { + expect(util.assertValidDate(obj, 'Test date')).toBe(obj); + } + + function verifyInvalidDate(obj) { + expect(() => util.assertValidDate(obj, 'Test date')).toThrowError(TypeError); + } + }); diff --git a/test/types/v1/temporal-types.test.ts b/test/types/v1/temporal-types.test.ts index 996b73208..30e000b8c 100644 --- a/test/types/v1/temporal-types.test.ts +++ b/test/types/v1/temporal-types.test.ts @@ -32,6 +32,7 @@ import { Time } from "../../../types/v1/temporal-types"; import Integer, {int} from "../../../types/v1/integer"; +import {StandardDate} from "../../../types/v1/graph-types"; const duration1: Duration = new Duration(int(1), int(1), int(1), int(1)); const months1: Integer = duration1.months; @@ -149,3 +150,15 @@ const isTimeValue: boolean = isTime(time1); const isDateValue: boolean = isDate(date1); const isLocalDateTimeValue: boolean = isLocalDateTime(localDateTime1); const isDateTimeValue: boolean = isDateTime(dateTime1); + +const dummy: any = null; +const standardDate: StandardDate = dummy; +const localTime3: LocalTime = LocalTime.fromStandardDate(standardDate); +const localTime4: LocalTime = LocalTime.fromStandardDate(standardDate, 42); +const time3: Time = Time.fromStandardDate(standardDate); +const time4: Time = Time.fromStandardDate(standardDate, 42); +const date3: Date = Date.fromStandardDate(standardDate); +const localDateTime3: LocalDateTime = LocalDateTime.fromStandardDate(standardDate); +const localDateTime4: LocalDateTime = LocalDateTime.fromStandardDate(standardDate, 42); +const dateTime5: DateTime = DateTime.fromStandardDate(standardDate); +const dateTime6: DateTime = DateTime.fromStandardDate(standardDate, 42); diff --git a/test/v1/temporal-types.test.js b/test/v1/temporal-types.test.js index cbc926f12..16647c7c6 100644 --- a/test/v1/temporal-types.test.js +++ b/test/v1/temporal-types.test.js @@ -19,6 +19,7 @@ import neo4j from '../../src'; import sharedNeo4j from '../internal/shared-neo4j'; +import {totalNanoseconds} from '../../src/v1/internal/temporal-util'; import {ServerVersion, VERSION_3_4_0} from '../../src/v1/internal/server-version'; import timesSeries from 'async/timesSeries'; import _ from 'lodash'; @@ -658,6 +659,147 @@ describe('temporal-types', () => { expect(() => new neo4j.types.DateTime(1, 2, 3, 4, 5, 6, 7, 8, 'UK')).toThrow(); }); + it('should convert standard Date to neo4j LocalTime', () => { + testStandardDateToLocalTimeConversion(new Date(2000, 1, 1, 0, 0, 0, 0)); + testStandardDateToLocalTimeConversion(new Date(1456, 7, 12, 12, 0, 0, 0)); + testStandardDateToLocalTimeConversion(new Date(2121, 11, 27, 21, 56, 0, 0)); + testStandardDateToLocalTimeConversion(new Date(1392, 2, 2, 3, 14, 59, 0)); + testStandardDateToLocalTimeConversion(new Date(1102, 6, 5, 17, 12, 32, 99)); + testStandardDateToLocalTimeConversion(new Date(2019, 2, 7, 0, 0, 0, 1)); + + testStandardDateToLocalTimeConversion(new Date(1351, 4, 7, 0, 0, 0, 0), neo4j.int(1)); + testStandardDateToLocalTimeConversion(new Date(3841, 1, 19, 0, 0, 0, 0), neo4j.int(99)); + testStandardDateToLocalTimeConversion(new Date(2222, 3, 29, 0, 0, 0, 0), neo4j.int(999999999)); + }); + + it('should fail to convert invalid standard Date to neo4j LocalTime', () => { + const LocalTime = neo4j.types.LocalTime; + + expect(() => LocalTime.fromStandardDate()).toThrowError(TypeError); + expect(() => LocalTime.fromStandardDate('2007-04-05T12:30-02:00')).toThrowError(TypeError); + expect(() => LocalTime.fromStandardDate({})).toThrowError(TypeError); + + expect(() => LocalTime.fromStandardDate(new Date({}))).toThrowError(TypeError); + expect(() => LocalTime.fromStandardDate(new Date([]))).toThrowError(TypeError); + expect(() => LocalTime.fromStandardDate(new Date(NaN))).toThrowError(TypeError); + + expect(() => LocalTime.fromStandardDate(new Date(), '1')).toThrowError(TypeError); + expect(() => LocalTime.fromStandardDate(new Date(), {nanosecond: 1})).toThrowError(TypeError); + expect(() => LocalTime.fromStandardDate(new Date(), [1])).toThrowError(TypeError); + }); + + it('should convert standard Date to neo4j Time', () => { + testStandardDateToTimeConversion(new Date(2000, 1, 1, 0, 0, 0, 0)); + testStandardDateToTimeConversion(new Date(1456, 7, 12, 12, 0, 0, 0)); + testStandardDateToTimeConversion(new Date(2121, 11, 27, 21, 56, 0, 0)); + testStandardDateToTimeConversion(new Date(1392, 2, 2, 3, 14, 59, 0)); + testStandardDateToTimeConversion(new Date(1102, 6, 5, 17, 12, 32, 99)); + testStandardDateToTimeConversion(new Date(2019, 2, 7, 0, 0, 0, 1)); + + testStandardDateToTimeConversion(new Date(1351, 4, 7, 0, 0, 0, 0), neo4j.int(1)); + testStandardDateToTimeConversion(new Date(3841, 1, 19, 0, 0, 0, 0), neo4j.int(99)); + testStandardDateToTimeConversion(new Date(2222, 3, 29, 0, 0, 0, 0), neo4j.int(999999999)); + }); + + it('should fail to convert invalid standard Date to neo4j Time', () => { + const Time = neo4j.types.Time; + + expect(() => Time.fromStandardDate()).toThrowError(TypeError); + expect(() => Time.fromStandardDate('2007-04-05T12:30-02:00')).toThrowError(TypeError); + expect(() => Time.fromStandardDate({})).toThrowError(TypeError); + + expect(() => Time.fromStandardDate(new Date({}))).toThrowError(TypeError); + expect(() => Time.fromStandardDate(new Date([]))).toThrowError(TypeError); + expect(() => Time.fromStandardDate(new Date(NaN))).toThrowError(TypeError); + + expect(() => Time.fromStandardDate(new Date(), '1')).toThrowError(TypeError); + expect(() => Time.fromStandardDate(new Date(), {nanosecond: 1})).toThrowError(TypeError); + expect(() => Time.fromStandardDate(new Date(), [1])).toThrowError(TypeError); + }); + + it('should convert standard Date to neo4j Date', () => { + testStandardDateToNeo4jDateConversion(new Date(2000, 1, 1)); + testStandardDateToNeo4jDateConversion(new Date(1456, 7, 12)); + testStandardDateToNeo4jDateConversion(new Date(2121, 11, 27)); + testStandardDateToNeo4jDateConversion(new Date(1392, 2, 2)); + testStandardDateToNeo4jDateConversion(new Date(1102, 6, 5)); + testStandardDateToNeo4jDateConversion(new Date(2019, 2, 7)); + + testStandardDateToNeo4jDateConversion(new Date(1351, 4, 7)); + testStandardDateToNeo4jDateConversion(new Date(3841, 1, 19)); + testStandardDateToNeo4jDateConversion(new Date(2222, 3, 29)); + }); + + it('should fail to convert invalid standard Date to neo4j Date', () => { + const Neo4jDate = neo4j.types.Date; + + expect(() => Neo4jDate.fromStandardDate()).toThrowError(TypeError); + expect(() => Neo4jDate.fromStandardDate('2007-04-05T12:30-02:00')).toThrowError(TypeError); + expect(() => Neo4jDate.fromStandardDate({})).toThrowError(TypeError); + + expect(() => Neo4jDate.fromStandardDate(new Date({}))).toThrowError(TypeError); + expect(() => Neo4jDate.fromStandardDate(new Date([]))).toThrowError(TypeError); + expect(() => Neo4jDate.fromStandardDate(new Date(NaN))).toThrowError(TypeError); + }); + + it('should convert standard Date to neo4j LocalDateTime', () => { + testStandardDateToLocalDateTimeConversion(new Date(2011, 9, 18)); + testStandardDateToLocalDateTimeConversion(new Date(1455, 0, 1)); + testStandardDateToLocalDateTimeConversion(new Date(0)); + testStandardDateToLocalDateTimeConversion(new Date(2056, 5, 22, 21, 59, 12, 999)); + + testStandardDateToLocalDateTimeConversion(new Date(0), 1); + testStandardDateToLocalDateTimeConversion(new Date(0), 999999999); + testStandardDateToLocalDateTimeConversion(new Date(1922, 1, 22, 23, 23, 45, 123), 456789); + + testStandardDateToLocalDateTimeConversion(new Date(1999, 1, 1, 10, 10, 10), neo4j.int(999)); + }); + + it('should fail to convert invalid standard Date to neo4j LocalDateTime', () => { + const LocalDateTime = neo4j.types.LocalDateTime; + + expect(() => LocalDateTime.fromStandardDate()).toThrowError(TypeError); + expect(() => LocalDateTime.fromStandardDate('2007-04-05T12:30-02:00')).toThrowError(TypeError); + expect(() => LocalDateTime.fromStandardDate({})).toThrowError(TypeError); + + expect(() => LocalDateTime.fromStandardDate(new Date({}))).toThrowError(TypeError); + expect(() => LocalDateTime.fromStandardDate(new Date([]))).toThrowError(TypeError); + expect(() => LocalDateTime.fromStandardDate(new Date(NaN))).toThrowError(TypeError); + + expect(() => LocalDateTime.fromStandardDate(new Date(), '1')).toThrowError(TypeError); + expect(() => LocalDateTime.fromStandardDate(new Date(), {nanosecond: 1})).toThrowError(TypeError); + expect(() => LocalDateTime.fromStandardDate(new Date(), [1])).toThrowError(TypeError); + }); + + it('should convert standard Date to neo4j DateTime', () => { + testStandardDateToDateTimeConversion(new Date(2011, 9, 18)); + testStandardDateToDateTimeConversion(new Date(1455, 0, 1)); + testStandardDateToDateTimeConversion(new Date(0)); + testStandardDateToDateTimeConversion(new Date(2056, 5, 22, 21, 59, 12, 999)); + + testStandardDateToDateTimeConversion(new Date(0), 1); + testStandardDateToDateTimeConversion(new Date(0), 999999999); + + testStandardDateToDateTimeConversion(new Date(1922, 1, 22, 23, 23, 45, 123), 456789); + testStandardDateToDateTimeConversion(new Date(1999, 1, 1, 10, 10, 10), neo4j.int(999)); + }); + + it('should fail to convert invalid standard Date to neo4j DateTime', () => { + const DateTime = neo4j.types.DateTime; + + expect(() => DateTime.fromStandardDate()).toThrowError(TypeError); + expect(() => DateTime.fromStandardDate('2007-04-05T12:30-02:00')).toThrowError(TypeError); + expect(() => DateTime.fromStandardDate({})).toThrowError(TypeError); + + expect(() => DateTime.fromStandardDate(new Date({}))).toThrowError(TypeError); + expect(() => DateTime.fromStandardDate(new Date([]))).toThrowError(TypeError); + expect(() => DateTime.fromStandardDate(new Date(NaN))).toThrowError(TypeError); + + expect(() => DateTime.fromStandardDate(new Date(), '1')).toThrowError(TypeError); + expect(() => DateTime.fromStandardDate(new Date(), {nanosecond: 1})).toThrowError(TypeError); + expect(() => DateTime.fromStandardDate(new Date(), [1])).toThrowError(TypeError); + }); + function testSendAndReceiveRandomTemporalValues(valueGenerator, done) { const asyncFunction = (index, callback) => { const next = () => callback(); @@ -844,4 +986,37 @@ describe('temporal-types', () => { function randomInt(lower, upper) { return neo4j.int(_.random(lower, upper)); } + + function testStandardDateToLocalTimeConversion(date, nanosecond) { + const converted = neo4j.types.LocalTime.fromStandardDate(date, nanosecond); + const expected = new neo4j.types.LocalTime(date.getHours(), date.getMinutes(), date.getSeconds(), totalNanoseconds(date, nanosecond)); + expect(converted).toEqual(expected); + } + + function testStandardDateToTimeConversion(date, nanosecond) { + const converted = neo4j.types.Time.fromStandardDate(date, nanosecond); + const expected = new neo4j.types.Time(date.getHours(), date.getMinutes(), date.getSeconds(), totalNanoseconds(date, nanosecond), + date.getTimezoneOffset() * 60); + expect(converted).toEqual(expected); + } + + function testStandardDateToNeo4jDateConversion(date) { + const converted = neo4j.types.Date.fromStandardDate(date); + const expected = new neo4j.types.Date(date.getFullYear(), date.getMonth(), date.getDate()); + expect(converted).toEqual(expected); + } + + function testStandardDateToLocalDateTimeConversion(date, nanosecond) { + const converted = neo4j.types.LocalDateTime.fromStandardDate(date, nanosecond); + const expected = new neo4j.types.LocalDateTime(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), + totalNanoseconds(date, nanosecond)); + expect(converted).toEqual(expected); + } + + function testStandardDateToDateTimeConversion(date, nanosecond) { + const converted = neo4j.types.DateTime.fromStandardDate(date, nanosecond); + const expected = new neo4j.types.DateTime(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), + totalNanoseconds(date, nanosecond), date.getTimezoneOffset() * 60); + expect(converted).toEqual(expected); + } }); diff --git a/types/v1/graph-types.d.ts b/types/v1/graph-types.d.ts index 333f5044b..58f2f5792 100644 --- a/types/v1/graph-types.d.ts +++ b/types/v1/graph-types.d.ts @@ -19,6 +19,7 @@ import Integer from "./integer"; +declare type StandardDate = Date; declare type NumberOrInteger = number | Integer; declare class Node { @@ -90,5 +91,6 @@ export { UnboundRelationship, Path, PathSegment, - NumberOrInteger + NumberOrInteger, + StandardDate } diff --git a/types/v1/temporal-types.d.ts b/types/v1/temporal-types.d.ts index f8760f965..c9af663f1 100644 --- a/types/v1/temporal-types.d.ts +++ b/types/v1/temporal-types.d.ts @@ -17,7 +17,7 @@ * limitations under the License. */ -import {NumberOrInteger} from './graph-types'; +import {NumberOrInteger, StandardDate} from './graph-types'; import Integer from "./integer"; declare class Duration { @@ -38,6 +38,8 @@ declare class LocalTime { readonly nanosecond: T; constructor(hour: T, minute: T, second: T, nanosecond: T); + + static fromStandardDate(standardDate: StandardDate, nanosecond?: number): LocalTime; } declare class Time { @@ -49,6 +51,8 @@ declare class Time { readonly timeZoneOffsetSeconds: T; constructor(hour: T, minute: T, second: T, nanosecond: T, timeZoneOffsetSeconds: T); + + static fromStandardDate(standardDate: StandardDate, nanosecond?: number): Time; } declare class Date { @@ -58,6 +62,8 @@ declare class Date { readonly day: T; constructor(year: T, month: T, day: T); + + static fromStandardDate(standardDate: StandardDate): Date; } declare class LocalDateTime { @@ -71,6 +77,8 @@ declare class LocalDateTime { readonly nanosecond: T; constructor(year: T, month: T, day: T, hour: T, minute: T, second: T, nanosecond: T); + + static fromStandardDate(standardDate: StandardDate, nanosecond?: number): LocalDateTime; } declare class DateTime { @@ -86,6 +94,8 @@ declare class DateTime { readonly timeZoneId?: string; constructor(year: T, month: T, day: T, hour: T, minute: T, second: T, nanosecond: T, timeZoneOffsetSeconds?: T, timeZoneId?: string); + + static fromStandardDate(standardDate: StandardDate, nanosecond?: number): DateTime; } declare function isDuration(obj: object): boolean; From 3063f5cac4d9038f5dccc377d2d3afe661eeeb52 Mon Sep 17 00:00:00 2001 From: lutovich Date: Thu, 21 Jun 2018 12:28:13 +0200 Subject: [PATCH 2/4] Fix JSDoc --- src/v1/internal/temporal-util.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v1/internal/temporal-util.js b/src/v1/internal/temporal-util.js index f8c44b40a..291878ae2 100644 --- a/src/v1/internal/temporal-util.js +++ b/src/v1/internal/temporal-util.js @@ -278,9 +278,9 @@ export function totalNanoseconds(standardDate, nanoseconds) { } /** - * Get the total number of nanoseconds from the given standard JavaScript date. + * Get the time zone offset in seconds from the given standard JavaScript date. * @param {global.Date} standardDate the standard JavaScript date. - * @return {number} the total amount of nanoseconds. + * @return {number} the time zone offset in seconds. */ export function timeZoneOffsetInSeconds(standardDate) { return standardDate.getTimezoneOffset() * SECONDS_PER_MINUTE; From dd1187d0b55580500a715b496b4479279d9d53c7 Mon Sep 17 00:00:00 2001 From: lutovich Date: Thu, 21 Jun 2018 12:52:50 +0200 Subject: [PATCH 3/4] Fix handling of standard dates with zero month Standard dates have zero-based month. Neo4j temporal types have 1-based month. Conversion from standard date with zero month was not handled correctly and resulted in zero month in neo4j temporal types. --- src/v1/temporal-types.js | 6 ++-- test/v1/temporal-types.test.js | 55 +++++++++++++++++++++++++++++++--- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/v1/temporal-types.js b/src/v1/temporal-types.js index 966747959..7f77deb15 100644 --- a/src/v1/temporal-types.js +++ b/src/v1/temporal-types.js @@ -214,7 +214,7 @@ export class Date { return new Date( standardDate.getFullYear(), - standardDate.getMonth(), + standardDate.getMonth() + 1, standardDate.getDate()); } @@ -273,7 +273,7 @@ export class LocalDateTime { return new LocalDateTime( standardDate.getFullYear(), - standardDate.getMonth(), + standardDate.getMonth() + 1, standardDate.getDate(), standardDate.getHours(), standardDate.getMinutes(), @@ -342,7 +342,7 @@ export class DateTime { return new DateTime( standardDate.getFullYear(), - standardDate.getMonth(), + standardDate.getMonth() + 1, standardDate.getDate(), standardDate.getHours(), standardDate.getMinutes(), diff --git a/test/v1/temporal-types.test.js b/test/v1/temporal-types.test.js index 16647c7c6..eea25e741 100644 --- a/test/v1/temporal-types.test.js +++ b/test/v1/temporal-types.test.js @@ -728,6 +728,8 @@ describe('temporal-types', () => { testStandardDateToNeo4jDateConversion(new Date(1351, 4, 7)); testStandardDateToNeo4jDateConversion(new Date(3841, 1, 19)); testStandardDateToNeo4jDateConversion(new Date(2222, 3, 29)); + + testStandardDateToNeo4jDateConversion(new Date(1567, 0, 29)); }); it('should fail to convert invalid standard Date to neo4j Date', () => { @@ -753,6 +755,9 @@ describe('temporal-types', () => { testStandardDateToLocalDateTimeConversion(new Date(1922, 1, 22, 23, 23, 45, 123), 456789); testStandardDateToLocalDateTimeConversion(new Date(1999, 1, 1, 10, 10, 10), neo4j.int(999)); + + testStandardDateToLocalDateTimeConversion(new Date(2192, 0, 17, 20, 30, 40)); + testStandardDateToLocalDateTimeConversion(new Date(2239, 0, 9, 1, 2, 3), 4); }); it('should fail to convert invalid standard Date to neo4j LocalDateTime', () => { @@ -782,6 +787,9 @@ describe('temporal-types', () => { testStandardDateToDateTimeConversion(new Date(1922, 1, 22, 23, 23, 45, 123), 456789); testStandardDateToDateTimeConversion(new Date(1999, 1, 1, 10, 10, 10), neo4j.int(999)); + + testStandardDateToDateTimeConversion(new Date(1899, 0, 7, 7, 7, 7, 7)); + testStandardDateToDateTimeConversion(new Date(2005, 0, 1, 2, 3, 4, 5), 100); }); it('should fail to convert invalid standard Date to neo4j DateTime', () => { @@ -800,6 +808,45 @@ describe('temporal-types', () => { expect(() => DateTime.fromStandardDate(new Date(), [1])).toThrowError(TypeError); }); + it('should send and receive neo4j Date created from standard Date with zero month', done => { + if (neo4jDoesNotSupportTemporalTypes(done)) { + return; + } + + // return numbers and not integers to simplify the equality comparison + session = driverWithNativeNumbers.session(); + + const standardDate = new Date(2000, 0, 1); + const neo4jDate = neo4j.types.Date.fromStandardDate(standardDate); + testSendReceiveTemporalValue(neo4jDate, done); + }); + + it('should send and receive neo4j LocalDateTime created from standard Date with zero month', done => { + if (neo4jDoesNotSupportTemporalTypes(done)) { + return; + } + + // return numbers and not integers to simplify the equality comparison + session = driverWithNativeNumbers.session(); + + const standardDate = new Date(2121, 0, 7, 10, 20, 30, 40); + const neo4jLocalDateTime = neo4j.types.LocalDateTime.fromStandardDate(standardDate); + testSendReceiveTemporalValue(neo4jLocalDateTime, done); + }); + + it('should send and receive neo4j DateTime created from standard Date with zero month', done => { + if (neo4jDoesNotSupportTemporalTypes(done)) { + return; + } + + // return numbers and not integers to simplify the equality comparison + session = driverWithNativeNumbers.session(); + + const standardDate = new Date(1756, 0, 29, 23, 15, 59, 12); + const neo4jDateTime = neo4j.types.DateTime.fromStandardDate(standardDate); + testSendReceiveTemporalValue(neo4jDateTime, done); + }); + function testSendAndReceiveRandomTemporalValues(valueGenerator, done) { const asyncFunction = (index, callback) => { const next = () => callback(); @@ -1002,20 +1049,20 @@ describe('temporal-types', () => { function testStandardDateToNeo4jDateConversion(date) { const converted = neo4j.types.Date.fromStandardDate(date); - const expected = new neo4j.types.Date(date.getFullYear(), date.getMonth(), date.getDate()); + const expected = new neo4j.types.Date(date.getFullYear(), date.getMonth() + 1, date.getDate()); expect(converted).toEqual(expected); } function testStandardDateToLocalDateTimeConversion(date, nanosecond) { const converted = neo4j.types.LocalDateTime.fromStandardDate(date, nanosecond); - const expected = new neo4j.types.LocalDateTime(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), - totalNanoseconds(date, nanosecond)); + const expected = new neo4j.types.LocalDateTime(date.getFullYear(), date.getMonth() + 1, date.getDate(), date.getHours(), date.getMinutes(), + date.getSeconds(), totalNanoseconds(date, nanosecond)); expect(converted).toEqual(expected); } function testStandardDateToDateTimeConversion(date, nanosecond) { const converted = neo4j.types.DateTime.fromStandardDate(date, nanosecond); - const expected = new neo4j.types.DateTime(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), + const expected = new neo4j.types.DateTime(date.getFullYear(), date.getMonth() + 1, date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), totalNanoseconds(date, nanosecond), date.getTimezoneOffset() * 60); expect(converted).toEqual(expected); } From cfcee420e86214a109a921241253e3034c1f1a7b Mon Sep 17 00:00:00 2001 From: lutovich Date: Thu, 21 Jun 2018 17:24:00 +0200 Subject: [PATCH 4/4] 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. --- src/v1/internal/temporal-util.js | 110 +++++++++++++++++++++++++ src/v1/temporal-types.js | 50 ++++++------ test/internal/temporal-util.test.js | 122 ++++++++++++++++++++++++++++ test/v1/temporal-types.test.js | 69 +++++++++++++++- 4 files changed, 325 insertions(+), 26 deletions(-) diff --git a/src/v1/internal/temporal-util.js b/src/v1/internal/temporal-util.js index 291878ae2..f94a642e6 100644 --- a/src/v1/internal/temporal-util.js +++ b/src/v1/internal/temporal-util.js @@ -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. @@ -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; @@ -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. diff --git a/src/v1/temporal-types.js b/src/v1/temporal-types.js index 7f77deb15..641ca8797 100644 --- a/src/v1/temporal-types.js +++ b/src/v1/temporal-types.js @@ -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); } @@ -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); } @@ -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); } @@ -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); } @@ -316,13 +316,13 @@ export class DateTime { * @param {string|null} timeZoneId the time zone id for the new date-time. Either this argument or timeZoneOffsetSeconds 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; diff --git a/test/internal/temporal-util.test.js b/test/internal/temporal-util.test.js index f0d05c387..ee3610a4b 100644 --- a/test/internal/temporal-util.test.js +++ b/test/internal/temporal-util.test.js @@ -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) { diff --git a/test/v1/temporal-types.test.js b/test/v1/temporal-types.test.js index eea25e741..f008b5624 100644 --- a/test/v1/temporal-types.test.js +++ b/test/v1/temporal-types.test.js @@ -463,7 +463,7 @@ describe('temporal-types', () => { it('should convert LocalDateTime to ISO string', () => { expect(localDateTime(1992, 11, 8, 9, 42, 17, 22).toString()).toEqual('1992-11-08T09:42:17.000000022'); expect(localDateTime(-10, 7, 15, 8, 15, 33, 500).toString()).toEqual('-0010-07-15T08:15:33.000000500'); - expect(localDateTime(0, 0, 0, 0, 0, 0, 1).toString()).toEqual('0000-00-00T00:00:00.000000001'); + expect(localDateTime(0, 1, 1, 0, 0, 0, 1).toString()).toEqual('0000-01-01T00:00:00.000000001'); }); it('should convert DateTime with time zone offset to ISO string', () => { @@ -847,6 +847,73 @@ describe('temporal-types', () => { testSendReceiveTemporalValue(neo4jDateTime, done); }); + it('should fail to create LocalTime with out of range values', () => { + expect(() => localTime(999, 1, 1, 1)).toThrow(); + expect(() => localTime(1, 999, 1, 1)).toThrow(); + expect(() => localTime(1, 1, 999, 1)).toThrow(); + expect(() => localTime(1, 1, 1, -999)).toThrow(); + expect(() => localTime(1, 1, 1, 1000000000)).toThrow(); + }); + + it('should fail to create Time with out of range values', () => { + expect(() => time(999, 1, 1, 1, 1)).toThrow(); + expect(() => time(1, 999, 1, 1, 1)).toThrow(); + expect(() => time(1, 1, 999, 1, 1)).toThrow(); + expect(() => time(1, 1, 1, -999, 1)).toThrow(); + expect(() => time(1, 1, 1, 1000000000, 1)).toThrow(); + }); + + it('should fail to create Date with out of range values', () => { + expect(() => date(1000000000, 1, 1)).toThrow(); + expect(() => date(1, 0, 1)).toThrow(); + expect(() => date(1, 13, 1)).toThrow(); + expect(() => date(1, 1, 0)).toThrow(); + expect(() => date(1, 1, -1)).toThrow(); + expect(() => date(1, 1, 33)).toThrow(); + }); + + it('should fail to create LocalDateTime with out of range values', () => { + expect(() => localDateTime(1000000000, 1, 1, 1, 1, 1, 1)).toThrow(); + expect(() => localDateTime(1, 0, 1, 1, 1, 1, 1)).toThrow(); + expect(() => localDateTime(1, 13, 1, 1, 1, 1, 1)).toThrow(); + expect(() => localDateTime(1, -1, 1, 1, 1, 1, 1)).toThrow(); + expect(() => localDateTime(1, 1, 0, 1, 1, 1, 1)).toThrow(); + expect(() => localDateTime(1, 1, -1, 1, 1, 1, 1)).toThrow(); + expect(() => localDateTime(1, 1, 33, 1, 1, 1, 1)).toThrow(); + expect(() => localDateTime(1, 1, 1, -1, 1, 1, 1)).toThrow(); + expect(() => localDateTime(1, 1, 1, 24, 1, 1, 1)).toThrow(); + expect(() => localDateTime(1, 1, 1, 42, 1, 1, 1)).toThrow(); + expect(() => localDateTime(1, 1, 1, 1, -1, 1, 1)).toThrow(); + expect(() => localDateTime(1, 1, 1, 1, 60, 1, 1)).toThrow(); + expect(() => localDateTime(1, 1, 1, 1, 999, 1, 1)).toThrow(); + expect(() => localDateTime(1, 1, 1, 1, 1, -1, 1)).toThrow(); + expect(() => localDateTime(1, 1, 1, 1, 1, 60, 1)).toThrow(); + expect(() => localDateTime(1, 1, 1, 1, 1, 99, 1)).toThrow(); + expect(() => localDateTime(1, 1, 1, 1, 1, 1, -1)).toThrow(); + expect(() => localDateTime(1, 1, 1, 1, 1, 1, 1000000000)).toThrow(); + }); + + it('should fail to create DateTime with out of range values', () => { + expect(() => dateTimeWithZoneOffset(1000000000, 1, 1, 1, 1, 1, 1, 0)).toThrow(); + expect(() => dateTimeWithZoneOffset(1, 0, 1, 1, 1, 1, 1, 0)).toThrow(); + expect(() => dateTimeWithZoneOffset(1, 13, 1, 1, 1, 1, 1, 0)).toThrow(); + expect(() => dateTimeWithZoneOffset(1, -1, 1, 1, 1, 1, 1, 0)).toThrow(); + expect(() => dateTimeWithZoneOffset(1, 1, 0, 1, 1, 1, 1, 0)).toThrow(); + expect(() => dateTimeWithZoneOffset(1, 1, -1, 1, 1, 1, 1, 0)).toThrow(); + expect(() => dateTimeWithZoneOffset(1, 1, 33, 1, 1, 1, 1, 0)).toThrow(); + expect(() => dateTimeWithZoneOffset(1, 1, 1, -1, 1, 1, 1, 0)).toThrow(); + expect(() => dateTimeWithZoneOffset(1, 1, 1, 24, 1, 1, 1, 0)).toThrow(); + expect(() => dateTimeWithZoneOffset(1, 1, 1, 42, 1, 1, 1, 0)).toThrow(); + expect(() => dateTimeWithZoneOffset(1, 1, 1, 1, -1, 1, 1, 0)).toThrow(); + expect(() => dateTimeWithZoneOffset(1, 1, 1, 1, 60, 1, 1, 0)).toThrow(); + expect(() => dateTimeWithZoneOffset(1, 1, 1, 1, 999, 1, 1, 0)).toThrow(); + expect(() => dateTimeWithZoneOffset(1, 1, 1, 1, 1, -1, 1, 0)).toThrow(); + expect(() => dateTimeWithZoneOffset(1, 1, 1, 1, 1, 60, 1, 0)).toThrow(); + expect(() => dateTimeWithZoneOffset(1, 1, 1, 1, 1, 99, 1, 0)).toThrow(); + expect(() => dateTimeWithZoneOffset(1, 1, 1, 1, 1, 1, -1, 0)).toThrow(); + expect(() => dateTimeWithZoneOffset(1, 1, 1, 1, 1, 1, 1000000000, 0)).toThrow(); + }); + function testSendAndReceiveRandomTemporalValues(valueGenerator, done) { const asyncFunction = (index, callback) => { const next = () => callback();