Skip to content

Commit 398e443

Browse files
authored
Merge pull request #370 from lutovich/1.6-ns-normalization
Handle nanosecond normalization in duration
2 parents 27f92ca + 330a656 commit 398e443

File tree

4 files changed

+139
-18
lines changed

4 files changed

+139
-18
lines changed

src/v1/internal/temporal-util.js

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ const DAYS_0000_TO_1970 = 719528;
4040
const DAYS_PER_400_YEAR_CYCLE = 146097;
4141
const SECONDS_PER_DAY = 86400;
4242

43+
export function normalizeSecondsForDuration(seconds, nanoseconds) {
44+
return int(seconds).add(floorDiv(nanoseconds, NANOS_PER_SECOND));
45+
}
46+
47+
export function normalizeNanosecondsForDuration(nanoseconds) {
48+
return floorMod(nanoseconds, NANOS_PER_SECOND);
49+
}
50+
4351
/**
4452
* Converts given local time into a single integer representing this same time in nanoseconds of the day.
4553
* @param {Integer|number|string} hour the hour of the local time to convert.
@@ -328,14 +336,30 @@ function formatSecondsAndNanosecondsForDuration(seconds, nanoseconds) {
328336
seconds = int(seconds);
329337
nanoseconds = int(nanoseconds);
330338

331-
const signString = seconds.isNegative() || nanoseconds.isNegative() ? '-' : '';
332-
seconds = seconds.isNegative() ? seconds.negate() : seconds;
333-
nanoseconds = nanoseconds.isNegative() ? nanoseconds.negate() : nanoseconds;
339+
let secondsString;
340+
let nanosecondsString;
341+
342+
const secondsNegative = seconds.isNegative();
343+
const nanosecondsGreaterThanZero = nanoseconds.greaterThan(0);
344+
if (secondsNegative && nanosecondsGreaterThanZero) {
345+
if (seconds.equals(-1)) {
346+
secondsString = '-0';
347+
} else {
348+
secondsString = seconds.add(1).toString();
349+
}
350+
} else {
351+
secondsString = seconds.toString();
352+
}
334353

335-
const secondsString = formatNumber(seconds);
336-
const nanosecondsString = formatNanosecond(nanoseconds);
354+
if (nanosecondsGreaterThanZero) {
355+
if (secondsNegative) {
356+
nanosecondsString = formatNanosecond(nanoseconds.negate().add(2 * NANOS_PER_SECOND).modulo(NANOS_PER_SECOND));
357+
} else {
358+
nanosecondsString = formatNanosecond(nanoseconds.add(NANOS_PER_SECOND).modulo(NANOS_PER_SECOND));
359+
}
360+
}
337361

338-
return signString + secondsString + nanosecondsString;
362+
return nanosecondsString ? secondsString + nanosecondsString : secondsString;
339363
}
340364

341365
/**

src/v1/temporal-types.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* limitations under the License.
1717
*/
1818

19-
import {dateToIsoString, durationToIsoString, timeToIsoString, timeZoneOffsetToIsoString} from './internal/temporal-util';
19+
import * as util from './internal/temporal-util';
2020
import {newError} from './error';
2121

2222
const IDENTIFIER_PROPERTY_ATTRIBUTES = {
@@ -48,13 +48,13 @@ export class Duration {
4848
constructor(months, days, seconds, nanoseconds) {
4949
this.months = months;
5050
this.days = days;
51-
this.seconds = seconds;
52-
this.nanoseconds = nanoseconds;
51+
this.seconds = util.normalizeSecondsForDuration(seconds, nanoseconds);
52+
this.nanoseconds = util.normalizeNanosecondsForDuration(nanoseconds);
5353
Object.freeze(this);
5454
}
5555

5656
toString() {
57-
return durationToIsoString(this.months, this.days, this.seconds, this.nanoseconds);
57+
return util.durationToIsoString(this.months, this.days, this.seconds, this.nanoseconds);
5858
}
5959
}
6060

@@ -91,7 +91,7 @@ export class LocalTime {
9191
}
9292

9393
toString() {
94-
return timeToIsoString(this.hour, this.minute, this.second, this.nanosecond);
94+
return util.timeToIsoString(this.hour, this.minute, this.second, this.nanosecond);
9595
}
9696
}
9797

