Skip to content

Handle nanosecond normalization in duration #370

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions src/v1/internal/temporal-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ const DAYS_0000_TO_1970 = 719528;
const DAYS_PER_400_YEAR_CYCLE = 146097;
const SECONDS_PER_DAY = 86400;

export function normalizeSecondsForDuration(seconds, nanoseconds) {
return int(seconds).add(floorDiv(nanoseconds, NANOS_PER_SECOND));
}

export function normalizeNanosecondsForDuration(nanoseconds) {
return floorMod(nanoseconds, NANOS_PER_SECOND);
}

/**
* Converts given local time into a single integer representing this same time in nanoseconds of the day.
* @param {Integer|number|string} hour the hour of the local time to convert.
Expand Down Expand Up @@ -328,14 +336,30 @@ function formatSecondsAndNanosecondsForDuration(seconds, nanoseconds) {
seconds = int(seconds);
nanoseconds = int(nanoseconds);

const signString = seconds.isNegative() || nanoseconds.isNegative() ? '-' : '';
seconds = seconds.isNegative() ? seconds.negate() : seconds;
nanoseconds = nanoseconds.isNegative() ? nanoseconds.negate() : nanoseconds;
let secondsString;
let nanosecondsString;

const secondsNegative = seconds.isNegative();
const nanosecondsGreaterThanZero = nanoseconds.greaterThan(0);
if (secondsNegative && nanosecondsGreaterThanZero) {
if (seconds.equals(-1)) {
secondsString = '-0';
} else {
secondsString = seconds.add(1).toString();
}
} else {
secondsString = seconds.toString();
}

const secondsString = formatNumber(seconds);
const nanosecondsString = formatNanosecond(nanoseconds);
if (nanosecondsGreaterThanZero) {
if (secondsNegative) {
nanosecondsString = formatNanosecond(nanoseconds.negate().add(2 * NANOS_PER_SECOND).modulo(NANOS_PER_SECOND));
} else {
nanosecondsString = formatNanosecond(nanoseconds.add(NANOS_PER_SECOND).modulo(NANOS_PER_SECOND));
}
}

return signString + secondsString + nanosecondsString;
return nanosecondsString ? secondsString + nanosecondsString : secondsString;
}

/**
Expand Down
18 changes: 9 additions & 9 deletions src/v1/temporal-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* limitations under the License.
*/

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

const IDENTIFIER_PROPERTY_ATTRIBUTES = {
Expand Down Expand Up @@ -48,13 +48,13 @@ export class Duration {
constructor(months, days, seconds, nanoseconds) {
this.months = months;
this.days = days;
this.seconds = seconds;
this.nanoseconds = nanoseconds;
this.seconds = util.normalizeSecondsForDuration(seconds, nanoseconds);
this.nanoseconds = util.normalizeNanosecondsForDuration(nanoseconds);
Object.freeze(this);
}

toString() {
return durationToIsoString(this.months, this.days, this.seconds, this.nanoseconds);
return util.durationToIsoString(this.months, this.days, this.seconds, this.nanoseconds);
}
}

Expand Down Expand Up @@ -91,7 +91,7 @@ export class LocalTime {
}

toString() {
return timeToIsoString(this.hour, this.minute, this.second, this.nanosecond);
return util.timeToIsoString(this.hour, this.minute, this.second, this.nanosecond);
}
}

Expand Down Expand Up @@ -130,7 +130,7 @@ export class Time {
}

toString() {
return timeToIsoString(this.hour, this.minute, this.second, this.nanosecond) + timeZoneOffsetToIsoString(this.timeZoneOffsetSeconds);
return util.timeToIsoString(this.hour, this.minute, this.second, this.nanosecond) + util.timeZoneOffsetToIsoString(this.timeZoneOffsetSeconds);
}
}

Expand Down Expand Up @@ -165,7 +165,7 @@ export class Date {
}

toString() {
return dateToIsoString(this.year, this.month, this.day);
return util.dateToIsoString(this.year, this.month, this.day);
}
}

