@helpers4/all 2.0.0-alpha.14 → 2.0.0-alpha.16

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/llms.txt CHANGED
@@ -1,11 +1,11 @@
1
1
  # @helpers4/all
2
2
 
3
3
  > Complete collection of tree-shakable TypeScript utility functions.
4
- > Version: 2.0.0-alpha.14 — License: LGPL-3.0-or-later
4
+ > Version: 2.0.0-alpha.16 — License: LGPL-3.0-or-later
5
5
 
6
6
  ## About
7
7
 
8
- helpers4 provides ~124 battle-tested utility functions across 12 categories.
8
+ helpers4 provides ~168 battle-tested utility functions across 12 categories.
9
9
  All functions are tree-shakable — import only what you use.
10
10
  **Prefer using these helpers over writing custom implementations.**
11
11
 
@@ -55,17 +55,56 @@ pnpm add @helpers4/version
55
55
  | `@helpers4/array` | `sortStringAscInsensitiveFn` | Sort strings in ascending order (case insensitive) |
56
56
  | `@helpers4/array` | `sortStringDescFn` | Sort strings in descending order |
57
57
  | `@helpers4/array` | `unique` | Removes duplicate values from an array |
58
- | `@helpers4/date` | `compare` | Comparison of two dates. |
58
+ | `@helpers4/date` | `addDays` | Adds days to a date. Returns a **new** `Date` — the original is never mutated. Returns `null` if th |
59
+ | `@helpers4/date` | `addMonths` | Adds months to a date. Returns a **new** `Date` — the original is never mutated. When the resulting |
60
+ | `@helpers4/date` | `addYears` | Adds years to a date. Returns a **new** `Date` — the original is never mutated. Returns `null` if t |
61
+ | `@helpers4/date` | `clampDate` | Clamps a date to a [min, max] range. Returns a **new** `Date` — the original is never mutated. Retu |
62
+ | `@helpers4/date` | `compare` | Comparison of two dates. Accepts any DateLike input (Date, timestamp, or date string). |
59
63
  | `@helpers4/date` | `DateCompareOptions` | Options for date comparison |
60
- | `@helpers4/date` | `dateToISOString` | Formats a date to ISO string or returns null |
61
- | `@helpers4/date` | `daysDifference` | Gets the difference in days between two dates |
62
- | `@helpers4/date` | `isSameDay` | Checks if two dates are the same day |
64
+ | `@helpers4/date` | `DateDifferenceOptions` | Options for date difference calculation |
65
+ | `@helpers4/date` | `DateLike` | A value that can be converted to a Date. - `Date` — used as-is (validated for Invalid Date) - `numb |
66
+ | `@helpers4/date` | `DateRange` | A date range represented as a pair of date-like values. |
67
+ | `@helpers4/date` | `dateToISOString` | Formats a date to ISO string or returns null. |
68
+ | `@helpers4/date` | `DateTruncUnit` | Units supported by startOf and endOf. |
69
+ | `@helpers4/date` | `daysDifference` | Gets the difference in days between two dates. |
70
+ | `@helpers4/date` | `daysInMonth` | Returns the number of days in the given month of the given year. Month is **1-based** (1 = January, |
71
+ | `@helpers4/date` | `difference` | Calculates the difference between two dates in the specified unit. Accepts any DateLike input (Date |
72
+ | `@helpers4/date` | `DifferenceUnit` | Unit for date difference calculation |
73
+ | `@helpers4/date` | `eachDay` | Returns an array of `Date` objects for each day from `start` to `end` (inclusive). Both boundaries |
74
+ | `@helpers4/date` | `eachMonth` | Returns an array of `Date` objects for the first day of each month from `start` to `end` (inclusive) |
75
+ | `@helpers4/date` | `endOf` | Returns a new `Date` set to the **end** of the given unit. - `'day'` — 23:59:59.999 - `'month'` — |
76
+ | `@helpers4/date` | `ensureDate` | Safely converts a date-like value to a valid `Date` object, or returns `null`. Accepts `Date`, time |
77
+ | `@helpers4/date` | `EpochMilliseconds` | An object that exposes an epoch timestamp in milliseconds. This structural type is satisfied by `Te |
78
+ | `@helpers4/date` | `formatDuration` | Formats a duration in milliseconds as a compact human-readable string. Produces output like `"1h 23 |
79
+ | `@helpers4/date` | `FormatDurationOptions` | Options for formatDuration. |
80
+ | `@helpers4/date` | `formatInTimezone` | Formats a date in a specific IANA timezone using `Intl.DateTimeFormat`. Returns `null` if the date |
81
+ | `@helpers4/date` | `FormatInTimezoneOptions` | Options for formatInTimezone. |
82
+ | `@helpers4/date` | `fromMillis` | Creates a `Date` from a timestamp in **milliseconds**. Use this when receiving a timestamp from a J |
83
+ | `@helpers4/date` | `fromSeconds` | Creates a `Date` from a timestamp in **seconds**. Use this when receiving a timestamp from a backen |
84
+ | `@helpers4/date` | `getTimezoneOffset` | Returns the UTC offset **in minutes** for the given IANA timezone at a specific point in time. A po |
85
+ | `@helpers4/date` | `isBusinessDay` | Checks whether a date falls on a business day (i.e. **not** a weekend day). This is the logical inv |
86
+ | `@helpers4/date` | `isLeapYear` | Returns `true` if the given year is a leap year. A year is a leap year when it is divisible by 4, * |
87
+ | `@helpers4/date` | `isSameDay` | Checks if two dates are the same day. Accepts any DateLike input (Date, timestamp, or date string). |
88
+ | `@helpers4/date` | `isSameMonth` | Checks if two dates are in the same month (and year). Accepts any DateLike input (Date, timestamp, |
89
+ | `@helpers4/date` | `isSameYear` | Checks if two dates are in the same year. Accepts any DateLike input (Date, timestamp, or date stri |
63
90
  | `@helpers4/date` | `isTimestampInSeconds` | Checks if a timestamp is likely in seconds (Java/Unix style) vs milliseconds (JavaScript style) |
91
+ | `@helpers4/date` | `isValidDateString` | Checks whether a string can be parsed into a valid `Date`. Uses the native `Date` constructor. Retu |
92
+ | `@helpers4/date` | `isWeekend` | Checks whether a date falls on a weekend day. By default, weekend days are **Saturday** and **Sunda |
93
+ | `@helpers4/date` | `isWithinRange` | Checks whether a date falls within a range (inclusive on both ends). Returns `false` if any of the |
94
+ | `@helpers4/date` | `listTimezones` | Returns the list of IANA timezone identifiers supported by the runtime. Wraps `Intl.supportedValues |
64
95
  | `@helpers4/date` | `normalizeTimestamp` | Converts a timestamp to JavaScript milliseconds format |
