@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tim-code/my-util",
3
- "version": "0.7.3",
3
+ "version": "0.8.1",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "author": "Tim Sprowl",
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 {boolean=} $1.floorMinute If true, floors to the nearest minute. If false, floors to the nearest second.
7
- * @param {number=} $1.timestamp Unix timestamp to use instead of current time.
8
- * @param {string=} $1.timeZone Time zone to use instead of Eastern time. A falsy value corresponds to local time.
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 (!timestamp) {
17
- timestamp = new Date().getTime() / 1000
20
+ if (floorMinute) {
21
+ timestamp = Math.floor(timestamp / 60) * 60
22
+ } else {
23
+ timestamp = Math.floor(timestamp)
18
24
  }
19
- timestamp = floorMinute ? Math.floor(timestamp / 60) * 60 : Math.floor(timestamp)
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 = new Date(timestamp * 1000).toLocaleString("en-CA", {
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 {number=} $1.timestamp Unix timestamp to use instead of current time.
40
- * @param {string=} $1.timeZone Time zone to use instead of local time. A falsy value (default) corresponds to local time.
41
- * @returns {Object} { timestamp, date, time, minute, datetime }
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({ floorMinute, timestamp, timeZone = false } = {}) {
44
- return getEasternTime({ floorMinute, timestamp, timeZone })
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::ss
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 represent a valid HH:mm:ss time.
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