From c6c1a087014d5ce977c5de60e5494b6a062b3bb1 Mon Sep 17 00:00:00 2001 From: lutovich Date: Tue, 19 Jun 2018 18:39:05 +0200 Subject: [PATCH 1/3] 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/3] 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/3] 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); }