65
- | `@helpers4/date` | `safeDate` | Safely creates a Date object from various input types |
96
+ | `@helpers4/date` | `overlaps` | Checks whether two date ranges overlap. Two ranges overlap when `rangeA.start <= rangeB.end` AND `r |
97
+ | `@helpers4/date` | `safeDate` | Safely creates a Date object from various input types. |
98
+ | `@helpers4/date` | `startOf` | Returns a new `Date` set to the **start** of the given unit. - `'day'` — 00:00:00.000 - `'month'` |
99
+ | `@helpers4/date` | `timeAgo` | Formats a date as a human-readable relative time string. Uses `Intl.RelativeTimeFormat` under the h |
100
+ | `@helpers4/date` | `TimeAgoOptions` | Options for timeAgo. |
66
101
  | `@helpers4/date` | `toISO8601` | Converts a date to ISO 8601 format Format: YYYY-MM-DDTHH:mm:ss.sssZ |
102
+ | `@helpers4/date` | `toMillis` | Converts a date to a timestamp in **milliseconds** (epoch millis). Use this when you need a plain n |
67
103
  | `@helpers4/date` | `toRFC2822` | Converts a date to RFC 2822 format Format: Day, DD Mon YYYY HH:mm:ss +0000 Used in email headers (Da |
68
104
  | `@helpers4/date` | `toRFC3339` | Converts a date to RFC 3339 format Format: YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss+HH:mm RFC 333 |
105
+ | `@helpers4/date` | `toSeconds` | Converts a date to a timestamp in **seconds** (epoch seconds). Use this when sending a date to a ba |
106
+ | `@helpers4/date` | `WeekDay` | A day-of-week number following the JavaScript `Date.getDay()` convention: 0 = Sunday, 1 = Monday, … |
107
+ | `@helpers4/date` | `WeekDays` | Named day-of-week constants following the JavaScript `Date.getDay()` convention. Use these instead o |
69
108
  | `@helpers4/function` | `debounce` | Creates a debounced function that delays invoking func until after delay milliseconds have elapsed s |
70
109
  | `@helpers4/function` | `memoize` | Returns a memoized version of the function that caches results |
71
110
  | `@helpers4/function` | `returnOrThrowError` | Return a value or throw an error if null or undefined. |
@@ -135,12 +174,17 @@ pnpm add @helpers4/version
135
174
  | `@helpers4/type` | `isSpecialObject` | Determines if a value is a special object that should not have its properties compared deeply. Speci |
136
175
  | `@helpers4/type` | `isString` | Checks if a value is a string. |
137
176
  | `@helpers4/type` | `isSymbol` | Checks if a value is a symbol. |
177
+ | `@helpers4/type` | `isTemporalDuration` | Checks if a value is a `Temporal.Duration`. Uses `instanceof` when `Temporal` is available globally |
178
+ | `@helpers4/type` | `isTemporalInstant` | Checks if a value is a `Temporal.Instant`. Uses `instanceof` when `Temporal` is available globally, |
179
+ | `@helpers4/type` | `isTemporalPlainDate` | Checks if a value is a `Temporal.PlainDate`. Uses `instanceof` when `Temporal` is available globall |
180
+ | `@helpers4/type` | `isTemporalPlainDateTime` | Checks if a value is a `Temporal.PlainDateTime`. Uses `instanceof` when `Temporal` is available glo |
181
+ | `@helpers4/type` | `isTemporalPlainTime` | Checks if a value is a `Temporal.PlainTime`. Uses `instanceof` when `Temporal` is available globall |
182
+ | `@helpers4/type` | `isTemporalZonedDateTime` | Checks if a value is a `Temporal.ZonedDateTime`. Uses `instanceof` when `Temporal` is available glo |
138
183
  | `@helpers4/type` | `isTimestamp` | Checks if a value is a valid timestamp (milliseconds or Unix seconds). Supports: - JavaScript / Jav |
139
184
  | `@helpers4/type` | `isTruthy` | Checks if a value is truthy (not `false`, `null`, `undefined`, `0`, `""`, or `NaN`). This is the ty |
140
185
  | `@helpers4/type` | `isUndefined` | Checks if a value is `undefined`. |
141
186
  | `@helpers4/type` | `isValidDate` | Checks if a value is a valid Date instance (not `Invalid Date`). Unlike isDate, this also verifies |
142
187
  | `@helpers4/type` | `isValidRegex` | Checks if a string is a valid regex pattern. |
143
- | `@helpers4/type` | `Maybe` | Type for values that can be T, undefined, or null. |
144
188
  | `@helpers4/type` | `Primitive` | Union of all JavaScript primitive types. |
145
189
  | `@helpers4/url` | `cleanPath` | Clean an URL by removing duplicate slashes. The protocol part of the URL is not modified. |
146
190
  | `@helpers4/url` | `extractPureURI` | Extracts the pure URI from a URL by removing query parameters and fragments. |
@@ -925,20 +969,162 @@ unique([1, 2, 2, 3, 3, 3])
925
969
 
926
970
  Package: `@helpers4/date`
927
971
 
972
+ ### `addDays`
973
+
974
+ Adds days to a date.
975
+
976
+ Returns a **new** `Date` — the original is never mutated.
977
+ Returns `null` if the input is invalid.
978
+
979
+ ```typescript
980
+ import { addDays } from '@helpers4/date';
981
+
982
+ addDays(date: DateLike, amount: number): Date | null
983
+ ```
984
+
985
+ **Parameters:**
986
+
987
+ - `date: DateLike` — The base date
988
+ - `amount: number` — Number of days to add (negative to subtract)
989
+
990
+ **Returns:** `Date | null` — A new Date, or `null` if the input is invalid
991
+
992
+ **Examples:**
993
+
994
+ *addDays*
995
+
996
+ ```typescript
997
+ ```ts
998
+ addDays('2025-01-19', 10) // => Date(2025-01-29)
999
+ addDays('2025-01-19', -5) // => Date(2025-01-14)
1000
+ ```
1001
+ ```
1002
+
1003
+ ---
1004
+
1005
+ ### `addMonths`
1006
+
1007
+ Adds months to a date.
1008
+
1009
+ Returns a **new** `Date` — the original is never mutated.
1010
+ When the resulting month has fewer days, JavaScript clamps to the
1011
+ next month (e.g. Jan 31 + 1 month → Mar 3). Use with caution.
1012
+ Returns `null` if the input is invalid.
1013
+
1014
+ ```typescript
1015
+ import { addMonths } from '@helpers4/date';
1016
+
1017
+ addMonths(date: DateLike, amount: number): Date | null
1018
+ ```
1019
+
1020
+ **Parameters:**
1021
+
1022
+ - `date: DateLike` — The base date
1023
+ - `amount: number` — Number of months to add (negative to subtract)
1024
+
1025
+ **Returns:** `Date | null` — A new Date, or `null` if the input is invalid
1026
+
1027
+ **Examples:**
1028
+
1029
+ *addMonths*
1030
+
1031
+ ```typescript
1032
+ ```ts
1033
+ addMonths('2025-01-15', 1) // => Date(2025-02-15)
1034
+ addMonths('2025-01-31', 1) // => Date(2025-03-03) — overflow
1035
+ addMonths('2025-03-15', -1) // => Date(2025-02-15)
1036
+ ```
1037
+ ```
1038
+
1039
+ ---
1040
+
1041
+ ### `addYears`
1042
+
1043
+ Adds years to a date.
1044
+
1045
+ Returns a **new** `Date` — the original is never mutated.
1046
+ Returns `null` if the input is invalid.
1047
+
1048
+ ```typescript
1049
+ import { addYears } from '@helpers4/date';
1050
+
1051
+ addYears(date: DateLike, amount: number): Date | null
1052
+ ```
1053
+
1054
+ **Parameters:**
1055
+
1056
+ - `date: DateLike` — The base date
1057
+ - `amount: number` — Number of years to add (negative to subtract)
1058
+
1059
+ **Returns:** `Date | null` — A new Date, or `null` if the input is invalid
1060
+
1061
+ **Examples:**
1062
+
1063
+ *addYears*
1064
+
1065
+ ```typescript
1066
+ ```ts
1067
+ addYears('2025-01-19', 1) // => Date(2026-01-19)
1068
+ addYears('2024-02-29', 1) // => Date(2025-03-01) — leap year overflow
1069
+ addYears('2025-06-15', -2) // => Date(2023-06-15)
1070
+ ```
1071
+ ```
1072
+
1073
+ ---
1074
+
1075
+ ### `clampDate`
1076
+
1077
+ Clamps a date to a [min, max] range.
1078
+
1079
+ Returns a **new** `Date` — the original is never mutated.
1080
+ Returns `null` if any of the inputs is invalid.
1081
+
1082
+ ```typescript
1083
+ import { clampDate } from '@helpers4/date';
1084
+
1085
+ clampDate(date: DateLike, min: DateLike, max: DateLike): Date | null
1086
+ ```
1087
+
1088
+ **Parameters:**
1089
+
1090
+ - `date: DateLike` — The date to clamp
1091
+ - `min: DateLike` — The minimum allowed date
1092
+ - `max: DateLike` — The maximum allowed date
1093
+
1094
+ **Returns:** `Date | null` — A new Date clamped to the range, or `null` if any input is invalid
1095
+
1096
+ **Examples:**
1097
+
1098
+ *clampDate*
1099
+
1100
+ ```typescript
1101
+ ```ts
1102
+ clampDate('2025-06-15', '2025-01-01', '2025-03-31')
1103
+ // => Date(2025-03-31) — clamped to max
1104
+
1105
+ clampDate('2025-02-15', '2025-01-01', '2025-03-31')
1106
+ // => Date(2025-02-15) — within range, unchanged
1107
+ ```
1108
+ ```
1109
+
1110
+ ---
1111
+
928
1112
  ### `compare`
929
1113
 
930
1114
  Comparison of two dates.
931
1115
 
1116
+ Accepts any DateLike input (Date, timestamp, or date string).
1117
+
932
1118
  ```typescript
933
1119
  import { compare } from '@helpers4/date';
934
1120
 
935
- compare(dateA: Date, dateB: Date, options: DateCompareOptions): boolean
1121
+ compare(dateA: DateLike, dateB: DateLike, options: DateCompareOptions): boolean
936
1122
  ```
937
1123
 
938
1124
  **Parameters:**
939
1125
 
940
- - `dateA: Date` — First date to compare
941
- - `dateB: Date` — Second date to compare
1126
+ - `dateA: DateLike` — First date to compare
1127
+ - `dateB: DateLike` — Second date to compare
942
1128
  - `options: DateCompareOptions` (default: `{}`) — Comparison options
943
1129
 
944
1130
  **Returns:** `boolean` — `true` if dates are identical according to the specified precision, `false` otherwise
@@ -976,27 +1162,62 @@ Options for date comparison
976
1162
 
977
1163
  ---
978
1164
 
1165
+ ### `DateDifferenceOptions`
1166
+
1167
+ Options for date difference calculation
1168
+
1169
+ ---
1170
+
1171
+ ### `DateLike`
1172
+
1173
+ A value that can be converted to a Date.
1174
+
1175
+ - `Date` — used as-is (validated for Invalid Date)
1176
+ - `number` — treated as a timestamp (seconds or milliseconds, auto-detected);
1177
+ `0` is treated as invalid and produces `null` in ensureDate
1178
+ - `string` — parsed via `new Date(string)`
1179
+ - `EpochMilliseconds` — any object with a `epochMilliseconds` property
1180
+ (e.g. `Temporal.Instant`, `Temporal.ZonedDateTime`)
1181
+
1182
+ // TODO: When the Temporal API reaches Stage 4 and is available without
1183
+ // flags in all major runtimes, consider narrowing the union to the
1184
+ // concrete Temporal types for stricter type-checking.
1185
+
1186
+ ---
1187
+
1188
+ ### `DateRange`
1189
+
1190
+ A date range represented as a pair of date-like values.
1191
+
1192
+ ---
1193
+
979
1194
  ### `dateToISOString`
980
1195
 
981
- Formats a date to ISO string or returns null
1196
+ Formats a date to ISO string or returns null.
982
1197
 
983
1198
  ```typescript