@@ -130,7 +130,7 @@ export class Time {
130130
}
131131

132132
toString() {
133-
return timeToIsoString(this.hour, this.minute, this.second, this.nanosecond) + timeZoneOffsetToIsoString(this.timeZoneOffsetSeconds);
133+
return util.timeToIsoString(this.hour, this.minute, this.second, this.nanosecond) + util.timeZoneOffsetToIsoString(this.timeZoneOffsetSeconds);
134134
}
135135
}
136136

@@ -165,7 +165,7 @@ export class Date {
165165
}
166166

167167
toString() {
168-
return dateToIsoString(this.year, this.month, this.day);
168+
return util.dateToIsoString(this.year, this.month, this.day);
169169
}
170170
}
171171

@@ -259,7 +259,7 @@ export class DateTime {
259259

260260
toString() {
261261
const localDateTimeStr = localDateTimeToString(this.year, this.month, this.day, this.hour, this.minute, this.second, this.nanosecond);
262-
const timeZoneStr = this.timeZoneId ? `[${this.timeZoneId}]` : timeZoneOffsetToIsoString(this.timeZoneOffsetSeconds);
262+
const timeZoneStr = this.timeZoneId ? `[${this.timeZoneId}]` : util.timeZoneOffsetToIsoString(this.timeZoneOffsetSeconds);
263263
return localDateTimeStr + timeZoneStr;
264264
}
265265
}
@@ -280,7 +280,7 @@ function hasIdentifierProperty(obj, property) {
280280
}
281281

282282
function localDateTimeToString(year, month, day, hour, minute, second, nanosecond) {
283-
return dateToIsoString(year, month, day) + 'T' + timeToIsoString(hour, minute, second, nanosecond);
283+
return util.dateToIsoString(year, month, day) + 'T' + util.timeToIsoString(hour, minute, second, nanosecond);
284284
}
285285

