postgres-interval 1.2.0 → 4.0.0

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.
Files changed (4) hide show
  1. package/index.d.ts +73 -7
  2. package/index.js +98 -64
  3. package/package.json +6 -8
  4. package/readme.md +19 -7
package/index.d.ts CHANGED
@@ -1,20 +1,86 @@
1
1
  declare namespace PostgresInterval {
2
2
  export interface IPostgresInterval {
3
- years?: number;
4
- months?: number;
5
- days?: number;
6
- hours?: number;
7
- minutes?: number;
8
- seconds?: number;
9
- milliseconds?: number;
3
+ years: number;
4
+ months: number;
5
+ days: number;
6
+ hours: number;
7
+ minutes: number;
8
+ seconds: number;
9
+ milliseconds: number;
10
10
 
11
+ /**
12
+ * Returns an interval string. This allows the interval object to be passed into prepared statements.
13
+ *
14
+ * ```js
15
+ * var parse = require('postgres-interval')
16
+ * var interval = parse('01:02:03')
17
+ * // => { hours: 1, minutes: 2, seconds: 3 }
18
+ * interval.toPostgres()
19
+ * // 1 hour 2 minutes 3 seconds
20
+ * ```
21
+ */
11
22
  toPostgres(): string;
12
23
 
24
+ /**
25
+ * Returns an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) compliant string, for example P0Y0M0DT0H9M0S.
26
+ *
27
+ * Also available as {@link toISOString toISOString}.
28
+ *
29
+ * ```js
30
+ * var parse = require('postgres-interval')
31
+ * var interval = parse('01:02:03')
32
+ * // => { hours: 1, minutes: 2, seconds: 3 }
33
+ * interval.toISO()
34
+ * // P0Y0M0DT1H2M3S
35
+ * ```
36
+ */
13
37
  toISO(): string;
38
+ /**
39
+ * Returns an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) compliant string, for example P0Y0M0DT0H9M0S.
40
+ *
41
+ * Also available as {@link toISO toISO} for backwards compatibility.
42
+ *
43
+ * ```js
44
+ * var parse = require('postgres-interval')
45
+ * var interval = parse('01:02:03')
46
+ * // => { hours: 1, minutes: 2, seconds: 3 }
47
+ * interval.toISOString()
48
+ * // P0Y0M0DT1H2M3S
49
+ * ```
50
+ */
14
51
  toISOString(): string;
52
+ /**
53
+ * Returns an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) compliant string shortened to minimum length, for example `PT9M`.
54
+ *
55
+ * ```js
56
+ * var parse = require('postgres-interval')
57
+ * var interval = parse('01:02:03')
58
+ * // => { hours: 1, minutes: 2, seconds: 3 }
59
+ * interval.toISOStringShort()
60
+ * // PT1H2M3S
61
+ * ```
62
+ */
63
+ toISOStringShort(): string;
15
64
  }
16
65
  }
17
66
 
67
+ /**
68
+ * Parse Postgres interval columns.
69
+ *
70
+ * ```js
71
+ * var parse = require('postgres-interval')
72
+ * var interval = parse('01:02:03')
73
+ * // => { hours: 1, minutes: 2, seconds: 3 }
74
+ * interval.toPostgres()
75
+ * // 1 hour 2 minutes 3 seconds
76
+ * interval.toISOString()
77
+ * // P0Y0M0DT1H2M3S
78
+ * interval.toISOStringShort()
79
+ * // PT1H2M3S
80
+ * ```
81
+ *
82
+ * @param raw A Postgres interval string.
83
+ */
18
84
  declare function PostgresInterval(raw: string): PostgresInterval.IPostgresInterval;
19
85
 
20
86
  export = PostgresInterval;
package/index.js CHANGED
@@ -1,28 +1,27 @@
1
1
  'use strict'
2
2
 
3
- var extend = require('xtend/mutable')
4
-
5
3
  module.exports = PostgresInterval
6
4
 
7
5
  function PostgresInterval (raw) {
8
6
  if (!(this instanceof PostgresInterval)) {
9
7
  return new PostgresInterval(raw)
10
8
  }
11
- extend(this, parse(raw))
9
+
10
+ Object.assign(this, parse(raw))
12
11
  }
