postgres-interval 4.0.0 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +218 -111
- package/package.json +2 -2
- package/readme.md +2 -0
package/index.js
CHANGED
|
@@ -7,34 +7,71 @@ function PostgresInterval (raw) {
|
|
|
7
7
|
return new PostgresInterval(raw)
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
this.years = 0
|
|
11
|
+
this.months = 0
|
|
12
|
+
this.days = 0
|
|
13
|
+
this.hours = 0
|
|
14
|
+
this.minutes = 0
|
|
15
|
+
this.seconds = 0
|
|
16
|
+
this.milliseconds = 0
|
|
17
|
+
|
|
18
|
+
parse(this, raw)
|
|
11
19
|
}
|
|
12
|
-
|
|
20
|
+
|
|
13
21
|
PostgresInterval.prototype.toPostgres = function () {
|
|
14
|
-
|
|
22
|
+
let postgresString = ''
|
|
15
23
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
filtered.push('seconds')
|
|
24
|
+
if (this.years) {
|
|
25
|
+
postgresString += this.years === 1 ? this.years + ' year' : this.years + ' years'
|
|
19
26
|
}
|
|
20
27
|
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
if (this.months) {
|
|
29
|
+
if (postgresString.length) {
|
|
30
|
+
postgresString += ' '
|
|
31
|
+
}
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
postgresString += this.months === 1 ? this.months + ' month' : this.months + ' months'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (this.days) {
|
|
37
|
+
if (postgresString.length) {
|
|
38
|
+
postgresString += ' '
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
postgresString += this.days === 1 ? this.days + ' day' : this.days + ' days'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (this.hours) {
|
|
45
|
+
if (postgresString.length) {
|
|
46
|
+
postgresString += ' '
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
postgresString += this.hours === 1 ? this.hours + ' hour' : this.hours + ' hours'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (this.minutes) {
|
|
53
|
+
if (postgresString.length) {
|
|
54
|
+
postgresString += ' '
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
postgresString += this.minutes === 1 ? this.minutes + ' minute' : this.minutes + ' minutes'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (this.seconds || this.milliseconds) {
|
|
61
|
+
if (postgresString.length) {
|
|
62
|
+
postgresString += ' '
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (this.milliseconds) {
|
|
66
|
+
const value = Math.trunc((this.seconds + this.milliseconds / 1000) * 1000000) / 1000000
|
|
67
|
+
|
|
68
|
+
postgresString += value === 1 ? value + ' second' : value + ' seconds'
|
|
69
|
+
} else {
|
|
70
|
+
postgresString += this.seconds === 1 ? this.seconds + ' second' : this.seconds + ' seconds'
|
|
71
|
+
}
|
|
72
|
+
}
|
|
31
73
|
|
|
32
|
-
|
|
33
|
-
const isSingular = String(value) === '1'
|
|
34
|
-
// Remove plural 's' when the value is singular
|
|
35
|
-
return value + ' ' + (isSingular ? property.replace(/s$/, '') : property)
|
|
36
|
-
}, this)
|
|
37
|
-
.join(' ')
|
|
74
|
+
return postgresString === '' ? '0' : postgresString
|
|
38
75
|
}
|
|
39
76
|
|
|
40
77
|
const propertiesISOEquivalent = {
|
|
@@ -45,115 +82,185 @@ const propertiesISOEquivalent = {
|
|
|
45
82
|
minutes: 'M',
|
|
46
83
|
seconds: 'S'
|
|
47
84
|
}
|
|
48
|
-
|
|
49
|
-
const timeProperties = ['hours', 'minutes', 'seconds']
|
|
85
|
+
|
|
50
86
|
// according to ISO 8601
|
|
51
|
-
PostgresInterval.prototype.toISOString = PostgresInterval.prototype.toISO =
|
|
52
|
-
|
|
53
|
-
}
|
|
87
|
+
PostgresInterval.prototype.toISOString = PostgresInterval.prototype.toISO =
|
|
88
|
+
function () {
|
|
89
|
+
return toISOString.call(this, { short: false })
|
|
90
|
+
}
|
|
54
91
|
|
|
55
92
|
PostgresInterval.prototype.toISOStringShort = function () {
|
|
56
93
|
return toISOString.call(this, { short: true })
|
|
57
94
|
}
|
|
58
95
|
|
|
59
|
-
function toISOString ({ short
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
96
|
+
function toISOString ({ short }) {
|
|
97
|
+
let datePart = ''
|
|
98
|
+
|
|
99
|
+
if (!short || this.years) {
|
|
100
|
+
datePart += this.years + propertiesISOEquivalent.years
|
|
101
|
+
}
|
|
63
102
|
|
|
64
|
-
|
|
65
|
-
.
|
|
66
|
-
|
|
103
|
+
if (!short || this.months) {
|
|
104
|
+
datePart += this.months + propertiesISOEquivalent.months
|
|
105
|
+
}
|
|
67
106
|
|
|
68
|
-
if (!
|
|
107
|
+
if (!short || this.days) {
|
|
108
|
+
datePart += this.days + propertiesISOEquivalent.days
|
|
109
|
+
}
|
|
69
110
|
|
|
70
|
-
|
|
111
|
+
let timePart = ''
|
|
112
|
+
|
|
113
|
+
if (!short || this.hours) {
|
|
114
|
+
timePart += this.hours + propertiesISOEquivalent.hours
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!short || this.minutes) {
|
|
118
|
+
timePart += this.minutes + propertiesISOEquivalent.minutes
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!short || (this.seconds || this.milliseconds)) {
|
|
122
|
+
if (this.milliseconds) {
|
|
123
|
+
timePart += (Math.trunc((this.seconds + this.milliseconds / 1000) * 1000000) / 1000000) + propertiesISOEquivalent.seconds
|
|
124
|
+
} else {
|
|
125
|
+
timePart += this.seconds + propertiesISOEquivalent.seconds
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!timePart && !datePart) {
|
|
130
|
+
return 'PT0S'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!timePart) {
|
|
134
|
+
return `P${datePart}`
|
|
135
|
+
}
|
|
71
136
|
|
|
72
137
|
return `P${datePart}T${timePart}`
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const position = { value: 0 }
|
|
73
141
|
|
|
74
|
-
|
|
75
|
-
|
|
142
|
+
function readNextNum (interval) {
|
|
143
|
+
let val = 0
|
|
76
144
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
145
|
+
while (position.value < interval.length) {
|
|
146
|
+
const char = interval[position.value]
|
|
147
|
+
|
|
148
|
+
if (char >= '0' && char <= '9') {
|
|
149
|
+
val = val * 10 + +char
|
|
150
|
+
position.value++
|
|
151
|
+
} else {
|
|
152
|
+
break
|
|
81
153
|
}
|
|
154
|
+
}
|
|
82
155
|
|
|
83
|
-
|
|
156
|
+
return val
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function parseMillisecond (interval) {
|
|
160
|
+
const previousPosition = position.value
|
|
161
|
+
const currentValue = readNextNum(interval)
|
|
162
|
+
const valueStringLength = position.value - previousPosition
|
|
84
163
|
|
|
85
|
-
|
|
164
|
+
switch (valueStringLength) {
|
|
165
|
+
case 1:
|
|
166
|
+
return currentValue * 100
|
|
167
|
+
case 2:
|
|
168
|
+
return currentValue * 10
|
|
169
|
+
case 3:
|
|
170
|
+
return currentValue
|
|
171
|
+
case 4:
|
|
172
|
+
return currentValue / 10
|
|
173
|
+
case 5:
|
|
174
|
+
return currentValue / 100
|
|
175
|
+
case 6:
|
|
176
|
+
return currentValue / 1000
|
|
86
177
|
}
|
|
178
|
+
|
|
179
|
+
// slow path
|
|
180
|
+
const remainder = valueStringLength - 3
|
|
181
|
+
return currentValue / Math.pow(10, remainder)
|
|
87
182
|
}
|
|
88
183
|
|
|
89
|
-
|
|
90
|
-
const YEAR = `${NUMBER}\\s+years?`
|
|
91
|
-
const MONTH = `${NUMBER}\\s+mons?`
|
|
92
|
-
const DAY = `${NUMBER}\\s+days?`
|
|
93
|
-
// NOTE: PostgreSQL automatically overflows seconds into minutes and minutes
|
|
94
|
-
// into hours, so we can rely on minutes and seconds always being 2 digits
|
|
95
|
-
// (plus decimal for seconds). The overflow stops at hours - hours do not
|
|
96
|
-
// overflow into days, so could be arbitrarily long.
|
|
97
|
-
const TIME = '([+-])?(\\d+):(\\d\\d):(\\d\\d(?:\\.\\d{1,6})?)'
|
|
98
|
-
const INTERVAL = new RegExp(
|
|
99
|
-
'^\\s*' +
|
|
100
|
-
// All parts of an interval are optional
|
|
101
|
-
[YEAR, MONTH, DAY, TIME].map((str) => '(?:' + str + ')?').join('\\s*') +
|
|
102
|
-
'\\s*$'
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
// All intervals will have exactly these properties:
|
|
106
|
-
const ZERO_INTERVAL = Object.freeze({
|
|
107
|
-
years: 0,
|
|
108
|
-
months: 0,
|
|
109
|
-
days: 0,
|
|
110
|
-
hours: 0,
|
|
111
|
-
minutes: 0,
|
|
112
|
-
seconds: 0,
|
|
113
|
-
milliseconds: 0.0
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
function parse (interval) {
|
|
184
|
+
function parse (instance, interval) {
|
|
117
185
|
if (!interval) {
|
|
118
|
-
return
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const matches = INTERVAL.exec(interval) || []
|
|
122
|
-
|
|
123
|
-
const [
|
|
124
|
-
,
|
|
125
|
-
yearsString,
|
|
126
|
-
monthsString,
|
|
127
|
-
daysString,
|
|
128
|
-
plusMinusTime,
|
|
129
|
-
hoursString,
|
|
130
|
-
minutesString,
|
|
131
|
-
secondsString
|
|
132
|
-
] = matches
|
|
133
|
-
|
|
134
|
-
const timeMultiplier = plusMinusTime === '-' ? -1 : 1
|
|
135
|
-
|
|
136
|
-
const years = yearsString ? parseInt(yearsString, 10) : 0
|
|
137
|
-
const months = monthsString ? parseInt(monthsString, 10) : 0
|
|
138
|
-
const days = daysString ? parseInt(daysString, 10) : 0
|
|
139
|
-
const hours = hoursString ? timeMultiplier * parseInt(hoursString, 10) : 0
|
|
140
|
-
const minutes = minutesString
|
|
141
|
-
? timeMultiplier * parseInt(minutesString, 10)
|
|
142
|
-
: 0
|
|
143
|
-
const secondsFloat = parseFloat(secondsString) || 0
|
|
144
|
-
// secondsFloat is guaranteed to be >= 0, so floor is safe
|
|
145
|
-
const absSeconds = Math.floor(secondsFloat)
|
|
146
|
-
const seconds = timeMultiplier * absSeconds
|
|
147
|
-
// Without the rounding, we end up with decimals like 455.99999999999994 instead of 456
|
|
148
|
-
const milliseconds = Math.round(timeMultiplier * (secondsFloat - absSeconds) * 1000000) / 1000
|
|
149
|
-
return {
|
|
150
|
-
years,
|
|
151
|
-
months,
|
|
152
|
-
days,
|
|
153
|
-
hours,
|
|
154
|
-
minutes,
|
|
155
|
-
seconds,
|
|
156
|
-
milliseconds
|
|
186
|
+
return
|
|
157
187
|
}
|
|
188
|
+
|
|
189
|
+
position.value = 0
|
|
190
|
+
|
|
191
|
+
let currentValue
|
|
192
|
+
let nextNegative = 1
|
|
193
|
+
|
|
194
|
+
while (position.value < interval.length) {
|
|
195
|
+
const char = interval[position.value]
|
|
196
|
+
|
|
197
|
+
if (char === '-') {
|
|
198
|
+
nextNegative = -1
|
|
199
|
+
position.value++
|
|
200
|
+
continue
|
|
201
|
+
} else if (char === '+') {
|
|
202
|
+
position.value++
|
|
203
|
+
continue
|
|
204
|
+
} else if (char === ' ') {
|
|
205
|
+
position.value++
|
|
206
|
+
continue
|
|
207
|
+
} else if (char < '0' || char > '9') {
|
|
208
|
+
position.value++
|
|
209
|
+
continue
|
|
210
|
+
} else {
|
|
211
|
+
currentValue = readNextNum(interval)
|
|
212
|
+
|
|
213
|
+
if (interval[position.value] === ':') {
|
|
214
|
+
instance.hours = currentValue ? nextNegative * currentValue : 0
|
|
215
|
+
|
|
216
|
+
position.value++
|
|
217
|
+
currentValue = readNextNum(interval)
|
|
218
|
+
instance.minutes = currentValue ? nextNegative * currentValue : 0
|
|
219
|
+
|
|
220
|
+
position.value++
|
|
221
|
+
currentValue = readNextNum(interval)
|
|
222
|
+
instance.seconds = currentValue ? nextNegative * currentValue : 0
|
|
223
|
+
|
|
224
|
+
if (interval[position.value] === '.') {
|
|
225
|
+
position.value++
|
|
226
|
+
|
|
227
|
+
currentValue = parseMillisecond(interval)
|
|
228
|
+
instance.milliseconds = currentValue ? nextNegative * currentValue : 0
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// skip space
|
|
235
|
+
position.value++
|
|
236
|
+
|
|
237
|
+
const unit = interval[position.value]
|
|
238
|
+
|
|
239
|
+
if (unit === 'y') {
|
|
240
|
+
instance.years = currentValue ? nextNegative * currentValue : 0
|
|
241
|
+
} else if (unit === 'm') {
|
|
242
|
+
instance.months = currentValue ? nextNegative * currentValue : 0
|
|
243
|
+
} else if (unit === 'd') {
|
|
244
|
+
instance.days = currentValue ? nextNegative * currentValue : 0
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
nextNegative = 1
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
PostgresInterval.parse = function (interval) {
|
|
253
|
+
const instance = {
|
|
254
|
+
years: 0,
|
|
255
|
+
months: 0,
|
|
256
|
+
days: 0,
|
|
257
|
+
hours: 0,
|
|
258
|
+
minutes: 0,
|
|
259
|
+
seconds: 0,
|
|
260
|
+
milliseconds: 0
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
parse(instance, interval)
|
|
264
|
+
|
|
265
|
+
return instance
|
|
158
266
|
}
|
|
159
|
-
PostgresInterval.parse = parse
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postgres-interval",
|
|
3
3
|
"main": "index.js",
|
|
4
|
-
"version": "4.0.
|
|
4
|
+
"version": "4.0.2",
|
|
5
5
|
"description": "Parse Postgres interval columns",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "bendrucker/postgres-interval",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"standard": "^
|
|
26
|
+
"standard": "^17.0.0",
|
|
27
27
|
"tape": "^5.0.0"
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
package/readme.md
CHANGED
|
@@ -41,6 +41,8 @@ Type: `string`
|
|
|
41
41
|
|
|
42
42
|
A Postgres interval string.
|
|
43
43
|
|
|
44
|
+
This package is focused on parsing Postgres outputs. It optimizes for performance by assuming that inputs follow the default interval format. It does not perform any validation on the input. If any interval field is not found, its value will be set to `0` in the returned `interval`.
|
|
45
|
+
|
|
44
46
|
#### `interval.toPostgres()` -> `string`
|
|
45
47
|
|
|
46
48
|
Returns an interval string. This allows the interval object to be passed into prepared statements.
|