286286
function verifyTimeZoneArguments(timeZoneOffsetSeconds, timeZoneId) {

test/internal/temporal-util.test.js

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,58 @@ import {types} from '../../src/v1';
2222

2323
describe('temporal-util', () => {
2424

25+
it('should normalize seconds for duration', () => {
26+
expect(util.normalizeSecondsForDuration(1, 0)).toEqual(int(1));
27+
expect(util.normalizeSecondsForDuration(3, 0)).toEqual(int(3));
28+
expect(util.normalizeSecondsForDuration(424242, 0)).toEqual(int(424242));
29+
30+
expect(util.normalizeSecondsForDuration(-1, 0)).toEqual(int(-1));
31+
expect(util.normalizeSecondsForDuration(-9, 0)).toEqual(int(-9));
32+
expect(util.normalizeSecondsForDuration(-42, 0)).toEqual(int(-42));
33+
34+
expect(util.normalizeSecondsForDuration(1, 19)).toEqual(int(1));
35+
expect(util.normalizeSecondsForDuration(42, 42)).toEqual(int(42));
36+
expect(util.normalizeSecondsForDuration(12345, 6789)).toEqual(int(12345));
37+
38+
expect(util.normalizeSecondsForDuration(-1, 42)).toEqual(int(-1));
39+
expect(util.normalizeSecondsForDuration(-42, 4242)).toEqual(int(-42));
40+
expect(util.normalizeSecondsForDuration(-123, 999)).toEqual(int(-123));
41+
42+
expect(util.normalizeSecondsForDuration(1, 1000000000)).toEqual(int(2));
43+
expect(util.normalizeSecondsForDuration(40, 2000000001)).toEqual(int(42));
44+
expect(util.normalizeSecondsForDuration(583, 7999999999)).toEqual(int(590));
45+
46+
expect(util.normalizeSecondsForDuration(1, -1000000000)).toEqual(int(0));
47+
expect(util.normalizeSecondsForDuration(1, -5000000000)).toEqual(int(-4));
48+
expect(util.normalizeSecondsForDuration(85, -42000000123)).toEqual(int(42));
49+
50+
expect(util.normalizeSecondsForDuration(-19, -1000000000)).toEqual(int(-20));
51+
expect(util.normalizeSecondsForDuration(-19, -11123456789)).toEqual(int(-31));
52+
expect(util.normalizeSecondsForDuration(-42, -2000000001)).toEqual(int(-45));
53+
});
54+
55+
it('should normalize nanoseconds for duration', () => {
56+
expect(util.normalizeNanosecondsForDuration(0)).toEqual(int(0));
57+
58+
expect(util.normalizeNanosecondsForDuration(1)).toEqual(int(1));
59+
expect(util.normalizeNanosecondsForDuration(42)).toEqual(int(42));
60+
expect(util.normalizeNanosecondsForDuration(123456789)).toEqual(int(123456789));
61+
expect(util.normalizeNanosecondsForDuration(999999999)).toEqual(int(999999999));
62+
63+
expect(util.normalizeNanosecondsForDuration(1000000000)).toEqual(int(0));
64+
expect(util.normalizeNanosecondsForDuration(1000000001)).toEqual(int(1));
65+
expect(util.normalizeNanosecondsForDuration(1000000042)).toEqual(int(42));
66+
expect(util.normalizeNanosecondsForDuration(1123456789)).toEqual(int(123456789));
67+
expect(util.normalizeNanosecondsForDuration(42999999999)).toEqual(int(999999999));
68+
69+
expect(util.normalizeNanosecondsForDuration(-1)).toEqual(int(999999999));
70+
expect(util.normalizeNanosecondsForDuration(-3)).toEqual(int(999999997));
71+
expect(util.normalizeNanosecondsForDuration(-100)).toEqual(int(999999900));
72+
expect(util.normalizeNanosecondsForDuration(-999999999)).toEqual(int(1));
73+
expect(util.normalizeNanosecondsForDuration(-1999999999)).toEqual(int(1));
74+
expect(util.normalizeNanosecondsForDuration(-1123456789)).toEqual(int(876543211));
75+
});
76+
2577
it('should convert date to ISO string', () => {
2678
expect(util.dateToIsoString(90, 2, 5)).toEqual('0090-02-05');
2779
expect(util.dateToIsoString(int(1), 1, int(1))).toEqual('0001-01-01');
@@ -65,7 +117,7 @@ describe('temporal-util', () => {
65117
expect(util.durationToIsoString(0, 0, 0, 123)).toEqual('P0M0DT0.000000123S');
66118
expect(util.durationToIsoString(11, 99, 100, 99901)).toEqual('P11M99DT100.000099901S');
67119
expect(util.durationToIsoString(int(3), int(9191), int(17), int(123456789))).toEqual('P3M9191DT17.123456789S');
68-
expect(util.durationToIsoString(-5, 2, -13, 123)).toEqual('P-5M2DT-13.000000123S');
120+
expect(util.durationToIsoString(-5, 2, -13, 123)).toEqual('P-5M2DT-12.999999877S');
69121
});
70122

71123
it('should convert epoch day to cypher date', () => {

test/v1/temporal-types.test.js

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,10 @@ describe('temporal-types', () => {
530530
}
531531

532532
testDurationToString([
533+
{duration: duration(0, 0, 0, 0), expectedString: 'P0M0DT0S'},
534+
535+
{duration: duration(0, 0, 42, 0), expectedString: 'P0M0DT42S'},
536+
{duration: duration(0, 0, -42, 0), expectedString: 'P0M0DT-42S'},
533537
{duration: duration(0, 0, 1, 0), expectedString: 'P0M0DT1S'},
534538
{duration: duration(0, 0, -1, 0), expectedString: 'P0M0DT-1S'},
535539

@@ -547,10 +551,51 @@ describe('temporal-types', () => {
547551
{duration: duration(0, 0, 1, -999999999), expectedString: 'P0M0DT0.000000001S'},
548552
{duration: duration(0, 0, -1, 999999999), expectedString: 'P0M0DT-0.000000001S'},
549553

550-
{duration: duration(0, 0, -78036, -143000000), expectedString: 'P0M0DT-78036.143000000S'}
554+
{duration: duration(0, 0, 28, 9), expectedString: 'P0M0DT28.000000009S'},
555+
{duration: duration(0, 0, -28, 9), expectedString: 'P0M0DT-27.999999991S'},
556+
{duration: duration(0, 0, 28, -9), expectedString: 'P0M0DT27.999999991S'},
557+
{duration: duration(0, 0, -28, -9), expectedString: 'P0M0DT-28.000000009S'},
558+
559+
{duration: duration(0, 0, -78036, -143000000), expectedString: 'P0M0DT-78036.143000000S'},
560+
561+
{duration: duration(0, 0, 0, 1000000000), expectedString: 'P0M0DT1S'},
562+
{duration: duration(0, 0, 0, -1000000000), expectedString: 'P0M0DT-1S'},
563+
{duration: duration(0, 0, 0, 1000000007), expectedString: 'P0M0DT1.000000007S'},
564+
{duration: duration(0, 0, 0, -1000000007), expectedString: 'P0M0DT-1.000000007S'},
565+
566+
{duration: duration(0, 0, 40, 2123456789), expectedString: 'P0M0DT42.123456789S'},
567+
{duration: duration(0, 0, -40, 2123456789), expectedString: 'P0M0DT-37.876543211S'},
568+
{duration: duration(0, 0, 40, -2123456789), expectedString: 'P0M0DT37.876543211S'},
569+
{duration: duration(0, 0, -40, -2123456789), expectedString: 'P0M0DT-42.123456789S'}
551570
], done);
552571
});
553572

573+
it('should normalize created duration', () => {
574+
const duration1 = duration(0, 0, 1, 1000000000);
575+
expect(duration1.seconds).toEqual(neo4j.int(2));
576+
expect(duration1.nanoseconds).toEqual(neo4j.int(0));
577+
578+
const duration2 = duration(0, 0, 42, 1000000001);
579+
expect(duration2.seconds).toEqual(neo4j.int(43));
580+
expect(duration2.nanoseconds).toEqual(neo4j.int(1));
581+
582+
const duration3 = duration(0, 0, 42, 42999111222);
583+
expect(duration3.seconds).toEqual(neo4j.int(84));
584+
expect(duration3.nanoseconds).toEqual(neo4j.int(999111222));
585+
586+
const duration4 = duration(0, 0, 1, -1000000000);
587+
expect(duration4.seconds).toEqual(neo4j.int(0));
588+
expect(duration4.nanoseconds).toEqual(neo4j.int(0));
589+
590+
const duration5 = duration(0, 0, 1, -1000000001);
591+
expect(duration5.seconds).toEqual(neo4j.int(-1));
592+
expect(duration5.nanoseconds).toEqual(neo4j.int(999999999));
593+
594+
const duration6 = duration(0, 0, 40, -12123456999);
595+
expect(duration6.seconds).toEqual(neo4j.int(27));
596+
expect(duration6.nanoseconds).toEqual(neo4j.int(876543001));
597+
});
598+
554599
function testSendAndReceiveRandomTemporalValues(valueGenerator, done) {
555600
const asyncFunction = (index, callback) => {
556601
const next = () => callback();
@@ -635,7 +680,7 @@ describe('temporal-types', () => {
635680
sign * _.random(0, Number.MAX_SAFE_INTEGER),
636681
sign * _.random(0, Number.MAX_SAFE_INTEGER),
637682
sign * _.random(0, Number.MAX_SAFE_INTEGER),
638-
sign * _.random(0, MAX_NANO_OF_SECOND),
683+
_.random(0, MAX_NANO_OF_SECOND),
639684
);
640685
}
641686

0 commit comments

Comments
 (0)