@tim-code/my-util 0.5.15 → 0.6.1
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/eslint.config.js +1 -0
- package/package.json +4 -13
- package/src/array.js +1 -2
- package/src/find.js +4 -4
- package/src/math.js +14 -11
- package/src/math.test.js +23 -0
- package/src/object.js +14 -11
- package/src/time.js +29 -10
- package/src/time.test.js +42 -17
package/eslint.config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "@tim-code/eslint-config"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tim-code/my-util",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Tim Sprowl",
|
|
@@ -21,24 +21,15 @@
|
|
|
21
21
|
"scripts": {
|
|
22
22
|
"test": "node --no-warnings --experimental-vm-modules node_modules/.bin/jest"
|
|
23
23
|
},
|
|
24
|
-
"eslintConfig": {
|
|
25
|
-
"root": true,
|
|
26
|
-
"extends": [
|
|
27
|
-
"@tim-code"
|
|
28
|
-
]
|
|
29
|
-
},
|
|
30
24
|
"devDependencies": {
|
|
31
|
-
"@jest/globals": "^
|
|
32
|
-
"@tim-code/eslint-config": "^
|
|
33
|
-
"jest": "^
|
|
25
|
+
"@jest/globals": "^30.2.0",
|
|
26
|
+
"@tim-code/eslint-config": "^2.0.1",
|
|
27
|
+
"jest": "^30.2.0"
|
|
34
28
|
},
|
|
35
29
|
"jest": {
|
|
36
30
|
"transform": {}
|
|
37
31
|
},
|
|
38
32
|
"publishConfig": {
|
|
39
33
|
"access": "public"
|
|
40
|
-
},
|
|
41
|
-
"dependencies": {
|
|
42
|
-
"@tim-code/my-util": "^0.5.5"
|
|
43
34
|
}
|
|
44
35
|
}
|
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))) {
|
|
@@ -194,7 +194,6 @@ export function multilevel(...comparators) {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
function siftDown(heap, i, compare, length) {
|
|
197
|
-
// eslint-disable-next-line no-constant-condition
|
|
198
197
|
while (true) {
|
|
199
198
|
const left = 2 * i + 1
|
|
200
199
|
const right = left + 1
|
package/src/find.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable complexity */
|
|
1
2
|
export function findEq(array, desired, { key } = {}) {
|
|
2
3
|
if (typeof key === "function") {
|
|
3
4
|
for (let i = 0; i < array.length; i++) {
|
|
@@ -181,7 +182,7 @@ export function findClosestGTE(array, desired, { key, cutoff = Infinity } = {})
|
|
|
181
182
|
* Find the closest element in an array. If there is a tie, then returns the first matching element by order in the array.
|
|
182
183
|
* If some values are undefined or null, they will be ignored. If no element is found, returns undefined.
|
|
183
184
|
* If using for strings, need to specify different values for "cutoff" and "comparator".
|
|
184
|
-
*
|
|
185
|
+
* "~" and "" are good cutoff string values for gt/gte and lt/lte respectively.
|
|
185
186
|
* @template T, V
|
|
186
187
|
* @param {Array<T>} array
|
|
187
188
|
* @param {V} value The desired value to search for
|
|
@@ -191,8 +192,8 @@ export function findClosestGTE(array, desired, { key, cutoff = Infinity } = {})
|
|
|
191
192
|
* If a function, called with the element, index and array (same as .map() callback) to produce the value to sort on.
|
|
192
193
|
* @param {string=} options.comparator "diff", "lt", "lte", "gt", "gte", "eq". Default is "diff" which implies T is number.
|
|
193
194
|
* @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
|
|
195
|
+
* If used with lt/lte, value must be greater than or equal to cutoff.
|
|
196
|
+
* If used with gt/gte, value must be less than or equal to cutoff.
|
|
196
197
|
* If used with diff, value's difference with desired must be less than or equal to cutoff.
|
|
197
198
|
* No effect with eq.
|
|
198
199
|
* @returns {T|undefined}
|
|
@@ -316,7 +317,6 @@ export function findMax(array, { key, cutoff = -Infinity } = {}) {
|
|
|
316
317
|
* When true, changes the default values of "from", "until", "to" to `array.length - 1`, `-1`, `0`.
|
|
317
318
|
* @returns {T}
|
|
318
319
|
*/
|
|
319
|
-
// eslint-disable-next-line complexity
|
|
320
320
|
export function findTruthy(
|
|
321
321
|
array,
|
|
322
322
|
{
|
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/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,15 +37,15 @@ 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
|
/**
|
|
48
|
-
* Get today's date in YYYY-MM-DD format.
|
|
48
|
+
* Get today's date in YYYY-MM-DD format using local time.
|
|
49
49
|
* @returns {string}
|
|
50
50
|
*/
|
|
51
51
|
export function today() {
|
|
@@ -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)
|