Skip to content

Commit a647133

Browse files
authored
Merge pull request #429 from lutovich/1.7-timezone-offset-conversion
Fix conversion from standard Date to Time & DateTime
2 parents 0d259a1 + 60f301e commit a647133

File tree

5 files changed

+86
-16
lines changed

5 files changed

+86
-16
lines changed

src/v1/internal/temporal-util.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -311,11 +311,22 @@ export function totalNanoseconds(standardDate, nanoseconds) {
311311

312312
/**
313313
* Get the time zone offset in seconds from the given standard JavaScript date.
314+
*
315+
* <b>Implementation note:</b>
316+
* Time zone offset returned by the standard JavaScript date is the difference, in minutes, from local time to UTC.
317+
* So positive value means offset is behind UTC and negative value means it is ahead.
318+
* For Neo4j temporal types, like `Time` or `DateTime` offset is in seconds and represents difference from UTC to local time.
319+
* This is different from standard JavaScript dates and that's why implementation negates the returned value.
320+
*
314321
* @param {global.Date} standardDate the standard JavaScript date.
315322
* @return {number} the time zone offset in seconds.
316323
*/
317324
export function timeZoneOffsetInSeconds(standardDate) {
318-
return standardDate.getTimezoneOffset() * SECONDS_PER_MINUTE;
325+
let offsetInMinutes = standardDate.getTimezoneOffset();
326+
if (offsetInMinutes === 0) {
327+
return 0;
328+
}
329+
return -1 * offsetInMinutes * SECONDS_PER_MINUTE;
319330
}
320331

321332
/**

src/v1/temporal-types.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ export class Time {
139139
* @param {Integer|number} minute the minute for the new local time.
140140
* @param {Integer|number} second the second for the new local time.
141141
* @param {Integer|number} nanosecond the nanosecond for the new local time.
142-
* @param {Integer|number} timeZoneOffsetSeconds the time zone offset in seconds.
142+
* @param {Integer|number} timeZoneOffsetSeconds the time zone offset in seconds. Value represents the difference, in seconds, from UTC to local time.
143+
* This is different from standard JavaScript `Date.getTimezoneOffset()` which is the difference, in minutes, from local time to UTC.
143144
*/
144145
constructor(hour, minute, second, nanosecond, timeZoneOffsetSeconds) {
145146
this.hour = util.assertValidHour(hour);
@@ -312,7 +313,9 @@ export class DateTime {
312313
* @param {Integer|number} minute the minute for the new date-time.
313314
* @param {Integer|number} second the second for the new date-time.
314315
* @param {Integer|number} nanosecond the nanosecond for the new date-time.
315-
* @param {Integer|number|null} timeZoneOffsetSeconds the total time zone offset in seconds for the new date-time. Either this argument or `timeZoneId` should be defined.
316+
* @param {Integer|number} timeZoneOffsetSeconds the time zone offset in seconds. Either this argument or `timeZoneId` should be defined.
317+
* Value represents the difference, in seconds, from UTC to local time.
318+
* This is different from standard JavaScript `Date.getTimezoneOffset()` which is the difference, in minutes, from local time to UTC.
316319
* @param {string|null} timeZoneId the time zone id for the new date-time. Either this argument or `timeZoneOffsetSeconds` should be defined.
317320
*/
318321
constructor(year, month, day, hour, minute, second, nanosecond, timeZoneOffsetSeconds, timeZoneId) {

test/internal/temporal-util.test.js

+7-10
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import {int} from '../../src/v1/integer';
2121
import * as util from '../../src/v1/internal/temporal-util';
2222
import {types} from '../../src/v1';
23+
import testUtils from './test-utils';
2324

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

@@ -189,10 +190,12 @@ describe('temporal-util', () => {
189190
});
190191

191192
it('should get timezone offset in seconds from standard date', () => {
192-
expect(util.timeZoneOffsetInSeconds(fakeStandardDateWithOffset(0))).toEqual(0);
193-
expect(util.timeZoneOffsetInSeconds(fakeStandardDateWithOffset(2))).toEqual(120);
194-
expect(util.timeZoneOffsetInSeconds(fakeStandardDateWithOffset(10))).toEqual(600);
195-
expect(util.timeZoneOffsetInSeconds(fakeStandardDateWithOffset(101))).toEqual(6060);
193+
expect(util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(0))).toBe(0);
194+
expect(util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(2))).toBe(-120);
195+
expect(util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(10))).toBe(-600);
196+
expect(util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(101))).toBe(-6060);
197+
expect(util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(-180))).toBe(10800);
198+
expect(util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(-600))).toBe(36000);
196199
});
197200

