From b337d8ca9f09d0a8f87f8b0b483bf0aa1ede5e95 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 19 May 2025 12:01:58 +0200 Subject: [PATCH 1/4] update duration stringification --- packages/core/src/internal/temporal-util.ts | 62 ++++++++++++++++-- .../lib/core/internal/temporal-util.ts | 62 ++++++++++++++++-- .../test/internal/temporal-util.test.js | 10 ++- .../neo4j-driver/test/temporal-types.test.js | 64 +++++++++---------- 4 files changed, 149 insertions(+), 49 deletions(-) diff --git a/packages/core/src/internal/temporal-util.ts b/packages/core/src/internal/temporal-util.ts index ece38c424..f89cd8574 100644 --- a/packages/core/src/internal/temporal-util.ts +++ b/packages/core/src/internal/temporal-util.ts @@ -84,15 +84,52 @@ export const DAYS_0000_TO_1970 = 719528 export const DAYS_PER_400_YEAR_CYCLE = 146097 export const SECONDS_PER_DAY = 86400 +export function normalizeYearsForDuration ( + months: NumberOrInteger | string +): Integer { + return int(months).div(12) +} + +export function normalizeMonthsForDuration ( + months: NumberOrInteger | string +): Integer { + return int(months).modulo(12) +} + +export function normalizeHoursForDuration ( + seconds: NumberOrInteger | string, + nanoseconds: NumberOrInteger | string +): Integer { + if (int(nanoseconds).greaterThan(0) && int(seconds).lessThan(0)) { + seconds = int(seconds).add(1) + } + return int(seconds).div(SECONDS_PER_HOUR) +} + +export function normalizeMinutesForDuration ( + seconds: NumberOrInteger | string, + nanoseconds: NumberOrInteger | string +): Integer { + if (int(nanoseconds).greaterThan(0) && int(seconds).lessThan(0)) { + seconds = int(seconds).add(1) + } + let minutes = int(seconds).div(SECONDS_PER_MINUTE) + const negativeMinutes = minutes.isNegative() + if (negativeMinutes) { + minutes = minutes.negate() + } + return floorMod(minutes, MINUTES_PER_HOUR).multiply(negativeMinutes ? -1 : 1) +} + export function normalizeSecondsForDuration ( - seconds: number | Integer | bigint, - nanoseconds: number | Integer | bigint + seconds: NumberOrInteger | string, + nanoseconds: NumberOrInteger | string ): Integer { return int(seconds).add(floorDiv(nanoseconds, NANOS_PER_SECOND)) } export function normalizeNanosecondsForDuration ( - nanoseconds: number | Integer | bigint + nanoseconds: NumberOrInteger | string ): Integer { return floorMod(nanoseconds, NANOS_PER_SECOND) } @@ -212,13 +249,24 @@ export function durationToIsoString ( seconds: NumberOrInteger | string, nanoseconds: NumberOrInteger | string ): string { - const monthsString = formatNumber(months) - const daysString = formatNumber(days) + if (int(months).equals(0) && int(days).equals(0) && int(seconds).equals(0) && int(nanoseconds).equals(0)) { + return 'PT0S' + } + const yearString = formatNumber(normalizeYearsForDuration(months)) + const monthString = formatNumber(normalizeMonthsForDuration(months)) + const dayString = formatNumber(days) + const hourString = formatNumber(normalizeHoursForDuration(seconds, nanoseconds)) + const minuteString = formatNumber(normalizeMinutesForDuration(seconds, nanoseconds)) const secondsAndNanosecondsString = formatSecondsAndNanosecondsForDuration( seconds, nanoseconds ) - return `P${monthsString}M${daysString}DT${secondsAndNanosecondsString}S` + return `P${yearString !== '0' ? yearString + 'Y' : ''}` + + `${monthString !== '0' ? monthString + 'M' : ''}` + + `${dayString !== '0' ? dayString + 'D' : ''}T` + + `${hourString !== '0' ? hourString + 'H' : ''}` + + `${minuteString !== '0' ? minuteString + 'M' : ''}` + + `${secondsAndNanosecondsString !== '0' ? secondsAndNanosecondsString + 'S' : ''}` } /** @@ -587,7 +635,7 @@ function formatSecondsAndNanosecondsForDuration ( seconds: NumberOrInteger | string, nanoseconds: NumberOrInteger | string ): string { - seconds = int(seconds) + seconds = int(seconds).modulo(60) nanoseconds = int(nanoseconds) let secondsString diff --git a/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts b/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts index 52e4d2655..56ae6b657 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts @@ -84,15 +84,52 @@ export const DAYS_0000_TO_1970 = 719528 export const DAYS_PER_400_YEAR_CYCLE = 146097 export const SECONDS_PER_DAY = 86400 +export function normalizeYearsForDuration( + months: NumberOrInteger | string, +): Integer { + return int(months).div(12) +} + +export function normalizeMonthsForDuration( + months: NumberOrInteger | string, +): Integer { + return int(months).modulo(12) +} + +export function normalizeHoursForDuration ( + seconds: NumberOrInteger | string, + nanoseconds: NumberOrInteger | string +): Integer { + if(int(nanoseconds).greaterThan(0) && int(seconds).lessThan(0)) { + seconds = int(seconds).add(1) + } + return int(seconds).div(SECONDS_PER_HOUR) +} + +export function normalizeMinutesForDuration ( + seconds: NumberOrInteger | string, + nanoseconds: NumberOrInteger | string +): Integer { + if(int(nanoseconds).greaterThan(0) && int(seconds).lessThan(0)) { + seconds = int(seconds).add(1) + } + let minutes = int(seconds).div(SECONDS_PER_MINUTE) + let negativeMinutes = minutes.isNegative() + if(negativeMinutes) { + minutes = minutes.negate() + } + return floorMod(minutes, MINUTES_PER_HOUR).multiply(negativeMinutes ? -1 : 1) +} + export function normalizeSecondsForDuration ( - seconds: number | Integer | bigint, - nanoseconds: number | Integer | bigint + seconds: NumberOrInteger | string, + nanoseconds: NumberOrInteger | string ): Integer { return int(seconds).add(floorDiv(nanoseconds, NANOS_PER_SECOND)) } export function normalizeNanosecondsForDuration ( - nanoseconds: number | Integer | bigint + nanoseconds: NumberOrInteger | string ): Integer { return floorMod(nanoseconds, NANOS_PER_SECOND) } @@ -212,13 +249,24 @@ export function durationToIsoString ( seconds: NumberOrInteger | string, nanoseconds: NumberOrInteger | string ): string { - const monthsString = formatNumber(months) - const daysString = formatNumber(days) + if(int(months).equals(0) && int(days).equals(0) && int(seconds).equals(0) && int(nanoseconds).equals(0)) { + return "PT0S" + } + const yearString = formatNumber(normalizeYearsForDuration(months)) + const monthString = formatNumber(normalizeMonthsForDuration(months)) + const dayString = formatNumber(days) + const hourString = formatNumber(normalizeHoursForDuration(seconds, nanoseconds)) + const minuteString = formatNumber(normalizeMinutesForDuration(seconds, nanoseconds)) const secondsAndNanosecondsString = formatSecondsAndNanosecondsForDuration( seconds, nanoseconds ) - return `P${monthsString}M${daysString}DT${secondsAndNanosecondsString}S` + return `P${yearString !== "0" ? yearString + "Y" : ""}`+ + `${monthString !== "0" ? monthString + "M" : ""}`+ + `${dayString !== "0" ? dayString + "D" : ""}T`+ + `${hourString !== "0" ? hourString + "H" : ""}`+ + `${minuteString !== "0" ? minuteString + "M" : ""}`+ + `${secondsAndNanosecondsString !== "0" ? secondsAndNanosecondsString + "S" : ""}` } /** @@ -587,7 +635,7 @@ function formatSecondsAndNanosecondsForDuration ( seconds: NumberOrInteger | string, nanoseconds: NumberOrInteger | string ): string { - seconds = int(seconds) + seconds = int(seconds).modulo(60) nanoseconds = int(nanoseconds) let secondsString diff --git a/packages/neo4j-driver/test/internal/temporal-util.test.js b/packages/neo4j-driver/test/internal/temporal-util.test.js index 6bf8427af..4995867b2 100644 --- a/packages/neo4j-driver/test/internal/temporal-util.test.js +++ b/packages/neo4j-driver/test/internal/temporal-util.test.js @@ -34,6 +34,7 @@ describe('#unit temporal-util', () => { expect(util.normalizeSecondsForDuration(12345, 6789)).toEqual(int(12345)) expect(util.normalizeSecondsForDuration(-1, 42)).toEqual(int(-1)) + expect(util.normalizeSecondsForDuration(1, -42)).toEqual(int(0)) expect(util.normalizeSecondsForDuration(-42, 4242)).toEqual(int(-42)) expect(util.normalizeSecondsForDuration(-123, 999)).toEqual(int(-123)) @@ -149,10 +150,13 @@ describe('#unit temporal-util', () => { }) it('should convert duration to ISO string', () => { - expect(util.durationToIsoString(0, 0, 0, 0)).toEqual('P0M0DT0S') - expect(util.durationToIsoString(0, 0, 0, 123)).toEqual('P0M0DT0.000000123S') + expect(util.durationToIsoString(0, 0, 0, 0)).toEqual('PT0S') + expect(util.durationToIsoString(0, 0, 0, 123)).toEqual('PT0.000000123S') expect(util.durationToIsoString(11, 99, 100, 99901)).toEqual( - 'P11M99DT100.000099901S' + 'P11M99DT1M40.000099901S' + ) + expect(util.durationToIsoString(13, 99, 100, 99901)).toEqual( + 'P1Y1M99DT1M40.000099901S' ) expect( util.durationToIsoString(int(3), int(9191), int(17), int(123456789)) diff --git a/packages/neo4j-driver/test/temporal-types.test.js b/packages/neo4j-driver/test/temporal-types.test.js index 3007397ea..b58a4bccd 100644 --- a/packages/neo4j-driver/test/temporal-types.test.js +++ b/packages/neo4j-driver/test/temporal-types.test.js @@ -558,9 +558,9 @@ describe('#integration temporal-types', () => { it('should convert Duration to ISO string', () => { expect(duration(13, 62, 3, 999111999).toString()).toEqual( - 'P13M62DT3.999111999S' + 'P1Y1M62DT3.999111999S' ) - expect(duration(0, 0, 0, 0).toString()).toEqual('P0M0DT0S') + expect(duration(0, 0, 0, 0).toString()).toEqual('PT0S') expect(duration(-1, -2, 10, 10).toString()).toEqual('P-1M-2DT10.000000010S') }, 90000) @@ -713,114 +713,114 @@ describe('#integration temporal-types', () => { } await testDurationToString([ - { duration: duration(0, 0, 0, 0), expectedString: 'P0M0DT0S' }, + { duration: duration(0, 0, 0, 0), expectedString: 'PT0S' }, - { duration: duration(0, 0, 42, 0), expectedString: 'P0M0DT42S' }, - { duration: duration(0, 0, -42, 0), expectedString: 'P0M0DT-42S' }, - { duration: duration(0, 0, 1, 0), expectedString: 'P0M0DT1S' }, - { duration: duration(0, 0, -1, 0), expectedString: 'P0M0DT-1S' }, + { duration: duration(0, 0, 42, 0), expectedString: 'PT42S' }, + { duration: duration(0, 0, -42, 0), expectedString: 'PT-42S' }, + { duration: duration(0, 0, 1, 0), expectedString: 'PT1S' }, + { duration: duration(0, 0, -1, 0), expectedString: 'PT-1S' }, { duration: duration(0, 0, 0, 5), - expectedString: 'P0M0DT0.000000005S' + expectedString: 'PT0.000000005S' }, { duration: duration(0, 0, 0, -5), - expectedString: 'P0M0DT-0.000000005S' + expectedString: 'PT-0.000000005S' }, { duration: duration(0, 0, 0, 999999999), - expectedString: 'P0M0DT0.999999999S' + expectedString: 'PT0.999999999S' }, { duration: duration(0, 0, 0, -999999999), - expectedString: 'P0M0DT-0.999999999S' + expectedString: 'PT-0.999999999S' }, { duration: duration(0, 0, 1, 5), - expectedString: 'P0M0DT1.000000005S' + expectedString: 'PT1.000000005S' }, { duration: duration(0, 0, -1, -5), - expectedString: 'P0M0DT-1.000000005S' + expectedString: 'PT-1.000000005S' }, { duration: duration(0, 0, 1, -5), - expectedString: 'P0M0DT0.999999995S' + expectedString: 'PT0.999999995S' }, { duration: duration(0, 0, -1, 5), - expectedString: 'P0M0DT-0.999999995S' + expectedString: 'PT-0.999999995S' }, { duration: duration(0, 0, 1, 999999999), - expectedString: 'P0M0DT1.999999999S' + expectedString: 'PT1.999999999S' }, { duration: duration(0, 0, -1, -999999999), - expectedString: 'P0M0DT-1.999999999S' + expectedString: 'PT-1.999999999S' }, { duration: duration(0, 0, 1, -999999999), - expectedString: 'P0M0DT0.000000001S' + expectedString: 'PT0.000000001S' }, { duration: duration(0, 0, -1, 999999999), - expectedString: 'P0M0DT-0.000000001S' + expectedString: 'PT-0.000000001S' }, { duration: duration(0, 0, 28, 9), - expectedString: 'P0M0DT28.000000009S' + expectedString: 'PT28.000000009S' }, { duration: duration(0, 0, -28, 9), - expectedString: 'P0M0DT-27.999999991S' + expectedString: 'PT-27.999999991S' }, { duration: duration(0, 0, 28, -9), - expectedString: 'P0M0DT27.999999991S' + expectedString: 'PT27.999999991S' }, { duration: duration(0, 0, -28, -9), - expectedString: 'P0M0DT-28.000000009S' + expectedString: 'PT-28.000000009S' }, { duration: duration(0, 0, -78036, -143000000), - expectedString: 'P0M0DT-78036.143000000S' + expectedString: 'PT-21H-40M-36.143000000S' }, - { duration: duration(0, 0, 0, 1000000000), expectedString: 'P0M0DT1S' }, + { duration: duration(0, 0, 0, 1000000000), expectedString: 'PT1S' }, { duration: duration(0, 0, 0, -1000000000), - expectedString: 'P0M0DT-1S' + expectedString: 'PT-1S' }, { duration: duration(0, 0, 0, 1000000007), - expectedString: 'P0M0DT1.000000007S' + expectedString: 'PT1.000000007S' }, { duration: duration(0, 0, 0, -1000000007), - expectedString: 'P0M0DT-1.000000007S' + expectedString: 'PT-1.000000007S' }, { duration: duration(0, 0, 40, 2123456789), - expectedString: 'P0M0DT42.123456789S' + expectedString: 'PT42.123456789S' }, { duration: duration(0, 0, -40, 2123456789), - expectedString: 'P0M0DT-37.876543211S' + expectedString: 'PT-37.876543211S' }, { duration: duration(0, 0, 40, -2123456789), - expectedString: 'P0M0DT37.876543211S' + expectedString: 'PT37.876543211S' }, { duration: duration(0, 0, -40, -2123456789), - expectedString: 'P0M0DT-42.123456789S' + expectedString: 'PT-42.123456789S' } ]) }, 90000) From c36c5cdf8a49612a4e182522b8bb334e3dbe00f8 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 19 May 2025 12:02:20 +0200 Subject: [PATCH 2/4] deno sync --- .../lib/core/internal/temporal-util.ts | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts b/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts index 56ae6b657..844b88b1c 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts @@ -84,14 +84,14 @@ export const DAYS_0000_TO_1970 = 719528 export const DAYS_PER_400_YEAR_CYCLE = 146097 export const SECONDS_PER_DAY = 86400 -export function normalizeYearsForDuration( - months: NumberOrInteger | string, +export function normalizeYearsForDuration ( + months: NumberOrInteger | string ): Integer { return int(months).div(12) } -export function normalizeMonthsForDuration( - months: NumberOrInteger | string, +export function normalizeMonthsForDuration ( + months: NumberOrInteger | string ): Integer { return int(months).modulo(12) } @@ -100,7 +100,7 @@ export function normalizeHoursForDuration ( seconds: NumberOrInteger | string, nanoseconds: NumberOrInteger | string ): Integer { - if(int(nanoseconds).greaterThan(0) && int(seconds).lessThan(0)) { + if (int(nanoseconds).greaterThan(0) && int(seconds).lessThan(0)) { seconds = int(seconds).add(1) } return int(seconds).div(SECONDS_PER_HOUR) @@ -110,12 +110,12 @@ export function normalizeMinutesForDuration ( seconds: NumberOrInteger | string, nanoseconds: NumberOrInteger | string ): Integer { - if(int(nanoseconds).greaterThan(0) && int(seconds).lessThan(0)) { + if (int(nanoseconds).greaterThan(0) && int(seconds).lessThan(0)) { seconds = int(seconds).add(1) } let minutes = int(seconds).div(SECONDS_PER_MINUTE) - let negativeMinutes = minutes.isNegative() - if(negativeMinutes) { + const negativeMinutes = minutes.isNegative() + if (negativeMinutes) { minutes = minutes.negate() } return floorMod(minutes, MINUTES_PER_HOUR).multiply(negativeMinutes ? -1 : 1) @@ -249,10 +249,10 @@ export function durationToIsoString ( seconds: NumberOrInteger | string, nanoseconds: NumberOrInteger | string ): string { - if(int(months).equals(0) && int(days).equals(0) && int(seconds).equals(0) && int(nanoseconds).equals(0)) { - return "PT0S" + if (int(months).equals(0) && int(days).equals(0) && int(seconds).equals(0) && int(nanoseconds).equals(0)) { + return 'PT0S' } - const yearString = formatNumber(normalizeYearsForDuration(months)) + const yearString = formatNumber(normalizeYearsForDuration(months)) const monthString = formatNumber(normalizeMonthsForDuration(months)) const dayString = formatNumber(days) const hourString = formatNumber(normalizeHoursForDuration(seconds, nanoseconds)) @@ -261,12 +261,12 @@ export function durationToIsoString ( seconds, nanoseconds ) - return `P${yearString !== "0" ? yearString + "Y" : ""}`+ - `${monthString !== "0" ? monthString + "M" : ""}`+ - `${dayString !== "0" ? dayString + "D" : ""}T`+ - `${hourString !== "0" ? hourString + "H" : ""}`+ - `${minuteString !== "0" ? minuteString + "M" : ""}`+ - `${secondsAndNanosecondsString !== "0" ? secondsAndNanosecondsString + "S" : ""}` + return `P${yearString !== '0' ? yearString + 'Y' : ''}` + + `${monthString !== '0' ? monthString + 'M' : ''}` + + `${dayString !== '0' ? dayString + 'D' : ''}T` + + `${hourString !== '0' ? hourString + 'H' : ''}` + + `${minuteString !== '0' ? minuteString + 'M' : ''}` + + `${secondsAndNanosecondsString !== '0' ? secondsAndNanosecondsString + 'S' : ''}` } /** From 83822225cdfcf8614b288a8b0d00ee74e28acd92 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 19 May 2025 12:54:45 +0200 Subject: [PATCH 3/4] a few additional tests --- packages/neo4j-driver/test/temporal-types.test.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/neo4j-driver/test/temporal-types.test.js b/packages/neo4j-driver/test/temporal-types.test.js index b58a4bccd..9d43932b3 100644 --- a/packages/neo4j-driver/test/temporal-types.test.js +++ b/packages/neo4j-driver/test/temporal-types.test.js @@ -805,7 +805,18 @@ describe('#integration temporal-types', () => { duration: duration(0, 0, 0, -1000000007), expectedString: 'PT-1.000000007S' }, - + { + duration: duration(0, 0, 1, -1000000007), + expectedString: 'PT-0.000000007S' + }, + { + duration: duration(0, 0, -60, 1), + expectedString: 'PT-59.999999999S' + }, + { + duration: duration(0, 0, -60, -1), + expectedString: 'PT-1M-0.000000001S' + }, { duration: duration(0, 0, 40, 2123456789), expectedString: 'PT42.123456789S' From c5052062da6f74bca2aee0e48d914113fc356dc8 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 19 May 2025 15:27:14 +0200 Subject: [PATCH 4/4] fix to stringify --- packages/core/src/internal/temporal-util.ts | 9 +++++---- .../neo4j-driver-deno/lib/core/internal/temporal-util.ts | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/core/src/internal/temporal-util.ts b/packages/core/src/internal/temporal-util.ts index f89cd8574..d99ad885f 100644 --- a/packages/core/src/internal/temporal-util.ts +++ b/packages/core/src/internal/temporal-util.ts @@ -635,7 +635,7 @@ function formatSecondsAndNanosecondsForDuration ( seconds: NumberOrInteger | string, nanoseconds: NumberOrInteger | string ): string { - seconds = int(seconds).modulo(60) + seconds = int(seconds) nanoseconds = int(nanoseconds) let secondsString @@ -644,13 +644,14 @@ function formatSecondsAndNanosecondsForDuration ( const secondsNegative = seconds.isNegative() const nanosecondsGreaterThanZero = nanoseconds.greaterThan(0) if (secondsNegative && nanosecondsGreaterThanZero) { - if (seconds.equals(-1)) { + seconds = seconds.add(1).negate().modulo(60).negate() + if (seconds.equals(0)) { secondsString = '-0' } else { - secondsString = seconds.add(1).toString() + secondsString = seconds.toString() } } else { - secondsString = seconds.toString() + secondsString = seconds.modulo(60).toString() } if (nanosecondsGreaterThanZero) { diff --git a/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts b/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts index 844b88b1c..e6d8573fd 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts @@ -635,7 +635,7 @@ function formatSecondsAndNanosecondsForDuration ( seconds: NumberOrInteger | string, nanoseconds: NumberOrInteger | string ): string { - seconds = int(seconds).modulo(60) + seconds = int(seconds) nanoseconds = int(nanoseconds) let secondsString @@ -644,13 +644,14 @@ function formatSecondsAndNanosecondsForDuration ( const secondsNegative = seconds.isNegative() const nanosecondsGreaterThanZero = nanoseconds.greaterThan(0) if (secondsNegative && nanosecondsGreaterThanZero) { - if (seconds.equals(-1)) { + seconds = seconds.add(1).negate().modulo(60).negate() + if (seconds.equals(0)) { secondsString = '-0' } else { - secondsString = seconds.add(1).toString() + secondsString = seconds.toString() } } else { - secondsString = seconds.toString() + secondsString = seconds.modulo(60).toString() } if (nanosecondsGreaterThanZero) {