Expand Down Expand Up @@ -259,7 +259,7 @@ export class DateTime {

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

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

function verifyTimeZoneArguments(timeZoneOffsetSeconds, timeZoneId) {
Expand Down
54 changes: 53 additions & 1 deletion test/internal/temporal-util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,58 @@ import {types} from '../../src/v1';

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

it('should normalize seconds for duration', () => {
expect(util.normalizeSecondsForDuration(1, 0)).toEqual(int(1));
expect(util.normalizeSecondsForDuration(3, 0)).toEqual(int(3));
expect(util.normalizeSecondsForDuration(424242, 0)).toEqual(int(424242));

expect(util.normalizeSecondsForDuration(-1, 0)).toEqual(int(-1));
expect(util.normalizeSecondsForDuration(-9, 0)).toEqual(int(-9));
expect(util.normalizeSecondsForDuration(-42, 0)).toEqual(int(-42));

expect(util.normalizeSecondsForDuration(1, 19)).toEqual(int(1));
expect(util.normalizeSecondsForDuration(42, 42)).toEqual(int(42));
expect(util.normalizeSecondsForDuration(12345, 6789)).toEqual(int(12345));

expect(util.normalizeSecondsForDuration(-1, 42)).toEqual(int(-1));
expect(util.normalizeSecondsForDuration(-42, 4242)).toEqual(int(-42));
expect(util.normalizeSecondsForDuration(-123, 999)).toEqual(int(-123));

expect(util.normalizeSecondsForDuration(1, 1000000000)).toEqual(int(2));
expect(util.normalizeSecondsForDuration(40, 2000000001)).toEqual(int(42));
expect(util.normalizeSecondsForDuration(583, 7999999999)).toEqual(int(590));

expect(util.normalizeSecondsForDuration(1, -1000000000)).toEqual(int(0));
expect(util.normalizeSecondsForDuration(1, -5000000000)).toEqual(int(-4));
expect(util.normalizeSecondsForDuration(85, -42000000123)).toEqual(int(42));

expect(util.normalizeSecondsForDuration(-19, -1000000000)).toEqual(int(-20));
expect(util.normalizeSecondsForDuration(-19, -11123456789)).toEqual(int(-31));
expect(util.normalizeSecondsForDuration(-42, -2000000001)).toEqual(int(-45));
});

it('should normalize nanoseconds for duration', () => {
expect(util.normalizeNanosecondsForDuration(0)).toEqual(int(0));

expect(util.normalizeNanosecondsForDuration(1)).toEqual(int(1));
expect(util.normalizeNanosecondsForDuration(42)).toEqual(int(42));
expect(util.normalizeNanosecondsForDuration(123456789)).toEqual(int(123456789));
expect(util.normalizeNanosecondsForDuration(999999999)).toEqual(int(999999999));

expect(util.normalizeNanosecondsForDuration(1000000000)).toEqual(int(0));
expect(util.normalizeNanosecondsForDuration(1000000001)).toEqual(int(1));
expect(util.normalizeNanosecondsForDuration(1000000042)).toEqual(int(42));
expect(util.normalizeNanosecondsForDuration(1123456789)).toEqual(int(123456789));
expect(util.normalizeNanosecondsForDuration(42999999999)).toEqual(int(999999999));

expect(util.normalizeNanosecondsForDuration(-1)).toEqual(int(999999999));
expect(util.normalizeNanosecondsForDuration(-3)).toEqual(int(999999997));
expect(util.normalizeNanosecondsForDuration(-100)).toEqual(int(999999900));
expect(util.normalizeNanosecondsForDuration(-999999999)).toEqual(int(1));
expect(util.normalizeNanosecondsForDuration(-1999999999)).toEqual(int(1));
expect(util.normalizeNanosecondsForDuration(-1123456789)).toEqual(int(876543211));
});

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');
Expand Down Expand Up @@ -65,7 +117,7 @@ describe('temporal-util', () => {
expect(util.durationToIsoString(0, 0, 0, 123)).toEqual('P0M0DT0.000000123S');
expect(util.durationToIsoString(11, 99, 100, 99901)).toEqual('P11M99DT100.000099901S');
expect(util.durationToIsoString(int(3), int(9191), int(17), int(123456789))).toEqual('P3M9191DT17.123456789S');
expect(util.durationToIsoString(-5, 2, -13, 123)).toEqual('P-5M2DT-13.000000123S');
expect(util.durationToIsoString(-5, 2, -13, 123)).toEqual('P-5M2DT-12.999999877S');
});

it('should convert epoch day to cypher date', () => {
Expand Down
49 changes: 47 additions & 2 deletions test/v1/temporal-types.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,10 @@ describe('temporal-types', () => {
}

testDurationToString([
{duration: duration(0, 0, 0, 0), expectedString: 'P0M0DT0S'},

{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'},

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

{duration: duration(0, 0, -78036, -143000000), expectedString: 'P0M0DT-78036.143000000S'}
{duration: duration(0, 0, 28, 9), expectedString: 'P0M0DT28.000000009S'},
{duration: duration(0, 0, -28, 9), expectedString: 'P0M0DT-27.999999991S'},
{duration: duration(0, 0, 28, -9), expectedString: 'P0M0DT27.999999991S'},
{duration: duration(0, 0, -28, -9), expectedString: 'P0M0DT-28.000000009S'},

{duration: duration(0, 0, -78036, -143000000), expectedString: 'P0M0DT-78036.143000000S'},

{duration: duration(0, 0, 0, 1000000000), expectedString: 'P0M0DT1S'},
{duration: duration(0, 0, 0, -1000000000), expectedString: 'P0M0DT-1S'},
{duration: duration(0, 0, 0, 1000000007), expectedString: 'P0M0DT1.000000007S'},
{duration: duration(0, 0, 0, -1000000007), expectedString: 'P0M0DT-1.000000007S'},

{duration: duration(0, 0, 40, 2123456789), expectedString: 'P0M0DT42.123456789S'},
{duration: duration(0, 0, -40, 2123456789), expectedString: 'P0M0DT-37.876543211S'},
{duration: duration(0, 0, 40, -2123456789), expectedString: 'P0M0DT37.876543211S'},
{duration: duration(0, 0, -40, -2123456789), expectedString: 'P0M0DT-42.123456789S'}
], done);
});

it('should normalize created duration', () => {
const duration1 = duration(0, 0, 1, 1000000000);
expect(duration1.seconds).toEqual(neo4j.int(2));
expect(duration1.nanoseconds).toEqual(neo4j.int(0));

const duration2 = duration(0, 0, 42, 1000000001);
expect(duration2.seconds).toEqual(neo4j.int(43));
expect(duration2.nanoseconds).toEqual(neo4j.int(1));

const duration3 = duration(0, 0, 42, 42999111222);
expect(duration3.seconds).toEqual(neo4j.int(84));
expect(duration3.nanoseconds).toEqual(neo4j.int(999111222));

const duration4 = duration(0, 0, 1, -1000000000);
expect(duration4.seconds).toEqual(neo4j.int(0));
expect(duration4.nanoseconds).toEqual(neo4j.int(0));

const duration5 = duration(0, 0, 1, -1000000001);
expect(duration5.seconds).toEqual(neo4j.int(-1));
expect(duration5.nanoseconds).toEqual(neo4j.int(999999999));

const duration6 = duration(0, 0, 40, -12123456999);
expect(duration6.seconds).toEqual(neo4j.int(27));
expect(duration6.nanoseconds).toEqual(neo4j.int(876543001));
});

function testSendAndReceiveRandomTemporalValues(valueGenerator, done) {
const asyncFunction = (index, callback) => {
const next = () => callback();
Expand Down Expand Up @@ -635,7 +680,7 @@ describe('temporal-types', () => {
sign * _.random(0, Number.MAX_SAFE_INTEGER),
sign * _.random(0, Number.MAX_SAFE_INTEGER),
sign * _.random(0, Number.MAX_SAFE_INTEGER),
sign * _.random(0, MAX_NANO_OF_SECOND),
_.random(0, MAX_NANO_OF_SECOND),
);
}

Expand Down