Skip to content

Commit

Permalink
Simplify date handling
Browse files Browse the repository at this point in the history
A major backwards-compatibility break, unfortunately.

The binary parser’s approach of creating a local timestamp then adjusting it by its timezone offset to get the same components in UTC just doesn’t work, producing the wrong results around daylight saving transitions, for example. But even when it’s fixed to work as well as the text parser does, some times are still completely unrepresentable because they don’t exist in local time (daylight saving transitions are, again, an example of this). The more reliable way is:

- `timestamptz` represents an instant in time
- `timestamp` preserves its components in UTC
- `date` remains a string
  • Loading branch information
charmander committed Jul 7, 2020
1 parent 3df787b commit 4054131
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 50 deletions.
22 changes: 3 additions & 19 deletions lib/binaryParsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,12 @@ var parseFloat64 = function (value) {
return value.readDoubleBE(0)
}

var parseDate = function (isUTC, value) {
var parseTimestampUTC = function (value) {
var rawValue = 0x100000000 * value.readInt32BE(0) + value.readUInt32BE(4)

// discard usecs and shift from 2000 to 1970
var result = new Date((rawValue / 1000) + 946684800000)

if (!isUTC) {
result.setTime(result.getTime() + result.getTimezoneOffset() * 60000)
}

// add microseconds to the date
result.usec = rawValue % 1000
result.getMicroSeconds = function () {
return this.usec
}
result.setMicroSeconds = function (value) {
this.usec = value
}
result.getUTCMicroSeconds = function () {
return this.usec
}

return result
}

Expand Down Expand Up @@ -127,8 +111,8 @@ var init = function (register) {
register(700, parseFloat32)
register(701, parseFloat64)
register(16, parseBool)
register(1114, parseDate.bind(null, false))
register(1184, parseDate.bind(null, true))
register(1114, parseTimestampUTC)
register(1184, parseTimestampUTC)
register(1000, parseArray)
register(1007, parseArray)
register(1016, parseArray)
Expand Down
30 changes: 20 additions & 10 deletions lib/textParsers.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
var array = require('postgres-array')
var parseDate = require('postgres-date')
var parseTimestampTz = require('postgres-date')
var parseInterval = require('postgres-interval')
var parseByteA = require('postgres-bytea')

Expand Down Expand Up @@ -50,9 +50,20 @@ var parseStringArray = function (value) {
return array.parse(value, undefined)
}

var parseDateArray = function (value) {
if (!value) { return null }
return array.parse(value, parseDate)
var parseTimestamp = function (value) {
var utc = value.endsWith(' BC')
? value.slice(0, -3) + 'Z BC'
: value + 'Z'

return parseTimestampTz(utc)
}

var parseTimestampArray = function (value) {
return array.parse(value, parseTimestamp)
}

var parseTimestampTzArray = function (value) {
return array.parse(value, parseTimestampTz)
}

var parseIntervalArray = function (value) {
Expand Down Expand Up @@ -125,9 +136,8 @@ var init = function (register) {
register(700, parseFloat) // float4/real
register(701, parseFloat) // float8/double
register(16, parseBool)
register(1082, parseDate) // date
register(1114, parseDate) // timestamp without timezone
register(1184, parseDate) // timestamp
register(1114, parseTimestamp) // timestamp without time zone
register(1184, parseTimestampTz) // timestamp with time zone
register(600, parsePoint) // point
register(651, parseStringArray) // cidr[]
register(718, parseCircle) // circle
Expand All @@ -147,9 +157,9 @@ var init = function (register) {
register(1009, parseStringArray)
register(1040, parseStringArray) // macaddr[]
register(1041, parseStringArray) // inet[]
register(1115, parseDateArray) // timestamp without time zone[]
register(1182, parseDateArray) // _date
register(1185, parseDateArray) // timestamp with time zone[]
register(1115, parseTimestampArray) // timestamp without time zone[]
register(1182, parseStringArray) // date[]
register(1185, parseTimestampTzArray) // timestamp with time zone[]
register(1186, parseInterval)
register(1187, parseIntervalArray)
register(17, parseByteA)
Expand Down
39 changes: 18 additions & 21 deletions test/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ exports.timestamptz = {
[
'2010-10-30 13:10:01+05',
dateEquals(2010, 9, 30, 8, 10, 1, 0)
],
[
'1000-01-01 00:00:00+00 BC',
dateEquals(-999, 0, 1, 0, 0, 0, 0)
]
]
}
Expand All @@ -112,12 +116,17 @@ exports.timestamp = {
'2010-10-31 00:00:00',
function (t, value) {
t.equal(
value.toUTCString(),
new Date(2010, 9, 31, 0, 0, 0, 0).toUTCString()
value.toISOString(),
'2010-10-31T00:00:00.000Z'
)
}
],
[
'1000-01-01 00:00:00 BC',
function (t, value) {
t.equal(
value.toString(),
new Date(2010, 9, 31, 0, 0, 0, 0).toString()
value.toISOString(),
'-000999-01-01T00:00:00.000Z'
)
}
]
Expand All @@ -129,13 +138,10 @@ exports.date = {
id: 1082,
tests: [
['2010-10-31', function (t, value) {
var now = new Date(2010, 9, 31)
dateEquals(
2010,
now.getUTCMonth(),
now.getUTCDate(),
now.getUTCHours(), 0, 0, 0)(t, value)
t.equal(value.getHours(), now.getHours())
t.equal(value, '2010-10-31')
}],
['2010-10-31 BC', function (t, value) {
t.equal(value, '2010-10-31 BC')
}]
]
}
Expand Down Expand Up @@ -400,16 +406,7 @@ exports['array/date'] = {
id: 1182,
tests: [
['{2014-01-01,2015-12-31}', function (t, value) {
var expecteds = [new Date(2014, 0, 1), new Date(2015, 11, 31)]
t.equal(value.length, 2)
value.forEach(function (date, index) {
var expected = expecteds[index]
dateEquals(
expected.getUTCFullYear(),
expected.getUTCMonth(),
expected.getUTCDate(),
expected.getUTCHours(), 0, 0, 0)(t, date)
})
t.deepEqual(value, ['2014-01-01', '2015-12-31'])
}]
]
}
Expand Down

0 comments on commit 4054131

Please sign in to comment.