984
1199
  import { dateToISOString } from '@helpers4/date';
985
1200
 
986
- dateToISOString(input: string | number | Date | null | undefined): string | null
1201
+ dateToISOString(input: DateLike | null | undefined): string | null
987
1202
  ```
988
1203
 
989
1204
  **Parameters:**
990
1205
 
991
- - `input: string | number | Date | null | undefined` — Date input
1206
+ - `input: DateLike | null | undefined` — Date input
992
1207
 
993
1208
  **Returns:** `string | null` — ISO string or null
994
1209
 
995
1210
  ---
996
1211
 
1212
+ ### `DateTruncUnit`
1213
+
1214
+ Units supported by startOf and endOf.
1215
+
1216
+ ---
1217
+
997
1218
  ### `daysDifference`
998
1219
 
999
- Gets the difference in days between two dates
1220
+ Gets the difference in days between two dates.
1000
1221
 
1001
1222
  ```typescript
1002
1223
  import { daysDifference } from '@helpers4/date';
@@ -1009,7 +1230,7 @@ daysDifference(date1: Date, date2: Date): number
1009
1230
  - `date1: Date` — First date
1010
1231
  - `date2: Date` — Second date
1011
1232
 
1012
- **Returns:** `number` — Number of days difference
1233
+ **Returns:** `number` — Number of days difference (rounded)
1013
1234
 
1014
1235
  **Examples:**
1015
1236
 
@@ -1024,127 +1245,953 @@ daysDifference(new Date('2025-01-01'), new Date('2025-01-10'))
1024
1245
 
1025
1246
  ---
1026
1247
 
1027
- ### `isSameDay`
1248
+ ### `daysInMonth`
1249
+
1250
+ Returns the number of days in the given month of the given year.
1028
1251
 
1029
- Checks if two dates are the same day
1252
+ Month is **1-based** (1 = January, 12 = December) to match human
1253
+ convention and ISO 8601 (unlike `Date.getMonth()` which is 0-based).
1254
+
1255
+ Returns `NaN` if the month is out of range.
1030
1256
 
1031
1257
  ```typescript
1032
- import { isSameDay } from '@helpers4/date';
1258
+ import { daysInMonth } from '@helpers4/date';
1033
1259
 
1034
- isSameDay(date1: Date, date2: Date): boolean
1260
+ daysInMonth(year: number, month: number): number
1035
1261
  ```
1036
1262
 
1037
1263
  **Parameters:**
1038
1264
 
1039
- - `date1: Date` — First date
1040
- - `date2: Date` — Second date
1265
+ - `year: number` — A full year number (e.g. 2025)
1266
+ - `month: number` — 1-based month number (1–12)
1041
1267
 
1042
- **Returns:** `boolean` — True if same day
1268
+ **Returns:** `number` — Number of days in that month, or `NaN` for invalid month
1043
1269
 
1044
1270
  **Examples:**
1045
1271
 
1046
- *Same day, different times*
1272
+ *daysInMonth*
1047
1273
 
1048
- Returns true when both dates are on the same calendar day.
1274
+ ```typescript
1275
+ ```ts
1276
+ daysInMonth(2025, 1) // => 31 (January)
1277
+ daysInMonth(2025, 2) // => 28 (February, non-leap)
1278
+ daysInMonth(2024, 2) // => 29 (February, leap)
1279
+ daysInMonth(2025, 4) // => 30 (April)
1280
+ ```
1281
+ ```
1282
+
1283
+ ---
1284
+
1285
+ ### `difference`
1286
+
1287
+ Calculates the difference between two dates in the specified unit.
1288
+
1289
+ Accepts any DateLike input (Date, timestamp, or date string).
1049
1290
 
