@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 +1 -1
- package/src/array.js +1 -1
- package/src/find.js +3 -3
- package/src/math.js +14 -11
- package/src/math.test.js +23 -0
- package/src/object.js +14 -11
- package/src/promise.js +8 -7
- package/src/promise.test.js +19 -0
- package/src/time.js +28 -9
- package/src/time.test.js +42 -17
package/package.json
CHANGED
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
|
-
*
|
|
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
|
|
195
|
-
* If used with gt
|
|
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]}
|
|
18
|
-
* @param {[number, number]}
|
|
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}
|
|
33
|
-
* @param {string|number|
|
|
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}
|
|
59
|
-
* @param {string|number|
|
|
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}
|
|
75
|
-
* @param {string|number|
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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":
|
|
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 {
|
|
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(
|
|
87
|
+
const chunked = chunk(iterable, limit)
|
|
87
88
|
for (const elements of chunked) {
|
|
88
89
|
const promises = elements.map(callback)
|
|
89
|
-
const
|
|
90
|
-
for (const result of
|
|
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)
|
package/src/promise.test.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
...(
|
|
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.
|
|
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,
|
|
44
|
-
return getEasternTime({ floorMinute, timestamp,
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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,
|
|
62
|
+
const eastern = getEasternTime({ timestamp: ts, timeZone: "America/New_York" })
|
|
62
63
|
// Los Angeles (PDT, UTC-7)
|
|
63
|
-
const pacific = getEasternTime({ timestamp: ts,
|
|
64
|
+
const pacific = getEasternTime({ timestamp: ts, timeZone: "America/Los_Angeles" })
|
|
64
65
|
// UTC
|
|
65
|
-
const utc = getEasternTime({ timestamp: ts,
|
|
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
|
|
75
|
-
// If
|
|
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,
|
|
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
|
|
145
|
-
// getTime({}) should use
|
|
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
|
|
165
|
-
// Should match getEasternTime with same
|
|
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,
|
|
168
|
-
const ref = getEasternTime({ timestamp: ts,
|
|
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
|
|
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("
|
|
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("
|
|
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)
|