RepoFold

Source Module (this page)

Purpose

The src module is the core of the ms library. It provides a complete set of functions for converting between human-readable time duration strings and their millisecond equivalents. The module supports two directions of conversion: parsing strings (e.g., "2 hours", "1.5d") into numeric millisecond values, and formatting numeric millisecond values back into human-readable strings (e.g., 3600000 becomes "1h" or "1 hour"). Both short (abbreviated) and long (verbose) output formats are supported.

This module is self-contained with no external dependencies and no outbound module edges. All functionality lives in a single source file, src/index.ts, with four test files covering each public function.

How It Works

Architecture Overview

The module is built around a set of constant multipliers that define the relationship between time units and milliseconds. These constants are defined at the top of src/index.ts:

const s = 1000;
const m = s * 60;
const h = m * 60;
const d = h * 24;
const w = d * 7;
const y = d * 365.25;
const mo = y / 12;

src/index.ts

The module defines a comprehensive set of TypeScript types that represent valid time unit strings. These types are used for type-safe parsing and are exported for consumer use:

TypePurpose
YearsUnion of 'years' | 'year' | 'yrs' | 'yr' | 'y'
MonthsUnion of 'months' | 'month' | 'mo'
WeeksUnion of 'weeks' | 'week' | 'w'
DaysUnion of 'days' | 'day' | 'd'
HoursUnion of 'hours' | 'hour' | 'hrs' | 'hr' | 'h'
MinutesUnion of 'minutes' | 'minute' | 'mins' | 'min' | 'm'
SecondsUnion of 'seconds' | 'second' | 'secs' | 'sec' | 's'
MillisecondsUnion of 'milliseconds' | 'millisecond' | 'msecs' | 'msec' | 'ms'

src/index.ts:9-16

These are combined into the Unit type src/index.ts:17-25, and then extended to UnitAnyCase which adds capitalized and uppercase variants src/index.ts:27-27. The StringValue type src/index.ts:29-32 represents all valid string inputs:

export type StringValue =
 | `${number}`
 | `${number}${UnitAnyCase}`
 | `${number} ${UnitAnyCase}`;

Function Dispatch

The main entry point is the ms() function, which acts as a dispatcher. It inspects the type of its first argument and delegates to either parse() (for string inputs) or format() (for number inputs):

export function ms(value: StringValue | number,
 options?: Options,
): number | string {
 if (typeof value === 'string') {
 return parse(value);
 } else if (typeof value === 'number') {
 return format(value, options);
 }
 throw new Error(`Value provided to ms() must be a string or number. value=${JSON.stringify(value)}`,
);
}

src/index.ts:50-62

The Options interface src/index.ts:34-39 accepts a single optional property:

PropertyTypeDefaultDescription
longbooleanfalseWhen true, uses verbose formatting (e.g., "2 hours" instead of "2h")

Walkthrough of Main Flows

Parsing Strings to Milliseconds

The parse() Function

The parse() function src/index.ts:71-147 accepts a string and returns a number representing milliseconds. It performs input validation first:

