@cutticat/ms
Parse a duration, format milliseconds, one function. Same job as ms, plus arithmetic, extra output formats, fractional milliseconds, custom formatters, and TypeScript that only allows options the chosen formatter actually has.
Contents
Overview
ms("10h + 5min") is 36_300_000. ms(36_300_000) is "10h 5m". Input accepts intervals, + / -, clock times (1:30:45), decimals, and scientific notation. Output is short by default; switch to long, ISO 8601, colon, or a raw number. Pass precision when leftover milliseconds should stay visible (123.5ms).
Need a different string? Hand createMs your own formatters. Keys and option shapes are inferred, so format and extra fields stay correlated.
Installation
pnpm i @cutticat/ms
Quick start
import { createMs, ms } from "@cutticat/ms"
ms("10h + 5min") // 36_300_000
ms("1:30:45") // 5_445_000
ms("-5min") // -300_000
ms("1d 2mo - 1:10:20") // 5_266_180_000
ms(36_300_000) // "10h 5m"
ms(36_300_000, { format: "long" }) // "10 hours 5 minutes"
ms(36_300_000, { format: "iso" }) // "PT10H5M"
ms(36_300_000, { format: "colon" }) // "10:05:00"
ms(36_300_000, { format: "number" }) // "36300000"
ms(123.5, { precision: 1 }) // "123.5ms"
const labeled = createMs({
formatters: {
labeled: (n, options?: { unit?: string }) => `${n}${options?.unit ?? "ms"}`,
},
defaultFormat: "labeled",
})
labeled(100, { unit: "ms" }) // "100ms"
Comparison with ms
ms is enough for "2 days" in and "5s" out. @cutticat/ms keeps that path and fills in the rest:
| Feature | ms | @cutticat/ms |
|---|---|---|
Simple parsing ("2 days", "1d") |
✅ | ✅ |
| Short / long output | ✅ | ✅ |
Arithmetic ("10h + 5min", "1d - 30min") |
❌ | ✅ |
Unary + / - ("-5min") |
❌ | ✅ |
Clock strings ("1:30:45") |
❌ | ✅ |
ISO 8601 duration ("PT10H5M") |
❌ | ✅ |
Colon output ("10:05:00") |
❌ | ✅ |
Decimal input ("1.5h") |
❌ | ✅ |
Scientific notation ("12e3") |
❌ | ✅ |
Fractional output (precision) |
❌ | ✅ |
Custom formatters (createMs) |
❌ | ✅ |
| Options typed per formatter | ❌ | ✅ |
Type safety
format is a discriminant. Extra fields are the options of that formatter: precision on short, nothing extra on iso, whatever you declared on a custom formatter. Omit format and the options follow the instance default (short for the exported ms, or defaultFormat from createMs).
ms(123.5, { precision: 1 })
ms(123.5, { format: "short", precision: 1 })
// @ts-expect-error — iso has no precision
ms(123.5, { format: "iso", precision: 1 })
const isoMs = createMs({ defaultFormat: "iso" })
isoMs(3_600_000) // "PT1H"
// @ts-expect-error — default format is iso
isoMs(123.5, { precision: 1 })
const labeled = createMs({
formatters: {
labeled: (n, options?: { unit?: string }) => `${n}${options?.unit ?? "ms"}`,
},
defaultFormat: "labeled",
})
labeled(100, { unit: "ms" })
// @ts-expect-error — labeled does not take precision
labeled(100, { precision: 1 })
createMs infers formatter keys and option shapes from the formatters object. A one-argument formatter stays optionless. Add a second parameter, and those fields show up on ms(value, { format, ... }).
API
Functions
ms(input, options?)— parse a string into milliseconds, or format a numbercreateMs(options?)— custom formatters and default format; types follow the registry
Output formats
short—"10h 5m";precisionkeeps leftover milliseconds ("123.5ms")long—"10 hours 5 minutes"iso— ISO 8601 Duration ("PT10H5M")colon—"10:05:00"number—"36300000"
Types
CreateMsOptions— options forcreateMsMsFormatOptions—formatplus the active formatter’s fieldsMs— type of themsfunction
Documentation
Full API documentation is in JSDoc. Use IDE autocomplete to browse it.
Requirements
- Node.js >= 22.18.0
- pnpm >= 10.17.0
- TypeScript >= 5.9.0