@tim-code/my-util 0.5.14 → 0.6.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tim-code/my-util",
3
- "version": "0.5.14",
3
+ "version": "0.6.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "author": "Tim Sprowl",
package/src/array.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * @param {Iterable} iterable
6
6
  * @param {number=} chunkSize If not provided, returns an array consisting of one chunk, which has all the elements of input iterable.
7
7
  * This has the same effect as passing a chunk size that is greater than the number of elements in the iterable.
8
- * @returns {Array}
8
+ * @returns {Array<Array>}
9
9
  */
10
10
  export function chunk(iterable, chunkSize = Infinity) {
11
11
  if (chunkSize !== Infinity && (chunkSize <= 0 || !Number.isInteger(chunkSize))) {
package/src/find.js CHANGED
@@ -181,7 +181,7 @@ export function findClosestGTE(array, desired, { key, cutoff = Infinity } = {})
181
181
  * Find the closest element in an array. If there is a tie, then returns the first matching element by order in the array.
182
182
  * If some values are undefined or null, they will be ignored. If no element is found, returns undefined.
183
183
  * If using for strings, need to specify different values for "cutoff" and "comparator".
184
- * "~" and "" are good cutoff string values for gt/gte and lt/lte respectively.
184
+ * "~" and "" are good cutoff string values for gt/gte and lt/lte respectively.
185
185
  * @template T, V
186
186
  * @param {Array<T>} array
187
187
  * @param {V} value The desired value to search for
@@ -191,8 +191,8 @@ export function findClosestGTE(array, desired, { key, cutoff = Infinity } = {})
191
191
  * If a function, called with the element, index and array (same as .map() callback) to produce the value to sort on.
192
192
  * @param {string=} options.comparator "diff", "lt", "lte", "gt", "gte", "eq". Default is "diff" which implies T is number.
193
193
  * @param {V=} options.cutoff If specified, sets a initial constraint on how close the found value must be.
194
- * If used with lt, lte, value must be greater than or equal to cutoff.
195
- * If used with gt, gte, value must be less than or equal to cutoff.
194
+ * If used with lt/lte, value must be greater than or equal to cutoff.
195
+ * If used with gt/gte, value must be less than or equal to cutoff.
196
196
  * If used with diff, value's difference with desired must be less than or equal to cutoff.
197
197
  * No effect with eq.
198
198
  * @returns {T|undefined}
package/src/math.js CHANGED
@@ -14,8 +14,8 @@ export function mod(number, modulus) {
14
14
  /**
15
15
  * Given two points, returns a function
16
16
  * This function, given an "x" value, returns a "y" value that is on the same line as the first two points.
17
- * @param {[number, number]} firstPoint
18
- * @param {[number, number]} secondPoint
17
+ * @param {[number, number]} point1
18
+ * @param {[number, number]} point2
19
19
  * @returns {Function}
20
20
  */
21
21
  export function line([x1, y1], [x2, y2]) {
@@ -29,8 +29,8 @@ export function line([x1, y1], [x2, y2]) {
29
29
  * Calculate the sum of values in an array.
30
30
  * @template T
31
31
  * @param {Array<T>} array
32
- * @param {Object} [options]
33
- * @param {string|number|((element: T, index: number, array: Array<T>) => number)=} options.key
32
+ * @param {Object} $1
33
+ * @param {string|number|Function=} $1.key Can specify a key of the object or a function.
34
34
  * @returns {number}
35
35
  */
36
36
  export function sum(array, { key } = {}) {
@@ -55,8 +55,8 @@ export function sum(array, { key } = {}) {
55
55
  * Calculate the average (mean) of values in an array.
56
56
  * @template T
57
57
  * @param {Array<T>} array
58
- * @param {Object} [options]
59
- * @param {string|number|((element: T, index: number, array: Array<T>) => number)=} options.key
58
+ * @param {Object} $1
59
+ * @param {string|number|Function=} $1.key Can specify a key of the object or a function.
60
60
  * @returns {number}
61
61
  * @throws {Error} If the array is empty.
62
62
  */
@@ -71,8 +71,8 @@ export function average(array, { key } = {}) {
71
71
  * Calculate the variance of values in an array.
72
72
  * @template T
73
73
  * @param {Array<T>} array
74
- * @param {Object} [options]
75
- * @param {string|number|((element: T, index: number, array: Array<T>) => number)=} options.key
74
+ * @param {Object} $1
75
+ * @param {string|number|Function=} $1.key Can specify a key of the object or a function.
76
76
  * @returns {number}
77
77
  * @throws {Error} If the array is empty.
78
78
  */
@@ -145,7 +145,8 @@ export function range(start, end, increment = 1) {
145
145
  }
146
146
 
147
147
  /**
148
- * Check if the argument is a number (typeof is "number" and not NaN).
148
+ * Check if the argument is a number.
149
+ * This excludes Infinity and NaN, but otherwise is equivalent to `typeof number === "number"`.
149
150
  * @param {any} number
150
151
  * @returns {boolean}
151
152
  */
@@ -163,9 +164,11 @@ export function isNumber(number) {
163
164
  * @param {string|number|Function=} $1.key Can specify a key of the object to sort on or a function.
164
165
  * @param {Function=} $1.method Method to use to choose which element when the percentile index is a fractional value.
165
166
  * Default is Math.round.
167
+ * @param {Function=} $.labeller Function that returns a quantile label given a fractional value (i.e. 33.3...).
168
+ * Default is Math.round.
166
169
  * @returns {Object|undefined} Returns undefined is array is empty
167
170
  */
168
- export function quantiles(array, { N, key, method = Math.round }) {
171
+ export function quantiles(array, { N, key, method = Math.round, labeller = Math.round }) {
169
172
  if (!(N > 0) || !Number.isInteger(N)) {
170
173
  throw new Error("N must be a positive integer")
171
174
  }
@@ -177,7 +180,7 @@ export function quantiles(array, { N, key, method = Math.round }) {
177
180
  for (let i = 0; i <= N; i++) {
178
181
  const percentile = i / N
179
182
  const percentileIndex = method(percentile * (sorted.length - 1))
180
- const label = method(i * (100 / N))
183
+ const label = labeller(i * (100 / N))
181
184
  result[label] = sorted[percentileIndex]
182
185
  }
183
186
  return result
package/src/math.test.js CHANGED
@@ -345,6 +345,9 @@ describe("isNumber", () => {
345
345
  })
346
346
 
347
347
  describe("quantiles", () => {
348
+ // ISSUE: JSDoc for labeller parameter uses "$.labeller" instead of "$1.labeller", which is inconsistent with the other params.
349
+ // ISSUE: If labeller maps multiple percent values to the same label, later entries overwrite earlier ones without warning.
350
+
348
351
  it("maps 0..100 deciles for an already sorted array of length 11 (default rounding)", () => {
349
352
  const arr = Array.from({ length: 11 }, (_, i) => i)
350
353
  const result = quantiles(arr, { N: 10 })
@@ -454,4 +457,24 @@ describe("quantiles", () => {
454
457
  expect(() => quantiles([1, 2, 3], { N: 0 })).toThrow("N must be a positive integer")
455
458
  expect(() => quantiles([1, 2, 3], { N: 2.5 })).toThrow("N must be a positive integer")
456
459
  })
460
+
461
+ it("supports a custom labeller (Math.floor) independent of the index method", () => {
462
+ const arr = Array.from({ length: 11 }, (_, i) => i) // 0..10
463
+ const result = quantiles(arr, { N: 3, labeller: Math.floor }) // labels 0,33,66,100
464
+ expect(result[0]).toBe(0)
465
+ expect(result[33]).toBe(3) // index still uses default Math.round
466
+ expect(result[66]).toBe(7) // label differs from default (which would be 67)
467
+ expect(result[100]).toBe(10)
468
+ })
469
+
470
+ it("supports a custom labeller that produces string labels", () => {
471
+ const arr = Array.from({ length: 9 }, (_, i) => i) // 0..8
472
+ const labeller = (p) => `Q${Math.round(p / 25)}`
473
+ const result = quantiles(arr, { N: 4, labeller })
474
+ expect(result.Q0).toBe(0)
475
+ expect(result.Q1).toBe(2)
476
+ expect(result.Q2).toBe(4)
477
+ expect(result.Q3).toBe(6)
478
+ expect(result.Q4).toBe(8)
479
+ })
457
480
  })
package/src/object.js CHANGED
@@ -10,7 +10,8 @@ export function isObject(thing) {
10
10
  /**
11
11
  * Creates a new object with values created by calling callback on each of argument's values.
12
12
  * @param {Object} object
13
- * @param {Function} callback (value, key, object) => newValue // note if not changing value, should return value
13
+ * @param {Function} callback (value, key, object) => newValue
14
+ * Note if not changing value, should return value.
14
15
  * @returns {Object}
15
16
  */
16
17
  export function mapValues(object, callback) {
@@ -25,7 +26,8 @@ export function mapValues(object, callback) {
25
26
  /**
26
27
  * Mutates the passed in object by calling callback on each of its values.
27
28
  * @param {Object} object
28
- * @param {Function} callback (value, key, object) => newValue // note if not changing value, should return value
29
+ * @param {Function} callback (value, key, object) => newValue
30
+ * Note if not changing value, should return value.
29
31
  * @returns {Object}
30
32
  */
31
33
  export function mutateValues(object, callback) {
@@ -83,6 +85,7 @@ export function like(template) {
83
85
  /**
84
86
  * Copies the source recursively.
85
87
  * Does not preserve constructors of source or constructors of its keys' values.
88
+ * Preserves distinction between an array and an object i.e. `[1]` and `{"0": 1}`.
86
89
  * @template T
87
90
  * @param {T} source
88
91
  * @returns {T}
@@ -105,9 +108,9 @@ export function deepCopy(source) {
105
108
  * the source object's value is a non-array object, recursively merges the source into the target.
106
109
  * Otherwise, assigns source's key's value into the target's key.
107
110
  * This means that arrays are never merged into arrays or other objects.
108
- * @param {Object} target The target object that will receive the merged properties.
109
- * @param {...Object} sources The source objects whose properties will be merged into the target.
110
- * @returns {Object} The target object with the merged properties from all source objects.
111
+ * @param {Object} target The target object that will receive the merged properties
112
+ * @param {...Object} sources The source objects whose properties will be merged into the target
113
+ * @returns {Object} The target object with the merged properties from all source objects
111
114
  */
112
115
  export function deepMerge(target, ...sources) {
113
116
  for (const source of sources) {
@@ -136,7 +139,7 @@ export function deepMerge(target, ...sources) {
136
139
 
137
140
  /**
138
141
  * Merges a deep copy of each source object into target. See deepCopy() and deepMerge() documentation for caveats.
139
- * @param {Object} target The target object that will receive the merged properties.
142
+ * @param {Object} target The target object that will receive the merged properties
140
143
  * @param {...Object} sources The source objects whose properties will be merged into the returned object
141
144
  * @returns {Object}
142
145
  */
@@ -151,11 +154,11 @@ export function deepMergeCopy(target, ...sources) {
151
154
  * Objects and arrays are compared recursively by their properties and elements.
152
155
  * Primitives are compared with strict equality.
153
156
  * Caveats:
154
- * Does not check class: [1] is considered equal to {0: 1}.
155
- * Any Symbol keys in the arguments are ignored (Object.keys only returns string keys).
156
- * @param {any} a The first value to compare.
157
- * @param {any} b The second value to compare.
158
- * @returns {boolean} True if the values are deeply equal, false otherwise.
157
+ * Does not check class: `[1]` is considered equal to `{"0": 1}`.
158
+ * Any `Symbol` keys in the arguments are ignored (Object.keys only returns string keys).
159
+ * @param {any} a The first value to compare
160
+ * @param {any} b The second value to compare
161
+ * @returns {boolean} True if the values are deeply equal, false otherwise
159
162
  */
160
163
  export function deepEqual(a, b) {
161
164
  if (a === b) {
package/src/promise.js CHANGED
@@ -59,35 +59,36 @@ export async function sleep(ms) {
59
59
  * Parallelize executions of a function using `Promise.allSettled()`.
60
60
  * This is useful because usually you want to set a limit to the number of parallel requests possible at once.
61
61
  * The output of this function contains the outcome of each call of the callback in a variety of formats.
62
- * - "results": Essentially the return value from Promise.allSettled().
62
+ * - "results": An array with the same elements as returned by Promise.allSettled().
63
63
  * - "values": The "value" property of each "result" object returned from allSettled(); this will be undefined if the associated promise rejects.
64
64
  * - "returned": The "value" property for each "result" object where the "status" property has a value of fulfilled.
65
65
  * This is arguably more useful than "values" unless you need to be able to index into the array based on the index of the promise.
66
66
  * - "errors": The "reason" property for each "result" object that did not have a status of "fulfilled".
67
67
  * @param {Object} $1
68
- * @param {Array} $1.array
68
+ * @param {Iterable} $1.array
69
+ * @param {Iterable=} $1.iterable An alternative, more accurate property name for specifying "array"
69
70
  * @param {number=} $1.limit The number of calls to do in parallel. If not provided, each call is done in parallel.
70
71
  * @param {Function=} $1.limiter A function awaited after a group of parallel calls is processed.
71
72
  * It is called with the number of parallel calls processed. Could be as simple as `() => sleep(10000)` if you wanted to wait 10 seconds between.
72
- * @param {boolean=} $1.flatten Flattens values before returning; useful if promises return arrays
73
+ * @param {boolean=} $1.flatten Flattens "values" and "returned" before returning; useful if promises return arrays
73
74
  * @param {boolean=} $1.abort If true, will return early if there are errors.
74
75
  * If false (default), will process all elements in the array (like Promise.allSettled()).
75
76
  * @param {Function} callback Default is identity function to enable passing promises as "array".
76
77
  * @returns {Object} {results, values, returned, errors}
77
78
  */
78
79
  export async function allSettled(
79
- { array, limit, limiter, flatten = false, abort = false },
80
+ { array, iterable = array, limit, limiter, flatten = false, abort = false },
80
81
  callback = (promise) => promise
81
82
  ) {
82
83
  const results = []
83
84
  let returned = []
84
85
  let values = []
85
86
  const errors = []
86
- const chunked = chunk(array, limit)
87
+ const chunked = chunk(iterable, limit)
87
88
  for (const elements of chunked) {
88
89
  const promises = elements.map(callback)
89
- const _results = await Promise.allSettled(promises)
90
- for (const result of _results) {
90
+ const chunkResults = await Promise.allSettled(promises)
91
+ for (const result of chunkResults) {
91
92
  const { value, status, reason } = result
92
93
  results.push(result)
93
94
  values.push(value)
@@ -1,6 +1,11 @@
1
1
  /* eslint-disable prefer-promise-reject-errors */
2
2
  import { jest } from "@jest/globals"
3
3
 
4
+ // Exported API under test:
5
+ // - class PollError
6
+ // - functions: poll, sleep, allSettled, intervalLimiter, alert, throwFirstReject
7
+ // Code changes in this diff affect: allSettled (added "iterable" param and generalized to Iterable)
8
+
4
9
  import {
5
10
  alert,
6
11
  allSettled,
@@ -214,6 +219,20 @@ describe("allSettled", () => {
214
219
  expect(cb).toHaveBeenCalledTimes(2)
215
220
  // Should not process [3,4] or [5,6]
216
221
  })
222
+
223
+ it("accepts non-Array iterable via iterable option (Map)", async () => {
224
+ const map = new Map([
225
+ ["a", 1],
226
+ ["b", 2],
227
+ ["c", 3],
228
+ ])
229
+ const cb = ([k, v]) => `${k}:${v * 2}`
230
+ const result = await allSettled({ iterable: map, limit: 2 }, cb)
231
+ expect(result.values).toEqual(["a:2", "b:4", "c:6"])
232
+ expect(result.returned).toEqual(["a:2", "b:4", "c:6"])
233
+ expect(result.errors).toEqual([])
234
+ expect(result.results.every((r) => r.status === "fulfilled")).toBe(true)
235
+ })
217
236
  })
218
237
 
219
238
  describe("intervalLimiter", () => {
package/src/time.js CHANGED
@@ -5,13 +5,13 @@ import { mod } from "./math.js"
5
5
  * @param {Object} $1
6
6
  * @param {boolean=} $1.floorMinute If true, floors to the nearest minute. If false, floors to the nearest second.
7
7
  * @param {number=} $1.timestamp Unix timestamp to use instead of current time.
8
- * @param {string=} $1.timezone Timezone to use instead of Eastern time.
8
+ * @param {string=} $1.timeZone Time zone to use instead of Eastern time. A falsy value corresponds to local time.
9
9
  * @returns {Object} { timestamp, date: YYYY-MM-DD, time: HH:mm:ss }
10
10
  */
11
11
  export function getEasternTime({
12
12
  floorMinute = false,
13
13
  timestamp = undefined,
14
- timezone = "America/New_York",
14
+ timeZone = "America/New_York",
15
15
  } = {}) {
16
16
  if (!timestamp) {
17
17
  timestamp = new Date().getTime() / 1000
@@ -19,7 +19,7 @@ export function getEasternTime({
19
19
  timestamp = floorMinute ? Math.floor(timestamp / 60) * 60 : Math.floor(timestamp)
20
20
  // 'en-CA' (English - Canada) formats dates as YYYY-MM-DD and times in 24-hour format by default
21
21
  const string = new Date(timestamp * 1000).toLocaleString("en-CA", {
22
- ...(timezone ? { timeZone: timezone } : {}),
22
+ ...(timeZone ? { timeZone } : {}),
23
23
  hour12: false,
24
24
  year: "numeric",
25
25
  month: "2-digit",
@@ -37,11 +37,11 @@ export function getEasternTime({
37
37
  * @param {Object} $1
38
38
  * @param {boolean=} $1.floorMinute If true, floors to the nearest minute. If false, floors to the nearest second.
39
39
  * @param {number=} $1.timestamp Unix timestamp to use instead of current time.
40
- * @param {string=} $1.timezone Timezone to use instead of local time. undefined corresponds to "America/New_York" and "" (falsy) corresponds to local time.
40
+ * @param {string=} $1.timeZone Time zone to use instead of local time. A falsy value (default) corresponds to local time.
41
41
  * @returns {Object} { timestamp, date, time, minute, datetime }
42
42
  */
43
- export function getTime({ floorMinute, timestamp, timezone = "" } = {}) {
44
- return getEasternTime({ floorMinute, timestamp, timezone })
43
+ export function getTime({ floorMinute, timestamp, timeZone = false } = {}) {
44
+ return getEasternTime({ floorMinute, timestamp, timeZone })
45
45
  }
46
46
 
47
47
  /**
@@ -82,7 +82,7 @@ export function getMinute(time) {
82
82
  * @param {string} string
83
83
  * @returns {boolean}
84
84
  */
85
- export function isDate(string) {
85
+ export function isDateString(string) {
86
86
  const match = /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/u.exec(string)
87
87
  if (!match) {
88
88
  return false
@@ -96,15 +96,33 @@ export function isDate(string) {
96
96
  )
97
97
  }
98
98
 
99
+ /**
100
+ * @deprecated Prefer isDateString()
101
+ * @param {string} string
102
+ * @returns {boolean}
103
+ */
104
+ export function isDate(string) {
105
+ return isDateString(string)
106
+ }
107
+
99
108
  /**
100
109
  * Checks if the string represent a valid HH:mm:ss time.
101
110
  * @param {string} string
102
111
  * @returns {boolean}
103
112
  */
104
- export function isTime(string) {
113
+ export function isTimeString(string) {
105
114
  return /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$/u.test(string)
106
115
  }
107
116
 
117
+ /**
118
+ * @deprecated Prefer isTimeString()
119
+ * @param {string} string
120
+ * @returns {boolean}
121
+ */
122
+ export function isTime(string) {
123
+ return isTimeString(string)
124
+ }
125
+
108
126
  /**
109
127
  * Checks if a number is a Unix timestamp (i.e. in seconds).
110
128
  * Would not validate timestamps set very far into the future.
@@ -173,7 +191,8 @@ export function getTimeRange(start, end, { hours = 0, minutes = 1 } = {}) {
173
191
  /**
174
192
  * Adds a number of days to a date string.
175
193
  * @param {string} dateString YYYY-MM-DD
176
- * @param {number} days
194
+ * @param {Object} $1
195
+ * @param {number} $1.days
177
196
  * @returns {string}
178
197
  */
179
198
  export function addDays(dateString, { days = 0 } = {}) {
package/src/time.test.js CHANGED
@@ -4,14 +4,15 @@ import {
4
4
  addTime,
5
5
  getDateRange,
6
6
  getDayIndexInWeek,
7
- // getDayOfWeek, // removed, replaced by getDayIndexInWeek
8
7
  getEasternTime,
9
8
  getMinute,
10
9
  getStartOfWeek,
11
10
  getTime,
12
11
  getTimeRange,
13
12
  isDate,
13
+ isDateString,
14
14
  isTime,
15
+ isTimeString,
15
16
  isUnixTimestamp,
16
17
  today,
17
18
  } from "./time.js"
@@ -54,15 +55,15 @@ describe("getEasternTime", () => {
54
55
  expect(def.timestamp).toEqual(explicit.timestamp)
55
56
  })
56
57
 
57
- test("respects timezone parameter", () => {
58
+ test("respects timeZone parameter", () => {
58
59
  // 2024-06-01T12:34:56Z (UTC)
59
60
  const ts = 1717245296
60
61
  // New York (EDT, UTC-4)
61
- const eastern = getEasternTime({ timestamp: ts, timezone: "America/New_York" })
62
+ const eastern = getEasternTime({ timestamp: ts, timeZone: "America/New_York" })
62
63
  // Los Angeles (PDT, UTC-7)
63
- const pacific = getEasternTime({ timestamp: ts, timezone: "America/Los_Angeles" })
64
+ const pacific = getEasternTime({ timestamp: ts, timeZone: "America/Los_Angeles" })
64
65
  // UTC
65
- const utc = getEasternTime({ timestamp: ts, timezone: "UTC" })
66
+ const utc = getEasternTime({ timestamp: ts, timeZone: "UTC" })
66
67
  expect(eastern.time).not.toBe(pacific.time)
67
68
  expect(eastern.time).not.toBe(utc.time)
68
69
  expect(pacific.time).not.toBe(utc.time)
@@ -71,11 +72,11 @@ describe("getEasternTime", () => {
71
72
  expect(utc.time.startsWith("12:34")).toBe(true) // UTC
72
73
  })
73
74
 
74
- test("returns local time if timezone is empty string", () => {
75
- // If timezone is falsy (""), should use local time zone
75
+ test("returns local time if timeZone is empty string", () => {
76
+ // If timeZone is falsy (""), should use local time zone
76
77
  // We'll compare to Date.toLocaleString with no timeZone option
77
78
  const ts = 1717245296
78
- const local = getEasternTime({ timestamp: ts, timezone: "" })
79
+ const local = getEasternTime({ timestamp: ts, timeZone: "" })
79
80
  const expected = new Date(ts * 1000).toLocaleString("en-US", {
80
81
  hour12: false,
81
82
  year: "numeric",
@@ -141,8 +142,8 @@ describe("getTime", () => {
141
142
  expect(result).not.toHaveProperty("datetime")
142
143
  })
143
144
 
144
- test("defaults to local time if timezone is not provided", () => {
145
- // getTime({}) should use timezone = false, which disables the timeZone option and uses local
145
+ test("defaults to local time if timeZone is not provided", () => {
146
+ // getTime({}) should use timeZone = false, which disables the timeZone option and uses local
146
147
  const ts = 1717245296
147
148
  const local = getTime({ timestamp: ts })
148
149
  const expected = new Date(ts * 1000).toLocaleString("en-US", {
@@ -161,11 +162,11 @@ describe("getTime", () => {
161
162
  expect(local.time).toBe(time)
162
163
  })
163
164
 
164
- test("passes timezone through to getEasternTime", () => {
165
- // Should match getEasternTime with same timezone
165
+ test("passes timeZone through to getEasternTime", () => {
166
+ // Should match getEasternTime with same timeZone
166
167
  const ts = 1717245296
167
- const pacific = getTime({ timestamp: ts, timezone: "America/Los_Angeles" })
168
- const ref = getEasternTime({ timestamp: ts, timezone: "America/Los_Angeles" })
168
+ const pacific = getTime({ timestamp: ts, timeZone: "America/Los_Angeles" })
169
+ const ref = getEasternTime({ timestamp: ts, timeZone: "America/Los_Angeles" })
169
170
  expect(pacific).toEqual(ref)
170
171
  })
171
172
 
@@ -208,7 +209,7 @@ describe("getDayIndexInWeek", () => {
208
209
 
209
210
  test("throws on invalid date strings", () => {
210
211
  expect(() => getDayIndexInWeek("not-a-date")).toThrow(/invalid date/u)
211
- // don't worry about bad dates like the following; can be caught with isDate()
212
+ // don't worry about bad dates like the following; can be caught with isDateString()
212
213
  expect(getDayIndexInWeek("2024-02-31")).toBe(6)
213
214
  })
214
215
  })
@@ -234,7 +235,19 @@ describe("getMinute", () => {
234
235
  })
235
236
  })
236
237
 
237
- describe("isDate", () => {
238
+ describe("isDateString", () => {
239
+ test("validates correct dates", () => {
240
+ expect(isDateString("2024-06-01")).toBe(true)
241
+ expect(isDateString("1999-12-31")).toBe(true)
242
+ })
243
+
244
+ test("rejects invalid dates and formats", () => {
245
+ expect(isDateString("2024-02-31")).toBe(false)
246
+ expect(isDateString("2024/06/01")).toBe(false)
247
+ })
248
+ })
249
+
250
+ describe("isDate (deprecated wrapper)", () => {
238
251
  test("returns true for valid YYYY-MM-DD dates", () => {
239
252
  expect(isDate("2024-06-01")).toBe(true)
240
253
  expect(isDate("1999-12-31")).toBe(true)
@@ -266,7 +279,19 @@ describe("isDate", () => {
266
279
  })
267
280
  })
268
281
 
269
- describe("isTime", () => {
282
+ describe("isTimeString", () => {
283
+ test("validates correct times", () => {
284
+ expect(isTimeString("00:00:00")).toBe(true)
285
+ expect(isTimeString("23:59:59")).toBe(true)
286
+ })
287
+
288
+ test("rejects invalid times and formats", () => {
289
+ expect(isTimeString("24:00:00")).toBe(false)
290
+ expect(isTimeString("12:34")).toBe(false)
291
+ })
292
+ })
293
+
294
+ describe("isTime (deprecated wrapper)", () => {
270
295
  test("returns true for valid HH:mm:ss times", () => {
271
296
  expect(isTime("00:00:00")).toBe(true)
272
297
  expect(isTime("23:59:59")).toBe(true)