13
- var properties = ['seconds', 'minutes', 'hours', 'days', 'months', 'years']
12
+ const properties = ['years', 'months', 'days', 'hours', 'minutes', 'seconds']
14
13
  PostgresInterval.prototype.toPostgres = function () {
15
- var filtered = properties.filter(this.hasOwnProperty, this)
14
+ const filtered = properties.filter(key => Object.prototype.hasOwnProperty.call(this, key) && this[key] !== 0)
16
15
 
17
16
  // In addition to `properties`, we need to account for fractions of seconds.
18
- if (this.milliseconds && filtered.indexOf('seconds') < 0) {
17
+ if (this.milliseconds && !filtered.includes('seconds')) {
19
18
  filtered.push('seconds')
20
19
  }
21
20
 
22
21
  if (filtered.length === 0) return '0'
23
22
  return filtered
24
23
  .map(function (property) {
25
- var value = this[property] || 0
24
+ let value = this[property] || 0
26
25
 
27
26
  // Account for fractional part of seconds,
28
27
  // remove trailing zeroes.
@@ -30,12 +29,15 @@ PostgresInterval.prototype.toPostgres = function () {
30
29
  value = (value + this.milliseconds / 1000).toFixed(6).replace(/\.?0+$/, '')
31
30
  }
32
31
 
33
- return value + ' ' + property
32
+ // fractional seconds will be a String, all others are Number
33
+ const isSingular = String(value) === '1'
34
+ // Remove plural 's' when the value is singular
35
+ return value + ' ' + (isSingular ? property.replace(/s$/, '') : property)
34
36
  }, this)
35
37
  .join(' ')
36
38
  }
37
39
 
38
- var propertiesISOEquivalent = {
40
+ const propertiesISOEquivalent = {
39
41
  years: 'Y',
40
42
  months: 'M',
41
43
  days: 'D',
@@ -43,22 +45,34 @@ var propertiesISOEquivalent = {
43
45
  minutes: 'M',
44
46
  seconds: 'S'
45
47
  }
46
- var dateProperties = ['years', 'months', 'days']
47
- var timeProperties = ['hours', 'minutes', 'seconds']
48
+ const dateProperties = ['years', 'months', 'days']
49
+ const timeProperties = ['hours', 'minutes', 'seconds']
48
50
  // according to ISO 8601
49
51
  PostgresInterval.prototype.toISOString = PostgresInterval.prototype.toISO = function () {
50
- var datePart = dateProperties
52
+ return toISOString.call(this, { short: false })
53
+ }
54
+
55
+ PostgresInterval.prototype.toISOStringShort = function () {
56
+ return toISOString.call(this, { short: true })
57
+ }
58
+
59
+ function toISOString ({ short = false }) {
60
+ const datePart = dateProperties
51
61
  .map(buildProperty, this)
52
62
  .join('')
53
63
 
54
- var timePart = timeProperties
64
+ const timePart = timeProperties
55
65
  .map(buildProperty, this)
56
66
  .join('')
57
67
 
58
- return 'P' + datePart + 'T' + timePart
68
+ if (!timePart.length && !datePart.length) return 'PT0S'
69
+
70
+ if (!timePart.length) return `P${datePart}`
71
+
72
+ return `P${datePart}T${timePart}`
59
73
 
60
74
  function buildProperty (property) {
61
- var value = this[property] || 0
75
+ let value = this[property] || 0
62
76
 
63
77
  // Account for fractional part of seconds,
64
78
  // remove trailing zeroes.
@@ -66,60 +80,80 @@ PostgresInterval.prototype.toISOString = PostgresInterval.prototype.toISO = func
66
80
  value = (value + this.milliseconds / 1000).toFixed(6).replace(/0+$/, '')
67
81
  }
68
82
 
83
+ if (short && !value) return ''
84
+
69
85
  return value + propertiesISOEquivalent[property]
70
86
  }
71
87
  }
72
88
 
73
- var NUMBER = '([+-]?\\d+)'
74
- var YEAR = NUMBER + '\\s+years?'
75
- var MONTH = NUMBER + '\\s+mons?'
76
- var DAY = NUMBER + '\\s+days?'
77
- var TIME = '([+-])?([\\d]*):(\\d\\d):(\\d\\d)\\.?(\\d{1,6})?'
78
- var INTERVAL = new RegExp([YEAR, MONTH, DAY, TIME].map(function (regexString) {
79
- return '(' + regexString + ')?'
89
+ const NUMBER = '([+-]?\\d+)'
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
80
114
  })
81
- .join('\\s*'))
82
-
83
- // Positions of values in regex match
84
- var positions = {
85
- years: 2,
86
- months: 4,
87
- days: 6,
88
- hours: 9,
89
- minutes: 10,
90
- seconds: 11,
91
- milliseconds: 12
92
- }
93
- // We can use negative time
94
- var negatives = ['hours', 'minutes', 'seconds', 'milliseconds']
95
-
96
- function parseMilliseconds (fraction) {
97
- // add omitted zeroes
98
- var microseconds = fraction + '000000'.slice(fraction.length)
99
- return parseInt(microseconds, 10) / 1000
100
- }
101
115
 
102
116
  function parse (interval) {
103
- if (!interval) return {}
104
- var matches = INTERVAL.exec(interval)
105
- var isNegative = matches[8] === '-'
106
- return Object.keys(positions)
107
- .reduce(function (parsed, property) {
108
- var position = positions[property]
109
- var value = matches[position]
110
- // no empty string
111
- if (!value) return parsed
112
- // milliseconds are actually microseconds (up to 6 digits)
113
- // with omitted trailing zeroes.
114
- value = property === 'milliseconds'
115
- ? parseMilliseconds(value)
116
- : parseInt(value, 10)
117
- // no zeros
118
- if (!value) return parsed
119
- if (isNegative && ~negatives.indexOf(property)) {
120
- value *= -1
121
- }
122
- parsed[property] = value
123
- return parsed
124
- }, {})
117
+ if (!interval) {
118
+ return ZERO_INTERVAL
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
157
+ }
125
158
  }
159
+ PostgresInterval.parse = parse
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "postgres-interval",
3
3
  "main": "index.js",
4
- "version": "1.2.0",
4
+ "version": "4.0.0",
5
5
  "description": "Parse Postgres interval columns",
6
6
  "license": "MIT",
7
7
  "repository": "bendrucker/postgres-interval",
8
8
  "author": {
9
9
  "name": "Ben Drucker",
10
10
  "email": "bvdrucker@gmail.com",
11
- "url": "bendrucker.me"
11
+ "url": "https://www.bendrucker.me"
12
12
  },
13
13
  "engines": {
14
- "node": ">=0.10.0"
14
+ "node": ">=12"
15
15
  },
16
16
  "scripts": {
17
17
  "test": "standard && tape test.js"
@@ -21,12 +21,10 @@
21
21
  "interval",
22
22
  "parser"
23
23
  ],
24
- "dependencies": {
25
- "xtend": "^4.0.0"
26
- },
24
+ "dependencies": {},
27
25
  "devDependencies": {
28
- "tape": "^4.0.0",
29
- "standard": "^12.0.1"
26
+ "standard": "^16.0.0",
27
+ "tape": "^5.0.0"
30
28
  },
31
29
  "files": [
32
30
  "index.js",
package/readme.md CHANGED
@@ -1,12 +1,12 @@
1
- # postgres-interval [![Build Status](https://travis-ci.org/bendrucker/postgres-interval.svg?branch=master)](https://travis-ci.org/bendrucker/postgres-interval) [![Greenkeeper badge](https://badges.greenkeeper.io/bendrucker/postgres-interval.svg)](https://greenkeeper.io/)
1
+ # postgres-interval [![tests](https://github.com/bendrucker/postgres-interval/workflows/tests/badge.svg)](https://github.com/bendrucker/postgres-interval/actions?query=workflow%3Atests)
2
2
 
3
3
  > Parse Postgres interval columns
4
4
 
5
5
 
6
6
  ## Install
7
7
 
8
- ```
9
- $ npm install --save postgres-interval
8
+ ```sh
9
+ npm install --save postgres-interval
10
10
  ```
11
11
 
12
12
 
@@ -15,11 +15,19 @@ $ npm install --save postgres-interval
15
15
  ```js
16
16
  var parse = require('postgres-interval')
17
17
  var interval = parse('01:02:03')
18
- //=> {hours: 1, minutes: 2, seconds: 3}
18
+ // => { hours: 1, minutes: 2, seconds: 3 }
19
19
  interval.toPostgres()
20
- // 3 seconds 2 minutes 1 hours
21
- interval.toISO()
20
+ // 1 hour 2 minutes 3 seconds
21
+ interval.toISOString()
22
22
  // P0Y0M0DT1H2M3S
23
+ interval.toISOStringShort()
24
+ // PT1H2M3S
25
+ ```
26
+
27
+ This package parses the default Postgres interval style. If you have changed [`intervalstyle`](https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-INTERVALSTYLE), you will need to set it back to the default:
28
+
29
+ ```sql
30
+ set intervalstyle to default;
23
31
  ```
24
32
 
25
33
  ## API
@@ -39,10 +47,14 @@ Returns an interval string. This allows the interval object to be passed into pr
39
47
 
40
48
  #### `interval.toISOString()` -> `string`
41
49
 
42
- Returns an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) compliant string.
50
+ Returns an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) compliant string, for example `P0Y0M0DT0H9M0S`.
43
51
 
44
52
  Also available as `interval.toISO()` for backwards compatibility.
45
53
 
54
+ #### `interval.toISOStringShort()` -> `string`
55
+
56
+ Returns an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) compliant string shortened to minimum length, for example `PT9M`.
57
+
46
58
  ## License
47
59
 
48
60
  MIT © [Ben Drucker](http://bendrucker.me)