export function parse(str: string): number {
 if (typeof str!== 'string' || str.length === 0 || str.length > 100) {
 throw new Error(`Value provided to ms.parse() must be a string with length between 1 and 99. value=${JSON.stringify(str)}`,
);
 }

src/index.ts:71-77

The function rejects empty strings and strings longer than 100 characters. After validation, it applies a regular expression to extract the numeric value and unit:

 const match =
 /^(?<value>-?\d*\.?\d+) *(?<unit>milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|months?|mo|years?|yrs?|y)?$/i.exec(str,
);

src/index.ts:78-80

The regex captures:

  • value: An optional negative sign, followed by digits and an optional decimal point and more digits (e.g., -1.5, .5, 100)
  • unit: An optional unit string matching any of the supported unit aliases, case-insensitive

If the regex does not match, the function returns NaN src/index.ts:82-84.

When the unit is captured, it is normalized to lowercase and matched against all known unit aliases in a switch statement src/index.ts:92-143. Each case multiplies the parsed numeric value by the corresponding constant:

Unit aliasesMultiplierExample
years, year, yrs, yr, yy (365.25 days)"1y" → 31557600000
months, month, momo (y/12)"1mo" → 2629800000
weeks, week, ww (7 days)"1w" → 604800000
days, day, dd (24 hours)"2d" → 172800000
hours, hour, hrs, hr, hh (60 minutes)"1.5h" → 5400000
minutes, minute, mins, min, mm (60 seconds)"1m" → 60000
seconds, second, secs, sec, ss (1000 ms)"1s" → 1000
milliseconds, millisecond, msecs, msec, ms1 (identity)"100ms" → 100

When no unit is provided, the value is treated as milliseconds directly src/index.ts:90-90.

The function handles several edge cases:

  • Negative values: The regex captures an optional leading -, so "-100ms" returns -100
  • Decimal values: parseFloat() handles decimals, so "1.5h" returns 5400000
  • Numbers starting with .: The regex pattern \d*\.?\d+ matches .5, so ".5ms" returns 0.5
  • Multiple spaces: The regex allows * (zero or more spaces) between value and unit, so "1 s" returns 1000
  • Case insensitivity: The /i flag on the regex makes matching case-insensitive, so "1.5H" works the same as "1.5h"
  • Invalid strings: Returns NaN for unparseable input like "☃", "10-.5", or "ms" (no numeric value)

The parseStrict() Function

The parseStrict() function src/index.ts:156-158 is a thin wrapper around parse() that provides type safety through the StringValue type:

export function parseStrict(value: StringValue): number {
 return parse(value);
}

The key difference is that parseStrict() accepts only StringValue as its parameter type, which restricts inputs at compile time to strings that match the pattern of an optional number followed by an optional unit. This means TypeScript consumers get compile-time validation that their input is a valid time string format. At runtime, parseStrict() behaves identically to parse().

Formatting Milliseconds to Strings

The format() Function

The format() function src/index.ts:225-231 converts a numeric millisecond value into a human-readable string:

export function format(ms: number, options?: Options): string {
 if (typeof ms!== 'number' ||!Number.isFinite(ms)) {
 throw new Error('Value provided to ms.format() must be of type number.');
 }

 return options?.long? fmtLong(ms): fmtShort(ms);
}

It validates that the input is a finite number, rejecting NaN, Infinity, and -Infinity. It then delegates to either fmtShort() or fmtLong() based on the long option.

Choosing the Best Unit

Both fmtShort() src/index.ts:163-187 and fmtLong() src/index.ts:192-216 use the same algorithm to select the most appropriate unit. They work by comparing the absolute value of the input against the time constants in descending order (largest to smallest):

function fmtShort(ms: number): StringValue {
 const msAbs = Math.abs(ms);
 if (msAbs >= y) {
 return `${Math.round(ms / y)}y`;
 }
 if (msAbs >= mo) {
 return `${Math.round(ms / mo)}mo`;
 }
 if (msAbs >= w) {
 return `${Math.round(ms / w)}w`;
 }
 if (msAbs >= d) {
 return `${Math.round(ms / d)}d`;
 }
 if (msAbs >= h) {
 return `${Math.round(ms / h)}h`;
 }
 if (msAbs >= m) {
 return `${Math.round(ms / m)}m`;
 }
 if (msAbs >= s) {
 return `${Math.round(ms / s)}s`;
 }
 return `${ms}ms`;
}

The algorithm selects the largest unit for which the absolute value is at least one full unit. For example:

  • 234234234 ms has an absolute value of ~234 million, which is >= d (86400000) but < w (604800000), so it formats as "3d" (234234234 / 86400000 = 2.71, rounded to 3)
  • 500 ms is less than s (1000), so it falls through to the final return and formats as "500ms"
  • 1200 ms is >= s but < m, so it formats as "1s" (1200 / 1000 = 1.2, rounded to 1)

The fmtLong() function uses the same logic but calls the plural() helper src/index.ts:236-244:

function plural(ms: number,
 msAbs: number,
 n: number,
 name: string,
): StringValue {
 const isPlural = msAbs >= n * 1.5;
 return `${Math.round(ms / n)} ${name}${isPlural? 's': ''}` as StringValue;
}

The pluralization threshold is n * 1.5, meaning a value of 1.5 or more times the unit is considered plural. This means:

  • 1000 ms formats as "1 second" (1000 / 1000 = 1, 1000 < 1500, so singular)
  • 1200 ms formats as "1 second" (1200 / 1000 = 1.2, rounded to 1, 1200 < 1500, so singular)
  • 10000 ms formats as "10 seconds" (10000 / 1000 = 10, 10000 >= 1500, so plural)

The ms() Function as Unified Entry Point

The ms() function serves as the primary API for most consumers. It combines both parsing and formatting into a single function that behaves differently based on input type:

  • String input: Delegates to parse(), returns a number (milliseconds)
  • Number input: Delegates to format(), returns a string (human-readable)

This dual behavior is demonstrated in the test file src/index.test.ts. For example, ms('1m') returns 60000, while ms(60000) returns "1m".

Public API

SymbolSignaturePurpose
StringValuetype StringValue = \${number}` | `${number}${UnitAnyCase}` | `${number} ${UnitAnyCase}``Type representing valid time string inputs for type-safe parsing
msms(value: StringValue, options?: Options): number; ms(value: number, options?: Options): stringMain entry point; parses strings to milliseconds or formats numbers to strings
parseparse(str: string): numberParses a time string to milliseconds; returns NaN for invalid input
parseStrictparseStrict(value: StringValue): numberType-safe version of parse() that accepts only StringValue at compile time
formatformat(ms: number, options?: Options): stringFormats milliseconds to a human-readable string (short or long form)
Optionsinterface Options { long?: boolean }Configuration object for formatting; long: true produces verbose output

Interactions with Other Modules

The src module has no inbound or outbound module edges. It is the sole source of functionality in the repository and does not depend on any other modules. All test files (src/format.test.ts, src/index.test.ts, src/parse-strict.test.ts, src/parse.test.ts) import directly from src/index.ts and test the public API.

Configuration and Conventions

Options

The only configuration option is the long property on the Options interface:

interface Options {
 /**
 * Set to `true` to use verbose formatting. Defaults to `false`.
 */
 long?: boolean;
}

src/index.ts:34-39

When long is false or omitted, format() produces short abbreviations:

  • "500ms", "1s", "1m", "1h", "1d", "1w", "1mo", "1y"

When long is true, format() produces verbose, pluralized output:

  • "500 ms", "1 second", "1 minute", "1 hour", "1 day", "1 week", "1 month", "1 year"

Error Handling Conventions

The module follows consistent error handling patterns:

  1. Type validation: Functions throw Error when the input is not of the expected type (e.g., passing null, undefined, objects, or arrays)
  2. Finite number check: format() and ms() (for number inputs) throw when given NaN, Infinity, or -Infinity
  3. String length check: parse() throws when the string is empty or longer than 100 characters
  4. Invalid parseable strings: parse() returns NaN for strings that don't match the expected pattern (e.g., "☃", "foo")

Unit Aliases

Each time unit has multiple accepted aliases for parsing, all case-insensitive:

UnitShort aliasesLong aliases
Yeary, yr, yrsyear, years
Monthmomonth, months
Weekwweek, weeks
Daydday, days
Hourh, hr, hrshour, hours
Minutem, min, minsminute, minutes
Seconds, sec, secssecond, seconds
Millisecondms, msec, msecsmillisecond, milliseconds

Time Constants

The module uses these millisecond constants:

ConstantValue (ms)Definition
s1000Base unit
m60000s * 60
h3600000m * 60
d86400000h * 24
w604800000d * 7
y31557600000d * 365.25
mo2629800000y / 12

Note that a year is defined as 365.25 days (accounting for leap years), and a month is defined as exactly one-twelfth of a year.

Generated from commit 4ff48ce