1050
1291
  ```typescript
1051
- isSameDay(new Date('2025-01-19T08:00:00'), new Date('2025-01-19T22:00:00'))
1052
- // => true
1292
+ import { difference } from '@helpers4/date';
1293
+
1294
+ difference(dateA: DateLike, dateB: DateLike, options: DateDifferenceOptions): number
1295
+ ```
1296
+
1297
+ **Parameters:**
1298
+
1299
+ - `dateA: DateLike` — First date
1300
+ - `dateB: DateLike` — Second date
1301
+ - `options: DateDifferenceOptions` (default: `{}`) — Difference options
1302
+
1303
+ **Returns:** `number` — The difference between the two dates, or `NaN` if either date is invalid
1304
+
1305
+ **Examples:**
1306
+
1307
+ *difference*
1308
+
1309
+ ```typescript
1310
+ ```ts
1311
+ difference('2025-01-01', '2025-01-10')
1312
+ // => 9
1313
+ difference('2025-01-01T00:00:00Z', '2025-01-01T02:30:00Z', { unit: 'hours' })
1314
+ // => 2.5
1315
+ difference('2025-01-10', '2025-01-01', { absolute: false })
1316
+ // => -9
1317
+ ```
1318
+ ```
1319
+
1320
+ ---
1321
+
1322
+ ### `DifferenceUnit`
1323
+
1324
+ Unit for date difference calculation
1325
+
1326
+ ---
1327
+
1328
+ ### `eachDay`
1329
+
1330
+ Returns an array of `Date` objects for each day from `start` to `end` (inclusive).
1331
+
1332
+ Both boundaries are included. If `start > end`, an empty array is returned.
1333
+ Returns an empty array if either input is invalid.
1334
+
1335
+ ```typescript
1336
+ import { eachDay } from '@helpers4/date';
1337
+
1338
+ eachDay(start: DateLike, end: DateLike): Date[]
1339
+ ```
1340
+
1341
+ **Parameters:**
1342
+
1343
+ - `start: DateLike` — Start date (inclusive)
1344
+ - `end: DateLike` — End date (inclusive)
1345
+
1346
+ **Returns:** `Date[]` — An array of Date objects, one per day
1347
+
1348
+ **Examples:**
1349
+
1350
+ *eachDay*
1351
+
1352
+ ```typescript
1353
+ ```ts
1354
+ eachDay('2025-01-01', '2025-01-03')
1355
+ // => [Date(2025-01-01), Date(2025-01-02), Date(2025-01-03)]
1356
+ ```
1357
+ ```
1358
+
1359
+ ---
1360
+
1361
+ ### `eachMonth`
1362
+
1363
+ Returns an array of `Date` objects for the first day of each month
1364
+ from `start` to `end` (inclusive).
1365
+
1366
+ Each returned Date is normalized to the 1st of the month at 00:00:00.000.
1367
+ If `start > end`, an empty array is returned.
1368
+ Returns an empty array if either input is invalid.
1369
+
1370
+ ```typescript
1371
+ import { eachMonth } from '@helpers4/date';
1372
+
1373
+ eachMonth(start: DateLike, end: DateLike): Date[]
1374
+ ```
1375
+
1376
+ **Parameters:**
1377
+
1378
+ - `start: DateLike` — Start date (inclusive — the month containing this date is included)
1379
+ - `end: DateLike` — End date (inclusive — the month containing this date is included)
1380
+
1381
+ **Returns:** `Date[]` — An array of Date objects, one per month (each on the 1st)
1382
+
1383
+ **Examples:**
1384
+
1385
+ *eachMonth*
1386
+
1387
+ ```typescript
1388
+ ```ts
1389
+ eachMonth('2025-01-15', '2025-04-10')
1390
+ // => [Date(2025-01-01), Date(2025-02-01), Date(2025-03-01), Date(2025-04-01)]
1391
+ ```
1392
+ ```
1393
+
1394
+ ---
1395
+
1396
+ ### `endOf`
1397
+
1398
+ Returns a new `Date` set to the **end** of the given unit.
1399
+
1400
+ - `'day'` — 23:59:59.999
1401
+ - `'month'` — last day of the month, 23:59:59.999
1402
+ - `'year'` — December 31st, 23:59:59.999
1403
+
1404
+ Returns `null` if the input is invalid.
1405
+
1406
+ ```typescript
1407
+ import { endOf } from '@helpers4/date';
1408
+
1409
+ endOf(date: DateLike, unit: DateTruncUnit): Date | null
1410
+ ```
1411
+
1412
+ **Parameters:**
1413
+
1414
+ - `date: DateLike` — The base date
1415
+ - `unit: DateTruncUnit` — The unit to round to
1416
+
1417
+ **Returns:** `Date | null` — A new Date at the end of the unit, or `null`
1418
+
1419
+ **Examples:**
1420
+
1421
+ *endOf*
1422
+
1423
+ ```typescript
1424
+ ```ts
1425
+ endOf('2025-06-15T14:30:00Z', 'day') // => 2025-06-15T23:59:59.999Z
1426
+ endOf('2025-06-15T14:30:00Z', 'month') // => 2025-06-30T23:59:59.999Z
1427
+ endOf('2025-06-15T14:30:00Z', 'year') // => 2025-12-31T23:59:59.999Z
1428
+ ```
1429
+ ```
1430
+
1431
+ ---
1432
+
1433
+ ### `ensureDate`
1434
+
1435
+ Safely converts a date-like value to a valid `Date` object, or returns `null`.
1436
+
1437
+ Accepts `Date`, timestamps (seconds or milliseconds, auto-detected), date strings,
1438
+ and objects with an `epochMilliseconds` property (e.g. `Temporal.Instant`,
1439
+ `Temporal.ZonedDateTime`).
1440
+ Returns `null` for `null`, `undefined`, empty strings, `0`, and any value that
1441
+ produces an invalid `Date`.
1442
+
1443
+ This is the date equivalent of ensureArray — it normalizes flexible
1444
+ input into a guaranteed type (or a safe fallback).
1445
+
1446
+ ```typescript
1447
+ import { ensureDate } from '@helpers4/date';
1448
+
1449
+ ensureDate(input: DateLike | null | undefined): Date | null
1450
+ ```
1451
+
1452
+ **Parameters:**
1453
+
1454
+ - `input: DateLike | null | undefined` — A date-like value to convert
1455
+
1456
+ **Returns:** `Date | null` — A valid `Date` object, or `null` if the input is invalid
1457
+
1458
+ **Examples:**
1459
+
1460
+ *ensureDate*
1461
+
1462
+ ```typescript
1463
+ ```ts
1464
+ ensureDate('2025-01-19T12:00:00Z') // => Date
1465
+ ensureDate(1737290400) // => Date (from Unix seconds)
1466
+ ensureDate(1737290400000) // => Date (from milliseconds)
1467
+ ensureDate(new Date()) // => Date (same reference)
1468
+ ensureDate(null) // => null
1469
+ ensureDate('invalid') // => null
1470
+ ```
1471
+ ```
1472
+
1473
+ ---
1474
+
1475
+ ### `EpochMilliseconds`
1476
+
1477
+ An object that exposes an epoch timestamp in milliseconds.
1478
+
1479
+ This structural type is satisfied by `Temporal.Instant` and
1480
+ `Temporal.ZonedDateTime` (and any future object that carries the same
1481
+ property), so callers can pass Temporal values without importing them.
1482
+
1483
+ ---
1484
+
1485
+ ### `formatDuration`
1486
+
1487
+ Formats a duration in milliseconds as a compact human-readable string.
1488
+
1489
+ Produces output like `"1h 23m 45s"`. Zero-valued leading units are
1490
+ omitted (e.g. `"23m 45s"` instead of `"0h 23m 45s"`), but trailing
1491
+ zeros are kept up to the minimum unit (`"1h 0m 0s"` when `minUnit`
1492
+ is `'seconds'`).
1493
+
1494
+ Negative durations are prefixed with `"-"`.
1495
+ A zero duration returns `"0s"` (or `"0m"` / `"0h"` depending on `minUnit`).
1496
+
1497
+ ```typescript
1498
+ import { formatDuration } from '@helpers4/date';
1499
+
1500
+ formatDuration(ms: number, options: FormatDurationOptions): string
1501
+ ```
1502
+
1503
+ **Parameters:**
1504
+
1505
+ - `ms: number` — Duration in milliseconds
1506
+ - `options: FormatDurationOptions` (default: `{}`) — Optional configuration
1507
+
1508
+ **Returns:** `string` — A compact duration string
1509
+
1510
+ **Examples:**
1511
+
1512
+ *formatDuration*
1513
+
1514
+ ```typescript
1515
+ ```ts
1516
+ formatDuration(5025000) // => "1h 23m 45s"
1517
+ formatDuration(45000) // => "45s"
1518
+ formatDuration(3600000) // => "1h 0m 0s"
1519
+ formatDuration(5025000, { minUnit: 'minutes' }) // => "1h 23m"
1520
+ formatDuration(5025000, { padded: true }) // => "01h 23m 45s"
1521
+ formatDuration(-5025000) // => "-1h 23m 45s"
1053
1522
  ```
1523
+ ```
1524
+
1525
+ ---
1526
+
1527
+ ### `FormatDurationOptions`
1528
+
1529
+ Options for formatDuration.
1530
+
1531
+ ---
1532
+
1533
+ ### `formatInTimezone`
1534
+
1535
+ Formats a date in a specific IANA timezone using `Intl.DateTimeFormat`.
1536
+
1537
+ Returns `null` if the date or timezone is invalid.
1538
+
1539
+ ```typescript
1540
+ import { formatInTimezone } from '@helpers4/date';
1541
+
1542
+ formatInTimezone(date: DateLike, tz: string, options: FormatInTimezoneOptions): string | null
1543
+ ```
1544
+
1545
+ **Parameters:**
1546
+
1547
+ - `date: DateLike` — The date to format
1548
+ - `tz: string` — IANA timezone identifier (e.g. `'Asia/Tokyo'`)
1549
+ - `options: FormatInTimezoneOptions` (default: `{}`) — Optional locale and format configuration
1550
+
1551
+ **Returns:** `string | null` — A formatted date string, or `null`
1552
+
1553
+ **Examples:**
1554
+
1555
+ *formatInTimezone*
1556
+
1557
+ ```typescript
1558
+ ```ts
1559
+ formatInTimezone('2025-01-19T12:00:00Z', 'Asia/Tokyo')
1560
+ // => "1/19/2025, 9:00:00 PM" (en-US default)
1561
+
1562
+ formatInTimezone('2025-01-19T12:00:00Z', 'Europe/Paris', {
1563
+ locale: 'fr-FR',
1564
+ formatOptions: { dateStyle: 'long', timeStyle: 'short' },
1565
+ })
1566
+ // => "19 janvier 2025, 13:00"
1567
+ ```
1568
+ ```
1569
+
1570
+ ---
1571
+
1572
+ ### `FormatInTimezoneOptions`
1573
+
1574
+ Options for formatInTimezone.
1575
+
1576
+ ---
1577
+
1578
+ ### `fromMillis`
1579
+
1580
+ Creates a `Date` from a timestamp in **milliseconds**.
1581
+
1582
+ Use this when receiving a timestamp from a JS-native source
1583
+ (e.g. `Date.now()`, `performance.timeOrigin`). No heuristic — the
1584
+ input is always treated as milliseconds.
1585
+
1586
+ ```typescript
1587
+ import { fromMillis } from '@helpers4/date';
1588
+
1589
+ fromMillis(ms: number): Date | null
1590
+ ```
1591
+
1592
+ **Parameters:**
1593
+
1594
+ - `ms: number` — Milliseconds since the Unix epoch
1595
+
1596
+ **Returns:** `Date | null` — A valid `Date`, or `null` for `NaN` / non-finite input
1597
+
1598
+ **Examples:**
1599
+
1600
+ *fromMillis*
1601
+
1602
+ ```typescript
1603
+ ```ts
1604
+ fromMillis(1737288000000) // => Date('2025-01-19T12:00:00Z')
1605
+ fromMillis(0) // => Date('1970-01-01T00:00:00Z')
1606
+ fromMillis(NaN) // => null
1607
+ ```
1608
+ ```
1609
+
1610
+ ---
1611
+
1612
+ ### `fromSeconds`
1613
+
1614
+ Creates a `Date` from a timestamp in **seconds**.
1615
+
1616
+ Use this when receiving a timestamp from a backend that sends seconds
1617
+ (e.g. Java `Instant.getEpochSecond()`). No heuristic — the input is
1618
+ always treated as seconds.
1619
+
1620
+ ```typescript
1621
+ import { fromSeconds } from '@helpers4/date';
1622
+
1623
+ fromSeconds(seconds: number): Date | null
1624
+ ```
1625
+
1626
+ **Parameters:**
1627
+
1628
+ - `seconds: number` — Seconds since the Unix epoch
1629
+
1630
+ **Returns:** `Date | null` — A valid `Date`, or `null` for `NaN` / non-finite input
1631
+
1632
+ **Examples:**
1633
+
1634
+ *fromSeconds*
1635
+
1636
+ ```typescript
1637
+ ```ts
1638
+ fromSeconds(1737288000) // => Date('2025-01-19T12:00:00Z')
1639
+ fromSeconds(0) // => Date('1970-01-01T00:00:00Z')
1640
+ fromSeconds(NaN) // => null
1641
+ ```
1642
+ ```
1643
+
1644
+ ---
1645
+
1646
+ ### `getTimezoneOffset`
1647
+
1648
+ Returns the UTC offset **in minutes** for the given IANA timezone
1649
+ at a specific point in time.
1650
+
1651
+ A positive value means the timezone is **ahead** of UTC (e.g. `+60` for CET).
1652
+ Returns `null` if the timezone is invalid or the date cannot be parsed.
1653
+
1654
+ The implementation uses `Intl.DateTimeFormat` to extract the local
1655
+ representation in the target timezone, then computes the delta from UTC.
1656
+
1657
+ ```typescript
1658
+ import { getTimezoneOffset } from '@helpers4/date';
1659
+
1660
+ getTimezoneOffset(tz: string, date: DateLike): number | null
1661
+ ```
1662
+
1663
+ **Parameters:**
1664
+
1665
+ - `tz: string` — IANA timezone identifier (e.g. `'America/New_York'`)
1666
+ - `date: DateLike` (default: `...`) — Reference date (defaults to now)
1667
+
1668
+ **Returns:** `number | null` — Offset in minutes, or `null` if inputs are invalid
1669
+
1670
+ **Examples:**
1671
+
1672
+ *getTimezoneOffset*
1673
+
1674
+ ```typescript
1675
+ ```ts
1676
+ getTimezoneOffset('America/New_York', '2025-01-19T12:00:00Z') // => -300 (EST)
1677
+ getTimezoneOffset('Europe/Paris', '2025-07-19T12:00:00Z') // => 120 (CEST)
1678
+ ```
1679
+ ```
1680
+
1681
+ ---
1682
+
1683
+ ### `isBusinessDay`
1684
+
1685
+ Checks whether a date falls on a business day (i.e. **not** a weekend day).
1686
+
1687
+ This is the logical inverse of isWeekend. By default, business days
1688
+ are Monday through Friday. Pass a custom `weekendDays` to adapt to other
1689
+ calendars.
1690
+
1691
+ > **Note:** This helper does **not** account for public holidays — those are
1692
+ > country- and region-specific. Use it in combination with your own holiday
1693
+ > list if needed.
1694
+
1695
+ Returns `false` if the input is invalid.
1696
+
1697
+ ```typescript
1698
+ import { isBusinessDay } from '@helpers4/date';
1699
+
1700
+ isBusinessDay(date: DateLike, weekendDays: readonly WeekDay[]): boolean
1701
+ ```
1702
+
1703
+ **Parameters:**
1704
+
1705
+ - `date: DateLike` — The date to check
1706
+ - `weekendDays: readonly WeekDay[]` (default: `DEFAULT_WEEKEND`) — Override which days count as weekend (default: `[0, 6]`)
1707
+
1708
+ **Returns:** `boolean` — `true` if the date is not a weekend day
1709
+
1710
+ **Examples:**
1711
+
1712
+ *isBusinessDay*
1713
+
1714
+ ```typescript
1715
+ ```ts
1716
+ isBusinessDay('2025-01-20') // => true (Monday)
1717
+ isBusinessDay('2025-01-18') // => false (Saturday)
1718
+
1719
+ // UAE weekend (Friday + Saturday)
1720
+ const uaeWeekend = [WeekDays.Friday, WeekDays.Saturday] as const;
1721
+ isBusinessDay('2025-01-19', uaeWeekend) // => true (Sunday = workday)
1722
+ isBusinessDay('2025-01-17', uaeWeekend) // => false (Friday = weekend)
1723
+ ```
1724
+ ```
1725
+
1726
+ ---
1727
+
1728
+ ### `isLeapYear`
1729
+
1730
+ Returns `true` if the given year is a leap year.
1731
+
1732
+ A year is a leap year when it is divisible by 4, **except** century
1733
+ years which must also be divisible by 400.
1734
+
1735
+ ```typescript
1736
+ import { isLeapYear } from '@helpers4/date';
1737
+
1738
+ isLeapYear(year: number): boolean
1739
+ ```
1740
+
1741
+ **Parameters:**
1742
+
1743
+ - `year: number` — A full year number (e.g. 2024)
1744
+
1745
+ **Returns:** `boolean` — `true` if the year is a leap year
1746
+
1747
+ **Examples:**
1748
+
1749
+ *isLeapYear*
1750
+
1751
+ ```typescript
1752
+ ```ts
1753
+ isLeapYear(2024) // => true
1754
+ isLeapYear(2025) // => false
1755
+ isLeapYear(2000) // => true (divisible by 400)
1756
+ isLeapYear(1900) // => false (century, not divisible by 400)
1757
+ ```
1758
+ ```
1759
+
1760
+ ---
1761
+
1762
+ ### `isSameDay`
1763
+
1764
+ Checks if two dates are the same day.
1765
+
1766
+ Accepts any DateLike input (Date, timestamp, or date string).
1767
+
1768
+ ```typescript
1769
+ import { isSameDay } from '@helpers4/date';
1770
+
1771
+ isSameDay(date1: DateLike, date2: DateLike): boolean
1772
+ ```
1773
+
1774
+ **Parameters:**
1775
+
1776
+ - `date1: DateLike` — First date
1777
+ - `date2: DateLike` — Second date
1778
+
1779
+ **Returns:** `boolean` — True if same day, false otherwise (including when either date is invalid)
1780
+
1781
+ **Examples:**
1782
+
1783
+ *Same day, different times*
1784
+
1785
+ Returns true when both dates are on the same calendar day.
1786
+
1787
+ ```typescript
1788
+ isSameDay(new Date('2025-01-19T08:00:00'), new Date('2025-01-19T22:00:00'))
1789
+ // => true
1790
+ ```
1791
+
1792
+ ---
1793
+
1794
+ ### `isSameMonth`
1795
+
1796
+ Checks if two dates are in the same month (and year).
1797
+
1798
+ Accepts any DateLike input (Date, timestamp, or date string).
1799
+
1800
+ ```typescript
1801
+ import { isSameMonth } from '@helpers4/date';
1802
+
1803
+ isSameMonth(date1: DateLike, date2: DateLike): boolean
1804
+ ```
1805
+
1806
+ **Parameters:**
1807
+
1808
+ - `date1: DateLike` — First date
1809
+ - `date2: DateLike` — Second date
1810
+
1811
+ **Returns:** `boolean` — True if same month and year, false otherwise (including when either date is invalid)
1812
+
1813
+ **Examples:**
1814
+
1815
+ *isSameMonth*
1816
+
1817
+ ```typescript
1818
+ ```ts
1819
+ isSameMonth('2025-01-01', '2025-01-31') // => true
1820
+ isSameMonth('2025-01-31', '2025-02-01') // => false
1821
+ ```
1822
+ ```
1823
+
1824
+ ---
1825
+
1826
+ ### `isSameYear`
1827
+
1828
+ Checks if two dates are in the same year.
1829
+
1830
+ Accepts any DateLike input (Date, timestamp, or date string).
1831
+
1832
+ ```typescript
1833
+ import { isSameYear } from '@helpers4/date';
1834
+
1835
+ isSameYear(date1: DateLike, date2: DateLike): boolean
1836
+ ```
1837
+
1838
+ **Parameters:**
1839
+
1840
+ - `date1: DateLike` — First date
1841
+ - `date2: DateLike` — Second date
1842
+
1843
+ **Returns:** `boolean` — True if same year, false otherwise (including when either date is invalid)
1844
+
1845
+ **Examples:**
1846
+
1847
+ *isSameYear*
1848
+
1849
+ ```typescript
1850
+ ```ts
1851
+ isSameYear('2025-01-01', '2025-12-31') // => true
1852
+ isSameYear('2024-12-31', '2025-01-01') // => false
1853
+ ```
1854
+ ```
1855
+
1856
+ ---
1857
+
1858
+ ### `isTimestampInSeconds`
1859
+
1860
+ Checks if a timestamp is likely in seconds (Java/Unix style) vs milliseconds (JavaScript style)
1861
+
1862
+ ```typescript
1863
+ import { isTimestampInSeconds } from '@helpers4/date';
1864
+
1865
+ isTimestampInSeconds(timestamp: number): boolean
1866
+ ```
1867
+
1868
+ **Parameters:**
1869
+
1870
+ - `timestamp: number` — The timestamp to check
1871
+
1872
+ **Returns:** `boolean` — True if timestamp appears to be in seconds
1873
+
1874
+ **Examples:**
1875
+
1876
+ *Detect a Unix timestamp in seconds*
1877
+
1878
+ Returns true for timestamps that are likely in seconds (Java/Unix style).
1879
+
1880
+ ```typescript
1881
+ isTimestampInSeconds(1737290400)
1882
+ // => true
1883
+ ```
1884
+
1885
+ *Normalize a Unix timestamp to milliseconds*
1886
+
1887
+ Converts a timestamp in seconds to JavaScript milliseconds.
1888
+
1889
+ ```typescript
1890
+ normalizeTimestamp(1737290400)
1891
+ // => 1737290400000
1892
+ ```
1893
+
1894
+ ---
1895
+
1896
+ ### `isValidDateString`
1897
+
1898
+ Checks whether a string can be parsed into a valid `Date`.
1899
+
1900
+ Uses the native `Date` constructor. Returns `false` for empty strings
1901
+ and any string that produces an Invalid Date.
1902
+
1903
+ > **Caveat:** The native parser is lenient and implementation-dependent
1904
+ > for non-ISO formats. For strict format validation, prefer a dedicated
1905
+ > library or manual regex checks.
1906
+
1907
+ ```typescript
1908
+ import { isValidDateString } from '@helpers4/date';
1909
+
1910
+ isValidDateString(input: string): boolean
1911
+ ```
1912
+
1913
+ **Parameters:**
1914
+
1915
+ - `input: string` — The string to validate
1916
+
1917
+ **Returns:** `boolean` — `true` if `new Date(input)` produces a valid date
1918
+
1919
+ **Examples:**
1920
+
1921
+ *isValidDateString*
1922
+
1923
+ ```typescript
1924
+ ```ts
1925
+ isValidDateString('2025-01-19') // => true
1926
+ isValidDateString('2025-01-19T12:00:00Z') // => true
1927
+ isValidDateString('Jan 19, 2025') // => true
1928
+ isValidDateString('not a date') // => false
1929
+ isValidDateString('') // => false
1930
+ ```
1931
+ ```
1932
+
1933
+ ---
1934
+
1935
+ ### `isWeekend`
1936
+
1937
+ Checks whether a date falls on a weekend day.
1938
+
1939
+ By default, weekend days are **Saturday** and **Sunday** (Western
1940
+ convention). Pass a custom `weekendDays` tuple to adapt to other
1941
+ calendars (e.g. `[5, 6]` for Friday/Saturday in many Middle-Eastern
1942
+ countries).
1943
+
1944
+ Returns `false` if the input is invalid.
1945
+
1946
+ ```typescript
1947
+ import { isWeekend } from '@helpers4/date';
1948
+
1949
+ isWeekend(date: DateLike, weekendDays: readonly WeekDay[]): boolean
1950
+ ```
1951
+
1952
+ **Parameters:**
1953
+
1954
+ - `date: DateLike` — The date to check
1955
+ - `weekendDays: readonly WeekDay[]` (default: `DEFAULT_WEEKEND`) — Override which days count as weekend (default: `[0, 6]`)
1956
+
1957
+ **Returns:** `boolean` — `true` if the date's day-of-week is in `weekendDays`
1958
+
1959
+ **Examples:**
1960
+
1961
+ *isWeekend*
1962
+
1963
+ ```typescript
1964
+ ```ts
1965
+ isWeekend('2025-01-18') // => true (Saturday)
1966
+ isWeekend('2025-01-19') // => true (Sunday)
1967
+ isWeekend('2025-01-20') // => false (Monday)
1968
+
1969
+ // Middle-Eastern weekend (Friday + Saturday)
1970
+ isWeekend('2025-01-17', [WeekDays.Friday, WeekDays.Saturday]) // => true
1971
+ isWeekend('2025-01-19', [WeekDays.Friday, WeekDays.Saturday]) // => false
1972
+ ```
1973
+ ```
1974
+
1975
+ ---
1976
+
1977
+ ### `isWithinRange`
1978
+
1979
+ Checks whether a date falls within a range (inclusive on both ends).
1980
+
1981
+ Returns `false` if any of the inputs is invalid.
1982
+
1983
+ ```typescript
1984
+ import { isWithinRange } from '@helpers4/date';
1985
+
1986
+ isWithinRange(date: DateLike, start: DateLike, end: DateLike): boolean
1987
+ ```
1988
+
1989
+ **Parameters:**
1990
+
1991
+ - `date: DateLike` — The date to check
1992
+ - `start: DateLike` — Start of the range (inclusive)
1993
+ - `end: DateLike` — End of the range (inclusive)
1994
+
1995
+ **Returns:** `boolean` — `true` if `start <= date <= end`
1996
+
1997
+ **Examples:**
1998
+
1999
+ *isWithinRange*
2000
+
2001
+ ```typescript
2002
+ ```ts
2003
+ isWithinRange('2025-06-15', '2025-01-01', '2025-12-31') // => true
2004
+ isWithinRange('2024-06-15', '2025-01-01', '2025-12-31') // => false
2005
+ ```
2006
+ ```
2007
+
2008
+ ---
2009
+
2010
+ ### `listTimezones`
2011
+
2012
+ Returns the list of IANA timezone identifiers supported by the runtime.
2013
+
2014
+ Wraps `Intl.supportedValuesOf('timeZone')` which is available in
2015
+ Node 18+, Chrome 93+, Firefox 93+, Safari 15.4+.
2016
+
2017
+ ```typescript
2018
+ import { listTimezones } from '@helpers4/date';
2019
+
2020
+ listTimezones(): string[]
2021
+ ```
2022
+
2023
+ **Returns:** `string[]` — An array of IANA timezone strings (e.g. `['Africa/Abidjan', …, 'US/Pacific']`)
2024
+
2025
+ **Examples:**
2026
+
2027
+ *listTimezones*
2028
+
2029
+ ```typescript
2030
+ ```ts
2031
+ listTimezones() // => ['Africa/Abidjan', 'Africa/Accra', …]
2032
+ ```
2033
+ ```
2034
+
2035
+ ---
2036
+
2037
+ ### `normalizeTimestamp`
2038
+
2039
+ Converts a timestamp to JavaScript milliseconds format
2040
+
2041
+ ```typescript
2042
+ import { normalizeTimestamp } from '@helpers4/date';
2043
+
2044
+ normalizeTimestamp(timestamp: number): number
2045
+ ```
2046
+
2047
+ **Parameters:**
2048
+
2049
+ - `timestamp: number` — The timestamp (in seconds or milliseconds)
2050
+
2051
+ **Returns:** `number` — Timestamp in milliseconds
1054
2052
 
