Skip to content

Commit

Permalink
Simplify date handling (#119)
Browse files Browse the repository at this point in the history
BREAKING

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

* Round to nearest millisecond in binary timestamp parser

to match the new postgres-date@1.0.5 behavior, compared to 1.0.4.
  • Loading branch information
charmander committed Oct 23, 2020
1 parent 69b5621 commit 8477d7c
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 34 deletions.
24 changes: 4 additions & 20 deletions lib/binaryParsers.js
Expand Up @@ -17,27 +17,11 @@ 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
}
var result = new Date(Math.round(rawValue / 1000) + 946684800000)

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
29 changes: 20 additions & 9 deletions lib/textParsers.js
@@ -1,5 +1,5 @@
var array = require('postgres-array')
var parseTimestamp = require('postgres-date')
var parseTimestampTz = require('postgres-date')
var parseInterval = require('postgres-interval')
var parseByteA = require('postgres-bytea')

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

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

return parseTimestampTz(utc)
}

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

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

var parseIntervalArray = function (value) {
if (!value) { return null }
return array.parse(value, parseInterval)
}

var parseByteAArray = function (value) {
if (!value) { return null }
return array.parse(value, parseByteA)
Expand Down Expand Up @@ -125,8 +136,8 @@ var init = function (register) {
register(700, parseFloat) // float4/real
register(701, parseFloat) // float8/double
register(16, parseBool)
register(1114, parseTimestamp) // timestamp without timezone
register(1184, parseTimestamp) // 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,8 +158,8 @@ var init = function (register) {
register(1040, parseStringArray) // macaddr[]
register(1041, parseStringArray) // inet[]
register(1115, parseTimestampArray) // timestamp without time zone[]
register(1182, parseStringArray) // _date
register(1185, parseTimestampArray) // timestamp with time zone[]
register(1182, parseStringArray) // date[]
register(1185, parseTimestampTzArray) // timestamp with time zone[]
register(1186, parseInterval)
register(1187, parseIntervalArray)
register(17, parseByteA)
Expand Down
20 changes: 15 additions & 5 deletions test/types.js
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 @@ -128,7 +137,8 @@ exports.date = {
format: 'text',
id: 1082,
tests: [
['2010-10-31', '2010-10-31']
['2010-10-31', '2010-10-31'],
['2010-10-31 BC', '2010-10-31 BC']
]
}

Expand Down

0 comments on commit 8477d7c

Please sign in to comment.