Skip to content
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

Allow to create temporal objects from standard JS Date #390

Merged
merged 3 commits into from
Jun 28, 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
24 changes: 23 additions & 1 deletion src/v1/internal/temporal-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* limitations under the License.
*/

import {int} from '../integer';
import {int, isInt} from '../integer';
import {Date, LocalDateTime, LocalTime} from '../temporal-types';

/*
Expand All @@ -35,6 +35,7 @@ const MINUTES_PER_HOUR = 60;
const SECONDS_PER_MINUTE = 60;
const SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR;
const NANOS_PER_SECOND = 1000000000;
const NANOS_PER_MILLISECOND = 1000000;
const NANOS_PER_MINUTE = NANOS_PER_SECOND * SECONDS_PER_MINUTE;
const NANOS_PER_HOUR = NANOS_PER_MINUTE * MINUTES_PER_HOUR;
const DAYS_0000_TO_1970 = 719528;
Expand Down Expand Up @@ -264,6 +265,27 @@ export function dateToIsoString(year, month, day) {
return `${yearString}-${monthString}-${dayString}`;
}

/**
* 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.
* @param {Integer|number|undefined} nanoseconds the optional number of nanoseconds.
* @return {Integer|number} the total amount of nanoseconds.
*/
export function totalNanoseconds(standardDate, nanoseconds) {
nanoseconds = (nanoseconds || 0);
const nanosFromMillis = standardDate.getMilliseconds() * NANOS_PER_MILLISECOND;
return isInt(nanoseconds) ? nanoseconds.add(nanosFromMillis) : nanoseconds + nanosFromMillis;
}

/**
* Get the time zone offset in seconds from the given standard JavaScript date.
* @param {global.Date} standardDate the standard JavaScript date.
* @return {number} the time zone offset in seconds.
*/
export function timeZoneOffsetInSeconds(standardDate) {
return standardDate.getTimezoneOffset() * SECONDS_PER_MINUTE;
}

/**
* Converts given local time into a single integer representing this same time in seconds of the day. Nanoseconds are skipped.
* @param {Integer|number|string} hour the hour of the local time.
Expand Down
11 changes: 11 additions & 0 deletions src/v1/internal/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ function assertNumberOrInteger(obj, objName) {
return obj;
}

function assertValidDate(obj, objName) {
if (Object.prototype.toString.call(obj) !== '[object Date]') {
throw new TypeError(objName + ' expected to be a standard JavaScript Date but was: ' + JSON.stringify(obj));
}
if (Number.isNaN(obj.getTime())) {
throw new TypeError(objName + ' expected to be valid JavaScript Date but its time was NaN: ' + JSON.stringify(obj));
}
return obj;
}

function assertCypherStatement(obj) {
assertString(obj, 'Cypher statement');
if (obj.trim().length === 0) {
Expand All @@ -112,6 +122,7 @@ export {
assertString,
assertNumber,
assertNumberOrInteger,
assertValidDate,
validateStatementAndParameters,
ENCRYPTION_ON,
ENCRYPTION_OFF
Expand Down
100 changes: 99 additions & 1 deletion src/v1/temporal-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/

import * as util from './internal/temporal-util';
import {assertNumberOrInteger, assertString} from './internal/util';
import {assertNumberOrInteger, assertString, assertValidDate} from './internal/util';
import {newError} from './error';

const IDENTIFIER_PROPERTY_ATTRIBUTES = {
Expand Down Expand Up @@ -94,6 +94,23 @@ export class LocalTime {
Object.freeze(this);
}

/**
* Create a local time object from the given standard JavaScript <code>Date</code> and optional nanoseconds.
* Year, month, day and time zone offset components of the given date are ignored.
* @param {global.Date} standardDate the standard JavaScript date to convert.
* @param {Integer|number|undefined} nanosecond the optional amount of nanoseconds.
* @return {LocalTime} new local time.
*/
static fromStandardDate(standardDate, nanosecond) {
verifyStandardDateAndNanos(standardDate, nanosecond);

return new LocalTime(
standardDate.getHours(),
standardDate.getMinutes(),
standardDate.getSeconds(),
util.totalNanoseconds(standardDate, nanosecond));
}

toString() {
return util.timeToIsoString(this.hour, this.minute, this.second, this.nanosecond);
}
Expand Down Expand Up @@ -133,6 +150,24 @@ export class Time {
Object.freeze(this);
}