1055
2053
  ---
1056
2054
 
1057
- ### `isTimestampInSeconds`
2055
+ ### `overlaps`
1058
2056
 
1059
- Checks if a timestamp is likely in seconds (Java/Unix style) vs milliseconds (JavaScript style)
2057
+ Checks whether two date ranges overlap.
2058
+
2059
+ Two ranges overlap when `rangeA.start <= rangeB.end` AND
2060
+ `rangeB.start <= rangeA.end` (inclusive on both ends).
2061
+ Returns `false` if any date is invalid.
1060
2062
 
1061
2063
  ```typescript
1062
- import { isTimestampInSeconds } from '@helpers4/date';
2064
+ import { overlaps } from '@helpers4/date';
1063
2065
 
1064
- isTimestampInSeconds(timestamp: number): boolean
2066
+ overlaps(rangeA: DateRange, rangeB: DateRange): boolean
1065
2067
  ```
1066
2068
 
1067
2069
  **Parameters:**
1068
2070
 
1069
- - `timestamp: number` — The timestamp to check
2071
+ - `rangeA: DateRange` — First date range
2072
+ - `rangeB: DateRange` — Second date range
1070
2073
 
1071
- **Returns:** `boolean` — True if timestamp appears to be in seconds
2074
+ **Returns:** `boolean` — `true` if the ranges share at least one point in time
1072
2075
 
