@padmaj/duration 1.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.
- package/README.md +82 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +59 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# @padmaj/duration
|
|
2
|
+
|
|
3
|
+
Parse and format duration strings. Zero dependencies. TypeScript-first. Works in Node and browser.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @padmaj/duration
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Parse a duration string → milliseconds
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { parseDuration } from '@padmaj/duration'
|
|
17
|
+
|
|
18
|
+
parseDuration('500ms') // → 500
|
|
19
|
+
parseDuration('30s') // → 30000
|
|
20
|
+
parseDuration('5m') // → 300000
|
|
21
|
+
parseDuration('2h') // → 7200000
|
|
22
|
+
parseDuration('1d') // → 86400000
|
|
23
|
+
parseDuration('1w') // → 604800000
|
|
24
|
+
|
|
25
|
+
// Compound durations
|
|
26
|
+
parseDuration('2h30m') // → 9000000
|
|
27
|
+
parseDuration('1d12h') // → 129600000
|
|
28
|
+
parseDuration('2h 30m') // → 9000000 (spaces OK)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Format milliseconds → duration string
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { formatDuration } from '@padmaj/duration'
|
|
35
|
+
|
|
36
|
+
formatDuration(500) // → '500ms'
|
|
37
|
+
formatDuration(30000) // → '30s'
|
|
38
|
+
formatDuration(9000000) // → '2h 30m'
|
|
39
|
+
formatDuration(129600000) // → '1d 12h'
|
|
40
|
+
formatDuration(0) // → '0ms'
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Supported units
|
|
44
|
+
|
|
45
|
+
| Unit | Meaning |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `ms` | milliseconds |
|
|
48
|
+
| `s` | seconds |
|
|
49
|
+
| `m` | minutes |
|
|
50
|
+
| `h` | hours |
|
|
51
|
+
| `d` | days |
|
|
52
|
+
| `w` | weeks |
|
|
53
|
+
|
|
54
|
+
## API
|
|
55
|
+
|
|
56
|
+
### `parseDuration(input: string): number`
|
|
57
|
+
|
|
58
|
+
Parses a duration string and returns milliseconds. Throws `DurationError` if the input is empty or contains no recognizable units.
|
|
59
|
+
|
|
60
|
+
### `formatDuration(ms: number): string`
|
|
61
|
+
|
|
62
|
+
Formats milliseconds into a human-readable duration string. Throws `RangeError` if the value is negative, `NaN`, or `Infinity`.
|
|
63
|
+
|
|
64
|
+
### `DurationError`
|
|
65
|
+
|
|
66
|
+
Extends `Error`. Thrown by `parseDuration` on invalid input.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { parseDuration, DurationError } from '@padmaj/duration'
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
parseDuration('abc')
|
|
73
|
+
} catch (e) {
|
|
74
|
+
if (e instanceof DurationError) {
|
|
75
|
+
console.error('Bad duration:', e.message)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT
|
package/dist/index.d.mts
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DurationError: () => DurationError,
|
|
24
|
+
formatDuration: () => formatDuration,
|
|
25
|
+
parseDuration: () => parseDuration
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
var UNITS = {
|
|
29
|
+
ms: 1,
|
|
30
|
+
s: 1e3,
|
|
31
|
+
m: 6e4,
|
|
32
|
+
h: 36e5,
|
|
33
|
+
d: 864e5,
|
|
34
|
+
w: 6048e5
|
|
35
|
+
};
|
|
36
|
+
var PARSE_RE = /(\d+(?:\.\d+)?)\s*(ms|s|m|h|d|w)/g;
|
|
37
|
+
var DurationError = class extends Error {
|
|
38
|
+
constructor(input) {
|
|
39
|
+
super(`Invalid duration string: "${input}"`);
|
|
40
|
+
this.name = "DurationError";
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
function parseDuration(input) {
|
|
44
|
+
const str = input.trim();
|
|
45
|
+
if (!str) throw new DurationError(input);
|
|
46
|
+
let total = 0;
|
|
47
|
+
let matched = false;
|
|
48
|
+
PARSE_RE.lastIndex = 0;
|
|
49
|
+
for (const match of str.matchAll(PARSE_RE)) {
|
|
50
|
+
total += parseFloat(match[1]) * UNITS[match[2]];
|
|
51
|
+
matched = true;
|
|
52
|
+
}
|
|
53
|
+
if (!matched) throw new DurationError(input);
|
|
54
|
+
return Math.round(total);
|
|
55
|
+
}
|
|
56
|
+
function formatDuration(ms) {
|
|
57
|
+
if (!Number.isFinite(ms) || ms < 0) {
|
|
58
|
+
throw new RangeError(`formatDuration expects a non-negative finite number, got ${ms}`);
|
|
59
|
+
}
|
|
60
|
+
if (ms === 0) return "0ms";
|
|
61
|
+
const units = [
|
|
62
|
+
["w", UNITS.w],
|
|
63
|
+
["d", UNITS.d],
|
|
64
|
+
["h", UNITS.h],
|
|
65
|
+
["m", UNITS.m],
|
|
66
|
+
["s", UNITS.s],
|
|
67
|
+
["ms", UNITS.ms]
|
|
68
|
+
];
|
|
69
|
+
const parts = [];
|
|
70
|
+
let remaining = Math.round(ms);
|
|
71
|
+
for (const [unit, value] of units) {
|
|
72
|
+
if (remaining >= value) {
|
|
73
|
+
const count = Math.floor(remaining / value);
|
|
74
|
+
parts.push(`${count}${unit}`);
|
|
75
|
+
remaining %= value;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return parts.join(" ");
|
|
79
|
+
}
|
|
80
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
81
|
+
0 && (module.exports = {
|
|
82
|
+
DurationError,
|
|
83
|
+
formatDuration,
|
|
84
|
+
parseDuration
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["const UNITS: Record<string, number> = {\n ms: 1,\n s: 1_000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n w: 604_800_000,\n}\n\nconst PARSE_RE = /(\\d+(?:\\.\\d+)?)\\s*(ms|s|m|h|d|w)/g\n\nexport class DurationError extends Error {\n constructor(input: string) {\n super(`Invalid duration string: \"${input}\"`)\n this.name = 'DurationError'\n }\n}\n\nexport function parseDuration(input: string): number {\n const str = input.trim()\n if (!str) throw new DurationError(input)\n\n let total = 0\n let matched = false\n\n PARSE_RE.lastIndex = 0\n for (const match of str.matchAll(PARSE_RE)) {\n total += parseFloat(match[1]) * UNITS[match[2]]\n matched = true\n }\n\n if (!matched) throw new DurationError(input)\n return Math.round(total)\n}\n\nexport function formatDuration(ms: number): string {\n if (!Number.isFinite(ms) || ms < 0) {\n throw new RangeError(`formatDuration expects a non-negative finite number, got ${ms}`)\n }\n\n if (ms === 0) return '0ms'\n\n const units: [string, number][] = [\n ['w', UNITS.w],\n ['d', UNITS.d],\n ['h', UNITS.h],\n ['m', UNITS.m],\n ['s', UNITS.s],\n ['ms', UNITS.ms],\n ]\n\n const parts: string[] = []\n let remaining = Math.round(ms)\n\n for (const [unit, value] of units) {\n if (remaining >= value) {\n const count = Math.floor(remaining / value)\n parts.push(`${count}${unit}`)\n remaining %= value\n }\n }\n\n return parts.join(' ')\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAM,QAAgC;AAAA,EACpC,IAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AACN;AAEA,IAAM,WAAW;AAEV,IAAM,gBAAN,cAA4B,MAAM;AAAA,EACvC,YAAY,OAAe;AACzB,UAAM,6BAA6B,KAAK,GAAG;AAC3C,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,cAAc,OAAuB;AACnD,QAAM,MAAM,MAAM,KAAK;AACvB,MAAI,CAAC,IAAK,OAAM,IAAI,cAAc,KAAK;AAEvC,MAAI,QAAQ;AACZ,MAAI,UAAU;AAEd,WAAS,YAAY;AACrB,aAAW,SAAS,IAAI,SAAS,QAAQ,GAAG;AAC1C,aAAS,WAAW,MAAM,CAAC,CAAC,IAAI,MAAM,MAAM,CAAC,CAAC;AAC9C,cAAU;AAAA,EACZ;AAEA,MAAI,CAAC,QAAS,OAAM,IAAI,cAAc,KAAK;AAC3C,SAAO,KAAK,MAAM,KAAK;AACzB;AAEO,SAAS,eAAe,IAAoB;AACjD,MAAI,CAAC,OAAO,SAAS,EAAE,KAAK,KAAK,GAAG;AAClC,UAAM,IAAI,WAAW,4DAA4D,EAAE,EAAE;AAAA,EACvF;AAEA,MAAI,OAAO,EAAG,QAAO;AAErB,QAAM,QAA4B;AAAA,IAChC,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,MAAM,MAAM,EAAE;AAAA,EACjB;AAEA,QAAM,QAAkB,CAAC;AACzB,MAAI,YAAY,KAAK,MAAM,EAAE;AAE7B,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO;AACjC,QAAI,aAAa,OAAO;AACtB,YAAM,QAAQ,KAAK,MAAM,YAAY,KAAK;AAC1C,YAAM,KAAK,GAAG,KAAK,GAAG,IAAI,EAAE;AAC5B,mBAAa;AAAA,IACf;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,GAAG;AACvB;","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var UNITS = {
|
|
3
|
+
ms: 1,
|
|
4
|
+
s: 1e3,
|
|
5
|
+
m: 6e4,
|
|
6
|
+
h: 36e5,
|
|
7
|
+
d: 864e5,
|
|
8
|
+
w: 6048e5
|
|
9
|
+
};
|
|
10
|
+
var PARSE_RE = /(\d+(?:\.\d+)?)\s*(ms|s|m|h|d|w)/g;
|
|
11
|
+
var DurationError = class extends Error {
|
|
12
|
+
constructor(input) {
|
|
13
|
+
super(`Invalid duration string: "${input}"`);
|
|
14
|
+
this.name = "DurationError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
function parseDuration(input) {
|
|
18
|
+
const str = input.trim();
|
|
19
|
+
if (!str) throw new DurationError(input);
|
|
20
|
+
let total = 0;
|
|
21
|
+
let matched = false;
|
|
22
|
+
PARSE_RE.lastIndex = 0;
|
|
23
|
+
for (const match of str.matchAll(PARSE_RE)) {
|
|
24
|
+
total += parseFloat(match[1]) * UNITS[match[2]];
|
|
25
|
+
matched = true;
|
|
26
|
+
}
|
|
27
|
+
if (!matched) throw new DurationError(input);
|
|
28
|
+
return Math.round(total);
|
|
29
|
+
}
|
|
30
|
+
function formatDuration(ms) {
|
|
31
|
+
if (!Number.isFinite(ms) || ms < 0) {
|
|
32
|
+
throw new RangeError(`formatDuration expects a non-negative finite number, got ${ms}`);
|
|
33
|
+
}
|
|
34
|
+
if (ms === 0) return "0ms";
|
|
35
|
+
const units = [
|
|
36
|
+
["w", UNITS.w],
|
|
37
|
+
["d", UNITS.d],
|
|
38
|
+
["h", UNITS.h],
|
|
39
|
+
["m", UNITS.m],
|
|
40
|
+
["s", UNITS.s],
|
|
41
|
+
["ms", UNITS.ms]
|
|
42
|
+
];
|
|
43
|
+
const parts = [];
|
|
44
|
+
let remaining = Math.round(ms);
|
|
45
|
+
for (const [unit, value] of units) {
|
|
46
|
+
if (remaining >= value) {
|
|
47
|
+
const count = Math.floor(remaining / value);
|
|
48
|
+
parts.push(`${count}${unit}`);
|
|
49
|
+
remaining %= value;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return parts.join(" ");
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
DurationError,
|
|
56
|
+
formatDuration,
|
|
57
|
+
parseDuration
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["const UNITS: Record<string, number> = {\n ms: 1,\n s: 1_000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n w: 604_800_000,\n}\n\nconst PARSE_RE = /(\\d+(?:\\.\\d+)?)\\s*(ms|s|m|h|d|w)/g\n\nexport class DurationError extends Error {\n constructor(input: string) {\n super(`Invalid duration string: \"${input}\"`)\n this.name = 'DurationError'\n }\n}\n\nexport function parseDuration(input: string): number {\n const str = input.trim()\n if (!str) throw new DurationError(input)\n\n let total = 0\n let matched = false\n\n PARSE_RE.lastIndex = 0\n for (const match of str.matchAll(PARSE_RE)) {\n total += parseFloat(match[1]) * UNITS[match[2]]\n matched = true\n }\n\n if (!matched) throw new DurationError(input)\n return Math.round(total)\n}\n\nexport function formatDuration(ms: number): string {\n if (!Number.isFinite(ms) || ms < 0) {\n throw new RangeError(`formatDuration expects a non-negative finite number, got ${ms}`)\n }\n\n if (ms === 0) return '0ms'\n\n const units: [string, number][] = [\n ['w', UNITS.w],\n ['d', UNITS.d],\n ['h', UNITS.h],\n ['m', UNITS.m],\n ['s', UNITS.s],\n ['ms', UNITS.ms],\n ]\n\n const parts: string[] = []\n let remaining = Math.round(ms)\n\n for (const [unit, value] of units) {\n if (remaining >= value) {\n const count = Math.floor(remaining / value)\n parts.push(`${count}${unit}`)\n remaining %= value\n }\n }\n\n return parts.join(' ')\n}\n"],"mappings":";AAAA,IAAM,QAAgC;AAAA,EACpC,IAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AACN;AAEA,IAAM,WAAW;AAEV,IAAM,gBAAN,cAA4B,MAAM;AAAA,EACvC,YAAY,OAAe;AACzB,UAAM,6BAA6B,KAAK,GAAG;AAC3C,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,cAAc,OAAuB;AACnD,QAAM,MAAM,MAAM,KAAK;AACvB,MAAI,CAAC,IAAK,OAAM,IAAI,cAAc,KAAK;AAEvC,MAAI,QAAQ;AACZ,MAAI,UAAU;AAEd,WAAS,YAAY;AACrB,aAAW,SAAS,IAAI,SAAS,QAAQ,GAAG;AAC1C,aAAS,WAAW,MAAM,CAAC,CAAC,IAAI,MAAM,MAAM,CAAC,CAAC;AAC9C,cAAU;AAAA,EACZ;AAEA,MAAI,CAAC,QAAS,OAAM,IAAI,cAAc,KAAK;AAC3C,SAAO,KAAK,MAAM,KAAK;AACzB;AAEO,SAAS,eAAe,IAAoB;AACjD,MAAI,CAAC,OAAO,SAAS,EAAE,KAAK,KAAK,GAAG;AAClC,UAAM,IAAI,WAAW,4DAA4D,EAAE,EAAE;AAAA,EACvF;AAEA,MAAI,OAAO,EAAG,QAAO;AAErB,QAAM,QAA4B;AAAA,IAChC,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,MAAM,MAAM,EAAE;AAAA,EACjB;AAEA,QAAM,QAAkB,CAAC;AACzB,MAAI,YAAY,KAAK,MAAM,EAAE;AAE7B,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO;AACjC,QAAI,aAAa,OAAO;AACtB,YAAM,QAAQ,KAAK,MAAM,YAAY,KAAK;AAC1C,YAAM,KAAK,GAAG,KAAK,GAAG,IAAI,EAAE;AAC5B,mBAAa;AAAA,IACf;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,GAAG;AACvB;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@padmaj/duration",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Parse and format duration strings. '2h30m' ↔ 9000000ms. Zero dependencies. TypeScript-first.",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": ["dist"],
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/padmajp4/duration.git"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
24
|
+
"prepublishOnly": "npm run build && npm test"
|
|
25
|
+
},
|
|
26
|
+
"keywords": ["duration", "time", "parse", "format", "ms", "human-readable"],
|
|
27
|
+
"author": "Padmaj P Kumar",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^26.0.1",
|
|
31
|
+
"tsup": "^8.5.1",
|
|
32
|
+
"typescript": "^6.0.3",
|
|
33
|
+
"vitest": "^4.1.9"
|
|
34
|
+
}
|
|
35
|
+
}
|