/**
* Create a time object from the given standard JavaScript <code>Date</code> and optional nanoseconds.
* Year, month and day components of the given date are ignored.
* @param {global.Date} standardDate the standard JavaScript date to convert.
* @param {Integer|number|undefined} nanosecond the optional amount of nanoseconds.
* @return {Time} new time.
*/
static fromStandardDate(standardDate, nanosecond) {
verifyStandardDateAndNanos(standardDate, nanosecond);

return new Time(
standardDate.getHours(),
standardDate.getMinutes(),
standardDate.getSeconds(),
util.totalNanoseconds(standardDate, nanosecond),
util.timeZoneOffsetInSeconds(standardDate));
}

toString() {
return util.timeToIsoString(this.hour, this.minute, this.second, this.nanosecond) + util.timeZoneOffsetToIsoString(this.timeZoneOffsetSeconds);
}
Expand Down Expand Up @@ -168,6 +203,21 @@ export class Date {
Object.freeze(this);
}

/**
* Create a date object from the given standard JavaScript <code>Date</code>.
* Hour, minute, second, millisecond and time zone offset components of the given date are ignored.
* @param {global.Date} standardDate the standard JavaScript date to convert.
* @return {Date} new date.
*/
static fromStandardDate(standardDate) {
verifyStandardDateAndNanos(standardDate, null);

return new Date(
standardDate.getFullYear(),
standardDate.getMonth() + 1,
standardDate.getDate());
}

toString() {
return util.dateToIsoString(this.year, this.month, this.day);
}
Expand Down Expand Up @@ -211,6 +261,26 @@ export class LocalDateTime {
Object.freeze(this);
}

/**
* Create a local date-time object from the given standard JavaScript <code>Date</code> and optional nanoseconds.
* Time zone offset component of the given date is ignored.
* @param {global.Date} standardDate the standard JavaScript date to convert.
* @param {Integer|number|undefined} nanosecond the optional amount of nanoseconds.
* @return {LocalDateTime} new local date-time.
*/
static fromStandardDate(standardDate, nanosecond) {
verifyStandardDateAndNanos(standardDate, nanosecond);

return new LocalDateTime(
standardDate.getFullYear(),
standardDate.getMonth() + 1,
standardDate.getDate(),
standardDate.getHours(),
standardDate.getMinutes(),
standardDate.getSeconds(),
util.totalNanoseconds(standardDate, nanosecond));
}

toString() {
return localDateTimeToString(this.year, this.month, this.day, this.hour, this.minute, this.second, this.nanosecond);
}
Expand Down Expand Up @@ -261,6 +331,27 @@ export class DateTime {
Object.freeze(this);
}

/**
* Create a date-time object from the given standard JavaScript <code>Date</code> and optional nanoseconds.
* @param {global.Date} standardDate the standard JavaScript date to convert.
* @param {Integer|number|undefined} nanosecond the optional amount of nanoseconds.
* @return {DateTime} new date-time.
*/
static fromStandardDate(standardDate, nanosecond) {
verifyStandardDateAndNanos(standardDate, nanosecond);

return new DateTime(
standardDate.getFullYear(),
standardDate.getMonth() + 1,
standardDate.getDate(),
standardDate.getHours(),
standardDate.getMinutes(),
standardDate.getSeconds(),
util.totalNanoseconds(standardDate, nanosecond),
util.timeZoneOffsetInSeconds(standardDate),
null /* no time zone id */);
}

toString() {
const localDateTimeStr = localDateTimeToString(this.year, this.month, this.day, this.hour, this.minute, this.second, this.nanosecond);
const timeZoneStr = this.timeZoneId ? `[${this.timeZoneId}]` : util.timeZoneOffsetToIsoString(this.timeZoneOffsetSeconds);
Expand Down Expand Up @@ -303,3 +394,10 @@ function verifyTimeZoneArguments(timeZoneOffsetSeconds, timeZoneId) {
throw newError(`Unable to create DateTime without either time zone offset or id. Please specify either of them. Given offset: ${timeZoneOffsetSeconds} and id: ${timeZoneId}`);
}
}

function verifyStandardDateAndNanos(standardDate, nanosecond) {
assertValidDate(standardDate, 'Standard date');
if (nanosecond !== null && nanosecond !== undefined) {
assertNumberOrInteger(nanosecond, 'Nanosecond');
}
}
32 changes: 32 additions & 0 deletions test/internal/temporal-util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,32 @@ describe('temporal-util', () => {
expect(util.localTimeToNanoOfDay(12, 51, 17, 808080)).toEqual(int(46277000808080));
});

it('should get total nanoseconds from standard date', () => {
expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 0))).toEqual(0);
expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 1))).toEqual(1000000);
expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 23))).toEqual(23000000);
expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 999))).toEqual(999000000);

expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 0), 0)).toEqual(0);
expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 0), 1)).toEqual(1);
expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 0), 999)).toEqual(999);
expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 1), 999)).toEqual(1000999);
expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 999), 111)).toEqual(999000111);

expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 0), int(0))).toEqual(int(0));
expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 0), int(1))).toEqual(int(1));
expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 0), int(999))).toEqual(int(999));
expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 1), int(999))).toEqual(int(1000999));
expect(util.totalNanoseconds(new Date(2000, 1, 1, 1, 1, 1, 999), int(111))).toEqual(int(999000111));
});

it('should get timezone offset in seconds from standard date', () => {
expect(util.timeZoneOffsetInSeconds(fakeStandardDateWithOffset(0))).toEqual(0);
expect(util.timeZoneOffsetInSeconds(fakeStandardDateWithOffset(2))).toEqual(120);
expect(util.timeZoneOffsetInSeconds(fakeStandardDateWithOffset(10))).toEqual(600);
expect(util.timeZoneOffsetInSeconds(fakeStandardDateWithOffset(101))).toEqual(6060);
});

});

function date(year, month, day) {
Expand All @@ -182,3 +208,9 @@ function localTime(hour, minute, second, nanosecond) {
function localDateTime(year, month, day, hour, minute, second, nanosecond) {
return new types.LocalDateTime(int(year), int(month), int(day), int(hour), int(minute), int(second), int(nanosecond));
}

function fakeStandardDateWithOffset(offsetMinutes) {
const date = new Date();
date.getTimezoneOffset = () => offsetMinutes;
return date;
}
25 changes: 25 additions & 0 deletions test/internal/util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,23 @@ describe('util', () => {
verifyInvalidNumberOrInteger({value: 42});
});

it('should check dates', () => {
verifyValidDate(new Date());
verifyValidDate(new Date(0));
verifyValidDate(new Date(-1));
verifyValidDate(new Date(2000, 10, 10));
verifyValidDate(new Date(2000, 10, 10, 10, 10, 10, 10));

verifyInvalidDate(new Date('not a valid date'));
verifyInvalidDate(new Date({}));
verifyInvalidDate(new Date([]));

verifyInvalidDate({});
verifyInvalidDate([]);
verifyInvalidDate('2007-04-05T12:30-02:00');
verifyInvalidDate(2019);
});

function verifyValidString(str) {
expect(util.assertString(str, 'Test string')).toBe(str);
}
Expand Down Expand Up @@ -171,4 +188,12 @@ describe('util', () => {
expect(() => util.validateStatementAndParameters('RETURN 1', obj)).toThrowError(TypeError);
}

function verifyValidDate(obj) {
expect(util.assertValidDate(obj, 'Test date')).toBe(obj);
}

function verifyInvalidDate(obj) {
expect(() => util.assertValidDate(obj, 'Test date')).toThrowError(TypeError);
}

});
13 changes: 13 additions & 0 deletions test/types/v1/temporal-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
Time
} from "../../../types/v1/temporal-types";
import Integer, {int} from "../../../types/v1/integer";
import {StandardDate} from "../../../types/v1/graph-types";

const duration1: Duration = new Duration(int(1), int(1), int(1), int(1));
const months1: Integer = duration1.months;
Expand Down Expand Up @@ -149,3 +150,15 @@ const isTimeValue: boolean = isTime(time1);
const isDateValue: boolean = isDate(date1);
const isLocalDateTimeValue: boolean = isLocalDateTime(localDateTime1);
const isDateTimeValue: boolean = isDateTime(dateTime1);

const dummy: any = null;
const standardDate: StandardDate = dummy;
const localTime3: LocalTime<number> = LocalTime.fromStandardDate(standardDate);
const localTime4: LocalTime<number> = LocalTime.fromStandardDate(standardDate, 42);
const time3: Time<number> = Time.fromStandardDate(standardDate);
const time4: Time<number> = Time.fromStandardDate(standardDate, 42);
const date3: Date<number> = Date.fromStandardDate(standardDate);
const localDateTime3: LocalDateTime<number> = LocalDateTime.fromStandardDate(standardDate);
const localDateTime4: LocalDateTime<number> = LocalDateTime.fromStandardDate(standardDate, 42);
const dateTime5: DateTime<number> = DateTime.fromStandardDate(standardDate);
const dateTime6: DateTime<number> = DateTime.fromStandardDate(standardDate, 42);
Loading