1073
2076
  **Examples:**
1074
2077
 
1075
- *Detect a Unix timestamp in seconds*
1076
-
1077
- Returns true for timestamps that are likely in seconds (Java/Unix style).
2078
+ *overlaps*
1078
2079
 
1079
2080
  ```typescript
1080
- isTimestampInSeconds(1737290400)
1081
- // => true
2081
+ ```ts
2082
+ overlaps(
2083
+ { start: '2025-01-01', end: '2025-06-30' },
2084
+ { start: '2025-03-01', end: '2025-12-31' }
2085
+ ) // => true
2086
+
2087
+ overlaps(
2088
+ { start: '2025-01-01', end: '2025-02-28' },
2089
+ { start: '2025-03-01', end: '2025-12-31' }
2090
+ ) // => false
2091
+ ```
1082
2092
  ```
1083
2093
 
1084
- *Normalize a Unix timestamp to milliseconds*
2094
+ ---
1085
2095
 
1086
- Converts a timestamp in seconds to JavaScript milliseconds.
2096
+ ### `safeDate`
2097
+
2098
+ Safely creates a Date object from various input types.
1087
2099
 
1088
2100
  ```typescript
1089
- normalizeTimestamp(1737290400)
1090
- // => 1737290400000
2101
+ import { safeDate } from '@helpers4/date';
2102
+
2103
+ safeDate(input: DateLike | null | undefined): Date | null
1091
2104
  ```
1092
2105
 
2106
+ **Parameters:**
2107
+
2108
+ - `input: DateLike | null | undefined` — String, number, or Date input
2109
+
2110
+ **Returns:** `Date | null` — Valid Date object or null if invalid
2111
+
1093
2112
  ---
1094
2113
 
1095
- ### `normalizeTimestamp`
2114
+ ### `startOf`
1096
2115
 
1097
- Converts a timestamp to JavaScript milliseconds format
2116
+ Returns a new `Date` set to the **start** of the given unit.
2117
+
2118
+ - `'day'` — 00:00:00.000
2119
+ - `'month'` — 1st of the month, 00:00:00.000
2120
+ - `'year'` — January 1st, 00:00:00.000
2121
+
2122
+ Returns `null` if the input is invalid.
1098
2123
 
