Skip to content

Commit 0d5fbae

Browse files
committed
Revert "fixing minute offset issue in certain time zones (#1238)"
This reverts commit ff3dd5f.
1 parent 549e7a9 commit 0d5fbae

File tree

9 files changed

+168
-133
lines changed

9 files changed

+168
-133
lines changed

packages/bolt-connection/test/test-utils.js

+7
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ function isServer () {
3131
return !isClient()
3232
}
3333

34+
function fakeStandardDateWithOffset (offsetMinutes) {
35+
const date = new Date()
36+
date.getTimezoneOffset = () => offsetMinutes
37+
return date
38+
}
39+
3440
const matchers = {
3541
toBeElementOf: function (actual, expected) {
3642
if (expected === undefined) {
@@ -155,6 +161,7 @@ function arbitraryTimeZoneId () {
155161
export default {
156162
isClient,
157163
isServer,
164+
fakeStandardDateWithOffset,
158165
matchers,
159166
MessageRecordingConnection,
160167
spyProtocolWrite,

packages/core/src/internal/temporal-util.ts

+13-43
Original file line numberDiff line numberDiff line change
@@ -339,54 +339,24 @@ export function totalNanoseconds (
339339
/**
340340
* Get the time zone offset in seconds from the given standard JavaScript date.
341341
*
342+
* <b>Implementation note:</b>
343+
* Time zone offset returned by the standard JavaScript date is the difference, in minutes, from local time to UTC.
344+
* So positive value means offset is behind UTC and negative value means it is ahead.
345+
* For Neo4j temporal types, like `Time` or `DateTime` offset is in seconds and represents difference from UTC to local time.
346+
* This is different from standard JavaScript dates and that's why implementation negates the returned value.
347+
*
342348
* @param {global.Date} standardDate the standard JavaScript date.
343349
* @return {number} the time zone offset in seconds.
344350
*/
345351
export function timeZoneOffsetInSeconds (standardDate: Date): number {
346-
const secondsPortion = standardDate.getSeconds() - standardDate.getUTCSeconds()
347-
const minutesPortion = standardDate.getMinutes() - standardDate.getUTCMinutes()
348-
const hoursPortion = standardDate.getHours() - standardDate.getUTCHours()
349-
const daysPortion = _getDayOffset(standardDate)
350-
return hoursPortion * SECONDS_PER_HOUR + minutesPortion * SECONDS_PER_MINUTE + secondsPortion + daysPortion * SECONDS_PER_DAY
351-
}
352-
353-
/**
354-
* Get the difference in days from the given JavaScript date in local time and UTC.
355-
*
356-
* @private
357-
* @param {global.Date} standardDate the date to evaluate
358-
* @returns {number} the difference in days between date local time and UTC
359-
*/
360-
function _getDayOffset (standardDate: Date): number {
361-
if (standardDate.getMonth() === standardDate.getUTCMonth()) {
362-
return standardDate.getDate() - standardDate.getUTCDate()
363-
} else if ((standardDate.getFullYear() > standardDate.getUTCFullYear()) || (standardDate.getMonth() > standardDate.getUTCMonth() && standardDate.getFullYear() === standardDate.getUTCFullYear())) {
364-
return standardDate.getDate() + _daysUntilNextMonth(standardDate.getUTCMonth(), standardDate.getUTCFullYear()) - standardDate.getUTCDate()
365-
} else {
366-
return standardDate.getDate() - (standardDate.getUTCDate() + _daysUntilNextMonth(standardDate.getMonth(), standardDate.getFullYear()))
367-
}
368-
}
369-
370-
/**
371-
* Get the number of days in a month, including a check for leap years.
372-
*
373-
* @private
374-
* @param {number} month the month of the date to evalutate
375-
* @param {number} year the month of the date to evalutate
376-
* @returns {number} the total number of days in the month evaluated
377-
*/
378-
function _daysUntilNextMonth (month: number, year: number): number {
379-
if (month === 1) {
380-
if (year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0)) {
381-
return 29
382-
} else {
383-
return 28
384-
}
385-
} else if ([0, 2, 4, 6, 7, 9, 11].includes(month)) {
386-
return 31
387-
} else {
388-
return 30
352+
const secondsPortion = standardDate.getSeconds() >= standardDate.getUTCSeconds()
353+
? standardDate.getSeconds() - standardDate.getUTCSeconds()
354+
: standardDate.getSeconds() - standardDate.getUTCSeconds() + 60
355+
const offsetInMinutes = standardDate.getTimezoneOffset()
356+
if (offsetInMinutes === 0) {
357+
return 0 + secondsPortion
389358
}
359+
return -1 * offsetInMinutes * SECONDS_PER_MINUTE + secondsPortion
390360
}
391361

392362
/**

packages/core/src/temporal-types.ts

-6
Original file line numberDiff line numberDiff line change
@@ -353,10 +353,6 @@ export class Date<T extends NumberOrInteger = Integer> {
353353
/**
354354
* Create a {@link Date} object from the given standard JavaScript `Date`.
355355
* Hour, minute, second, millisecond and time zone offset components of the given date are ignored.
356-
*
357-
* NOTE: the function {@link toStandardDate} and {@link fromStandardDate} are not inverses of one another. {@link fromStandardDate} takes the Day, Month and Year in local time from the supplies JavaScript Date object, while {@link toStandardDate} creates a new JavaScript Date object at midnight UTC. This incongruity will be rectified in 6.0
358-
* If your timezone has a negative offset from UTC, creating a JavaScript Date at midnight UTC and converting it with {@link fromStandardDate} will result in a Date for the day before.
359-
*
360356
* @param {global.Date} standardDate - The standard JavaScript date to convert.
361357
* @return {Date} New Date.
362358
*/
@@ -376,8 +372,6 @@ export class Date<T extends NumberOrInteger = Integer> {
376372
* The time component of the returned `Date` is set to midnight
377373
* and the time zone is set to UTC.
378374
*
379-
* NOTE: the function {@link toStandardDate} and {@link fromStandardDate} are not inverses of one another. {@link fromStandardDate} takes the Day, Month and Year in local time from the supplies JavaScript Date object, while {@link toStandardDate} creates a new JavaScript Date object at midnight UTC. This incongruity will be rectified in 6.0
380-
*
381375
* @returns {StandardDate} Standard JavaScript `Date` at `00:00:00.000` UTC.
382376
*/
383377
toStandardDate (): StandardDate {

packages/core/test/temporal-types.test.ts

+49-35
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18+
import { StandardDate } from '../src/graph-types'
1819
import { LocalDateTime, Date, DateTime, Duration, isDuration, LocalTime, isLocalTime, Time, isTime, isDate, isLocalDateTime, isDateTime } from '../src/temporal-types'
1920
import { temporalUtil } from '../src/internal'
2021
import fc from 'fast-check'
@@ -30,9 +31,9 @@ describe('Date', () => {
3031

3132
const standardDate = localDatetime.toStandardDate()
3233

33-
expect(standardDate.getUTCFullYear()).toEqual(localDatetime.year)
34-
expect(standardDate.getUTCMonth()).toEqual(localDatetime.month - 1)
35-
expect(standardDate.getUTCDate()).toEqual(localDatetime.day)
34+
expect(standardDate.getFullYear()).toEqual(localDatetime.year)
35+
expect(standardDate.getMonth()).toEqual(localDatetime.month - 1)
36+
expect(standardDate.getDate()).toEqual(localDatetime.day)
3637
})
3738

3839
it('should be the reverse operation of fromStandardDate but losing time information', () => {
@@ -46,11 +47,14 @@ describe('Date', () => {
4647
const date = Date.fromStandardDate(standardDate)
4748
const receivedDate = date.toStandardDate()
4849

49-
expect(receivedDate.getUTCFullYear()).toEqual(standardDate.getFullYear()) // Date converts from local time but to UTC
50-
expect(receivedDate.getUTCMonth()).toEqual(standardDate.getMonth())
51-
expect(receivedDate.getUTCDate()).toEqual(standardDate.getDate())
52-
expect(receivedDate.getUTCHours()).toEqual(0)
53-
expect(receivedDate.getUTCMinutes()).toEqual(0)
50+
const adjustedDateTime = temporalUtil.newDate(standardDate)
51+
adjustedDateTime.setHours(0, offset(receivedDate))
52+
53+
expect(receivedDate.getFullYear()).toEqual(adjustedDateTime.getFullYear())
54+
expect(receivedDate.getMonth()).toEqual(adjustedDateTime.getMonth())
55+
expect(receivedDate.getDate()).toEqual(adjustedDateTime.getDate())
56+
expect(receivedDate.getHours()).toEqual(adjustedDateTime.getHours())
57+
expect(receivedDate.getMinutes()).toEqual(adjustedDateTime.getMinutes())
5458
})
5559
)
5660
})
@@ -109,33 +113,35 @@ describe('DateTime', () => {
109113

110114
const standardDate = datetime.toStandardDate()
111115

112-
expect(standardDate.getUTCFullYear()).toEqual(datetime.year)
113-
expect(standardDate.getUTCMonth()).toEqual(datetime.month - 1)
114-
expect(standardDate.getUTCDate()).toEqual(datetime.day) // The datetime in this test will never cross the date line in conversion, it is therefore safe to use UTC here to avoid machine timezone from altering the result of the test.
115-
const offsetAdjust = (datetime.timeZoneOffsetSeconds ?? 0) / 60
116-
const hourDiff = Math.abs((offsetAdjust - offsetAdjust % 60) / 60)
116+
expect(standardDate.getFullYear()).toEqual(datetime.year)
117+
expect(standardDate.getMonth()).toEqual(datetime.month - 1)
118+
expect(standardDate.getDate()).toEqual(datetime.day)
119+
const offsetInMinutes = offset(standardDate)
120+
const offsetAdjust = offsetInMinutes - (datetime.timeZoneOffsetSeconds ?? 0) / 60
121+
const hourDiff = Math.abs(offsetAdjust / 60)
117122
const minuteDiff = Math.abs(offsetAdjust % 60)
118-
expect(standardDate.getUTCHours()).toBe(datetime.hour - hourDiff)
119-
expect(standardDate.getUTCMinutes()).toBe(datetime.minute - minuteDiff)
120-
expect(standardDate.getUTCSeconds()).toBe(datetime.second)
121-
expect(standardDate.getUTCMilliseconds()).toBe(Math.round(datetime.nanosecond / 1000000))
123+
expect(standardDate.getHours()).toBe(datetime.hour - hourDiff)
124+
expect(standardDate.getMinutes()).toBe(datetime.minute - minuteDiff)
125+
expect(standardDate.getSeconds()).toBe(datetime.second)
126+
expect(standardDate.getMilliseconds()).toBe(Math.round(datetime.nanosecond / 1000000))
122127
})
123128

124129
it('should convert to a standard date (offset)', () => {
125130
const datetime = new DateTime(2020, 12, 15, 12, 2, 3, 4000000, 120 * 60)
126131

127132
const standardDate = datetime.toStandardDate()
128133

129-
expect(standardDate.getUTCFullYear()).toEqual(datetime.year)
130-
expect(standardDate.getUTCMonth()).toEqual(datetime.month - 1)
131-
expect(standardDate.getUTCDate()).toEqual(datetime.day)
132-
const offsetAdjust = (datetime.timeZoneOffsetSeconds ?? 0) / 60
133-
const hourDiff = Math.abs((offsetAdjust - offsetAdjust % 60) / 60)
134+
expect(standardDate.getFullYear()).toEqual(datetime.year)
135+
expect(standardDate.getMonth()).toEqual(datetime.month - 1)
136+
expect(standardDate.getDate()).toEqual(datetime.day)
137+
const offsetInMinutes = offset(standardDate)
138+
const offsetAdjust = offsetInMinutes - (datetime.timeZoneOffsetSeconds ?? 0) / 60
139+
const hourDiff = Math.abs(offsetAdjust / 60)
134140
const minuteDiff = Math.abs(offsetAdjust % 60)
135-
expect(standardDate.getUTCHours()).toBe(datetime.hour - hourDiff)
136-
expect(standardDate.getUTCMinutes()).toBe(datetime.minute - minuteDiff)
137-
expect(standardDate.getUTCSeconds()).toBe(datetime.second)
138-
expect(standardDate.getUTCMilliseconds()).toBe(Math.round(datetime.nanosecond / 1000000))
141+
expect(standardDate.getHours()).toBe(datetime.hour - hourDiff)
142+
expect(standardDate.getMinutes()).toBe(datetime.minute - minuteDiff)
143+
expect(standardDate.getSeconds()).toBe(datetime.second)
144+
expect(standardDate.getMilliseconds()).toBe(Math.round(datetime.nanosecond / 1000000))
139145
})
140146

141147
it('should not convert to a standard date (zoneid)', () => {
@@ -147,16 +153,12 @@ describe('DateTime', () => {
147153

148154
it('should be the reverse operation of fromStandardDate', () => {
149155
fc.assert(
150-
fc.property(
151-
fc.date({
152-
max: temporalUtil.newDate(MAX_UTC_IN_MS - ONE_DAY_IN_MS),
153-
min: temporalUtil.newDate(MIN_UTC_IN_MS + ONE_DAY_IN_MS)
154-
}), (date) => {
155-
const datetime = DateTime.fromStandardDate(date)
156-
const receivedDate = datetime.toStandardDate()
156+
fc.property(fc.date(), (date) => {
157+
const datetime = DateTime.fromStandardDate(date)
158+
const receivedDate = datetime.toStandardDate()
157159

158-
expect(receivedDate).toEqual(date)
159-
})
160+
expect(receivedDate).toEqual(date)
161+
})
160162
)
161163
})
162164
})
@@ -282,3 +284,15 @@ describe('isDateTime', () => {
282284
}
283285
})
284286
})
287+
288+
/**
289+
* The offset in StandardDate is the number of minutes
290+
* to sum to the date and time to get the UTC time.
291+
*
292+
* This function change the sign of the offset,
293+
* this way using the most common meaning.
294+
* The time to add to UTC to get the local time.
295+
*/
296+
function offset (date: StandardDate): number {
297+
return date.getTimezoneOffset() * -1
298+
}

packages/neo4j-driver-deno/lib/core/internal/temporal-util.ts

+13-43
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/neo4j-driver-deno/lib/core/temporal-types.ts

-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/neo4j-driver/test/internal/temporal-util.test.js

+22
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
import { int, internal } from 'neo4j-driver-core'
19+
import testUtils from './test-utils'
1920

2021
const { temporalUtil: util } = internal
2122

@@ -260,6 +261,27 @@ describe('#unit temporal-util', () => {
260261
).toEqual(BigInt(999000111))
261262
})
262263

264+
it('should get timezone offset in seconds from standard date', () => {
265+
expect(
266+
util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(0))
267+
).toBe(0)
268+
expect(
269+
util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(2))
270+
).toBe(-120)
271+
expect(
272+
util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(10))
273+
).toBe(-600)
274+
expect(
275+
util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(101))
276+
).toBe(-6060)
277+
expect(
278+
util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(-180))
279+
).toBe(10800)
280+
expect(
281+
util.timeZoneOffsetInSeconds(testUtils.fakeStandardDateWithOffset(-600))
282+
).toBe(36000)
283+
})
284+
263285
it('should verify year', () => {
264286
expect(util.assertValidYear(-1)).toEqual(-1)
265287
expect(util.assertValidYear(-2010)).toEqual(-2010)

packages/neo4j-driver/test/internal/test-utils.js

+7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ 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
const matchers = {
2834
toBeElementOf: function (util, customEqualityTesters) {
2935
return {
@@ -132,6 +138,7 @@ function spyProtocolWrite (protocol, callRealMethod = false) {
132138
export default {
133139
isClient,
134140
isServer,
141+
fakeStandardDateWithOffset,
135142
matchers,
136143
MessageRecordingConnection,
137144
spyProtocolWrite

0 commit comments

Comments
 (0)