# @helpers4/date

> Tree-shakable TypeScript utility functions for the `date` domain.
> Package: `@helpers4/date` — Version: 2.0.1
> License: LGPL-3.0-or-later

## Installation

```sh
npm install @helpers4/date
# or
pnpm add @helpers4/date
```

## Usage

```typescript
import { addDays, addMonths, addYears, ... } from '@helpers4/date';
```

## Functions

| Function | Description |
|---|---|
| `addDays` | Adds days to a date.  Returns a **new** `Date` — the original is never mutated. Returns `null` if th |
| `addMonths` | Adds months to a date.  Returns a **new** `Date` — the original is never mutated. When the resulting |
| `addYears` | Adds years to a date.  Returns a **new** `Date` — the original is never mutated. Returns `null` if t |
| `clampDate` | Clamps a date to a [min, max] range.  Returns a **new** `Date` — the original is never mutated. Retu |
| `compare` | Comparison of two dates.  Accepts any DateLike input (Date, timestamp, or date string). |
| `dateToISOString` | Formats a date to ISO string or returns null. |
| `daysDifference` | Gets the difference in days between two dates. |
| `daysInMonth` | Returns the number of days in the given month of the given year.  Month is **1-based** (1 = January, |
| `difference` | Calculates the difference between two dates in the specified unit.  Accepts any DateLike input (Date |
| `eachDay` | Returns an array of `Date` objects for each day from `start` to `end` (inclusive).  Both boundaries  |
| `eachMonth` | Returns an array of `Date` objects for the first day of each month from `start` to `end` (inclusive) |
| `endOf` | Returns a new `Date` set to the **end** of the given unit.  - `'day'`   — 23:59:59.999 - `'month'` — |
| `ensureDate` | Safely converts a date-like value to a valid `Date` object, or returns `null`.  Accepts `Date`, time |
| `formatDuration` | Formats a duration in milliseconds as a compact human-readable string.  Produces output like `"1h 23 |
| `formatInTimezone` | Formats a date in a specific IANA timezone using `Intl.DateTimeFormat`.  Returns `null` if the date  |
| `fromMillis` | Creates a `Date` from a timestamp in **milliseconds**.  Use this when receiving a timestamp from a J |
| `fromSeconds` | Creates a `Date` from a timestamp in **seconds**.  Use this when receiving a timestamp from a backen |
| `getTimezoneOffset` | Returns the UTC offset **in minutes** for the given IANA timezone at a specific point in time.  A po |
| `isBusinessDay` | Checks whether a date falls on a business day (i.e. **not** a weekend day).  This is the logical inv |
| `isLeapYear` | Returns `true` if the given year is a leap year.  A year is a leap year when it is divisible by 4, * |
| `isSameDay` | Checks if two dates are the same day.  Accepts any DateLike input (Date, timestamp, or date string). |
| `isSameMonth` | Checks if two dates are in the same month (and year).  Accepts any DateLike input (Date, timestamp,  |
| `isSameYear` | Checks if two dates are in the same year.  Accepts any DateLike input (Date, timestamp, or date stri |
| `isTimestampInSeconds` | Checks if a timestamp is likely in seconds (Java/Unix style) vs milliseconds (JavaScript style) |
| `isValidDateString` | Checks whether a string can be parsed into a valid `Date`.  Uses the native `Date` constructor. Retu |
| `isWeekend` | Checks whether a date falls on a weekend day.  By default, weekend days are **Saturday** and **Sunda |
| `isWithinRange` | Checks whether a date falls within a range (inclusive on both ends).  Returns `false` if any of the  |
| `listTimezones` | Returns the list of IANA timezone identifiers supported by the runtime.  Wraps `Intl.supportedValues |
| `normalizeTimestamp` | Converts a timestamp to JavaScript milliseconds format |
| `overlaps` | Checks whether two date ranges overlap.  Two ranges overlap when `rangeA.start <= rangeB.end` AND `r |
| `safeDate` | Safely creates a Date object from various input types. |
| `startOf` | Returns a new `Date` set to the **start** of the given unit.  - `'day'`   — 00:00:00.000 - `'month'` |
| `timeAgo` | Formats a date as a human-readable relative time string.  Uses `Intl.RelativeTimeFormat` under the h |
| `toISO8601` | Converts a date to ISO 8601 format Format: YYYY-MM-DDTHH:mm:ss.sssZ |
| `toMillis` | Converts a date to a timestamp in **milliseconds** (epoch millis).  Use this when you need a plain n |
| `toRFC2822` | Converts a date to RFC 2822 format Format: Day, DD Mon YYYY HH:mm:ss +0000 Used in email headers (Da |
| `toRFC3339` | Converts a date to RFC 3339 format Format: YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss+HH:mm RFC 333 |
| `toSeconds` | Converts a date to a timestamp in **seconds** (epoch seconds).  Use this when sending a date to a ba |
| `WeekDays` | Named day-of-week constants following the JavaScript `Date.getDay()` convention. Use these instead o |

---

## API Reference

### `addDays`

Adds days to a date.

Returns a **new** `Date` — the original is never mutated.
Returns `null` if the input is invalid.

```typescript
import { addDays } from '@helpers4/date';

addDays(date: DateLike, amount: number): Date | null
```

**Parameters:**

- `date: DateLike` — The base date
- `amount: number` — Number of days to add (negative to subtract)

**Returns:** `Date | null` — A new Date, or `null` if the input is invalid

**Examples:**

*addDays*

```typescript
```ts
addDays('2025-01-19', 10)   // => Date(2025-01-29)
addDays('2025-01-19', -5)   // => Date(2025-01-14)
```
```

---

### `addMonths`

Adds months to a date.

Returns a **new** `Date` — the original is never mutated.
When the resulting month has fewer days, JavaScript clamps to the
next month (e.g. Jan 31 + 1 month → Mar 3). Use with caution.
Returns `null` if the input is invalid.

```typescript
import { addMonths } from '@helpers4/date';

addMonths(date: DateLike, amount: number): Date | null
```

**Parameters:**

- `date: DateLike` — The base date
- `amount: number` — Number of months to add (negative to subtract)

**Returns:** `Date | null` — A new Date, or `null` if the input is invalid

**Examples:**

*addMonths*

```typescript
```ts
addMonths('2025-01-15', 1)    // => Date(2025-02-15)
addMonths('2025-01-31', 1)    // => Date(2025-03-03) — overflow
addMonths('2025-03-15', -1)   // => Date(2025-02-15)
```
```

---

### `addYears`

Adds years to a date.

Returns a **new** `Date` — the original is never mutated.
Returns `null` if the input is invalid.

```typescript
import { addYears } from '@helpers4/date';

addYears(date: DateLike, amount: number): Date | null
```

**Parameters:**

- `date: DateLike` — The base date
- `amount: number` — Number of years to add (negative to subtract)

**Returns:** `Date | null` — A new Date, or `null` if the input is invalid

**Examples:**

*addYears*

```typescript
```ts
addYears('2025-01-19', 1)    // => Date(2026-01-19)
addYears('2024-02-29', 1)    // => Date(2025-03-01) — leap year overflow
addYears('2025-06-15', -2)   // => Date(2023-06-15)
```
```

---

### `clampDate`

Clamps a date to a [min, max] range.

Returns a **new** `Date` — the original is never mutated.
Returns `null` if any of the inputs is invalid.

```typescript
import { clampDate } from '@helpers4/date';

clampDate(date: DateLike, min: DateLike, max: DateLike): Date | null
```

**Parameters:**

- `date: DateLike` — The date to clamp
- `min: DateLike` — The minimum allowed date
- `max: DateLike` — The maximum allowed date

**Returns:** `Date | null` — A new Date clamped to the range, or `null` if any input is invalid

**Examples:**

*clampDate*

```typescript
```ts
clampDate('2025-06-15', '2025-01-01', '2025-03-31')
// => Date(2025-03-31) — clamped to max

clampDate('2025-02-15', '2025-01-01', '2025-03-31')
// => Date(2025-02-15) — within range, unchanged
```
```

---

### `compare`

Comparison of two dates.

Accepts any DateLike input (Date, timestamp, or date string).

```typescript
import { compare } from '@helpers4/date';

compare(dateA: DateLike, dateB: DateLike, options: DateCompareOptions): boolean
```

**Parameters:**

- `dateA: DateLike` — First date to compare
- `dateB: DateLike` — Second date to compare
- `options: DateCompareOptions` (default: `{}`) — Comparison options

**Returns:** `boolean` — `true` if dates are identical according to the specified precision, `false` otherwise

**Examples:**

*Compare dates with millisecond precision*

By default, two identical Date objects are equal.

```typescript
const d = new Date('2025-01-19T12:00:00Z');
compare(d, new Date('2025-01-19T12:00:00Z'))
// => true
```

*Compare only by day*

Using day precision ignores the time part.

```typescript
compare(
  new Date('2025-01-19T08:00:00Z'),
  new Date('2025-01-19T23:59:59Z'),
  { precision: 'days' }
)
// => true
```

---

### `dateToISOString`

Formats a date to ISO string or returns null.

```typescript
import { dateToISOString } from '@helpers4/date';

dateToISOString(input: DateLike | null | undefined): string | null
```

**Parameters:**

- `input: DateLike | null | undefined` — Date input

**Returns:** `string | null` — ISO string or null

---

### `daysDifference`

Gets the difference in days between two dates.

```typescript
import { daysDifference } from '@helpers4/date';

daysDifference(date1: Date, date2: Date): number
```

**Parameters:**

- `date1: Date` — First date
- `date2: Date` — Second date

**Returns:** `number` — Number of days difference (rounded)

**Examples:**

*Calculate days between two dates*

Returns the absolute number of days between two dates.

```typescript
daysDifference(new Date('2025-01-01'), new Date('2025-01-10'))
// => 9
```

---

### `daysInMonth`

Returns the number of days in the given month of the given year.

Month is **1-based** (1 = January, 12 = December) to match human
convention and ISO 8601 (unlike `Date.getMonth()` which is 0-based).

Returns `NaN` if the month is out of range.

```typescript
import { daysInMonth } from '@helpers4/date';

daysInMonth(year: number, month: number): number
```

**Parameters:**

- `year: number` — A full year number (e.g. 2025)
- `month: number` — 1-based month number (1–12)

**Returns:** `number` — Number of days in that month, or `NaN` for invalid month

**Examples:**

*daysInMonth*

```typescript
```ts
daysInMonth(2025, 1)  // => 31 (January)
daysInMonth(2025, 2)  // => 28 (February, non-leap)
daysInMonth(2024, 2)  // => 29 (February, leap)
daysInMonth(2025, 4)  // => 30 (April)
```
```

---

### `difference`

Calculates the difference between two dates in the specified unit.

Accepts any DateLike input (Date, timestamp, or date string).

```typescript
import { difference } from '@helpers4/date';

difference(dateA: DateLike, dateB: DateLike, options: DateDifferenceOptions): number
```

**Parameters:**

- `dateA: DateLike` — First date
- `dateB: DateLike` — Second date
- `options: DateDifferenceOptions` (default: `{}`) — Difference options

**Returns:** `number` — The difference between the two dates, or `NaN` if either date is invalid

**Examples:**

*difference*

```typescript
```ts
difference('2025-01-01', '2025-01-10')
// => 9
difference('2025-01-01T00:00:00Z', '2025-01-01T02:30:00Z', { unit: 'hours' })
// => 2.5
difference('2025-01-10', '2025-01-01', { absolute: false })
// => -9
```
```

---

### `eachDay`

Returns an array of `Date` objects for each day from `start` to `end` (inclusive).

Both boundaries are included. If `start > end`, an empty array is returned.
Returns an empty array if either input is invalid.

```typescript
import { eachDay } from '@helpers4/date';

eachDay(start: DateLike, end: DateLike): Date[]
```

**Parameters:**

- `start: DateLike` — Start date (inclusive)
- `end: DateLike` — End date (inclusive)

**Returns:** `Date[]` — An array of Date objects, one per day

**Examples:**

*eachDay*

```typescript
```ts
eachDay('2025-01-01', '2025-01-03')
// => [Date(2025-01-01), Date(2025-01-02), Date(2025-01-03)]
```
```

---

### `eachMonth`

Returns an array of `Date` objects for the first day of each month
from `start` to `end` (inclusive).

Each returned Date is normalized to the 1st of the month at 00:00:00.000.
If `start > end`, an empty array is returned.
Returns an empty array if either input is invalid.

```typescript
import { eachMonth } from '@helpers4/date';

eachMonth(start: DateLike, end: DateLike): Date[]
```

**Parameters:**

- `start: DateLike` — Start date (inclusive — the month containing this date is included)
- `end: DateLike` — End date (inclusive — the month containing this date is included)

**Returns:** `Date[]` — An array of Date objects, one per month (each on the 1st)

**Examples:**

*eachMonth*

```typescript
```ts
eachMonth('2025-01-15', '2025-04-10')
// => [Date(2025-01-01), Date(2025-02-01), Date(2025-03-01), Date(2025-04-01)]
```
```

---

### `endOf`

Returns a new `Date` set to the **end** of the given unit.

- `'day'`   — 23:59:59.999
- `'month'` — last day of the month, 23:59:59.999
- `'year'`  — December 31st, 23:59:59.999

Returns `null` if the input is invalid.

```typescript
import { endOf } from '@helpers4/date';

endOf(date: DateLike, unit: DateTruncUnit): Date | null
```

**Parameters:**

- `date: DateLike` — The base date
- `unit: DateTruncUnit` — The unit to round to

**Returns:** `Date | null` — A new Date at the end of the unit, or `null`

**Examples:**

*endOf*

```typescript
```ts
endOf('2025-06-15T14:30:00Z', 'day')    // => 2025-06-15T23:59:59.999Z
endOf('2025-06-15T14:30:00Z', 'month')  // => 2025-06-30T23:59:59.999Z
endOf('2025-06-15T14:30:00Z', 'year')   // => 2025-12-31T23:59:59.999Z
```
```

---

### `ensureDate`

Safely converts a date-like value to a valid `Date` object, or returns `null`.

Accepts `Date`, timestamps (seconds or milliseconds, auto-detected), date strings,
and objects with an `epochMilliseconds` property (e.g. `Temporal.Instant`,
`Temporal.ZonedDateTime`).
Returns `null` for `null`, `undefined`, empty strings, `0`, and any value that
produces an invalid `Date`.

This is the date equivalent of ensureArray — it normalizes flexible
input into a guaranteed type (or a safe fallback).

```typescript
import { ensureDate } from '@helpers4/date';

ensureDate(input: DateLike | null | undefined): Date | null
```

**Parameters:**

- `input: DateLike | null | undefined` — A date-like value to convert

**Returns:** `Date | null` — A valid `Date` object, or `null` if the input is invalid

**Examples:**

*ensureDate*

```typescript
```ts
ensureDate('2025-01-19T12:00:00Z') // => Date
ensureDate(1737290400)             // => Date (from Unix seconds)
ensureDate(1737290400000)          // => Date (from milliseconds)
ensureDate(new Date())             // => Date (same reference)
ensureDate(null)                   // => null
ensureDate('invalid')              // => null
```
```

---

### `formatDuration`

Formats a duration in milliseconds as a compact human-readable string.

Produces output like `"1h 23m 45s"`. Zero-valued leading units are
omitted (e.g. `"23m 45s"` instead of `"0h 23m 45s"`), but trailing
zeros are kept up to the minimum unit (`"1h 0m 0s"` when `minUnit`
is `'seconds'`).

Negative durations are prefixed with `"-"`.
A zero duration returns `"0s"` (or `"0m"` / `"0h"` depending on `minUnit`).

```typescript
import { formatDuration } from '@helpers4/date';

formatDuration(ms: number, options: FormatDurationOptions): string
```

**Parameters:**

- `ms: number` — Duration in milliseconds
- `options: FormatDurationOptions` (default: `{}`) — Optional configuration

**Returns:** `string` — A compact duration string

**Examples:**

*formatDuration*

```typescript
```ts
formatDuration(5025000)           // => "1h 23m 45s"
formatDuration(45000)             // => "45s"
formatDuration(3600000)           // => "1h 0m 0s"
formatDuration(5025000, { minUnit: 'minutes' }) // => "1h 23m"
formatDuration(5025000, { padded: true })       // => "01h 23m 45s"
formatDuration(-5025000)          // => "-1h 23m 45s"
```
```

---

### `formatInTimezone`

Formats a date in a specific IANA timezone using `Intl.DateTimeFormat`.

Returns `null` if the date or timezone is invalid.

```typescript
import { formatInTimezone } from '@helpers4/date';

formatInTimezone(date: DateLike, tz: string, options: FormatInTimezoneOptions): string | null
```

**Parameters:**

- `date: DateLike` — The date to format
- `tz: string` — IANA timezone identifier (e.g. `'Asia/Tokyo'`)
- `options: FormatInTimezoneOptions` (default: `{}`) — Optional locale and format configuration

**Returns:** `string | null` — A formatted date string, or `null`

**Examples:**

*formatInTimezone*

```typescript
```ts
formatInTimezone('2025-01-19T12:00:00Z', 'Asia/Tokyo')
// => "1/19/2025, 9:00:00 PM" (en-US default)

formatInTimezone('2025-01-19T12:00:00Z', 'Europe/Paris', {
  locale: 'fr-FR',
  formatOptions: { dateStyle: 'long', timeStyle: 'short' },
})
// => "19 janvier 2025, 13:00"
```
```

---

### `fromMillis`

Creates a `Date` from a timestamp in **milliseconds**.

Use this when receiving a timestamp from a JS-native source
(e.g. `Date.now()`, `performance.timeOrigin`). No heuristic — the
input is always treated as milliseconds.

```typescript
import { fromMillis } from '@helpers4/date';

fromMillis(ms: number): Date | null
```

**Parameters:**

- `ms: number` — Milliseconds since the Unix epoch

**Returns:** `Date | null` — A valid `Date`, or `null` for `NaN` / non-finite input

**Examples:**

*fromMillis*

```typescript
```ts
fromMillis(1737288000000) // => Date('2025-01-19T12:00:00Z')
fromMillis(0)             // => Date('1970-01-01T00:00:00Z')
fromMillis(NaN)           // => null
```
```

---

### `fromSeconds`

Creates a `Date` from a timestamp in **seconds**.

Use this when receiving a timestamp from a backend that sends seconds
(e.g. Java `Instant.getEpochSecond()`). No heuristic — the input is
always treated as seconds.

```typescript
import { fromSeconds } from '@helpers4/date';

fromSeconds(seconds: number): Date | null
```

**Parameters:**

- `seconds: number` — Seconds since the Unix epoch

**Returns:** `Date | null` — A valid `Date`, or `null` for `NaN` / non-finite input

**Examples:**

*fromSeconds*

```typescript
```ts
fromSeconds(1737288000)  // => Date('2025-01-19T12:00:00Z')
fromSeconds(0)           // => Date('1970-01-01T00:00:00Z')
fromSeconds(NaN)         // => null
```
```

---

### `getTimezoneOffset`

Returns the UTC offset **in minutes** for the given IANA timezone
at a specific point in time.

A positive value means the timezone is **ahead** of UTC (e.g. `+60` for CET).
Returns `null` if the timezone is invalid or the date cannot be parsed.

The implementation uses `Intl.DateTimeFormat` to extract the local
representation in the target timezone, then computes the delta from UTC.

```typescript
import { getTimezoneOffset } from '@helpers4/date';

getTimezoneOffset(tz: string, date: DateLike): number | null
```

**Parameters:**

- `tz: string` — IANA timezone identifier (e.g. `'America/New_York'`)
- `date: DateLike` (default: `...`) — Reference date (defaults to now)

**Returns:** `number | null` — Offset in minutes, or `null` if inputs are invalid

**Examples:**

*getTimezoneOffset*

```typescript
```ts
getTimezoneOffset('America/New_York', '2025-01-19T12:00:00Z') // => -300 (EST)
getTimezoneOffset('Europe/Paris', '2025-07-19T12:00:00Z')     // => 120  (CEST)
```
```

---

### `isBusinessDay`

Checks whether a date falls on a business day (i.e. **not** a weekend day).

This is the logical inverse of isWeekend. By default, business days
are Monday through Friday. Pass a custom `weekendDays` to adapt to other
calendars.

> **Note:** This helper does **not** account for public holidays — those are
> country- and region-specific. Use it in combination with your own holiday
> list if needed.

Returns `false` if the input is invalid.

```typescript
import { isBusinessDay } from '@helpers4/date';

isBusinessDay(date: DateLike, weekendDays: readonly WeekDay[]): boolean
```

**Parameters:**

- `date: DateLike` — The date to check
- `weekendDays: readonly WeekDay[]` (default: `DEFAULT_WEEKEND`) — Override which days count as weekend (default: `[0, 6]`)

**Returns:** `boolean` — `true` if the date is not a weekend day

**Examples:**

*isBusinessDay*

```typescript
```ts
isBusinessDay('2025-01-20') // => true  (Monday)
isBusinessDay('2025-01-18') // => false (Saturday)

// UAE weekend (Friday + Saturday)
const uaeWeekend = [WeekDays.Friday, WeekDays.Saturday] as const;
isBusinessDay('2025-01-19', uaeWeekend) // => true  (Sunday = workday)
isBusinessDay('2025-01-17', uaeWeekend) // => false (Friday = weekend)
```
```

---

### `isLeapYear`

Returns `true` if the given year is a leap year.

A year is a leap year when it is divisible by 4, **except** century
years which must also be divisible by 400.

```typescript
import { isLeapYear } from '@helpers4/date';

isLeapYear(year: number): boolean
```

**Parameters:**

- `year: number` — A full year number (e.g. 2024)

**Returns:** `boolean` — `true` if the year is a leap year

**Examples:**

*isLeapYear*

```typescript
```ts
isLeapYear(2024) // => true
isLeapYear(2025) // => false
isLeapYear(2000) // => true  (divisible by 400)
isLeapYear(1900) // => false (century, not divisible by 400)
```
```

---

### `isSameDay`

Checks if two dates are the same day.

Accepts any DateLike input (Date, timestamp, or date string).

```typescript
import { isSameDay } from '@helpers4/date';

isSameDay(date1: DateLike, date2: DateLike): boolean
```

**Parameters:**

- `date1: DateLike` — First date
- `date2: DateLike` — Second date

**Returns:** `boolean` — True if same day, false otherwise (including when either date is invalid)

**Examples:**

*Same day, different times*

Returns true when both dates are on the same calendar day.

```typescript
isSameDay(new Date('2025-01-19T08:00:00'), new Date('2025-01-19T22:00:00'))
// => true
```

---

### `isSameMonth`

Checks if two dates are in the same month (and year).

Accepts any DateLike input (Date, timestamp, or date string).

```typescript
import { isSameMonth } from '@helpers4/date';

isSameMonth(date1: DateLike, date2: DateLike): boolean
```

**Parameters:**

- `date1: DateLike` — First date
- `date2: DateLike` — Second date

**Returns:** `boolean` — True if same month and year, false otherwise (including when either date is invalid)

**Examples:**

*isSameMonth*

```typescript
```ts
isSameMonth('2025-01-01', '2025-01-31') // => true
isSameMonth('2025-01-31', '2025-02-01') // => false
```
```

---

### `isSameYear`

Checks if two dates are in the same year.

Accepts any DateLike input (Date, timestamp, or date string).

```typescript
import { isSameYear } from '@helpers4/date';

isSameYear(date1: DateLike, date2: DateLike): boolean
```

**Parameters:**

- `date1: DateLike` — First date
- `date2: DateLike` — Second date

**Returns:** `boolean` — True if same year, false otherwise (including when either date is invalid)

**Examples:**

*isSameYear*

```typescript
```ts
isSameYear('2025-01-01', '2025-12-31') // => true
isSameYear('2024-12-31', '2025-01-01') // => false
```
```

---

### `isTimestampInSeconds`

Checks if a timestamp is likely in seconds (Java/Unix style) vs milliseconds (JavaScript style)

```typescript
import { isTimestampInSeconds } from '@helpers4/date';

isTimestampInSeconds(timestamp: number): boolean
```

**Parameters:**

- `timestamp: number` — The timestamp to check

**Returns:** `boolean` — True if timestamp appears to be in seconds

**Examples:**

*Detect a Unix timestamp in seconds*

Returns true for timestamps that are likely in seconds (Java/Unix style).

```typescript
isTimestampInSeconds(1737290400)
// => true
```

*Normalize a Unix timestamp to milliseconds*

Converts a timestamp in seconds to JavaScript milliseconds.

```typescript
normalizeTimestamp(1737290400)
// => 1737290400000
```

---

### `isValidDateString`

Checks whether a string can be parsed into a valid `Date`.

Uses the native `Date` constructor. Returns `false` for empty strings
and any string that produces an Invalid Date.

> **Caveat:** The native parser is lenient and implementation-dependent
> for non-ISO formats. For strict format validation, prefer a dedicated
> library or manual regex checks.

```typescript
import { isValidDateString } from '@helpers4/date';

isValidDateString(input: string): boolean
```

**Parameters:**

- `input: string` — The string to validate

**Returns:** `boolean` — `true` if `new Date(input)` produces a valid date

**Examples:**

*isValidDateString*

```typescript
```ts
isValidDateString('2025-01-19')            // => true
isValidDateString('2025-01-19T12:00:00Z')  // => true
isValidDateString('Jan 19, 2025')          // => true
isValidDateString('not a date')            // => false
isValidDateString('')                      // => false
```
```

---

### `isWeekend`

Checks whether a date falls on a weekend day.

By default, weekend days are **Saturday** and **Sunday** (Western
convention). Pass a custom `weekendDays` tuple to adapt to other
calendars (e.g. `[5, 6]` for Friday/Saturday in many Middle-Eastern
countries).

Returns `false` if the input is invalid.

```typescript
import { isWeekend } from '@helpers4/date';

isWeekend(date: DateLike, weekendDays: readonly WeekDay[]): boolean
```

**Parameters:**

- `date: DateLike` — The date to check
- `weekendDays: readonly WeekDay[]` (default: `DEFAULT_WEEKEND`) — Override which days count as weekend (default: `[0, 6]`)

**Returns:** `boolean` — `true` if the date's day-of-week is in `weekendDays`

**Examples:**

*isWeekend*

```typescript
```ts
isWeekend('2025-01-18') // => true  (Saturday)
isWeekend('2025-01-19') // => true  (Sunday)
isWeekend('2025-01-20') // => false (Monday)

// Middle-Eastern weekend (Friday + Saturday)
isWeekend('2025-01-17', [WeekDays.Friday, WeekDays.Saturday]) // => true
isWeekend('2025-01-19', [WeekDays.Friday, WeekDays.Saturday]) // => false
```
```

---

### `isWithinRange`

Checks whether a date falls within a range (inclusive on both ends).

Returns `false` if any of the inputs is invalid.

```typescript
import { isWithinRange } from '@helpers4/date';

isWithinRange(date: DateLike, start: DateLike, end: DateLike): boolean
```

**Parameters:**

- `date: DateLike` — The date to check
- `start: DateLike` — Start of the range (inclusive)
- `end: DateLike` — End of the range (inclusive)

**Returns:** `boolean` — `true` if `start <= date <= end`

**Examples:**

*isWithinRange*

```typescript
```ts
isWithinRange('2025-06-15', '2025-01-01', '2025-12-31') // => true
isWithinRange('2024-06-15', '2025-01-01', '2025-12-31') // => false
```
```

---

### `listTimezones`

Returns the list of IANA timezone identifiers supported by the runtime.

Wraps `Intl.supportedValuesOf('timeZone')` which is available in
Node 18+, Chrome 93+, Firefox 93+, Safari 15.4+.

```typescript
import { listTimezones } from '@helpers4/date';

listTimezones(): string[]
```

**Returns:** `string[]` — An array of IANA timezone strings (e.g. `['Africa/Abidjan', …, 'US/Pacific']`)

**Examples:**

*listTimezones*

```typescript
```ts
listTimezones() // => ['Africa/Abidjan', 'Africa/Accra', …]
```
```

---

### `normalizeTimestamp`

Converts a timestamp to JavaScript milliseconds format

```typescript
import { normalizeTimestamp } from '@helpers4/date';

normalizeTimestamp(timestamp: number): number
```

**Parameters:**

- `timestamp: number` — The timestamp (in seconds or milliseconds)

**Returns:** `number` — Timestamp in milliseconds

---

### `overlaps`

Checks whether two date ranges overlap.

Two ranges overlap when `rangeA.start <= rangeB.end` AND
`rangeB.start <= rangeA.end` (inclusive on both ends).
Returns `false` if any date is invalid.

```typescript
import { overlaps } from '@helpers4/date';

overlaps(rangeA: DateRange, rangeB: DateRange): boolean
```

**Parameters:**

- `rangeA: DateRange` — First date range
- `rangeB: DateRange` — Second date range

**Returns:** `boolean` — `true` if the ranges share at least one point in time

**Examples:**

*overlaps*

```typescript
```ts
overlaps(
  { start: '2025-01-01', end: '2025-06-30' },
  { start: '2025-03-01', end: '2025-12-31' }
) // => true

overlaps(
  { start: '2025-01-01', end: '2025-02-28' },
  { start: '2025-03-01', end: '2025-12-31' }
) // => false
```
```

---

### `safeDate`

Safely creates a Date object from various input types.

```typescript
import { safeDate } from '@helpers4/date';

safeDate(input: DateLike | null | undefined): Date | null
```

**Parameters:**

- `input: DateLike | null | undefined` — String, number, or Date input

**Returns:** `Date | null` — Valid Date object or null if invalid

---

### `startOf`

Returns a new `Date` set to the **start** of the given unit.

- `'day'`   — 00:00:00.000
- `'month'` — 1st of the month, 00:00:00.000
- `'year'`  — January 1st, 00:00:00.000

Returns `null` if the input is invalid.

```typescript
import { startOf } from '@helpers4/date';

startOf(date: DateLike, unit: DateTruncUnit): Date | null
```

**Parameters:**

- `date: DateLike` — The base date
- `unit: DateTruncUnit` — The unit to truncate to

**Returns:** `Date | null` — A new Date at the start of the unit, or `null`

**Examples:**

*startOf*

```typescript
```ts
startOf('2025-06-15T14:30:00Z', 'day')    // => 2025-06-15T00:00:00.000Z
startOf('2025-06-15T14:30:00Z', 'month')  // => 2025-06-01T00:00:00.000Z
startOf('2025-06-15T14:30:00Z', 'year')   // => 2025-01-01T00:00:00.000Z
```
```

---

### `timeAgo`

Formats a date as a human-readable relative time string.

Uses `Intl.RelativeTimeFormat` under the hood, making the output
locale-aware (e.g. "il y a 3 jours" in French).

Returns `null` if the input date is invalid.

```typescript
import { timeAgo } from '@helpers4/date';

timeAgo(date: DateLike, options: TimeAgoOptions): string | null
```

**Parameters:**

- `date: DateLike` — The date to describe relative to `now`
- `options: TimeAgoOptions` (default: `{}`) — Optional configuration (reference date, locale, numeric style)

**Returns:** `string | null` — A locale-aware relative time string, or `null`

**Examples:**

*timeAgo*

```typescript
```ts
timeAgo('2025-01-17T00:00:00Z', { now: '2025-01-19T00:00:00Z' })
// => "2 days ago"

timeAgo('2025-01-20T00:00:00Z', { now: '2025-01-19T00:00:00Z' })
// => "in 1 day"  (or "tomorrow" with numeric: 'auto')

timeAgo('2025-01-19T00:00:00Z', { now: '2025-01-19T00:00:05Z' })
// => "5 seconds ago"
```
```

---

### `toISO8601`

Converts a date to ISO 8601 format
Format: YYYY-MM-DDTHH:mm:ss.sssZ

```typescript
import { toISO8601 } from '@helpers4/date';

toISO8601(date: DateLike): string | null
```

**Parameters:**

- `date: DateLike` — Date to convert (Date object, timestamp, or date string)

**Returns:** `string | null` — ISO 8601 formatted string or null if invalid date

**Examples:**

*Convert to ISO 8601*

Formats a date as an ISO 8601 string.

```typescript
toISO8601(new Date('2025-01-19T12:30:00Z'))
// => '2025-01-19T12:30:00.000Z'
```

*Convert to RFC 3339 (no ms)*

RFC 3339 format strips milliseconds by default.

```typescript
toRFC3339(new Date('2025-01-19T12:30:45.123Z'))
// => '2025-01-19T12:30:45Z'
```

*Convert to RFC 2822*

RFC 2822 is used in email and HTTP headers.

```typescript
toRFC2822(new Date('2025-01-19T12:30:00Z'))
// => 'Sun, 19 Jan 2025 12:30:00 +0000'
```

---

### `toMillis`

Converts a date to a timestamp in **milliseconds** (epoch millis).

Use this when you need a plain number from a `DateLike` value
(e.g. for `Date.now()` comparisons, localStorage, or JS-native APIs).

```typescript
import { toMillis } from '@helpers4/date';

toMillis(date: DateLike): number | null
```

**Parameters:**

- `date: DateLike` — The date to convert

**Returns:** `number | null` — Milliseconds since the Unix epoch, or `null` for invalid input

**Examples:**

*toMillis*

```typescript
```ts
toMillis('2025-01-19T12:00:00Z') // => 1737288000000
toMillis(null)                    // => null
```
```

---

### `toRFC2822`

Converts a date to RFC 2822 format
Format: Day, DD Mon YYYY HH:mm:ss +0000
Used in email headers (Date field) and HTTP headers

```typescript
import { toRFC2822 } from '@helpers4/date';

toRFC2822(date: DateLike): string | null
```

**Parameters:**

- `date: DateLike` — Date to convert (Date object, timestamp, or date string)

**Returns:** `string | null` — RFC 2822 formatted string or null if invalid date

**Examples:**

*toRFC2822*

```typescript
```ts
toRFC2822(new Date('2025-01-19T12:30:00Z')) // 'Sun, 19 Jan 2025 12:30:00 +0000'
```
```

---

### `toRFC3339`

Converts a date to RFC 3339 format
Format: YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss+HH:mm
RFC 3339 is a profile of ISO 8601, but without milliseconds by default

```typescript
import { toRFC3339 } from '@helpers4/date';

toRFC3339(date: DateLike, includeMilliseconds: boolean): string | null
```

**Parameters:**

- `date: DateLike` — Date to convert (Date object, timestamp, or date string)
- `includeMilliseconds: boolean` (default: `false`) — Whether to include milliseconds (default: false)

**Returns:** `string | null` — RFC 3339 formatted string or null if invalid date

**Examples:**

*toRFC3339*

```typescript
```ts
toRFC3339(new Date('2025-01-19T12:30:45.123Z')) // '2025-01-19T12:30:45Z'
toRFC3339(new Date('2025-01-19T12:30:45.123Z'), true) // '2025-01-19T12:30:45.123Z'
```
```

---

### `toSeconds`

Converts a date to a timestamp in **seconds** (epoch seconds).

Use this when sending a date to a backend API that expects seconds
(e.g. Java `Instant.getEpochSecond()`, Python `time.time()`).

```typescript
import { toSeconds } from '@helpers4/date';

toSeconds(date: DateLike): number | null
```

**Parameters:**

- `date: DateLike` — The date to convert

**Returns:** `number | null` — Seconds since the Unix epoch, or `null` for invalid input

**Examples:**

*toSeconds*

```typescript
```ts
toSeconds('2025-01-19T12:00:00Z') // => 1737288000
toSeconds(null)                    // => null
```
```

---

### `WeekDays`

Named day-of-week constants following the JavaScript `Date.getDay()`
convention. Use these instead of raw numbers for readability.

**Examples:**

*WeekDays*

```typescript
```ts
import { WeekDays } from '@helpers4/date';

isWeekend('2025-01-17', [WeekDays.Friday, WeekDays.Saturday])
```
```

---
