From 2e3a6225b0dbeaed4724615d4ec5c5abfde575c3 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Fri, 16 Sep 2022 17:03:13 +0200 Subject: [PATCH 1/6] Add property-based testing to temporal-types conversion Add this type of testing to `.toStandardDate()` ('should be the reverse operation of fromStandardDate but losing time information') helps to cover corner cases and solve special cases such: * Negative date time not being serialized correctly in the iso standard. Years should always have 6 digits and the signal in front for working correctly with negative years and high numbers. This also avoids the year 2000 problem. See, https://en.wikipedia.org/wiki/ISO_8601 * `Date.fromStandardDate` factory was not taking in consideration the `seconds` contribuition in the timezone offset. This is not a quite common scenarion, but there are dates with timezone offset of for example `50 minutes` and `20 seconds`. * Fix `Date.toStandardDate` for dates with offsets of seconds. Javascript Date contructor doesn't create dates from iso strings with seconds in the offset. For instance, `new Date("2010-01-12T14:44:53+00:00:10")`. So, the date should be re-created from the utc timestamp. --- packages/core/package.json | 1 + packages/core/src/internal/temporal-util.ts | 40 ++++++++---- packages/core/src/temporal-types.ts | 37 ++++++++--- packages/core/test/temporal-types.test.ts | 65 +++++++++++++------ .../test/internal/temporal-util.test.js | 16 ++--- .../neo4j-driver/test/temporal-types.test.js | 8 +-- 6 files changed, 110 insertions(+), 57 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index bd6b8fcce..55f34166b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -34,6 +34,7 @@ "esdoc": "^1.1.0", "esdoc-importpath-plugin": "^1.0.2", "esdoc-standard-plugin": "^1.0.0", + "fast-check": "^3.1.3", "jest": "^27.5.1", "ts-jest": "^27.1.4", "ts-node": "^10.3.0", diff --git a/packages/core/src/internal/temporal-util.ts b/packages/core/src/internal/temporal-util.ts index f23e43884..cdf36d2fe 100644 --- a/packages/core/src/internal/temporal-util.ts +++ b/packages/core/src/internal/temporal-util.ts @@ -288,16 +288,7 @@ export function dateToIsoString ( month: NumberOrInteger | string, day: NumberOrInteger | string ): string { - year = int(year) - const isNegative = year.isNegative() - if (isNegative) { - year = year.multiply(-1) - } - let yearString = formatNumber(year, 4) - if (isNegative) { - yearString = '-' + yearString - } - + const yearString = formatNumber(year, 6, { usePositiveSign: true }) const monthString = formatNumber(month, 2) const dayString = formatNumber(day, 2) return `${yearString}-${monthString}-${dayString}` @@ -313,6 +304,16 @@ export function isoStringToStandardDate (isoString: string): Date { return new Date(isoString) } +/** + * Convert the given utc timestamp to a JavaScript Date object + * + * @param {number} utc Timestamp in UTC + * @returns {Date} the date + */ +export function toStandardDate (utc: number): Date { + return new Date(utc) +} + /** * 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. @@ -341,11 +342,14 @@ export function totalNanoseconds ( * @return {number} the time zone offset in seconds. */ export function timeZoneOffsetInSeconds (standardDate: Date): number { + const secondsPortion = standardDate.getSeconds() >= standardDate.getUTCSeconds() + ? standardDate.getSeconds() - standardDate.getUTCSeconds() + : standardDate.getSeconds() - standardDate.getUTCSeconds() + 60 const offsetInMinutes = standardDate.getTimezoneOffset() if (offsetInMinutes === 0) { - return 0 + return 0 + secondsPortion } - return -1 * offsetInMinutes * SECONDS_PER_MINUTE + return -1 * offsetInMinutes * SECONDS_PER_MINUTE + secondsPortion } /** @@ -583,7 +587,10 @@ function formatNanosecond (value: NumberOrInteger | string): string { */ function formatNumber ( num: NumberOrInteger | string, - stringLength?: number + stringLength?: number, + params?: { + usePositiveSign?: boolean + } ): string { num = int(num) const isNegative = num.isNegative() @@ -598,7 +605,12 @@ function formatNumber ( numString = '0' + numString } } - return isNegative ? '-' + numString : numString + if (isNegative) { + return '-' + numString + } else if (params?.usePositiveSign === true) { + return '+' + numString + } + return numString } function add (x: NumberOrInteger, y: number): NumberOrInteger { diff --git a/packages/core/src/temporal-types.ts b/packages/core/src/temporal-types.ts index 36281048e..974f97c16 100644 --- a/packages/core/src/temporal-types.ts +++ b/packages/core/src/temporal-types.ts @@ -667,16 +667,7 @@ export class DateTime { * @throws {Error} If the time zone offset is not defined in the object. */ toStandardDate (): StandardDate { - if (this.timeZoneOffsetSeconds === undefined) { - throw new Error('Requires DateTime created with time zone offset') - } - return util.isoStringToStandardDate( - // the timezone name should be removed from the - // string, otherwise the javascript parse doesn't - // read the datetime correctly - this.toString().replace( - this.timeZoneId != null ? `[${this.timeZoneId}]` : '', '') - ) + return util.toStandardDate(this._toUTC()) } /** @@ -703,6 +694,32 @@ export class DateTime { return localDateTimeStr + timeOffset + timeZoneStr } + + /** + * @private + * @returns {number} + */ + private _toUTC (): number { + if (this.timeZoneOffsetSeconds === undefined) { + throw new Error('Requires DateTime created with time zone offset') + } + const epochSecond = util.localDateTimeToEpochSecond( + this.year, + this.month, + this.day, + this.hour, + this.minute, + this.second, + this.nanosecond + ) + + const utcSecond = epochSecond.subtract(this.timeZoneOffsetSeconds ?? 0) + + return int(utcSecond) + .multiply(1000) + .add(int(this.nanosecond).div(1_000_000)) + .toNumber() + } } Object.defineProperty( diff --git a/packages/core/test/temporal-types.test.ts b/packages/core/test/temporal-types.test.ts index 10de7564c..3968d7701 100644 --- a/packages/core/test/temporal-types.test.ts +++ b/packages/core/test/temporal-types.test.ts @@ -19,6 +19,7 @@ import { StandardDate } from '../src/graph-types' import { LocalDateTime, Date, DateTime } from '../src/temporal-types' +import fc from 'fast-check' describe('Date', () => { describe('.toStandardDate()', () => { @@ -33,15 +34,33 @@ describe('Date', () => { }) it('should be the reverse operation of fromStandardDate but losing time information', () => { - const standardDate = new global.Date() - - const date = Date.fromStandardDate(standardDate) - const receivedDate = date.toStandardDate() - - // Setting 00:00:00:000 UTC - standardDate.setHours(0, -1 * standardDate.getTimezoneOffset(), 0, 0) - - expect(receivedDate).toEqual(standardDate) + fc.assert( + fc.property(fc.date(), (standardDate) => { + // @ts-expect-error + if (isNaN(standardDate)) { + // Should not create from a non-valid date. + expect(() => Date.fromStandardDate(standardDate)).toThrow(TypeError) + return + } + + const date = Date.fromStandardDate(standardDate) + const receivedDate = date.toStandardDate() + + const hour = standardDate.setHours(0, -1 * receivedDate.getTimezoneOffset()) + + // In some situations, the setHours result in a NaN hour. + // In this case, the test should be discarded + if (isNaN(hour)) { + return + } + + expect(receivedDate.getFullYear()).toEqual(standardDate.getFullYear()) + expect(receivedDate.getMonth()).toEqual(standardDate.getMonth()) + expect(receivedDate.getDate()).toEqual(standardDate.getDate()) + expect(receivedDate.getHours()).toEqual(standardDate.getHours()) + expect(receivedDate.getMinutes()).toEqual(standardDate.getMinutes()) + }) + ) }) }) }) @@ -63,12 +82,14 @@ describe('LocalDateTime', () => { }) it('should be the reverse operation of fromStandardDate', () => { - const date = new global.Date() - - const localDatetime = LocalDateTime.fromStandardDate(date) - const receivedDate = localDatetime.toStandardDate() - - expect(receivedDate).toEqual(date) + fc.assert( + fc.property(fc.date(), (date) => { + const localDatetime = LocalDateTime.fromStandardDate(date) + const receivedDate = localDatetime.toStandardDate() + + expect(receivedDate).toEqual(date) + }) + ) }) }) }) @@ -135,12 +156,14 @@ describe('DateTime', () => { }) it('should be the reverse operation of fromStandardDate', () => { - const date = new global.Date() - - const datetime = DateTime.fromStandardDate(date) - const receivedDate = datetime.toStandardDate() - - expect(receivedDate).toEqual(date) + fc.assert( + fc.property(fc.date(), (date) => { + const datetime = DateTime.fromStandardDate(date) + const receivedDate = datetime.toStandardDate() + + expect(receivedDate).toEqual(date) + }) + ) }) }) }) diff --git a/packages/neo4j-driver/test/internal/temporal-util.test.js b/packages/neo4j-driver/test/internal/temporal-util.test.js index 0d696c2f2..886b0a334 100644 --- a/packages/neo4j-driver/test/internal/temporal-util.test.js +++ b/packages/neo4j-driver/test/internal/temporal-util.test.js @@ -88,19 +88,19 @@ describe('#unit temporal-util', () => { }) it('should convert date to ISO string', () => { - expect(util.dateToIsoString(90, 2, 5)).toEqual('0090-02-05') - expect(util.dateToIsoString(int(1), 1, int(1))).toEqual('0001-01-01') - expect(util.dateToIsoString(-123, int(12), int(23))).toEqual('-0123-12-23') + expect(util.dateToIsoString(90, 2, 5)).toEqual('+000090-02-05') + expect(util.dateToIsoString(int(1), 1, int(1))).toEqual('+000001-01-01') + expect(util.dateToIsoString(-123, int(12), int(23))).toEqual('-000123-12-23') expect(util.dateToIsoString(int(-999), int(9), int(10))).toEqual( - '-0999-09-10' + '-000999-09-10' ) - expect(util.dateToIsoString(1999, 12, 19)).toEqual('1999-12-19') + expect(util.dateToIsoString(1999, 12, 19)).toEqual('+001999-12-19') expect(util.dateToIsoString(int(2023), int(8), int(16))).toEqual( - '2023-08-16' + '+002023-08-16' ) - expect(util.dateToIsoString(12345, 12, 31)).toEqual('12345-12-31') + expect(util.dateToIsoString(12345, 12, 31)).toEqual('+012345-12-31') expect(util.dateToIsoString(int(19191919), int(11), int(30))).toEqual( - '19191919-11-30' + '+19191919-11-30' ) expect(util.dateToIsoString(-909090, 9, 9)).toEqual('-909090-09-09') expect(util.dateToIsoString(int(-888999777), int(7), int(26))).toEqual( diff --git a/packages/neo4j-driver/test/temporal-types.test.js b/packages/neo4j-driver/test/temporal-types.test.js index 39733e8fe..51663d569 100644 --- a/packages/neo4j-driver/test/temporal-types.test.js +++ b/packages/neo4j-driver/test/temporal-types.test.js @@ -588,10 +588,10 @@ describe('#integration temporal-types', () => { }, 60000) it('should convert Date to ISO string', () => { - expect(date(2015, 10, 12).toString()).toEqual('2015-10-12') - expect(date(881, 1, 1).toString()).toEqual('0881-01-01') - expect(date(-999, 12, 24).toString()).toEqual('-0999-12-24') - expect(date(-9, 1, 1).toString()).toEqual('-0009-01-01') + expect(date(2015, 10, 12).toString()).toEqual('+002015-10-12') + expect(date(881, 1, 1).toString()).toEqual('+000881-01-01') + expect(date(-999, 12, 24).toString()).toEqual('-000999-12-24') + expect(date(-9, 1, 1).toString()).toEqual('-000009-01-01') }, 60000) it('should convert LocalDateTime to ISO string', () => { From 89ea967193af8df402c7e7dd75b44d3260316d8c Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Fri, 16 Sep 2022 17:43:57 +0200 Subject: [PATCH 2/6] Add changes to package-lock-json --- packages/core/package-lock.json | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json index 5530b890f..c11eaa9bf 100644 --- a/packages/core/package-lock.json +++ b/packages/core/package-lock.json @@ -13,6 +13,7 @@ "esdoc": "^1.1.0", "esdoc-importpath-plugin": "^1.0.2", "esdoc-standard-plugin": "^1.0.0", + "fast-check": "^3.1.3", "jest": "^27.5.1", "ts-jest": "^27.1.4", "ts-node": "^10.3.0", @@ -2941,6 +2942,22 @@ ], "optional": true }, + "node_modules/fast-check": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.1.3.tgz", + "integrity": "sha512-IFY7xJrOUktiC1ZnaJdrinaRpFgDZtURRPwzAiOhL8eyt2NbBTHNF1CO7vZUla1BoUeJVI7gLnTQA+Lko0T2dQ==", + "dev": true, + "dependencies": { + "pure-rand": "^5.0.1" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6108,6 +6125,16 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-5.0.3.tgz", + "integrity": "sha512-9N8x1h8dptBQpHyC7aZMS+iNOAm97WMGY0AFrguU1cpfW3I5jINkWe5BIY5md0ofy+1TCIELsVcm/GJXZSaPbw==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + }, "node_modules/qs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", @@ -9622,6 +9649,15 @@ "dev": true, "optional": true }, + "fast-check": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.1.3.tgz", + "integrity": "sha512-IFY7xJrOUktiC1ZnaJdrinaRpFgDZtURRPwzAiOhL8eyt2NbBTHNF1CO7vZUla1BoUeJVI7gLnTQA+Lko0T2dQ==", + "dev": true, + "requires": { + "pure-rand": "^5.0.1" + } + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -12090,6 +12126,12 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "pure-rand": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-5.0.3.tgz", + "integrity": "sha512-9N8x1h8dptBQpHyC7aZMS+iNOAm97WMGY0AFrguU1cpfW3I5jINkWe5BIY5md0ofy+1TCIELsVcm/GJXZSaPbw==", + "dev": true + }, "qs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", From 9f088592d2c9cd200315e9c22e68dd5893b03b6a Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Mon, 19 Sep 2022 10:52:34 +0200 Subject: [PATCH 3/6] Amending tests --- .../neo4j-driver/test/temporal-types.test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/neo4j-driver/test/temporal-types.test.js b/packages/neo4j-driver/test/temporal-types.test.js index 51663d569..6e2e00cc4 100644 --- a/packages/neo4j-driver/test/temporal-types.test.js +++ b/packages/neo4j-driver/test/temporal-types.test.js @@ -596,26 +596,26 @@ describe('#integration 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' + '+001992-11-08T09:42:17.000000022' ) expect(localDateTime(-10, 7, 15, 8, 15, 33, 500).toString()).toEqual( - '-0010-07-15T08:15:33.000000500' + '-000010-07-15T08:15:33.000000500' ) expect(localDateTime(0, 1, 1, 0, 0, 0, 1).toString()).toEqual( - '0000-01-01T00:00:00.000000001' + '+000000-01-01T00:00:00.000000001' ) }, 60000) it('should convert DateTime with time zone offset to ISO string', () => { expect( dateTimeWithZoneOffset(2025, 9, 17, 23, 22, 21, 999888, 37800).toString() - ).toEqual('2025-09-17T23:22:21.000999888+10:30') + ).toEqual('+002025-09-17T23:22:21.000999888+10:30') expect( dateTimeWithZoneOffset(1, 2, 3, 4, 5, 6, 7, -49376).toString() - ).toEqual('0001-02-03T04:05:06.000000007-13:42:56') + ).toEqual('+000001-02-03T04:05:06.000000007-13:42:56') expect( dateTimeWithZoneOffset(-3, 3, 9, 9, 33, 27, 999000, 15300).toString() - ).toEqual('-0003-03-09T09:33:27.000999000+04:15') + ).toEqual('-000003-03-09T09:33:27.000999000+04:15') }, 60000) it('should convert DateTime with time zone id to ISO-like string', () => { @@ -630,7 +630,7 @@ describe('#integration temporal-types', () => { 15000000, 'Europe/Zaporozhye' ).toString() - ).toEqual('1949-10-07T06:10:15.015000000[Europe/Zaporozhye]') + ).toEqual('+001949-10-07T06:10:15.015000000[Europe/Zaporozhye]') expect( dateTimeWithZoneId( -30455, @@ -642,7 +642,7 @@ describe('#integration temporal-types', () => { 123, 'Asia/Yangon' ).toString() - ).toEqual('-30455-05-05T12:24:10.000000123[Asia/Yangon]') + ).toEqual('-030455-05-05T12:24:10.000000123[Asia/Yangon]') }, 60000) it('should expose local time components in time', () => { From ec34f38a467b98abb0f1dfb13b154b7790def80c Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Mon, 19 Sep 2022 11:51:22 +0200 Subject: [PATCH 4/6] Make changes in the iso format less radical --- packages/core/src/internal/temporal-util.ts | 15 ++++++++++++++- .../test/internal/temporal-util.test.js | 8 ++++---- packages/neo4j-driver/test/temporal-types.test.js | 14 +++++++------- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/packages/core/src/internal/temporal-util.ts b/packages/core/src/internal/temporal-util.ts index cdf36d2fe..d80eb8099 100644 --- a/packages/core/src/internal/temporal-util.ts +++ b/packages/core/src/internal/temporal-util.ts @@ -288,7 +288,7 @@ export function dateToIsoString ( month: NumberOrInteger | string, day: NumberOrInteger | string ): string { - const yearString = formatNumber(year, 6, { usePositiveSign: true }) + const yearString = formatYear(year) const monthString = formatNumber(month, 2) const dayString = formatNumber(day, 2) return `${yearString}-${monthString}-${dayString}` @@ -580,6 +580,19 @@ function formatNanosecond (value: NumberOrInteger | string): string { return value.equals(0) ? '' : '.' + formatNumber(value, 9) } +/** + * + * @param {Integer|number|string} year The year to be formatted + * @return {string} formatted year + */ +function formatYear (year: NumberOrInteger | string): string { + const yearInteger = int(year) + if (yearInteger.isNegative() || yearInteger.greaterThan(9999)) { + return formatNumber(yearInteger, 6, { usePositiveSign: true }) + } + return formatNumber(yearInteger, 4) +} + /** * @param {Integer|number|string} num the number to format. * @param {number} [stringLength=undefined] the string length to left-pad to. diff --git a/packages/neo4j-driver/test/internal/temporal-util.test.js b/packages/neo4j-driver/test/internal/temporal-util.test.js index 886b0a334..95b4430e6 100644 --- a/packages/neo4j-driver/test/internal/temporal-util.test.js +++ b/packages/neo4j-driver/test/internal/temporal-util.test.js @@ -88,15 +88,15 @@ describe('#unit temporal-util', () => { }) it('should convert date to ISO string', () => { - expect(util.dateToIsoString(90, 2, 5)).toEqual('+000090-02-05') - expect(util.dateToIsoString(int(1), 1, int(1))).toEqual('+000001-01-01') + expect(util.dateToIsoString(90, 2, 5)).toEqual('0090-02-05') + expect(util.dateToIsoString(int(1), 1, int(1))).toEqual('0001-01-01') expect(util.dateToIsoString(-123, int(12), int(23))).toEqual('-000123-12-23') expect(util.dateToIsoString(int(-999), int(9), int(10))).toEqual( '-000999-09-10' ) - expect(util.dateToIsoString(1999, 12, 19)).toEqual('+001999-12-19') + expect(util.dateToIsoString(1999, 12, 19)).toEqual('1999-12-19') expect(util.dateToIsoString(int(2023), int(8), int(16))).toEqual( - '+002023-08-16' + '2023-08-16' ) expect(util.dateToIsoString(12345, 12, 31)).toEqual('+012345-12-31') expect(util.dateToIsoString(int(19191919), int(11), int(30))).toEqual( diff --git a/packages/neo4j-driver/test/temporal-types.test.js b/packages/neo4j-driver/test/temporal-types.test.js index 6e2e00cc4..1c5309182 100644 --- a/packages/neo4j-driver/test/temporal-types.test.js +++ b/packages/neo4j-driver/test/temporal-types.test.js @@ -588,31 +588,31 @@ describe('#integration temporal-types', () => { }, 60000) it('should convert Date to ISO string', () => { - expect(date(2015, 10, 12).toString()).toEqual('+002015-10-12') - expect(date(881, 1, 1).toString()).toEqual('+000881-01-01') + expect(date(2015, 10, 12).toString()).toEqual('2015-10-12') + expect(date(881, 1, 1).toString()).toEqual('0881-01-01') expect(date(-999, 12, 24).toString()).toEqual('-000999-12-24') expect(date(-9, 1, 1).toString()).toEqual('-000009-01-01') }, 60000) it('should convert LocalDateTime to ISO string', () => { expect(localDateTime(1992, 11, 8, 9, 42, 17, 22).toString()).toEqual( - '+001992-11-08T09:42:17.000000022' + '1992-11-08T09:42:17.000000022' ) expect(localDateTime(-10, 7, 15, 8, 15, 33, 500).toString()).toEqual( '-000010-07-15T08:15:33.000000500' ) expect(localDateTime(0, 1, 1, 0, 0, 0, 1).toString()).toEqual( - '+000000-01-01T00:00:00.000000001' + '0000-01-01T00:00:00.000000001' ) }, 60000) it('should convert DateTime with time zone offset to ISO string', () => { expect( dateTimeWithZoneOffset(2025, 9, 17, 23, 22, 21, 999888, 37800).toString() - ).toEqual('+002025-09-17T23:22:21.000999888+10:30') + ).toEqual('2025-09-17T23:22:21.000999888+10:30') expect( dateTimeWithZoneOffset(1, 2, 3, 4, 5, 6, 7, -49376).toString() - ).toEqual('+000001-02-03T04:05:06.000000007-13:42:56') + ).toEqual('0001-02-03T04:05:06.000000007-13:42:56') expect( dateTimeWithZoneOffset(-3, 3, 9, 9, 33, 27, 999000, 15300).toString() ).toEqual('-000003-03-09T09:33:27.000999000+04:15') @@ -630,7 +630,7 @@ describe('#integration temporal-types', () => { 15000000, 'Europe/Zaporozhye' ).toString() - ).toEqual('+001949-10-07T06:10:15.015000000[Europe/Zaporozhye]') + ).toEqual('1949-10-07T06:10:15.015000000[Europe/Zaporozhye]') expect( dateTimeWithZoneId( -30455, From 0ea7eff0e560fa9fb4d23366eba046a9b6be53f7 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Mon, 19 Sep 2022 16:17:17 +0200 Subject: [PATCH 5/6] Ajust Date.toStandardDate() reverse operation test --- packages/core/test/temporal-types.test.ts | 56 +++++++++++++---------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/packages/core/test/temporal-types.test.ts b/packages/core/test/temporal-types.test.ts index 3968d7701..f56d92fa9 100644 --- a/packages/core/test/temporal-types.test.ts +++ b/packages/core/test/temporal-types.test.ts @@ -21,6 +21,10 @@ import { StandardDate } from '../src/graph-types' import { LocalDateTime, Date, DateTime } from '../src/temporal-types' import fc from 'fast-check' +const MIN_UTC_IN_MS = -8_640_000_000_000_000 +const MAX_UTC_IN_MS = 8_640_000_000_000_000 +const ONE_DAY_IN_MS = 86_400_000 + describe('Date', () => { describe('.toStandardDate()', () => { it('should convert to a standard date', () => { @@ -35,31 +39,24 @@ describe('Date', () => { it('should be the reverse operation of fromStandardDate but losing time information', () => { fc.assert( - fc.property(fc.date(), (standardDate) => { - // @ts-expect-error - if (isNaN(standardDate)) { - // Should not create from a non-valid date. - expect(() => Date.fromStandardDate(standardDate)).toThrow(TypeError) - return - } - - const date = Date.fromStandardDate(standardDate) - const receivedDate = date.toStandardDate() - - const hour = standardDate.setHours(0, -1 * receivedDate.getTimezoneOffset()) - - // In some situations, the setHours result in a NaN hour. - // In this case, the test should be discarded - if (isNaN(hour)) { - return - } - - expect(receivedDate.getFullYear()).toEqual(standardDate.getFullYear()) - expect(receivedDate.getMonth()).toEqual(standardDate.getMonth()) - expect(receivedDate.getDate()).toEqual(standardDate.getDate()) - expect(receivedDate.getHours()).toEqual(standardDate.getHours()) - expect(receivedDate.getMinutes()).toEqual(standardDate.getMinutes()) - }) + fc.property( + fc.date({ + max: newDate(MAX_UTC_IN_MS - ONE_DAY_IN_MS), + min: newDate(MIN_UTC_IN_MS + ONE_DAY_IN_MS) + }), + standardDate => { + const date = Date.fromStandardDate(standardDate) + const receivedDate = date.toStandardDate() + + const adjustedDateTime = newDate(standardDate) + adjustedDateTime.setHours(0, offset(receivedDate)) + + expect(receivedDate.getFullYear()).toEqual(adjustedDateTime.getFullYear()) + expect(receivedDate.getMonth()).toEqual(adjustedDateTime.getMonth()) + expect(receivedDate.getDate()).toEqual(adjustedDateTime.getDate()) + expect(receivedDate.getHours()).toEqual(adjustedDateTime.getHours()) + expect(receivedDate.getMinutes()).toEqual(adjustedDateTime.getMinutes()) + }) ) }) }) @@ -179,3 +176,12 @@ describe('DateTime', () => { function offset (date: StandardDate): number { return date.getTimezoneOffset() * -1 } + +/** + * Shortcut for creating a new StandardDate + * @param date + * @returns {StandardDate} the standard date + */ +function newDate (date: string | number | globalThis.Date): StandardDate { + return new globalThis.Date(date) +} From 9321cc9ed8f760973c356d2459b7266f7beab5e7 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Mon, 19 Sep 2022 16:58:47 +0200 Subject: [PATCH 6/6] Move newDate temporal-utils --- packages/core/src/internal/temporal-util.ts | 9 +++++++++ packages/core/test/temporal-types.test.ts | 16 ++++------------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/core/src/internal/temporal-util.ts b/packages/core/src/internal/temporal-util.ts index d80eb8099..474fdefff 100644 --- a/packages/core/src/internal/temporal-util.ts +++ b/packages/core/src/internal/temporal-util.ts @@ -314,6 +314,15 @@ export function toStandardDate (utc: number): Date { return new Date(utc) } +/** + * Shortcut for creating a new StandardDate + * @param date + * @returns {Date} the standard date + */ +export function newDate (date: string | number | Date): Date { + return new Date(date) +} + /** * 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. diff --git a/packages/core/test/temporal-types.test.ts b/packages/core/test/temporal-types.test.ts index f56d92fa9..6c41fe02b 100644 --- a/packages/core/test/temporal-types.test.ts +++ b/packages/core/test/temporal-types.test.ts @@ -19,6 +19,7 @@ import { StandardDate } from '../src/graph-types' import { LocalDateTime, Date, DateTime } from '../src/temporal-types' +import { temporalUtil } from '../src/internal' import fc from 'fast-check' const MIN_UTC_IN_MS = -8_640_000_000_000_000 @@ -41,14 +42,14 @@ describe('Date', () => { fc.assert( fc.property( fc.date({ - max: newDate(MAX_UTC_IN_MS - ONE_DAY_IN_MS), - min: newDate(MIN_UTC_IN_MS + ONE_DAY_IN_MS) + max: temporalUtil.newDate(MAX_UTC_IN_MS - ONE_DAY_IN_MS), + min: temporalUtil.newDate(MIN_UTC_IN_MS + ONE_DAY_IN_MS) }), standardDate => { const date = Date.fromStandardDate(standardDate) const receivedDate = date.toStandardDate() - const adjustedDateTime = newDate(standardDate) + const adjustedDateTime = temporalUtil.newDate(standardDate) adjustedDateTime.setHours(0, offset(receivedDate)) expect(receivedDate.getFullYear()).toEqual(adjustedDateTime.getFullYear()) @@ -176,12 +177,3 @@ describe('DateTime', () => { function offset (date: StandardDate): number { return date.getTimezoneOffset() * -1 } - -/** - * Shortcut for creating a new StandardDate - * @param date - * @returns {StandardDate} the standard date - */ -function newDate (date: string | number | globalThis.Date): StandardDate { - return new globalThis.Date(date) -}