Skip to content

Commit

Permalink
Allow to create temporal objects from standard JS Date
Browse files Browse the repository at this point in the history
This commit adds `#fromStandardDate()` functions to all temporal types
except `Duration`. Such functions allow to create temporal objects
from the provided standard JavaScript `Date`.
  • Loading branch information
lutovich committed Jun 19, 2018
1 parent 0ce603e commit 6e679bc
Show file tree
Hide file tree
Showing 9 changed files with 387 additions and 4 deletions.
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 total number of nanoseconds from the given standard JavaScript date.
* @param {global.Date} standardDate the standard JavaScript date.
* @return {number} the total amount of nanoseconds.
*/
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(),
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(),
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(),
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

0 comments on commit 6e679bc

Please sign in to comment.