Skip to content

Commit 959ff20

Browse files
committed
Represent Cypher DateTime with a single type
DateTime contains time zone information. It might either be an offset in seconds (that corresponds to ±HH:MM) or the time zone id (like 'Europe/London'). Previously driver exposed DateTime as two different types: `DateTimeWithZoneOffset` and `DateTimeWithZoneId`. This commit combines them into a single type: ``` class DateTime { year: Integer|number, month: Integer|number, day: Integer|number, hour: Integer|number, minute: Integer|number, second: Integer|number, nanosecond: Integer|number, timeZoneOffsetSeconds: Integer|number, timeZoneId: string } ``` where either `timeZoneOffsetSeconds` or `timeZoneId` is defined. Such single type better corresponds to the Cypher DateTime type and feels more idiomatic to JavaScript. Shape of objects (set of their properties) feels more important than the class/type. Also renamed `Time.offsetSeconds` to `Time.timeZoneOffsetSeconds` to better represent the meaning of the property.
1 parent d9a6a2b commit 959ff20

File tree

7 files changed

+132
-213
lines changed

7 files changed

+132
-213
lines changed

src/v1/index.js

+6-26
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,7 @@ import {assertString, isEmptyObjectOrNull} from './internal/util';
3030
import urlUtil from './internal/url-util';
3131
import HttpDriver from './internal/http/http-driver';
3232
import {isPoint, Point} from './spatial-types';
33-
import {
34-
Date,
35-
DateTimeWithZoneId,
36-
DateTimeWithZoneOffset,
37-
Duration,
38-
isDate,
39-
isDateTimeWithZoneId,
40-
isDateTimeWithZoneOffset,
41-
isDuration,
42-
isLocalDateTime,
43-
isLocalTime,
44-
isTime,
45-
LocalDateTime,
46-
LocalTime,
47-
Time
48-
} from './temporal-types';
33+
import {Date, DateTime, Duration, isDate, isDateTime, isDuration, isLocalDateTime, isLocalTime, isTime, LocalDateTime, LocalTime, Time} from './temporal-types';
4934