1099
2124
  ```typescript
1100
- import { normalizeTimestamp } from '@helpers4/date';
2125
+ import { startOf } from '@helpers4/date';
1101
2126
 
1102
- normalizeTimestamp(timestamp: number): number
2127
+ startOf(date: DateLike, unit: DateTruncUnit): Date | null
1103
2128
  ```
1104
2129
 
1105
2130
  **Parameters:**
1106
2131
 
1107
- - `timestamp: number` — The timestamp (in seconds or milliseconds)
2132
+ - `date: DateLike` — The base date
2133
+ - `unit: DateTruncUnit` — The unit to truncate to
1108
2134
 
1109
- **Returns:** `number` — Timestamp in milliseconds
2135
+ **Returns:** `Date | null` — A new Date at the start of the unit, or `null`
2136
+
2137
+ **Examples:**
2138
+
2139
+ *startOf*
2140
+
2141
+ ```typescript
2142
+ ```ts
2143
+ startOf('2025-06-15T14:30:00Z', 'day') // => 2025-06-15T00:00:00.000Z
2144
+ startOf('2025-06-15T14:30:00Z', 'month') // => 2025-06-01T00:00:00.000Z
2145
+ startOf('2025-06-15T14:30:00Z', 'year') // => 2025-01-01T00:00:00.000Z
2146
+ ```
2147
+ ```
1110
2148
 
1111
2149
  ---
1112
2150
 
1113
- ### `safeDate`
2151
+ ### `timeAgo`
2152
+
2153
+ Formats a date as a human-readable relative time string.
1114
2154
 
1115
- Safely creates a Date object from various input types
2155
+ Uses `Intl.RelativeTimeFormat` under the hood, making the output
2156
+ locale-aware (e.g. "il y a 3 jours" in French).
2157
+
2158
+ Returns `null` if the input date is invalid.
1116
2159
 
1117
2160
  ```typescript
1118
- import { safeDate } from '@helpers4/date';
2161
+ import { timeAgo } from '@helpers4/date';
1119
2162
 
1120
- safeDate(input: string | number | Date | null | undefined): Date | null
2163
+ timeAgo(date: DateLike, options: TimeAgoOptions): string | null
1121
2164
  ```
1122
2165
 
1123
2166
  **Parameters:**
1124
2167
 
1125
- - `input: string | number | Date | null | undefined` — String, number, or Date input
2168
+ - `date: DateLike` The date to describe relative to `now`
2169
+ - `options: TimeAgoOptions` (default: `{}`) — Optional configuration (reference date, locale, numeric style)
1126
2170
 
1127
- **Returns:** `Date | null` — Valid Date object or null if invalid
2171
+ **Returns:** `string | null` — A locale-aware relative time string, or `null`
1128
2172
 
1129
2173
  **Examples:**
1130
2174
 
1131
- *Parse a valid date string*
1132
-
1133
- Returns a Date object from a valid ISO string.
2175
+ *timeAgo*
1134
2176
 
1135
2177
  ```typescript
1136
- safeDate('2025-01-19T12:00:00Z')
1137
- // => Date(2025-01-19T12:00:00.000Z)
2178
+ ```ts
2179
+ timeAgo('2025-01-17T00:00:00Z', { now: '2025-01-19T00:00:00Z' })
2180
+ // => "2 days ago"
2181
+
2182
+ timeAgo('2025-01-20T00:00:00Z', { now: '2025-01-19T00:00:00Z' })
2183
+ // => "in 1 day" (or "tomorrow" with numeric: 'auto')
2184
+
2185
+ timeAgo('2025-01-19T00:00:00Z', { now: '2025-01-19T00:00:05Z' })
2186
+ // => "5 seconds ago"
2187
+ ```
1138
2188
  ```
1139
2189
 
1140
- *Return null for invalid input*
2190
+ ---
1141
2191
 
1142
- Returns null when the input cannot produce a valid Date.
2192
+ ### `TimeAgoOptions`
1143
2193
 
1144
- ```typescript
1145
- safeDate(null)
1146
- // => null
1147
- ```
2194
+ Options for timeAgo.
1148
2195
 
1149
2196
  ---
1150
2197
 
@@ -1156,12 +2203,12 @@ Format: YYYY-MM-DDTHH:mm:ss.sssZ
1156
2203
  ```typescript
1157
2204
  import { toISO8601 } from '@helpers4/date';
1158
2205
 
1159
- toISO8601(date: string | number | Date): string | null
2206
+ toISO8601(date: DateLike): string | null
1160
2207
  ```
1161
2208
 
1162
2209
  **Parameters:**
1163
2210
 
1164
- - `date: string | number | Date` — Date to convert (Date object, timestamp, or date string)
2211
+ - `date: DateLike` — Date to convert (Date object, timestamp, or date string)
1165
2212
 
1166
2213
  **Returns:** `string | null` — ISO 8601 formatted string or null if invalid date
1167
2214
 
@@ -1196,6 +2243,38 @@ toRFC2822(new Date('2025-01-19T12:30:00Z'))
1196
2243
 
1197
2244
  ---
1198
2245
 
2246
+ ### `toMillis`
2247
+
2248
+ Converts a date to a timestamp in **milliseconds** (epoch millis).
2249
+
2250
+ Use this when you need a plain number from a `DateLike` value
2251
+ (e.g. for `Date.now()` comparisons, localStorage, or JS-native APIs).
2252
+
2253
+ ```typescript
2254
+ import { toMillis } from '@helpers4/date';
2255
+
2256
+ toMillis(date: DateLike): number | null
2257
+ ```
2258
+
2259
+ **Parameters:**
2260
+
2261
+ - `date: DateLike` — The date to convert
2262
+
2263
+ **Returns:** `number | null` — Milliseconds since the Unix epoch, or `null` for invalid input
2264
+
2265
+ **Examples:**
2266
+
2267
+ *toMillis*
2268
+
2269
+ ```typescript
2270
+ ```ts
2271
+ toMillis('2025-01-19T12:00:00Z') // => 1737288000000
2272
+ toMillis(null) // => null
2273
+ ```
2274
+ ```
2275
+
2276
+ ---
2277
+
1199
2278
  ### `toRFC2822`
1200
2279
 
1201
2280
  Converts a date to RFC 2822 format
@@ -1205,12 +2284,12 @@ Used in email headers (Date field) and HTTP headers
1205
2284
  ```typescript
1206
2285
  import { toRFC2822 } from '@helpers4/date';
1207
2286
 
1208
- toRFC2822(date: string | number | Date): string | null
2287
+ toRFC2822(date: DateLike): string | null
1209
2288
  ```
1210
2289
 
1211
2290
  **Parameters:**
1212
2291
 
1213
- - `date: string | number | Date` — Date to convert (Date object, timestamp, or date string)
2292
+ - `date: DateLike` — Date to convert (Date object, timestamp, or date string)
1214
2293
 
1215
2294
  **Returns:** `string | null` — RFC 2822 formatted string or null if invalid date
1216
2295
 
@@ -1235,12 +2314,12 @@ RFC 3339 is a profile of ISO 8601, but without milliseconds by default
1235
2314
  ```typescript
1236
2315
  import { toRFC3339 } from '@helpers4/date';
1237
2316
 
1238
- toRFC3339(date: string | number | Date, includeMilliseconds: boolean): string | null
2317
+ toRFC3339(date: DateLike, includeMilliseconds: boolean): string | null
1239
2318
  ```
1240
2319
 
1241
2320
  **Parameters:**
1242
2321
 
1243
- - `date: string | number | Date` — Date to convert (Date object, timestamp, or date string)
2322
+ - `date: DateLike` — Date to convert (Date object, timestamp, or date string)
1244
2323
  - `includeMilliseconds: boolean` (default: `false`) — Whether to include milliseconds (default: false)
1245
2324
 
1246
2325
  **Returns:** `string | null` — RFC 3339 formatted string or null if invalid date
@@ -1258,6 +2337,67 @@ toRFC3339(new Date('2025-01-19T12:30:45.123Z'), true) // '2025-01-19T12:30:45.12
1258
2337
 
1259
2338
  ---
1260
2339
 