198201
it('should verify year', () => {
@@ -330,9 +333,3 @@ function localTime(hour, minute, second, nanosecond) {
330333
function localDateTime(year, month, day, hour, minute, second, nanosecond) {
331334
return new types.LocalDateTime(int(year), int(month), int(day), int(hour), int(minute), int(second), int(nanosecond));
332335
}
333-
334-
function fakeStandardDateWithOffset(offsetMinutes) {
335-
const date = new Date();
336-
date.getTimezoneOffset = () => offsetMinutes;
337-
return date;
338-
}

test/internal/test-utils.js

+7
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,14 @@ function isServer() {
2424
return !isClient();
2525
}
2626

27+
function fakeStandardDateWithOffset(offsetMinutes) {
28+
const date = new Date();
29+
date.getTimezoneOffset = () => offsetMinutes;
30+
return date;
31+
}
32+
2733
export default {
2834
isClient,
2935
isServer,
36+
fakeStandardDateWithOffset
3037
};

test/v1/temporal-types.test.js

+55-3
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@
1919

2020
import neo4j from '../../src';
2121
import sharedNeo4j from '../internal/shared-neo4j';
22-
import {totalNanoseconds} from '../../src/v1/internal/temporal-util';
22+
import {timeZoneOffsetInSeconds, totalNanoseconds} from '../../src/v1/internal/temporal-util';
2323
import {ServerVersion, VERSION_3_4_0} from '../../src/v1/internal/server-version';
2424
import timesSeries from 'async/timesSeries';
2525
import _ from 'lodash';
26+
import testUtils from '../internal/test-utils';
2627

2728
const RANDOM_VALUES_TO_TEST = 2000;
2829
const MIN_TEMPORAL_ARRAY_LENGTH = 20;
@@ -921,6 +922,50 @@ describe('temporal-types', () => {
921922
expect(() => dateTimeWithZoneOffset(1, 1, 1, 1, 1, 1, 1000000000, 0)).toThrow();
922923
});
923924

925+
it('should convert standard Date with offset to neo4j Time', () => {
926+
const standardDate1 = testUtils.fakeStandardDateWithOffset(0);
927+
const neo4jTime1 = neo4j.types.Time.fromStandardDate(standardDate1);
928+
verifyTimeZoneOffset(neo4jTime1, 0, 'Z');
929+
930+
const standardDate2 = testUtils.fakeStandardDateWithOffset(-600);
931+
const neo4jTime2 = neo4j.types.Time.fromStandardDate(standardDate2);
932+
verifyTimeZoneOffset(neo4jTime2, 600 * 60, '+10:00');
933+
934+
const standardDate3 = testUtils.fakeStandardDateWithOffset(480);
935+
const neo4jTime3 = neo4j.types.Time.fromStandardDate(standardDate3);
936+
verifyTimeZoneOffset(neo4jTime3, -1 * 480 * 60, '-08:00');
937+
938+
const standardDate4 = testUtils.fakeStandardDateWithOffset(-180);
939+
const neo4jTime4 = neo4j.types.Time.fromStandardDate(standardDate4);
940+
verifyTimeZoneOffset(neo4jTime4, 180 * 60, '+03:00');
941+
942+
const standardDate5 = testUtils.fakeStandardDateWithOffset(150);
943+
const neo4jTime5 = neo4j.types.Time.fromStandardDate(standardDate5);
944+
verifyTimeZoneOffset(neo4jTime5, -1 * 150 * 60, '-02:30');
945+
});
946+
947+
it('should convert standard Date with offset to neo4j DateTime', () => {
948+
const standardDate1 = testUtils.fakeStandardDateWithOffset(0);
949+
const neo4jDateTime1 = neo4j.types.DateTime.fromStandardDate(standardDate1);
950+
verifyTimeZoneOffset(neo4jDateTime1, 0, 'Z');
951+
952+
const standardDate2 = testUtils.fakeStandardDateWithOffset(-600);
953+
const neo4jDateTime2 = neo4j.types.DateTime.fromStandardDate(standardDate2);
954+
verifyTimeZoneOffset(neo4jDateTime2, 600 * 60, '+10:00');
955+
956+
const standardDate3 = testUtils.fakeStandardDateWithOffset(480);
957+
const neo4jDateTime3 = neo4j.types.DateTime.fromStandardDate(standardDate3);
958+
verifyTimeZoneOffset(neo4jDateTime3, -1 * 480 * 60, '-08:00');
959+
960+
const standardDate4 = testUtils.fakeStandardDateWithOffset(-180);
961+
const neo4jDateTime4 = neo4j.types.DateTime.fromStandardDate(standardDate4);
962+
verifyTimeZoneOffset(neo4jDateTime4, 180 * 60, '+03:00');
963+
964+
const standardDate5 = testUtils.fakeStandardDateWithOffset(150);
965+
const neo4jDateTime5 = neo4j.types.DateTime.fromStandardDate(standardDate5);
966+
verifyTimeZoneOffset(neo4jDateTime5, -1 * 150 * 60, '-02:30');
967+
});
968+
924969
function testSendAndReceiveRandomTemporalValues(valueGenerator, done) {
925970
const asyncFunction = (index, callback) => {
926971
const next = () => callback();
@@ -1117,7 +1162,7 @@ describe('temporal-types', () => {
11171162
function testStandardDateToTimeConversion(date, nanosecond) {
11181163
const converted = neo4j.types.Time.fromStandardDate(date, nanosecond);
11191164
const expected = new neo4j.types.Time(date.getHours(), date.getMinutes(), date.getSeconds(), totalNanoseconds(date, nanosecond),
1120-
date.getTimezoneOffset() * 60);
1165+
timeZoneOffsetInSeconds(date));
11211166
expect(converted).toEqual(expected);
11221167
}
11231168

@@ -1137,7 +1182,14 @@ describe('temporal-types', () => {
11371182
function testStandardDateToDateTimeConversion(date, nanosecond) {
11381183
const converted = neo4j.types.DateTime.fromStandardDate(date, nanosecond);
11391184
const expected = new neo4j.types.DateTime(date.getFullYear(), date.getMonth() + 1, date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(),
1140-
totalNanoseconds(date, nanosecond), date.getTimezoneOffset() * 60);
1185+
totalNanoseconds(date, nanosecond), timeZoneOffsetInSeconds(date));
11411186
expect(converted).toEqual(expected);
11421187
}
1188+
1189+
function verifyTimeZoneOffset(temporal, expectedValue, expectedStringValue) {
1190+
expect(temporal.timeZoneOffsetSeconds).toEqual(expectedValue);
1191+
const isoString = temporal.toString();
1192+
// assert ISO string ends with the expected suffix
1193+
expect(isoString.indexOf(expectedStringValue, isoString.length - expectedStringValue.length)).toBeGreaterThan(0);
1194+
}
11431195
});

0 commit comments

Comments
 (0)