5035
/**
5136
* @property {function(username: string, password: string, realm: ?string)} basic the function to create a
@@ -225,8 +210,7 @@ const types = {
225210
Record,
226211
Point,
227212
Date,
228-
DateTimeWithZoneId,
229-
DateTimeWithZoneOffset,
213+
DateTime,
230214
Duration,
231215
LocalDateTime,
232216
LocalTime,
@@ -275,8 +259,7 @@ const forExport = {
275259
Point,
276260
isPoint,
277261
Date,
278-
DateTimeWithZoneId,
279-
DateTimeWithZoneOffset,
262+
DateTime,
280263
Duration,
281264
LocalDateTime,
282265
LocalTime,
@@ -286,8 +269,7 @@ const forExport = {
286269
isTime,
287270
isDate,
288271
isLocalDateTime,
289-
isDateTimeWithZoneOffset,
290-
isDateTimeWithZoneId
272+
isDateTime
291273
};
292274

293275
export {
@@ -303,8 +285,7 @@ export {
303285
Point,
304286
isPoint,
305287
Date,
306-
DateTimeWithZoneId,
307-
DateTimeWithZoneOffset,
288+
DateTime,
308289
Duration,
309290
LocalDateTime,
310291
LocalTime,
@@ -314,7 +295,6 @@ export {
314295
isTime,
315296
isDate,
316297
isLocalDateTime,
317-
isDateTimeWithZoneOffset,
318-
isDateTimeWithZoneId
298+
isDateTime
319299
};
320300
export default forExport;

src/v1/internal/packstream-v2.js

+32-33
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,7 @@
1919

2020
import * as v1 from './packstream-v1';
2121
import {isPoint, Point} from '../spatial-types';
22-
import {
23-
Date,
24-
DateTimeWithZoneId,
25-
DateTimeWithZoneOffset,
26-
Duration,
27-
isDate,
28-
isDateTimeWithZoneId,
29-
isDateTimeWithZoneOffset,
30-
isDuration,
31-
isLocalDateTime,
32-
isLocalTime,
33-
isTime,
34-
Time
35-
} from '../temporal-types';
22+
import {Date, DateTime, Duration, isDate, isDateTime, isDuration, isLocalDateTime, isLocalTime, isTime, Time} from '../temporal-types';
3623
import {int} from '../integer';
3724
import {
3825
dateToEpochDay,
@@ -97,10 +84,8 @@ export class Packer extends v1.Packer {
9784
return () => packDate(obj, this, onError);
9885
} else if (isLocalDateTime(obj)) {
9986
return () => packLocalDateTime(obj, this, onError);
100-
} else if (isDateTimeWithZoneOffset(obj)) {
101-
return () => packDateTimeWithZoneOffset(obj, this, onError);
102-
} else if (isDateTimeWithZoneId(obj)) {
103-
return () => packDateTimeWithZoneId(obj, this, onError);
87+
} else if (isDateTime(obj)) {
88+
return () => packDateTime(obj, this, onError);
10489
} else {
10590
return super.packable(obj, onError);
10691
}
@@ -301,7 +286,7 @@ function unpackLocalTime(unpacker, structSize, buffer) {
301286
*/
302287
function packTime(value, packer, onError) {
303288
const nanoOfDay = localTimeToNanoOfDay(value.hour, value.minute, value.second, value.nanosecond);
304-
const offsetSeconds = int(value.offsetSeconds);
289+
const offsetSeconds = int(value.timeZoneOffsetSeconds);
305290

306291
const packableStructFields = [
307292
packer.packable(nanoOfDay, onError),
@@ -389,21 +374,35 @@ function unpackLocalDateTime(unpacker, structSize, buffer) {
389374
return epochSecondAndNanoToLocalDateTime(epochSecond, nano);
390375
}
391376

377+
/**
378+
* Pack given date time.
379+
* @param {DateTime} value the date time value to pack.
380+
* @param {Packer} packer the packer to use.
381+
* @param {function} onError the error callback.
382+
*/
383+
function packDateTime(value, packer, onError) {
384+
if (value.timeZoneId) {
385+
packDateTimeWithZoneId(value, packer, onError);
386+
} else {
387+
packDateTimeWithZoneOffset(value, packer, onError);
388+
}
389+
}
390+
392391
/**
393392
* Pack given date time with zone offset.
394-
* @param {DateTimeWithZoneOffset} value the date time value to pack.
393+
* @param {DateTime} value the date time value to pack.
395394
* @param {Packer} packer the packer to use.
396395
* @param {function} onError the error callback.
397396
*/
398397
function packDateTimeWithZoneOffset(value, packer, onError) {
399398
const epochSecond = localDateTimeToEpochSecond(value.year, value.month, value.day, value.hour, value.minute, value.second, value.nanosecond);
400399
const nano = int(value.nanosecond);
401-
const offsetSeconds = int(value.offsetSeconds);
400+
const timeZoneOffsetSeconds = int(value.timeZoneOffsetSeconds);
402401

403402
const packableStructFields = [
404403
packer.packable(epochSecond, onError),
405404
packer.packable(nano, onError),
406-
packer.packable(offsetSeconds, onError)
405+
packer.packable(timeZoneOffsetSeconds, onError)
407406
];
408407
packer.packStruct(DATE_TIME_WITH_ZONE_OFFSET, packableStructFields, onError);
409408
}
@@ -413,35 +412,35 @@ function packDateTimeWithZoneOffset(value, packer, onError) {
413412
* @param {Unpacker} unpacker the unpacker to use.
414413
* @param {number} structSize the retrieved struct size.
415414
* @param {BaseBuffer} buffer the buffer to unpack from.
416-
* @return {DateTimeWithZoneOffset} the unpacked date time with zone offset value.
415+
* @return {DateTime} the unpacked date time with zone offset value.
417416
*/
418417
function unpackDateTimeWithZoneOffset(unpacker, structSize, buffer) {
419418
unpacker._verifyStructSize('DateTimeWithZoneOffset', DATE_TIME_WITH_ZONE_OFFSET_STRUCT_SIZE, structSize);
420419

421420
const epochSecond = unpacker.unpack(buffer);
422421
const nano = unpacker.unpack(buffer);
423-
const offsetSeconds = unpacker.unpack(buffer);
422+
const timeZoneOffsetSeconds = unpacker.unpack(buffer);
424423

425424
const localDateTime = epochSecondAndNanoToLocalDateTime(epochSecond, nano);
426-
return new DateTimeWithZoneOffset(localDateTime.year, localDateTime.month, localDateTime.day, localDateTime.hour,
427-
localDateTime.minute, localDateTime.second, localDateTime.nanosecond, offsetSeconds);
425+
return new DateTime(localDateTime.year, localDateTime.month, localDateTime.day, localDateTime.hour,
426+
localDateTime.minute, localDateTime.second, localDateTime.nanosecond, timeZoneOffsetSeconds, null);
428427
}
429428

430429
/**
431430
* Pack given date time with zone id.
432-
* @param {DateTimeWithZoneId} value the date time value to pack.
431+
* @param {DateTime} value the date time value to pack.
433432
* @param {Packer} packer the packer to use.
434433
* @param {function} onError the error callback.
435434
*/
436435
function packDateTimeWithZoneId(value, packer, onError) {
437436
const epochSecond = localDateTimeToEpochSecond(value.year, value.month, value.day, value.hour, value.minute, value.second, value.nanosecond);
438437
const nano = int(value.nanosecond);
439-
const zoneId = value.zoneId;
438+
const timeZoneId = value.timeZoneId;
440439

441440
const packableStructFields = [
442441
packer.packable(epochSecond, onError),
443442
packer.packable(nano, onError),
444-
packer.packable(zoneId, onError)
443+
packer.packable(timeZoneId, onError)
445444
];
446445
packer.packStruct(DATE_TIME_WITH_ZONE_ID, packableStructFields, onError);
447446
}
@@ -451,16 +450,16 @@ function packDateTimeWithZoneId(value, packer, onError) {
451450
* @param {Unpacker} unpacker the unpacker to use.
452451
* @param {number} structSize the retrieved struct size.
453452
* @param {BaseBuffer} buffer the buffer to unpack from.
454-
* @return {DateTimeWithZoneId} the unpacked date time with zone id value.
453+
* @return {DateTime} the unpacked date time with zone id value.
455454
*/
456455
function unpackDateTimeWithZoneId(unpacker, structSize, buffer) {
457456
unpacker._verifyStructSize('DateTimeWithZoneId', DATE_TIME_WITH_ZONE_ID_STRUCT_SIZE, structSize);
458457

459458
const epochSecond = unpacker.unpack(buffer);
460459
const nano = unpacker.unpack(buffer);
461-
const zoneId = unpacker.unpack(buffer);
460+
const timeZoneId = unpacker.unpack(buffer);
462461

463462
const localDateTime = epochSecondAndNanoToLocalDateTime(epochSecond, nano);
464-
return new DateTimeWithZoneId(localDateTime.year, localDateTime.month, localDateTime.day, localDateTime.hour,
465-
localDateTime.minute, localDateTime.second, localDateTime.nanosecond, zoneId);
463+
return new DateTime(localDateTime.year, localDateTime.month, localDateTime.day, localDateTime.hour,
464+
localDateTime.minute, localDateTime.second, localDateTime.nanosecond, null, timeZoneId);
466465
}

src/v1/temporal-types.js

+44-68
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919

2020
import {dateToIsoString, durationToIsoString, timeToIsoString, timeZoneOffsetToIsoString} from './internal/temporal-util';
21+
import {newError} from './error';
2122

2223
const IDENTIFIER_PROPERTY_ATTRIBUTES = {
2324
value: true,
@@ -30,8 +31,7 @@ const LOCAL_TIME_IDENTIFIER_PROPERTY = '__isLocalTime__';
3031
const TIME_IDENTIFIER_PROPERTY = '__isTime__';
3132
const DATE_IDENTIFIER_PROPERTY = '__isDate__';
3233
const LOCAL_DATE_TIME_IDENTIFIER_PROPERTY = '__isLocalDateTime__';
33-
const DATE_TIME_WITH_ZONE_OFFSET_IDENTIFIER_PROPERTY = '__isDateTimeWithZoneOffset__';
34-
const DATE_TIME_WITH_ZONE_ID_IDENTIFIER_PROPERTY = '__isDateTimeWithZoneId__';
34+
const DATE_TIME_IDENTIFIER_PROPERTY = '__isDateTime__';
3535

3636
/**
3737
* Represents an ISO 8601 duration. Contains both date-based values (years, months, days) and time-based values (seconds, nanoseconds).
@@ -119,19 +119,19 @@ export class Time {
119119
* @param {Integer|number} minute the minute for the new local time.
120120
* @param {Integer|number} second the second for the new local time.
121121
* @param {Integer|number} nanosecond the nanosecond for the new local time.
122-
* @param {Integer|number} offsetSeconds the time zone offset in seconds.
122+
* @param {Integer|number} timeZoneOffsetSeconds the time zone offset in seconds.
123123
*/
124-
constructor(hour, minute, second, nanosecond, offsetSeconds) {
124+
constructor(hour, minute, second, nanosecond, timeZoneOffsetSeconds) {
125125
this.hour = hour;
126126
this.minute = minute;
127127
this.second = second;
128128
this.nanosecond = nanosecond;
129-
this.offsetSeconds = offsetSeconds;
129+
this.timeZoneOffsetSeconds = timeZoneOffsetSeconds;
130130
Object.freeze(this);
131131
}
132132

133133
toString() {
134-
return timeToIsoString(this.hour, this.minute, this.second, this.nanosecond) + timeZoneOffsetToIsoString(this.offsetSeconds);
134+
return timeToIsoString(this.hour, this.minute, this.second, this.nanosecond) + timeZoneOffsetToIsoString(this.timeZoneOffsetSeconds);
135135
}
136136
}
137137

@@ -226,93 +226,54 @@ export function isLocalDateTime(obj) {
226226

227227
/**
228228
* Represents an instant capturing the date, the time and the timezone identifier.
229-
* Created <code>DateTimeWithZoneOffset</code> objects are frozen with {@link Object#freeze()} in constructor and thus immutable.
229+
* Created <code>DateTime</code> objects are frozen with {@link Object#freeze()} in constructor and thus immutable.
230230
*/
231-
export class DateTimeWithZoneOffset {
231+
export class DateTime {
232232

233233
/**
234234
* @constructor
235-
* @param {Integer|number} year the year for the new local date.
236-
* @param {Integer|number} month the month for the new local date.
237-
* @param {Integer|number} day the day for the new local date.
238-
* @param {Integer|number} hour the hour for the new local time.
239-
* @param {Integer|number} minute the minute for the new local time.
240-
* @param {Integer|number} second the second for the new local time.
241-
* @param {Integer|number} nanosecond the nanosecond for the new local time.
242-
* @param {Integer|number} offsetSeconds the timezone offset in seconds for the new timezone-aware date-time.
235+
* @param {Integer|number} year the year for the new date-time.
236+
* @param {Integer|number} month the month for the new date-time.
237+
* @param {Integer|number} day the day for the new date-time.
238+
* @param {Integer|number} hour the hour for the new date-time.
239+
* @param {Integer|number} minute the minute for the new date-time.
240+
* @param {Integer|number} second the second for the new date-time.
241+
* @param {Integer|number} nanosecond the nanosecond for the new date-time.
242+
* @param {Integer|number|null} timeZoneOffsetSeconds the total time zone offset in seconds for the new date-time. Either this argument or <code>timeZoneId</code> should be defined.
243+
* @param {string|null} timeZoneId the time zone id for the new date-time. Either this argument or <code>timeZoneOffsetSeconds</code> should be defined.
243244
*/
244-
constructor(year, month, day, hour, minute, second, nanosecond, offsetSeconds) {
245+
constructor(year, month, day, hour, minute, second, nanosecond, timeZoneOffsetSeconds, timeZoneId) {
245246
this.year = year;
246247
this.month = month;
247248
this.day = day;
248249
this.hour = hour;
249250
this.minute = minute;
250251
this.second = second;
251252
this.nanosecond = nanosecond;
252-
this.offsetSeconds = offsetSeconds;
253-
Object.freeze(this);
254-
}
255253

256-
toString() {
257-
return localDateTimeToString(this.year, this.month, this.day, this.hour, this.minute, this.second, this.nanosecond) +
258-
timeZoneOffsetToIsoString(this.offsetSeconds);
259-
}
260-
}
261-
262-
Object.defineProperty(DateTimeWithZoneOffset.prototype, DATE_TIME_WITH_ZONE_OFFSET_IDENTIFIER_PROPERTY, IDENTIFIER_PROPERTY_ATTRIBUTES);
263-
264-
/**
265-
* Test if given object is an instance of {@link DateTimeWithZoneOffset} class.
266-
* @param {object} obj the object to test.
267-
* @return {boolean} <code>true</code> if given object is a {@link DateTimeWithZoneOffset}, <code>false</code> otherwise.
268-
*/
269-
export function isDateTimeWithZoneOffset(obj) {
270-
return hasIdentifierProperty(obj, DATE_TIME_WITH_ZONE_OFFSET_IDENTIFIER_PROPERTY);
271-
}
272-
273-
/**
274-
* Represents an instant capturing the date, the time and the timezone identifier.
275-
* Created <code>DateTimeWithZoneId</code> objects are frozen with {@link Object#freeze()} in constructor and thus immutable.
276-
*/
277-
export class DateTimeWithZoneId {
254+
const [offset, id] = verifyTimeZoneArguments(timeZoneOffsetSeconds, timeZoneId);
255+
this.timeZoneOffsetSeconds = offset;
256+
this.timeZoneId = id;
278257

279-
/**
280-
* @constructor
281-
* @param {Integer|number} year the year for the new local date.
282-
* @param {Integer|number} month the month for the new local date.
283-
* @param {Integer|number} day the day for the new local date.
284-
* @param {Integer|number} hour the hour for the new local time.
285-
* @param {Integer|number} minute the minute for the new local time.
286-
* @param {Integer|number} second the second for the new local time.
287-
* @param {Integer|number} nanosecond the nanosecond for the new local time.
288-
* @param {string} zoneId the timezone identifier for the new timezone-aware date-time.
289-
*/
290-
constructor(year, month, day, hour, minute, second, nanosecond, zoneId) {
291-
this.year = year;
292-
this.month = month;
293-
this.day = day;
294-
this.hour = hour;
295-
this.minute = minute;
296-
this.second = second;
297-
this.nanosecond = nanosecond;
298-
this.zoneId = zoneId;
299258
Object.freeze(this);
300259
}
301260

302261
toString() {
303-
return localDateTimeToString(this.year, this.month, this.day, this.hour, this.minute, this.second, this.nanosecond) + `[${this.zoneId}]`;
262+
const localDateTimeStr = localDateTimeToString(this.year, this.month, this.day, this.hour, this.minute, this.second, this.nanosecond);
263+
const timeZoneStr = this.timeZoneId ? `[${this.timeZoneId}]` : timeZoneOffsetToIsoString(this.timeZoneOffsetSeconds);
264+
return localDateTimeStr + timeZoneStr;
304265
}
305266
}
306267

307-
Object.defineProperty(DateTimeWithZoneId.prototype, DATE_TIME_WITH_ZONE_ID_IDENTIFIER_PROPERTY, IDENTIFIER_PROPERTY_ATTRIBUTES);
268+
Object.defineProperty(DateTime.prototype, DATE_TIME_IDENTIFIER_PROPERTY, IDENTIFIER_PROPERTY_ATTRIBUTES);
308269

309270
/**
310-
* Test if given object is an instance of {@link DateTimeWithZoneId} class.
271+
* Test if given object is an instance of {@link DateTime} class.
311272
* @param {object} obj the object to test.
312-
* @return {boolean} <code>true</code> if given object is a {@link DateTimeWithZoneId}, <code>false</code> otherwise.
273+
* @return {boolean} <code>true</code> if given object is a {@link DateTime}, <code>false</code> otherwise.
313274
*/
314-
export function isDateTimeWithZoneId(obj) {
315-
return hasIdentifierProperty(obj, DATE_TIME_WITH_ZONE_ID_IDENTIFIER_PROPERTY);
275+
export function isDateTime(obj) {
276+
return hasIdentifierProperty(obj, DATE_TIME_IDENTIFIER_PROPERTY);
316277
}
317278

318279
function hasIdentifierProperty(obj, property) {
@@ -322,3 +283,18 @@ function hasIdentifierProperty(obj, property) {
322283
function localDateTimeToString(year, month, day, hour, minute, second, nanosecond) {
323284
return dateToIsoString(year, month, day) + 'T' + timeToIsoString(hour, minute, second, nanosecond);
324285
}
286+
287+
function verifyTimeZoneArguments(timeZoneOffsetSeconds, timeZoneId) {
288+
const offsetDefined = timeZoneOffsetSeconds || timeZoneOffsetSeconds === 0;
289+
const idDefined = timeZoneId && timeZoneId !== '';
290+
291+
if (offsetDefined && !idDefined) {
292+
return [timeZoneOffsetSeconds, null];
293+
} else if (!offsetDefined && idDefined) {
294+
return [null, timeZoneId];
295+
} else if (offsetDefined && idDefined) {
296+
throw newError(`Unable to create DateTime with both time zone offset and id. Please specify either of them. Given offset: ${timeZoneOffsetSeconds} and id: ${timeZoneId}`);
297+
} else {
298+
throw newError(`Unable to create DateTime without either time zone offset or id. Please specify either of them. Given offset: ${timeZoneOffsetSeconds} and id: ${timeZoneId}`);
299+
}
300+
}

0 commit comments

Comments
 (0)