Skip to content

Commit 54d0d15

Browse files
committed
feat: add toTemporalDuration() converter
Adds an opt-in PostgresInterval.prototype.toTemporalDuration() that returns a Temporal.Duration, feature-detected via globalThis.Temporal with no new dependency. Mixed-sign intervals throw RangeError from Temporal's own validation. Refreshes the CI matrix to Node 20/22/24/26 so the Temporal path runs, and bumps the checkout/setup-node action pins. Closes #55.
1 parent 09c75cd commit 54d0d15

5 files changed

Lines changed: 88 additions & 4 deletions

File tree

.github/workflows/test.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@ jobs:
22
build:
33
runs-on: ubuntu-latest
44
steps:
5-
- uses: actions/checkout@v2
5+
- uses: actions/checkout@v4
66
- name: Use Node.js ${{ matrix.node-version }}
7-
uses: actions/setup-node@v1
7+
uses: actions/setup-node@v4
88
with:
99
node-version: ${{ matrix.node-version }}
1010
- run: npm install
1111
- run: npm test
1212
strategy:
1313
matrix:
1414
node-version:
15-
- "12"
16-
- "14"
15+
- "20"
16+
- "22"
17+
- "24"
18+
- "26"
1719
name: tests
1820
"on":
1921
- push

index.d.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,24 @@ declare namespace PostgresInterval {
6161
* ```
6262
*/
6363
toISOStringShort(): string;
64+
/**
65+
* Returns a [`Temporal.Duration`](https://tc39.es/proposal-temporal/docs/duration.html) representing the interval.
66+
*
67+
* Requires `globalThis.Temporal` (Node 26+, or a polyfill assigned to the global). Throws otherwise.
68+
*
69+
* Postgres mixed-sign intervals (e.g. `1 mon -1 days`) throw a `RangeError`, because `Temporal.Duration` requires a single sign across all fields.
70+
*
71+
* The `Temporal` types are not yet in the default TypeScript lib. For this signature to resolve, your project needs Temporal lib types (the TypeScript lib once available, or `@js-temporal/polyfill` types).
72+
*
73+
* ```js
74+
* var parse = require('postgres-interval')
75+
* var interval = parse('01:02:03')
76+
* // => { hours: 1, minutes: 2, seconds: 3 }
77+
* interval.toTemporalDuration().toString()
78+
* // PT1H2M3S
79+
* ```
80+
*/
81+
toTemporalDuration(): Temporal.Duration;
6482
}
6583
}
6684

index.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,25 @@ PostgresInterval.prototype.toISOStringShort = function () {
9393
return toISOString.call(this, { short: true })
9494
}
9595

96+
PostgresInterval.prototype.toTemporalDuration = function () {
97+
if (typeof globalThis.Temporal === 'undefined') {
98+
throw new Error('Temporal is not available. It ships unflagged in Node 26+. On older runtimes, install a polyfill (e.g. @js-temporal/polyfill) and assign it to globalThis.Temporal.')
99+
}
100+
101+
const totalMicroseconds = Math.round(this.milliseconds * 1000)
102+
103+
return globalThis.Temporal.Duration.from({
104+
years: this.years,
105+
months: this.months,
106+
days: this.days,
107+
hours: this.hours,
108+
minutes: this.minutes,
109+
seconds: this.seconds,
110+
milliseconds: Math.trunc(totalMicroseconds / 1000),
111+
microseconds: totalMicroseconds % 1000
112+
})
113+
}
114+
96115
function toISOString ({ short }) {
97116
let datePart = ''
98117

readme.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ Also available as `interval.toISO()` for backwards compatibility.
5757

5858
Returns an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) compliant string shortened to minimum length, for example `PT9M`.
5959

60+
#### `interval.toTemporalDuration()` -> `Temporal.Duration`
61+
62+
Returns a [`Temporal.Duration`](https://tc39.es/proposal-temporal/docs/duration.html) representing the interval.
63+
64+
Requires `globalThis.Temporal`. It ships unflagged in Node 26+. On older runtimes, install a polyfill such as [`@js-temporal/polyfill`](https://www.npmjs.com/package/@js-temporal/polyfill) and assign it to `globalThis.Temporal`. The method throws if `Temporal` is unavailable.
65+
66+
Postgres mixed-sign intervals (e.g. `1 mon -1 days`) throw a `RangeError`. `Temporal.Duration` requires all fields to share a single sign, which these intervals violate.
67+
68+
The `Temporal` types are not yet in the default TypeScript lib. To resolve the return type, your project needs Temporal lib types: the TypeScript lib once available, or the `@js-temporal/polyfill` types. This package adds no type dependency.
69+
6070
## License
6171

6272
MIT © [Ben Drucker](http://bendrucker.me)

test.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,5 +114,40 @@ test(function (t) {
114114
t.end()
115115
})
116116

117+
if (typeof globalThis.Temporal !== 'undefined') {
118+
t.test('toTemporalDuration', function (t) {
119+
const simple = interval('01:02:03').toTemporalDuration()
120+
t.equal(simple.hours, 1)
121+
t.equal(simple.minutes, 2)
122+
t.equal(simple.seconds, 3)
123+
124+
const full = interval('1 year 2 mons 3 days 04:05:06').toTemporalDuration()
125+
t.equal(full.years, 1)
126+
t.equal(full.months, 2)
127+
t.equal(full.days, 3)
128+
t.equal(full.hours, 4)
129+
t.equal(full.minutes, 5)
130+
t.equal(full.seconds, 6)
131+
132+
const subSecond = interval('00:00:00.123456').toTemporalDuration()
133+
t.equal(subSecond.milliseconds, 123)
134+
t.equal(subSecond.microseconds, 456)
135+
136+
t.throws(function () {
137+
interval('1 mon -1 days').toTemporalDuration()
138+
}, RangeError, 'mixed-sign interval throws')
139+
140+
const negative = interval('-01:02:03').toTemporalDuration()
141+
t.equal(negative.sign, -1)
142+
t.equal(negative.hours, -1)
143+
t.equal(negative.minutes, -2)
144+
t.equal(negative.seconds, -3)
145+
146+
t.end()
147+
})
148+
} else {
149+
t.skip('toTemporalDuration (Temporal unavailable)')
150+
}
151+
117152
t.end()
118153
})

0 commit comments

Comments
 (0)