@tim-code/my-util 0.7.3 → 0.8.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/package.json +1 -1
- package/src/array.js +6 -0
- package/src/array.test.js +24 -0
- package/src/time.js +53 -15
- package/src/time.test.js +67 -2
package/package.json
CHANGED
package/src/array.js
CHANGED
|
@@ -194,6 +194,12 @@ function naturalCompare(a, b) {
|
|
|
194
194
|
b = b.slice(1, b.length)
|
|
195
195
|
return b.localeCompare(a, undefined, { numeric: true })
|
|
196
196
|
}
|
|
197
|
+
if (a[0] === "+" && !Number.isNaN(Number(a))) {
|
|
198
|
+
a = a.slice(1, a.length)
|
|
199
|
+
}
|
|
200
|
+
if (b[0] === "+" && !Number.isNaN(Number(b))) {
|
|
201
|
+
b = b.slice(1, b.length)
|
|
202
|
+
}
|
|
197
203
|
return a.localeCompare(b, undefined, { numeric: true })
|
|
198
204
|
}
|
|
199
205
|
|
package/src/array.test.js
CHANGED
|
@@ -542,6 +542,18 @@ describe("naturalAsc", () => {
|
|
|
542
542
|
arr.sort(naturalAsc())
|
|
543
543
|
expect(arr).toEqual(["-10", "-2", "-1"])
|
|
544
544
|
})
|
|
545
|
+
|
|
546
|
+
it("can sort negative to positive", () => {
|
|
547
|
+
const arr = ["-2", "+5", "0"]
|
|
548
|
+
arr.sort(naturalAsc())
|
|
549
|
+
expect(arr).toEqual(["-2", "0", "+5"])
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
it("handles leading plus signs on numeric-like strings", () => {
|
|
553
|
+
const arr = ["a", "+10", "+2", "+1"]
|
|
554
|
+
arr.sort(naturalAsc())
|
|
555
|
+
expect(arr).toEqual(["+1", "+2", "+10", "a"])
|
|
556
|
+
})
|
|
545
557
|
})
|
|
546
558
|
|
|
547
559
|
describe("naturalDesc", () => {
|
|
@@ -586,6 +598,18 @@ describe("naturalDesc", () => {
|
|
|
586
598
|
arr.sort(naturalDesc())
|
|
587
599
|
expect(arr).toEqual(["-1", "-2", "-10"])
|
|
588
600
|
})
|
|
601
|
+
|
|
602
|
+
it("can sort positive to negative", () => {
|
|
603
|
+
const arr = ["+5", "-2", "0"]
|
|
604
|
+
arr.sort(naturalDesc())
|
|
605
|
+
expect(arr).toEqual(["+5", "0", "-2"])
|
|
606
|
+
})
|
|
607
|
+
|
|
608
|
+
it("handles leading plus signs on numeric-like strings (descending)", () => {
|
|
609
|
+
const arr = ["+1", "+2", "+10", "a"]
|
|
610
|
+
arr.sort(naturalDesc())
|
|
611
|
+
expect(arr).toEqual(["a", "+10", "+2", "+1"])
|
|
612
|
+
})
|
|
589
613
|
})
|
|
590
614
|
|
|
591
615
|
describe("multilevel", () => {
|
package/src/time.js
CHANGED
|
@@ -3,22 +3,28 @@ import { mod } from "./math.js"
|
|
|
3
3
|
/**
|
|
4
4
|
* Gets various ways of representing the current time in EDT. Floors to nearest second by default.
|
|
5
5
|
* @param {Object} $1
|
|
6
|
-
* @param {
|
|
7
|
-
* @param {
|
|
8
|
-
*
|
|
6
|
+
* @param {number=} $1.timestamp Unix timestamp to use instead of now (in seconds).
|
|
7
|
+
* @param {Date=} $1.dateInstance Alternative to specifying timestamp that is a Date instance.
|
|
8
|
+
* This can be used to specify UTC: `getEasternTime({ dateInstance: new Date(utc) })`.
|
|
9
|
+
* @param {boolean=} $1.floorMinute If true, floors to the nearest minute. If false, floors to the nearest second (default).
|
|
10
|
+
* @param {string=} $1.timeZone Time zone to use instead of Eastern time. A falsy, not-undefined value corresponds to local time.
|
|
9
11
|
* @returns {Object} { timestamp, date: YYYY-MM-DD, time: HH:mm:ss }
|
|
12
|
+
* If invalid timestamp or dateInstance specified: { timestamp: NaN|Infinity, date: "Invalid date", time: undefined }
|
|
10
13
|
*/
|
|
11
14
|
export function getEasternTime({
|
|
15
|
+
dateInstance = undefined,
|
|
16
|
+
timestamp = (dateInstance ?? new Date()).getTime() / 1000,
|
|
12
17
|
floorMinute = false,
|
|
13
|
-
timestamp = undefined,
|
|
14
18
|
timeZone = "America/New_York",
|
|
15
19
|
} = {}) {
|
|
16
|
-
if (
|
|
17
|
-
timestamp =
|
|
20
|
+
if (floorMinute) {
|
|
21
|
+
timestamp = Math.floor(timestamp / 60) * 60
|
|
22
|
+
} else {
|
|
23
|
+
timestamp = Math.floor(timestamp)
|
|
18
24
|
}
|
|
19
|
-
|
|
25
|
+
const flooredDateInstance = new Date(timestamp * 1000)
|
|
20
26
|
// 'en-CA' (English - Canada) formats dates as YYYY-MM-DD and times in 24-hour format by default
|
|
21
|
-
const string =
|
|
27
|
+
const string = flooredDateInstance.toLocaleString("en-CA", {
|
|
22
28
|
...(timeZone ? { timeZone } : {}),
|
|
23
29
|
hour12: false,
|
|
24
30
|
year: "numeric",
|
|
@@ -35,13 +41,16 @@ export function getEasternTime({
|
|
|
35
41
|
/**
|
|
36
42
|
* Gets various ways of representing the current time in local timezone. Floors to nearest second by default.
|
|
37
43
|
* @param {Object} $1
|
|
44
|
+
* @param {number=} $1.timestamp Unix timestamp to use instead of now (in seconds).
|
|
45
|
+
* @param {Date=} $1.dateInstance Alternative to specifying timestamp that is a Date instance.
|
|
46
|
+
* This can be used to specify UTC: `getEasternTime({ dateInstance: new Date(utc) })`.
|
|
38
47
|
* @param {boolean=} $1.floorMinute If true, floors to the nearest minute. If false, floors to the nearest second.
|
|
39
|
-
* @param {
|
|
40
|
-
* @
|
|
41
|
-
*
|
|
48
|
+
* @param {string=} $1.timeZone Time zone to use instead of local time. A falsy value corresponds to local time (default) .
|
|
49
|
+
* @returns {Object} { timestamp, date: YYYY-MM-DD, time: HH:mm:ss }
|
|
50
|
+
* If invalid timestamp or dateInstance specified: { timestamp: NaN|Infinity, date: "Invalid date", time: undefined }
|
|
42
51
|
*/
|
|
43
|
-
export function getTime({
|
|
44
|
-
return getEasternTime({
|
|
52
|
+
export function getTime({ timestamp, dateInstance, floorMinute, timeZone = false } = {}) {
|
|
53
|
+
return getEasternTime({ timestamp, dateInstance, floorMinute, timeZone })
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
/**
|
|
@@ -78,7 +87,7 @@ export function getDayIndexInWeek(string = today()) {
|
|
|
78
87
|
|
|
79
88
|
/**
|
|
80
89
|
* Get the minute from a time string.
|
|
81
|
-
* @param {string} time HH:mm or HH:mm
|
|
90
|
+
* @param {string} time HH:mm or HH:mm:ss
|
|
82
91
|
* @returns {number} Between 0 and 59 inclusive
|
|
83
92
|
*/
|
|
84
93
|
export function getMinute(time) {
|
|
@@ -107,7 +116,9 @@ export function isDateString(string) {
|
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
/**
|
|
110
|
-
* Checks if the string
|
|
119
|
+
* Checks if the string represents a valid HH:mm:ss time.
|
|
120
|
+
* This will return false for times like "24:00:00".
|
|
121
|
+
* Does not handle milliseconds.
|
|
111
122
|
* @param {string} string
|
|
112
123
|
* @returns {boolean}
|
|
113
124
|
*/
|
|
@@ -115,6 +126,33 @@ export function isTimeString(string) {
|
|
|
115
126
|
return /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$/u.test(string)
|
|
116
127
|
}
|
|
117
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Checks if the string represents a valid date time string similar to YYYY-MM-DDTHH:mm:ss.
|
|
131
|
+
* Does not handle milliseconds.
|
|
132
|
+
* @param {string} string
|
|
133
|
+
* @param {Object} $1
|
|
134
|
+
* @param {string=} $1.separator Can specify a different string separator between date and time. Defaults to "T".
|
|
135
|
+
* @returns {boolean}
|
|
136
|
+
*/
|
|
137
|
+
export function isDateTimeString(string, { separator = "T" } = {}) {
|
|
138
|
+
const [date, time] = string.split(separator)
|
|
139
|
+
return isDateString(date) && isTimeString(time)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Checks if the string represents a valid UTC date time similar to YYYY-MM-DDTHH:mm:ssZ.
|
|
144
|
+
* Does not handle milliseconds or UTC offsets.
|
|
145
|
+
* @param {string} string
|
|
146
|
+
* @returns {boolean}
|
|
147
|
+
*/
|
|
148
|
+
export function isUTCString(string) {
|
|
149
|
+
if (!string.endsWith("Z")) {
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
const datetime = string.slice(0, string.length - 1)
|
|
153
|
+
return isDateTimeString(datetime)
|
|
154
|
+
}
|
|
155
|
+
|
|
118
156
|
/**
|
|
119
157
|
* Checks if a number is a Unix timestamp (i.e. in seconds).
|
|
120
158
|
* Would not validate timestamps set very far into the future.
|
package/src/time.test.js
CHANGED
|
@@ -11,11 +11,18 @@ import {
|
|
|
11
11
|
getTimeRange,
|
|
12
12
|
getUnixTimestamp,
|
|
13
13
|
isDateString,
|
|
14
|
+
isDateTimeString,
|
|
14
15
|
isTimeString,
|
|
16
|
+
isUTCString,
|
|
15
17
|
isUnixTimestamp,
|
|
16
18
|
today,
|
|
17
19
|
} from "./time.js"
|
|
18
20
|
|
|
21
|
+
// Exported functions:
|
|
22
|
+
// getEasternTime, getTime, getUnixTimestamp, today, getDayIndexInWeek, getMinute,
|
|
23
|
+
// isDateString, isTimeString, isDateTimeString, isUTCString, isUnixTimestamp,
|
|
24
|
+
// addTime, getTimeRange, addDays, getDateRange, getStartOfWeek
|
|
25
|
+
|
|
19
26
|
describe("getEasternTime", () => {
|
|
20
27
|
test("returns correct structure and types", () => {
|
|
21
28
|
const result = getEasternTime()
|
|
@@ -46,6 +53,22 @@ describe("getEasternTime", () => {
|
|
|
46
53
|
expect(floored.time.endsWith(":00")).toBe(true)
|
|
47
54
|
})
|
|
48
55
|
|
|
56
|
+
test("accepts dateInstance and matches equivalent timestamp", () => {
|
|
57
|
+
const ts = 1717245296 // 2024-06-01T12:34:56Z
|
|
58
|
+
const di = new Date(ts * 1000)
|
|
59
|
+
const a = getEasternTime({ dateInstance: di, timeZone: "UTC" })
|
|
60
|
+
const b = getEasternTime({ timestamp: ts, timeZone: "UTC" })
|
|
61
|
+
expect(a).toEqual(b)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test("dateInstance path respects floorMinute", () => {
|
|
65
|
+
const ts = 1717245296 // ...:56
|
|
66
|
+
const di = new Date(ts * 1000)
|
|
67
|
+
const floored = getEasternTime({ dateInstance: di, floorMinute: true, timeZone: "UTC" })
|
|
68
|
+
expect(floored.timestamp % 60).toBe(0)
|
|
69
|
+
expect(floored.time).toBe("12:34:00")
|
|
70
|
+
})
|
|
71
|
+
|
|
49
72
|
test("default parameters yield consistent output", () => {
|
|
50
73
|
const def = getEasternTime()
|
|
51
74
|
const explicit = getEasternTime({ floorMinute: false })
|
|
@@ -169,6 +192,14 @@ describe("getTime", () => {
|
|
|
169
192
|
expect(pacific).toEqual(ref)
|
|
170
193
|
})
|
|
171
194
|
|
|
195
|
+
test("accepts dateInstance and matches equivalent timestamp", () => {
|
|
196
|
+
const ts = 1717245296
|
|
197
|
+
const di = new Date(ts * 1000)
|
|
198
|
+
const a = getTime({ dateInstance: di, timeZone: "UTC" })
|
|
199
|
+
const b = getTime({ timestamp: ts, timeZone: "UTC" })
|
|
200
|
+
expect(a).toEqual(b)
|
|
201
|
+
})
|
|
202
|
+
|
|
172
203
|
test("floors to minute if floorMinute is true", () => {
|
|
173
204
|
const ts = 1717245296
|
|
174
205
|
const floored = getTime({ timestamp: ts, floorMinute: true })
|
|
@@ -233,7 +264,6 @@ describe("getDayIndexInWeek", () => {
|
|
|
233
264
|
})
|
|
234
265
|
})
|
|
235
266
|
|
|
236
|
-
// New tests for getMinute
|
|
237
267
|
describe("getMinute", () => {
|
|
238
268
|
test("extracts minute from HH:mm:ss", () => {
|
|
239
269
|
expect(getMinute("12:34:56")).toBe(34)
|
|
@@ -278,6 +308,42 @@ describe("isTimeString", () => {
|
|
|
278
308
|
})
|
|
279
309
|
})
|
|
280
310
|
|
|
311
|
+
describe("isDateTimeString", () => {
|
|
312
|
+
test("valid with default T separator", () => {
|
|
313
|
+
expect(isDateTimeString("2024-06-01T12:34:56")).toBe(true)
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
test("rejects invalid combinations", () => {
|
|
317
|
+
expect(isDateTimeString("2024-06-01T12:34")).toBe(false) // missing seconds
|
|
318
|
+
expect(isDateTimeString("2024-06-01T24:00:00")).toBe(false) // invalid hour
|
|
319
|
+
expect(isDateTimeString("2024-02-31T12:00:00")).toBe(false) // invalid date
|
|
320
|
+
expect(isDateTimeString("2024-06-01 12:34:56")).toBe(false) // wrong separator
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
test("supports custom separator", () => {
|
|
324
|
+
expect(isDateTimeString("2024-06-01 12:34:56", { separator: " " })).toBe(true)
|
|
325
|
+
expect(isDateTimeString("2024-06-01T12:34:56", { separator: " " })).toBe(false)
|
|
326
|
+
})
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
describe("isUTCString", () => {
|
|
330
|
+
test("validates proper Zulu UTC format", () => {
|
|
331
|
+
expect(isUTCString("2024-06-01T12:34:56Z")).toBe(true)
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
test("rejects strings without trailing Z", () => {
|
|
335
|
+
expect(isUTCString("2024-06-01T12:34:56")).toBe(false)
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
test("rejects invalid time/date and unsupported variants", () => {
|
|
339
|
+
expect(isUTCString("2024-06-01T24:00:00Z")).toBe(false) // invalid hour
|
|
340
|
+
expect(isUTCString("2024-02-31T12:00:00Z")).toBe(false) // invalid date
|
|
341
|
+
expect(isUTCString("2024-06-01T12:34:56+00:00")).toBe(false) // offset not supported
|
|
342
|
+
expect(isUTCString("2024-06-01T12:34:56.123Z")).toBe(false) // milliseconds not supported
|
|
343
|
+
expect(isUTCString("2024-06-01 12:34:56Z")).toBe(false) // wrong separator
|
|
344
|
+
})
|
|
345
|
+
})
|
|
346
|
+
|
|
281
347
|
describe("isUnixTimestamp", () => {
|
|
282
348
|
test("returns true for valid unix timestamps in seconds", () => {
|
|
283
349
|
expect(isUnixTimestamp(0)).toBe(true)
|
|
@@ -494,7 +560,6 @@ describe("getDateRange", () => {
|
|
|
494
560
|
})
|
|
495
561
|
})
|
|
496
562
|
|
|
497
|
-
// Tests for getStartOfWeek (newly exported function)
|
|
498
563
|
describe("getStartOfWeek", () => {
|
|
499
564
|
test("returns same date if input is Sunday", () => {
|
|
500
565
|
expect(getStartOfWeek("2024-06-02")).toBe("2024-06-02") // Sunday
|