diff --git a/packages/core/src/internal/temporal-util.ts b/packages/core/src/internal/temporal-util.ts index ece38c424..d99ad885f 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' : ''}` } /** @@ -596,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 52e4d2655..e6d8573fd 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) + 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' : ''}` } /** @@ -596,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/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..9d43932b3 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,125 @@ 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, 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: '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)