# @jsxx/ms

[![NPM Version](https://img.shields.io/npm/v/@jsxx/bytes.svg)](https://www.npmjs.com/package/@jsxx/bytes)
[![License](https://img.shields.io/npm/l/@jsxx/bytes.svg)](https://github.com/vajra-labs/jsxx/blob/main/LICENSE)

A lightweight, type-safe utility library for converting between time durations and human-readable strings.

## Features

- **Zero dependencies** - Minimal footprint
- **Type-safe** - Full TypeScript support with template literal types
- **Dual API** - Single function for both formatting and parsing
- **Flexible units** - Supports ms, s, m, h, d, w, mo, y with multiple aliases
- **Long format** - Optional verbose output (e.g., "1 second" vs "1s")
- **Fast** - Optimized with precompiled regex and Map-based lookups
- **Well-tested** - Comprehensive test coverage with 43+ tests

## Installation

```bash
npm add @jsxx/ms
# or
pnpm add @jsxx/ms
# or
yarn add @jsxx/ms
# or
bun add @jsxx/ms
```

## Quick Start

```typescript
import ms from '@jsxx/ms';

// Parse time strings to milliseconds
ms('1s'); // 1000
ms('5m'); // 300000
ms('2h'); // 7200000
ms('1d'); // 86400000

// Format milliseconds to time strings
ms(1000); // "1s"
ms(60000); // "1m"
ms(3600000); // "1h"
ms(1000, {long: true}); // "1 second"
```

## API Reference

### `ms(value, options?)`

Main function with overloaded signatures for bidirectional conversion.

#### Signature 1: Parse time string to milliseconds

```typescript
function ms(value: StringValue, options?: Options): number;
```

Converts a human-readable time string into milliseconds.

**Parameters:**

- `value` (StringValue): A time string (e.g., `"1s"`, `"5m"`, `"2h"`)
- `options` (Options): Not used for parsing (reserved for future use)

**Returns:** `number` - The number of milliseconds

**Examples:**

```typescript
ms('1s'); // 1000
ms('5m'); // 300000
ms('2h'); // 7200000
ms('3d'); // 259200000
ms('1w'); // 604800000
ms('6mo'); // 15778800000
ms('1y'); // 31557600000

// With spaces
ms('1 s'); // 1000
ms('5 m'); // 300000

// Decimal values
ms('1.5s'); // 1500
ms('2.5m'); // 150000
ms('0.5h'); // 1800000

// Negative values
ms('-1s'); // -1000
ms('-5m'); // -300000

// Default to milliseconds
ms('100'); // 100
ms('500'); // 500
```

**Supported Units:**

| Unit | Aliases                                    | Example   |
| ---- | ------------------------------------------ | --------- |
| ms   | milliseconds, millisecond, msecs, msec, ms | `"100ms"` |
| s    | seconds, second, secs, sec, s              | `"1s"`    |
| m    | minutes, minute, mins, min, m              | `"5m"`    |
| h    | hours, hour, hrs, hr, h                    | `"2h"`    |
| d    | days, day, d                               | `"3d"`    |
| w    | weeks, week, w                             | `"1w"`    |
| mo   | months, month, mo                          | `"6mo"`   |
| y    | years, year, yrs, yr, y                    | `"1y"`    |

**Edge Cases:**

- Returns `NaN` for invalid input (e.g., `"invalid"`, `"1xyz"`)
- Case-insensitive: `"1S"`, `"1s"`, `"1Second"` all work
- Throws for empty string, non-string input, or strings > 100 characters

---

#### Signature 2: Format milliseconds to time string

```typescript
function ms(value: number, options?: Options): string;
```

Converts milliseconds into a human-readable time string.

**Parameters:**

- `value` (number): The number of milliseconds
- `options` (Options): Optional formatting options

**Returns:** `string` - Formatted string like `"1s"` or `"1 second"`

**Examples:**

```typescript
// Short format (default)
ms(1000); // "1s"
ms(60000); // "1m"
ms(3600000); // "1h"
ms(86400000); // "1d"
ms(604800000); // "1w"
ms(2629800000); // "1mo"
ms(31557600000); // "1y"

// Long format
ms(1000, {long: true}); // "1 second"
ms(2000, {long: true}); // "2 seconds"
ms(60000, {long: true}); // "1 minute"
ms(120000, {long: true}); // "2 minutes"
ms(3600000, {long: true}); // "1 hour"
ms(7200000, {long: true}); // "2 hours"

// Negative values
ms(-1000); // "-1s"
ms(-60000); // "-1m"
ms(-1000, {long: true}); // "-1 second"

// Rounding
ms(1500); // "2s" (rounds to nearest)
ms(90000); // "2m"
ms(5400000); // "2h"
```

**Throws:**

- Non-number input
- `Infinity` or `-Infinity`
- `NaN`

---

### `parse(str)`

Explicit function to parse a time string into milliseconds.

```typescript
function parse(str: string): number;
```

**Parameters:**

- `str` (string): A time string such as `"1s"`, `"5m"`, `"2h"`

**Returns:** `number` - The number of milliseconds, or `NaN` if invalid

**Throws:** If the string is empty, not a string, or longer than 100 characters

**Example:**

```typescript
import {parse} from '@jsxx/ms';

parse('1s'); // 1000
parse('5m'); // 300000
parse('2h'); // 7200000
parse('invalid'); // NaN
```

---

### `format(msValue, options?)`

Explicit function to format milliseconds into a time string.

```typescript
function format(msValue: number, options?: Options): string;
```

**Parameters:**

- `msValue` (number): The number of milliseconds
- `options` (Options): Optional formatting options

**Returns:** `string` - Formatted string

**Throws:** If the value is not a finite number

**Example:**

```typescript
import {format} from '@jsxx/ms';

format(1000); // "1s"
format(60000); // "1m"
format(3600000); // "1h"
format(1000, {long: true}); // "1 second"
format(120000, {long: true}); // "2 minutes"
```

---

### Types

#### `StringValue`

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

Template literal type for type-safe time strings. Supports any case variation of units.

**Valid formats:**

- `"1s"`, `"5m"`, `"2h"` (lowercase)
- `"1S"`, `"5M"`, `"2H"` (uppercase)
- `"1Second"`, `"5Minutes"` (capitalized)
- `"1 s"`, `"5 m"` (with space)
- `"100"` (number only, defaults to ms)

#### `Options`

```typescript
type Options = {
  /**
   * Use verbose formatting (e.g., "1 second" instead of "1s").
   * @default false
   */
  long?: boolean;
};
```

Options for customizing time formatting.

## Supported Units

All units are based on standard time conversions:

| Unit | Name        | Milliseconds   | Calculation                       |
| ---- | ----------- | -------------- | --------------------------------- |
| ms   | Millisecond | 1              | 1                                 |
| s    | Second      | 1,000          | 1000                              |
| m    | Minute      | 60,000         | 60 × 1000                         |
| h    | Hour        | 3,600,000      | 60 × 60 × 1000                    |
| d    | Day         | 86,400,000     | 24 × 60 × 60 × 1000               |
| w    | Week        | 604,800,000    | 7 × 24 × 60 × 60 × 1000           |
| mo   | Month       | 2,629,800,000  | 365.25 ÷ 12 × 24 × 60 × 60 × 1000 |
| y    | Year        | 31,557,600,000 | 365.25 × 24 × 60 × 60 × 1000      |

**Note:** Months and years use average values (365.25 days/year, 30.4375 days/month) for consistency.

## Usage Examples

### Basic Parsing

```typescript
import ms from '@jsxx/ms';

console.log(ms('1s')); // 1000
console.log(ms('5m')); // 300000
console.log(ms('2h')); // 7200000
console.log(ms('3d')); // 259200000
console.log(ms('1w')); // 604800000
console.log(ms('1y')); // 31557600000
```

### Basic Formatting

```typescript
import ms from '@jsxx/ms';

console.log(ms(1000)); // "1s"
console.log(ms(60000)); // "1m"
console.log(ms(3600000)); // "1h"
console.log(ms(86400000)); // "1d"
```

### Long Format

```typescript
import ms from '@jsxx/ms';

console.log(ms(1000, {long: true})); // "1 second"
console.log(ms(2000, {long: true})); // "2 seconds"
console.log(ms(60000, {long: true})); // "1 minute"
console.log(ms(120000, {long: true})); // "2 minutes"
console.log(ms(3600000, {long: true})); // "1 hour"
console.log(ms(7200000, {long: true})); // "2 hours"
```

### Timeout Configuration

```typescript
import ms from '@jsxx/ms';

function setTimeout(callback: () => void, delay: string) {
  const ms = parse(delay);
  return globalThis.setTimeout(callback, ms);
}

setTimeout(() => console.log('Done!'), '5s');
setTimeout(() => console.log('Later!'), '2m');
```

### API Rate Limiting

```typescript
import ms from '@jsxx/ms';

interface RateLimitConfig {
  window: string;
  maxRequests: number;
}

const config: RateLimitConfig = {
  window: '1m',
  maxRequests: 100,
};

const windowMs = ms(config.window); // 60000
```

### Cache TTL

```typescript
import ms from '@jsxx/ms';

class Cache {
  constructor(private ttl: string) {}

  getTTLMs(): number {
    return ms(this.ttl);
  }
}

const cache = new Cache('5m');
console.log(cache.getTTLMs()); // 300000
```

### Duration Display

```typescript
import ms from '@jsxx/ms';

function formatDuration(milliseconds: number, verbose = false): string {
  return ms(milliseconds, {long: verbose});
}

const duration = 125000;
console.log(formatDuration(duration)); // "2m"
console.log(formatDuration(duration, true)); // "2 minutes"
```

### Uptime Display

```typescript
import ms from '@jsxx/ms';

function showUptime(startTime: number): string {
  const uptime = Date.now() - startTime;
  return `Uptime: ${ms(uptime, {long: true})}`;
}

const serverStart = Date.now() - 3600000;
console.log(showUptime(serverStart)); // "Uptime: 1 hour"
```

### Countdown Timer

```typescript
import ms from '@jsxx/ms';

function countdown(duration: string) {
  let remaining = ms(duration);

  const interval = setInterval(() => {
    console.log(`Time remaining: ${ms(remaining)}`);
    remaining -= 1000;

    if (remaining <= 0) {
      clearInterval(interval);
      console.log('Done!');
    }
  }, 1000);
}

countdown('10s');
```

### Configuration Parsing

```typescript
import ms from '@jsxx/ms';

interface ServerConfig {
  requestTimeout: string;
  sessionExpiry: string;
  cacheExpiry: string;
}

const config: ServerConfig = {
  requestTimeout: '30s',
  sessionExpiry: '1d',
  cacheExpiry: '1h',
};

const timeouts = {
  request: ms(config.requestTimeout), // 30000
  session: ms(config.sessionExpiry), // 86400000
  cache: ms(config.cacheExpiry), // 3600000
};
```

### Retry Logic

```typescript
import ms from '@jsxx/ms';

async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  maxRetries = 3,
  baseDelay = '1s',
): Promise<T> {
  let lastError: Error;

  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error as Error;
      const delay = ms(baseDelay) * Math.pow(2, i);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }

  throw lastError!;
}
```

### Performance Monitoring

```typescript
import ms from '@jsxx/ms';

function measurePerformance<T>(fn: () => T, label: string): T {
  const start = performance.now();
  const result = fn();
  const duration = performance.now() - start;

  console.log(`${label}: ${ms(duration)}`);
  return result;
}

measurePerformance(() => {
  // Some operation
}, 'Operation completed in');
```

## Edge Cases & Behavior

### Zero and Special Values

```typescript
ms(0); // "0ms"
ms('0'); // 0
ms('0ms'); // 0
```

### Negative Numbers

```typescript
ms(-1000); // "-1s"
ms(-60000); // "-1m"
ms('-1s'); // -1000
ms('-5m'); // -300000
```

### Rounding Behavior

Format rounds to the nearest unit:

```typescript
ms(1500); // "2s" (1.5s rounds to 2s)
ms(1400); // "1s" (1.4s rounds to 1s)
ms(90000); // "2m" (1.5m rounds to 2m)
ms(45000); // "45s" (doesn't round up to 1m)
```

### Invalid Input

```typescript
// Parsing returns NaN
parse('invalid'); // NaN
parse('1xyz'); // NaN
parse('abc123'); // NaN

// Parsing throws for bad input
parse(''); // Error: must be string with length 1-99
parse(123 as any); // Error: must be string with length 1-99
parse('x'.repeat(101)); // Error: must be string with length 1-99

// Formatting throws for bad input
format('1000' as any); // Error: must be of type number
format(Infinity); // Error: must be of type number
format(NaN); // Error: must be of type number
```

### Case Insensitivity

```typescript
ms('1s'); // 1000
ms('1S'); // 1000
ms('1Second'); // 1000
ms('1SECOND'); // 1000
```

### Whitespace Handling

```typescript
ms('1 s'); // 1000
ms('5  m'); // 300000
ms('2   h'); // 7200000
```

### Decimal Precision

```typescript
ms('1.5s'); // 1500
ms('2.5m'); // 150000
ms('0.5h'); // 1800000
ms('0.001s'); // 1
ms('0.1s'); // 100
```

## TypeScript Usage

### Type Safety

```typescript
import {ms, StringValue} from '@jsxx/ms';

// Valid at compile-time
const duration: StringValue = '1s';
ms(duration); // ✓

// Type inference
const milliseconds = ms('1s'); // type: number
const formatted = ms(1000); // type: string
```

### Function Overloads

TypeScript correctly infers return types:

```typescript
const num = ms('1s'); // type: number
const str = ms(1000); // type: string
const longStr = ms(1000, {long: true}); // type: string
```

### Generic Usage

```typescript
function convertTime<T extends number | StringValue>(
  value: T,
): T extends number ? string : number {
  return ms(value as any);
}

convertTime(1000); // Returns string
convertTime('1s'); // Returns number
```

## Performance

- **Lightweight**: ~1KB minified + gzipped
- **Fast parsing**: O(1) Map-based unit lookup with precompiled regex
- **Fast formatting**: O(1) direct if-else chain (no iteration)
- **Optimized**: Precompiled regex and efficient data structures

## Browser Support

Works in all modern browsers and Node.js environments that support:

- ES2015+ features
- `Map` and `Set`
- `Number.isFinite()`
- `Math.abs()`, `Math.round()`
- Regular expressions

## Why @jsxx/ms?

- **Lightweight** - ~1KB minified + gzipped
- **Type-safe** - Template literal types for compile-time safety
- **Fast** - Optimized with Map-based lookups and precompiled regex
- **Modern** - Written with modern JavaScript, ESM-first
- **Well-tested** - 43+ tests covering all features and edge cases
- **Tree-shakeable** - Import only what you need
- **Part of @jsxx ecosystem** - Consistent API across @jsxx packages

## Contributing

Contributions are welcome! Please ensure:

1. All tests pass: `pnpm test`
2. Code follows existing style: `pnpm lint`
3. Types are correct: `pnpm check-types`
4. Add tests for new features
5. Update documentation

## License

MIT
