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

Add getTimezoneOffsetInMilliseconds utility function #789

Merged
merged 2 commits into from Jun 21, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -639,6 +639,9 @@ for the list of changes made since `v2.0.0-alpha.1`.

- `isDate` now works properly with dates passed across iframes [#754](https://github.com/date-fns/date-fns/pull/754).

- Fix a few bugs that appear in timezones with offsets that include seconds (e.g. GMT+00:57:44).
See PR [#789](https://github.com/date-fns/date-fns/pull/789).

## [1.28.5] - 2017-05-19

### Fixed
Expand Down
15 changes: 0 additions & 15 deletions src/_lib/addUTCMinutes/index.js

This file was deleted.

61 changes: 0 additions & 61 deletions src/_lib/addUTCMinutes/test.js

This file was deleted.

21 changes: 21 additions & 0 deletions src/_lib/getTimezoneOffsetInMilliseconds/index.js
@@ -0,0 +1,21 @@
var MILLISECONDS_IN_MINUTE = 60000

/**
* Google Chrome as of 67.0.3396.87 introduced timezones with offset that includes seconds.
* They usually appear for dates that denote time before the timezones were introduced
* (e.g. for 'Europe/Prague' timezone the offset is GMT+00:57:44 before 1 October 1891
* and GMT+01:00:00 after that date)
*
* Date#getTimezoneOffset returns the offset in minutes and would return 57 for the example above,
* which would lead to incorrect calculations.
*
* This function returns the timezone offset in milliseconds that takes seconds in account.
*/
export default function getTimezoneOffsetInMilliseconds (dirtyDate) {
var date = new Date(dirtyDate.getTime())
var baseTimezoneOffset = date.getTimezoneOffset()
date.setSeconds(0, 0)
var millisecondsPartOfTimezoneOffset = date.getTime() % MILLISECONDS_IN_MINUTE

return baseTimezoneOffset * MILLISECONDS_IN_MINUTE + millisecondsPartOfTimezoneOffset
}
28 changes: 28 additions & 0 deletions src/_lib/getTimezoneOffsetInMilliseconds/test.js
@@ -0,0 +1,28 @@
// @flow
/* eslint-env mocha */

import assert from 'power-assert'
import getTimezoneOffsetInMilliseconds from '.'

describe('getTimezoneOffsetInMilliseconds', function () {
it('works for a modern date', function () {
var date = new Date(2018, 0 /* Jan */, 1, 12, 34, 56, 789)
var result = date.getTime() - getTimezoneOffsetInMilliseconds(date)
var expectedResult = Date.UTC(2018, 0 /* Jan */, 1, 12, 34, 56, 789)
assert(result === expectedResult)
})

it('works for a date before standardized timezones', function () {
var date = new Date(1800, 0 /* Jan */, 1, 12, 34, 56, 789)
var result = date.getTime() - getTimezoneOffsetInMilliseconds(date)
var expectedResult = Date.UTC(1800, 0 /* Jan */, 1, 12, 34, 56, 789)
assert(result === expectedResult)
})

it('works for a date BC', function () {
var date = new Date(-500, 0 /* Jan */, 1, 12, 34, 56, 789)
var result = date.getTime() - getTimezoneOffsetInMilliseconds(date)
var expectedResult = Date.UTC(-500, 0 /* Jan */, 1, 12, 34, 56, 789)
assert(result === expectedResult)
})
})
6 changes: 3 additions & 3 deletions src/differenceInCalendarDays/index.js
@@ -1,6 +1,6 @@
import getTimezoneOffsetInMilliseconds from '../_lib/getTimezoneOffsetInMilliseconds/index.js'
import startOfDay from '../startOfDay/index.js'

var MILLISECONDS_IN_MINUTE = 60000
var MILLISECONDS_IN_DAY = 86400000

/**
Expand Down Expand Up @@ -45,9 +45,9 @@ export default function differenceInCalendarDays (dirtyDateLeft, dirtyDateRight,
var startOfDayRight = startOfDay(dirtyDateRight, dirtyOptions)

var timestampLeft = startOfDayLeft.getTime() -
startOfDayLeft.getTimezoneOffset() * MILLISECONDS_IN_MINUTE
getTimezoneOffsetInMilliseconds(startOfDayLeft)
var timestampRight = startOfDayRight.getTime() -
startOfDayRight.getTimezoneOffset() * MILLISECONDS_IN_MINUTE
getTimezoneOffsetInMilliseconds(startOfDayRight)

// Round the number of days to the nearest integer
// because the number of milliseconds in a day is not constant
Expand Down
6 changes: 3 additions & 3 deletions src/differenceInCalendarISOWeeks/index.js
@@ -1,6 +1,6 @@
import getTimezoneOffsetInMilliseconds from '../_lib/getTimezoneOffsetInMilliseconds/index.js'
import startOfISOWeek from '../startOfISOWeek/index.js'

var MILLISECONDS_IN_MINUTE = 60000
var MILLISECONDS_IN_WEEK = 604800000

/**
Expand Down Expand Up @@ -38,9 +38,9 @@ export default function differenceInCalendarISOWeeks (dirtyDateLeft, dirtyDateRi
var startOfISOWeekRight = startOfISOWeek(dirtyDateRight, dirtyOptions)

var timestampLeft = startOfISOWeekLeft.getTime() -
startOfISOWeekLeft.getTimezoneOffset() * MILLISECONDS_IN_MINUTE
getTimezoneOffsetInMilliseconds(startOfISOWeekLeft)
var timestampRight = startOfISOWeekRight.getTime() -
startOfISOWeekRight.getTimezoneOffset() * MILLISECONDS_IN_MINUTE
getTimezoneOffsetInMilliseconds(startOfISOWeekRight)

// Round the number of days to the nearest integer
// because the number of milliseconds in a week is not constant
Expand Down
6 changes: 3 additions & 3 deletions src/differenceInCalendarWeeks/index.js
@@ -1,6 +1,6 @@
import getTimezoneOffsetInMilliseconds from '../_lib/getTimezoneOffsetInMilliseconds/index.js'
import startOfWeek from '../startOfWeek/index.js'

var MILLISECONDS_IN_MINUTE = 60000
var MILLISECONDS_IN_WEEK = 604800000

/**
Expand Down Expand Up @@ -49,9 +49,9 @@ export default function differenceInCalendarWeeks (dirtyDateLeft, dirtyDateRight
var startOfWeekRight = startOfWeek(dirtyDateRight, dirtyOptions)

var timestampLeft = startOfWeekLeft.getTime() -
startOfWeekLeft.getTimezoneOffset() * MILLISECONDS_IN_MINUTE
getTimezoneOffsetInMilliseconds(startOfWeekLeft)
var timestampRight = startOfWeekRight.getTime() -
startOfWeekRight.getTimezoneOffset() * MILLISECONDS_IN_MINUTE
getTimezoneOffsetInMilliseconds(startOfWeekRight)

// Round the number of days to the nearest integer
// because the number of milliseconds in a week is not constant
Expand Down
7 changes: 4 additions & 3 deletions src/format/index.js
@@ -1,10 +1,11 @@
import toInteger from '../_lib/toInteger/index.js'
import getTimezoneOffsetInMilliseconds from '../_lib/getTimezoneOffsetInMilliseconds/index.js'
import toDate from '../toDate/index.js'
import isValid from '../isValid/index.js'
import defaultLocale from '../locale/en-US/index.js'
import formatters from './_lib/formatters/index.js'
import longFormatters from './_lib/longFormatters/index.js'
import addUTCMinutes from '../_lib/addUTCMinutes/index.js'
import subMilliseconds from '../subMilliseconds/index.js'

// This RegExp consists of three parts separated by `|`:
// - [yYQqMLwIdDecihHKkms]o matches any available ordinal number token
Expand Down Expand Up @@ -359,8 +360,8 @@ export default function format (dirtyDate, dirtyFormatStr, dirtyOptions) {
// Convert the date in system timezone to the same date in UTC+00:00 timezone.
// This ensures that when UTC functions will be implemented, locales will be compatible with them.
// See an issue about UTC functions: https://github.com/date-fns/date-fns/issues/376
var timezoneOffset = originalDate.getTimezoneOffset()
var utcDate = addUTCMinutes(originalDate, -timezoneOffset, options)
var timezoneOffset = getTimezoneOffsetInMilliseconds(originalDate)
var utcDate = subMilliseconds(originalDate, timezoneOffset, options)

var formatterOptions = {
firstWeekContainsDate: firstWeekContainsDate,
Expand Down
5 changes: 3 additions & 2 deletions src/formatDistance/index.js
@@ -1,3 +1,4 @@
import getTimezoneOffsetInMilliseconds from '../_lib/getTimezoneOffsetInMilliseconds/index.js'
import compareAsc from '../compareAsc/index.js'
import toDate from '../toDate/index.js'
import differenceInSeconds from '../differenceInSeconds/index.js'
Expand Down Expand Up @@ -130,8 +131,8 @@ export default function formatDistance (dirtyDate, dirtyBaseDate, dirtyOptions)
}

var seconds = differenceInSeconds(dateRight, dateLeft, options)
var offset = dateRight.getTimezoneOffset() - dateLeft.getTimezoneOffset()
var minutes = Math.round(seconds / 60) - offset
var offsetInSeconds = (getTimezoneOffsetInMilliseconds(dateRight) - getTimezoneOffsetInMilliseconds(dateLeft)) / 1000
var minutes = Math.round((seconds - offsetInSeconds) / 60)
var months

// 0 up to 2 mins
Expand Down
5 changes: 3 additions & 2 deletions src/formatDistanceStrict/index.js
@@ -1,3 +1,4 @@
import getTimezoneOffsetInMilliseconds from '../_lib/getTimezoneOffsetInMilliseconds/index.js'
import compareAsc from '../compareAsc/index.js'
import toDate from '../toDate/index.js'
import differenceInSeconds from '../differenceInSeconds/index.js'
Expand Down Expand Up @@ -145,8 +146,8 @@ export default function formatDistanceStrict (dirtyDate, dirtyBaseDate, dirtyOpt
}

var seconds = differenceInSeconds(dateRight, dateLeft, dirtyOptions)
var offset = dateRight.getTimezoneOffset() - dateLeft.getTimezoneOffset()
var minutes = roundingMethodFn(seconds / 60) - offset
var offsetInSeconds = (getTimezoneOffsetInMilliseconds(dateRight) - getTimezoneOffsetInMilliseconds(dateLeft)) / 1000
var minutes = roundingMethodFn((seconds - offsetInSeconds) / 60)

var unit
if (options.unit == null) {
Expand Down
7 changes: 4 additions & 3 deletions src/formatRelative/index.js
@@ -1,8 +1,9 @@
import getTimezoneOffsetInMilliseconds from '../_lib/getTimezoneOffsetInMilliseconds/index.js'
import toDate from '../toDate/index.js'
import format from '../format/index.js'
import differenceInCalendarDays from '../differenceInCalendarDays/index.js'
import defaultLocale from '../locale/en-US/index.js'
import subMinutes from '../subMinutes/index.js'
import subMilliseconds from '../subMilliseconds/index.js'

/**
* @name formatRelative
Expand Down Expand Up @@ -78,8 +79,8 @@ export default function formatRelative (dirtyDate, dirtyBaseDate, dirtyOptions)
token = 'other'
}

var utcDate = subMinutes(date, date.getTimezoneOffset(), options)
var utcBaseDate = subMinutes(baseDate, date.getTimezoneOffset(), options)
var utcDate = subMilliseconds(date, getTimezoneOffsetInMilliseconds(date), options)
var utcBaseDate = subMilliseconds(baseDate, getTimezoneOffsetInMilliseconds(baseDate), options)
var formatStr = locale.formatRelative(token, utcDate, utcBaseDate, options)
return format(date, formatStr, options)
}
5 changes: 3 additions & 2 deletions src/parse/index.js
@@ -1,6 +1,7 @@
import toInteger from '../_lib/toInteger/index.js'
import getTimezoneOffsetInMilliseconds from '../_lib/getTimezoneOffsetInMilliseconds/index.js'
import toDate from '../toDate/index.js'
import subMinutes from '../subMinutes/index.js'
import subMilliseconds from '../subMilliseconds/index.js'
import defaultLocale from '../locale/en-US/index.js'
import parsers from './_lib/parsers/index.js'

Expand Down Expand Up @@ -416,7 +417,7 @@ export default function parse (dirtyDateString, dirtyFormatString, dirtyBaseDate
// Convert the date in system timezone to the same date in UTC+00:00 timezone.
// This ensures that when UTC functions will be implemented, locales will be compatible with them.
// See an issue about UTC functions: https://github.com/date-fns/date-fns/issues/37
var utcDate = subMinutes(date, date.getTimezoneOffset())
var utcDate = subMilliseconds(date, getTimezoneOffsetInMilliseconds(date))

for (i = 0; i < uniquePrioritySetters.length; i++) {
var setter = uniquePrioritySetters[i]
Expand Down
11 changes: 6 additions & 5 deletions src/toDate/index.js
@@ -1,4 +1,5 @@
import toInteger from '../_lib/toInteger/index.js'
import getTimezoneOffsetInMilliseconds from '../_lib/getTimezoneOffsetInMilliseconds/index.js'

var MILLISECONDS_IN_HOUR = 3600000
var MILLISECONDS_IN_MINUTE = 60000
Expand Down Expand Up @@ -128,11 +129,11 @@ export default function toDate (argument, dirtyOptions) {
offset = parseTimezone(dateStrings.timezone)
} else {
// get offset accurate to hour in timezones that change offset
offset = new Date(timestamp + time).getTimezoneOffset()
offset = new Date(timestamp + time + offset * MILLISECONDS_IN_MINUTE).getTimezoneOffset()
offset = getTimezoneOffsetInMilliseconds(new Date(timestamp + time))
offset = getTimezoneOffsetInMilliseconds(new Date(timestamp + time + offset))
}

return new Date(timestamp + time + offset * MILLISECONDS_IN_MINUTE)
return new Date(timestamp + time + offset)
} else {
return new Date(NaN)
}
Expand Down Expand Up @@ -314,14 +315,14 @@ function parseTimezone (timezoneString) {
// ±hh
token = patterns.timezoneHH.exec(timezoneString)
if (token) {
absoluteOffset = parseInt(token[2], 10) * 60
absoluteOffset = parseInt(token[2], 10) * MILLISECONDS_IN_HOUR
return (token[1] === '+') ? -absoluteOffset : absoluteOffset
}

// ±hh:mm or ±hhmm
token = patterns.timezoneHHMM.exec(timezoneString)
if (token) {
absoluteOffset = parseInt(token[2], 10) * 60 + parseInt(token[3], 10)
absoluteOffset = parseInt(token[2], 10) * MILLISECONDS_IN_HOUR + parseInt(token[3], 10) * MILLISECONDS_IN_MINUTE
return (token[1] === '+') ? -absoluteOffset : absoluteOffset
}

Expand Down