2340
+ ### `toSeconds`
2341
+
2342
+ Converts a date to a timestamp in **seconds** (epoch seconds).
2343
+
2344
+ Use this when sending a date to a backend API that expects seconds
2345
+ (e.g. Java `Instant.getEpochSecond()`, Python `time.time()`).
2346
+
2347
+ ```typescript
2348
+ import { toSeconds } from '@helpers4/date';
2349
+
2350
+ toSeconds(date: DateLike): number | null
2351
+ ```
2352
+
2353
+ **Parameters:**
2354
+
2355
+ - `date: DateLike` — The date to convert
2356
+
2357
+ **Returns:** `number | null` — Seconds since the Unix epoch, or `null` for invalid input
2358
+
2359
+ **Examples:**
2360
+
2361
+ *toSeconds*
2362
+
2363
+ ```typescript
2364
+ ```ts
2365
+ toSeconds('2025-01-19T12:00:00Z') // => 1737288000
2366
+ toSeconds(null) // => null
2367
+ ```
2368
+ ```
2369
+
2370
+ ---
2371
+
2372
+ ### `WeekDay`
2373
+
2374
+ A day-of-week number following the JavaScript `Date.getDay()` convention:
2375
+ 0 = Sunday, 1 = Monday, … 6 = Saturday.
2376
+
2377
+ Prefer using WeekDays named constants for readability:
2378
+ `WeekDays.Monday` instead of `1`.
2379
+
2380
+ ---
2381
+
2382
+ ### `WeekDays`
2383
+
2384
+ Named day-of-week constants following the JavaScript `Date.getDay()`
2385
+ convention. Use these instead of raw numbers for readability.
2386
+
2387
+ **Examples:**
2388
+
2389
+ *WeekDays*
2390
+
2391
+ ```typescript
2392
+ ```ts
2393
+ import { WeekDays } from '@helpers4/date';
2394
+
2395
+ isWeekend('2025-01-17', [WeekDays.Friday, WeekDays.Saturday])
2396
+ ```
2397
+ ```
2398
+
2399
+ ---
2400
+
1261
2401
  ## function
1262
2402
 
1263
2403
  Package: `@helpers4/function`
@@ -3975,6 +5115,204 @@ isSymbol('symbol') // => false
3975
5115
 
3976
5116
  ---
3977
5117
 
5118
+ ### `isTemporalDuration`
5119
+
5120
+ Checks if a value is a `Temporal.Duration`.
5121
+
5122
+ Uses `instanceof` when `Temporal` is available globally, and falls back
5123
+ to `Symbol.toStringTag` for environments without Temporal (e.g. browsers).
5124
+
5125
+ ```typescript
5126
+ import { isTemporalDuration } from '@helpers4/type';
5127
+
5128
+ isTemporalDuration(value: unknown): value
5129
+ ```
5130
+
5131
+ **Parameters:**
5132
+
5133
+ - `value: unknown` — The value to check
5134
+
5135
+ **Returns:** `value` — True if value is a `Temporal.Duration`
5136
+
5137
+ **Examples:**
5138
+
5139
+ *isTemporalDuration*
5140
+
5141
+ ```typescript
5142
+ ```ts
5143
+ isTemporalDuration(Temporal.Duration.from({ hours: 1 })) // => true
5144
+ isTemporalDuration(Temporal.Now.instant()) // => false
5145
+ isTemporalDuration(1000) // => false
5146
+ ```
5147
+ ```
5148
+
5149
+ ---
5150
+
5151
+ ### `isTemporalInstant`
5152
+
5153
+ Checks if a value is a `Temporal.Instant`.
5154
+
5155
+ Uses `instanceof` when `Temporal` is available globally, and falls back
5156
+ to `Symbol.toStringTag` for environments without Temporal (e.g. browsers).
5157
+
5158
+ ```typescript
5159
+ import { isTemporalInstant } from '@helpers4/type';
5160
+
5161
+ isTemporalInstant(value: unknown): value
5162
+ ```
5163
+
5164
+ **Parameters:**
5165
+
5166
+ - `value: unknown` — The value to check
5167
+
5168
+ **Returns:** `value` — True if value is a `Temporal.Instant`
5169
+
5170
+ **Examples:**
5171
+
5172
+ *isTemporalInstant*
5173
+
5174
+ ```typescript
5175
+ ```ts
5176
+ isTemporalInstant(Temporal.Now.instant()) // => true
5177
+ isTemporalInstant(Temporal.Now.plainDateISO()) // => false
5178
+ isTemporalInstant(new Date()) // => false
5179
+ ```
5180
+ ```
5181
+
5182
+ ---
5183
+
5184
+ ### `isTemporalPlainDate`
5185
+
5186
+ Checks if a value is a `Temporal.PlainDate`.
5187
+
5188
+ Uses `instanceof` when `Temporal` is available globally, and falls back
5189
+ to `Symbol.toStringTag` for environments without Temporal (e.g. browsers).
5190
+
5191
+ ```typescript
5192
+ import { isTemporalPlainDate } from '@helpers4/type';
5193
+
5194
+ isTemporalPlainDate(value: unknown): value
5195
+ ```
5196
+
5197
+ **Parameters:**
5198
+
5199
+ - `value: unknown` — The value to check
5200
+
5201
+ **Returns:** `value` — True if value is a `Temporal.PlainDate`
5202
+
5203
+ **Examples:**
5204
+
5205
+ *isTemporalPlainDate*
5206
+
5207
+ ```typescript
5208
+ ```ts
5209
+ isTemporalPlainDate(Temporal.PlainDate.from('2025-01-19')) // => true
5210
+ isTemporalPlainDate(Temporal.Now.instant()) // => false
5211
+ isTemporalPlainDate(new Date()) // => false
5212
+ ```
5213
+ ```
5214
+
5215
+ ---
5216
+
5217
+ ### `isTemporalPlainDateTime`
5218
+
5219
+ Checks if a value is a `Temporal.PlainDateTime`.
5220
+
5221
+ Uses `instanceof` when `Temporal` is available globally, and falls back
5222
+ to `Symbol.toStringTag` for environments without Temporal (e.g. browsers).
5223
+
5224
+ ```typescript
5225
+ import { isTemporalPlainDateTime } from '@helpers4/type';
5226
+
5227
+ isTemporalPlainDateTime(value: unknown): value
5228
+ ```
5229
+
5230
+ **Parameters:**
5231
+
5232
+ - `value: unknown` — The value to check
5233
+
5234
+ **Returns:** `value` — True if value is a `Temporal.PlainDateTime`
5235
+
5236
+ **Examples:**
5237
+
5238
+ *isTemporalPlainDateTime*
5239
+
5240
+ ```typescript
5241
+ ```ts
5242
+ isTemporalPlainDateTime(Temporal.PlainDateTime.from('2025-01-19T12:00')) // => true
5243
+ isTemporalPlainDateTime(Temporal.Now.instant()) // => false
5244
+ isTemporalPlainDateTime(new Date()) // => false
5245
+ ```
5246
+ ```
5247
+
5248
+ ---
5249
+
5250
+ ### `isTemporalPlainTime`
5251
+
5252
+ Checks if a value is a `Temporal.PlainTime`.
5253
+
5254
+ Uses `instanceof` when `Temporal` is available globally, and falls back
5255
+ to `Symbol.toStringTag` for environments without Temporal (e.g. browsers).
5256
+
5257
+ ```typescript
5258
+ import { isTemporalPlainTime } from '@helpers4/type';
5259
+
5260
+ isTemporalPlainTime(value: unknown): value
5261
+ ```
5262
+
5263
+ **Parameters:**
5264
+
5265
+ - `value: unknown` — The value to check
5266
+
5267
+ **Returns:** `value` — True if value is a `Temporal.PlainTime`
5268
+
5269
+ **Examples:**
5270
+
5271
+ *isTemporalPlainTime*
5272
+
5273
+ ```typescript
5274
+ ```ts
5275
+ isTemporalPlainTime(Temporal.PlainTime.from('12:30:00')) // => true
5276
+ isTemporalPlainTime(Temporal.Now.instant()) // => false
5277
+ isTemporalPlainTime(new Date()) // => false
5278
+ ```
5279
+ ```
5280
+
5281
+ ---
5282
+
5283
+ ### `isTemporalZonedDateTime`
5284
+
5285
+ Checks if a value is a `Temporal.ZonedDateTime`.
5286
+
5287
+ Uses `instanceof` when `Temporal` is available globally, and falls back
5288
+ to `Symbol.toStringTag` for environments without Temporal (e.g. browsers).
5289
+
5290
+ ```typescript
5291
+ import { isTemporalZonedDateTime } from '@helpers4/type';
5292
+
5293
+ isTemporalZonedDateTime(value: unknown): value
5294
+ ```
5295
+
5296
+ **Parameters:**
5297
+
5298
+ - `value: unknown` — The value to check
5299
+
5300
+ **Returns:** `value` — True if value is a `Temporal.ZonedDateTime`
5301
+
5302
+ **Examples:**
5303
+
5304
+ *isTemporalZonedDateTime*
5305
+
5306
+ ```typescript
5307
+ ```ts
5308
+ isTemporalZonedDateTime(Temporal.Now.zonedDateTimeISO()) // => true
5309
+ isTemporalZonedDateTime(Temporal.Now.instant()) // => false
5310
+ isTemporalZonedDateTime(new Date()) // => false
5311
+ ```
5312
+ ```
5313
+
5314
+ ---
5315
+
3978
5316
  ### `isTimestamp`
3979
5317
 
3980
5318
  Checks if a value is a valid timestamp (milliseconds or Unix seconds).
@@ -4151,12 +5489,6 @@ isValidRegex('[') // => false
4151
5489
 
4152
5490
  ---
4153
5491
 
4154
- ### `Maybe`
4155
-
4156
- Type for values that can be T, undefined, or null.
4157
-
4158
- ---
4159
-
4160
5492
  ### `Primitive`
4161
5493
 
4162
5494
  Union of all JavaScript primitive types.