@tim-code/my-util 0.0.4 → 0.0.6

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.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "author": "",
package/src/array.js CHANGED
@@ -93,14 +93,25 @@ export function descending(key) {
93
93
  }
94
94
 
95
95
  /**
96
- * Parse an integer in base 10. Safe to use for array.map() since it only takes one argument and ignores the rest.
97
- * @param {string} number
98
- * @returns {number} Integer
96
+ * Creates a function that accesses an object's value at key.
97
+ * @param {string} key
98
+ * @returns {any}
99
99
  */
100
- export function parseIntSafe(number) {
101
- return parseInt(number, 10)
100
+ export function via(key) {
101
+ return (object) => object[key]
102
102
  }
103
103
 
104
+ // export function contains(template) {
105
+ // return (object) => {
106
+ // for (const key in template) {
107
+ // if (object[key] !== template[key]) {
108
+ // return false
109
+ // }
110
+ // }
111
+ // return true
112
+ // }
113
+ // }
114
+
104
115
  // not sure how far we want to go down "key" rabbit hole:
105
116
  // export function sum(array, key) {}
106
117
  // or maybe
package/src/array.test.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable no-restricted-syntax */
2
2
  import { describe, expect, it, jest } from "@jest/globals"
3
3
 
4
- const { chunk, unique, mutateValues, ascending, descending } = await import("./array.js")
4
+ const { chunk, unique, mutateValues, ascending, descending, via } = await import("./array.js")
5
5
 
6
6
  describe("chunk", () => {
7
7
  it("splits array into chunks of specified size", () => {
@@ -187,3 +187,29 @@ describe("descending", () => {
187
187
  expect(arr.map((o) => o.v)).toEqual([3, 2, undefined])
188
188
  })
189
189
  })
190
+
191
+ describe("via", () => {
192
+ it("returns a function that accesses the given key", () => {
193
+ const getFoo = via("foo")
194
+ expect(getFoo({ foo: 42 })).toBe(42)
195
+ expect(getFoo({ foo: "bar" })).toBe("bar")
196
+ })
197
+
198
+ it("returns undefined if the key does not exist", () => {
199
+ const getX = via("x")
200
+ expect(getX({})).toBeUndefined()
201
+ expect(getX({ y: 1 })).toBeUndefined()
202
+ })
203
+
204
+ it("works with numeric keys", () => {
205
+ const get0 = via(0)
206
+ expect(get0([10, 20])).toBe(10)
207
+ expect(get0({ 0: "zero" })).toBe("zero")
208
+ })
209
+
210
+ it("returns undefined if object is missing", () => {
211
+ const getFoo = via("foo")
212
+ expect(() => getFoo(undefined)).toThrow(TypeError)
213
+ expect(() => getFoo(null)).toThrow(TypeError)
214
+ })
215
+ })
package/src/time.js CHANGED
@@ -1,21 +1,17 @@
1
- import { parseIntSafe } from "./array.js"
2
1
  import { mod } from "./math.js"
3
2
 
4
3
  /**
5
4
  * Gets various ways of representing the current time in EDT. Floors to nearest second by default.
6
5
  * @param {Object} $1
7
- * @param {number} $1.days An offset in days to the current time
8
- * @param {boolean} $1.floorMinute If true, floors to the nearest minute. If false, floors to the nearest second.
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.
9
8
  * @returns {Object} { timestamp, date, time, minute, datetime }
10
9
  */
11
- export function getEasternTime({ days = 0, floorMinute = false } = {}) {
12
- const now = new Date()
13
- if (days) {
14
- now.setDate(now.getDate() + days)
10
+ export function getEasternTime({ floorMinute = false, timestamp = undefined } = {}) {
11
+ if (!timestamp) {
12
+ timestamp = new Date().getTime() / 1000
15
13
  }
16
- const timestamp = floorMinute
17
- ? Math.floor(now.getTime() / 1000 / 60) * 60
18
- : Math.floor(now.getTime() / 1000)
14
+ timestamp = floorMinute ? Math.floor(timestamp / 60) * 60 : Math.floor(timestamp)
19
15
  const string = new Date(timestamp * 1000).toLocaleString("en-US", {
20
16
  timeZone: "America/New_York",
21
17
  hour12: false,
@@ -84,7 +80,7 @@ export function isUnixTimestamp(ts, { max = 9999999999 } = {}) {
84
80
  * @returns {string} HH:mm:ss
85
81
  */
86
82
  export function addTime(timeString, { minutes = 0, hours = 0 }) {
87
- let [hour, minute, second = 0] = timeString.split(":").map(parseIntSafe)
83
+ let [hour, minute, second = 0] = timeString.split(":").map(Number)
88
84
  hour = mod(hour + hours, 24)
89
85
  minute += minutes
90
86
  while (minute >= 60) {
@@ -100,3 +96,16 @@ export function addTime(timeString, { minutes = 0, hours = 0 }) {
100
96
  .join(":")
101
97
  return newTime
102
98
  }
99
+
100
+ /**
101
+ * Adds a number of days to a date string.
102
+ * @param {string} dateString
103
+ * @param {number} days
104
+ * @returns {string}
105
+ */
106
+ export function addDays(dateString, days = 0) {
107
+ const [year, month, day] = dateString.split("-").map(Number)
108
+ const date = new Date(Date.UTC(year, month - 1, day))
109
+ date.setUTCDate(date.getUTCDate() + days)
110
+ return date.toISOString().slice(0, 10)
111
+ }
package/src/time.test.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, test } from "@jest/globals"
2
- import { addTime, getEasternTime, isDate, isTime, isUnixTimestamp } from "./time.js"
2
+ import { addDays, addTime, getEasternTime, isDate, isTime, isUnixTimestamp } from "./time.js"
3
3
 
4
+ // getEasternTime changed: removed "days" param, added "timestamp" param
4
5
  describe("getEasternTime", () => {
5
6
  test("returns correct structure and types", () => {
6
7
  const result = getEasternTime()
@@ -18,33 +19,66 @@ describe("getEasternTime", () => {
18
19
  expect(r2.timestamp).toBeLessThanOrEqual(r1.timestamp)
19
20
  })
20
21
 
21
- test("adds days offset", () => {
22
- const today = getEasternTime()
23
- const tomorrow = getEasternTime({ days: 1 })
24
- const [y, m, d] = today.date.split("-").map(Number)
25
- const [y2, m2, d2] = tomorrow.date.split("-").map(Number)
26
- expect(new Date(y2, m2 - 1, d2).getTime() - new Date(y, m - 1, d).getTime()).toBeCloseTo(
27
- 24 * 60 * 60 * 1000,
28
- -2
29
- )
22
+ test("uses provided timestamp and floors correctly", () => {
23
+ // 2024-06-01T12:34:56Z (UTC) = 1717245296
24
+ // EDT is UTC-4, so local time should be 08:34:56
25
+ const ts = 1717245296
26
+ const result = getEasternTime({ timestamp: ts })
27
+ expect(result.timestamp).toBe(ts)
28
+ expect(result.time.startsWith("08:34")).toBe(true)
29
+ // floorMinute should zero out seconds
30
+ const floored = getEasternTime({ timestamp: ts, floorMinute: true })
31
+ expect(floored.timestamp % 60).toBe(0)
32
+ expect(floored.time.endsWith(":00")).toBe(true)
30
33
  })
31
34
 
32
- test("returns correct format for different days and floorMinute", () => {
33
- const base = getEasternTime()
34
- const plus2 = getEasternTime({ days: 2, floorMinute: true })
35
- expect(plus2.date).not.toEqual(base.date)
36
- expect(plus2.timestamp % 60).toBe(0)
37
- })
38
-
39
- // The following test ensures all code paths are covered, including when days=0 and floorMinute=false (the defaults).
40
35
  test("default parameters yield consistent output", () => {
41
36
  const def = getEasternTime()
42
- const explicit = getEasternTime({ days: 0, floorMinute: false })
37
+ const explicit = getEasternTime({ floorMinute: false })
43
38
  expect(def.date).toEqual(explicit.date)
44
39
  expect(def.time).toEqual(explicit.time)
45
40
  expect(def.timestamp).toEqual(explicit.timestamp)
46
41
  expect(def.datetime).toEqual(explicit.datetime)
47
42
  })
43
+
44
+ // DST boundary tests
45
+ test("handles DST start (spring forward) correctly", () => {
46
+ // In 2024, DST starts in US/Eastern at 2024-03-10 02:00:00 local time (clocks jump to 03:00:00)
47
+ // 2024-03-10T06:59:59Z = 1:59:59 EST (should be 01:59:59)
48
+ let ts = Date.UTC(2024, 2, 10, 6, 59, 59) / 1000
49
+ let r = getEasternTime({ timestamp: ts })
50
+ expect(r.date).toBe("2024-03-10")
51
+ expect(r.time).toBe("01:59:59")
52
+ // 2024-03-10T07:00:00Z = 3:00:00 EDT (should be 03:00:00)
53
+ ts = Date.UTC(2024, 2, 10, 7, 0, 0) / 1000
54
+ r = getEasternTime({ timestamp: ts })
55
+ expect(r.date).toBe("2024-03-10")
56
+ expect(r.time).toBe("03:00:00")
57
+ })
58
+
59
+ test("handles DST end (fall back) correctly", () => {
60
+ // In 2024, DST ends in US/Eastern at 2024-11-03 02:00:00 local time (clocks go back to 01:00:00)
61
+ // 2024-11-03T05:59:59Z = 01:59:59 EDT (should be 01:59:59)
62
+ let ts = Date.UTC(2024, 10, 3, 5, 59, 59) / 1000
63
+ let r = getEasternTime({ timestamp: ts })
64
+ expect(r.date).toBe("2024-11-03")
65
+ expect(r.time).toBe("01:59:59")
66
+ // 2024-11-03T06:00:00Z = 01:00:00 EST (should be 01:00:00)
67
+ ts = Date.UTC(2024, 10, 3, 6, 0, 0) / 1000
68
+ r = getEasternTime({ timestamp: ts })
69
+ expect(r.date).toBe("2024-11-03")
70
+ expect(r.time).toBe("01:00:00")
71
+ // 2024-11-03T06:59:59Z = 01:59:59 EST (should be 01:59:59)
72
+ ts = Date.UTC(2024, 10, 3, 6, 59, 59) / 1000
73
+ r = getEasternTime({ timestamp: ts })
74
+ expect(r.date).toBe("2024-11-03")
75
+ expect(r.time).toBe("01:59:59")
76
+ // 2024-11-03T07:00:00Z = 02:00:00 EST (should be 02:00:00)
77
+ ts = Date.UTC(2024, 10, 3, 7, 0, 0) / 1000
78
+ r = getEasternTime({ timestamp: ts })
79
+ expect(r.date).toBe("2024-11-03")
80
+ expect(r.time).toBe("02:00:00")
81
+ })
48
82
  })
49
83
 
50
84
  describe("isDate", () => {
@@ -179,3 +213,54 @@ describe("addTime", () => {
179
213
  expect(addTime("00:00:00", { hours: 0, minutes: 0 })).toBe("00:00:00")
180
214
  })
181
215
  })
216
+
217
+ describe("addDays", () => {
218
+ test("adds days within the same month", () => {
219
+ expect(addDays("2024-06-01", 5)).toBe("2024-06-06")
220
+ expect(addDays("2024-06-10", 0)).toBe("2024-06-10")
221
+ })
222
+
223
+ test("adds days with month rollover", () => {
224
+ expect(addDays("2024-06-28", 5)).toBe("2024-07-03")
225
+ })
226
+
227
+ test("adds days with year rollover", () => {
228
+ expect(addDays("2024-12-30", 5)).toBe("2025-01-04")
229
+ })
230
+
231
+ test("subtracts days", () => {
232
+ expect(addDays("2024-06-10", -10)).toBe("2024-05-31")
233
+ })
234
+
235
+ test("handles leap years", () => {
236
+ expect(addDays("2024-02-28", 1)).toBe("2024-02-29")
237
+ expect(addDays("2024-02-28", 2)).toBe("2024-03-01")
238
+ expect(addDays("2023-02-28", 1)).toBe("2023-03-01")
239
+ })
240
+
241
+ test("handles negative result across year boundary", () => {
242
+ expect(addDays("2024-01-01", -1)).toBe("2023-12-31")
243
+ })
244
+
245
+ // DST boundary: adding days across US DST start (spring forward)
246
+ test("adds days across DST start (spring forward)", () => {
247
+ // DST starts in US/Eastern on 2024-03-10
248
+ // Adding 1 day to 2024-03-09 should yield 2024-03-10
249
+ expect(addDays("2024-03-09", 1)).toBe("2024-03-10")
250
+ // Adding 2 days to 2024-03-09 should yield 2024-03-11
251
+ expect(addDays("2024-03-09", 2)).toBe("2024-03-11")
252
+ // Subtracting 1 day from 2024-03-10 should yield 2024-03-09
253
+ expect(addDays("2024-03-10", -1)).toBe("2024-03-09")
254
+ })
255
+
256
+ // DST boundary: adding days across US DST end (fall back)
257
+ test("adds days across DST end (fall back)", () => {
258
+ // DST ends in US/Eastern on 2024-11-03
259
+ // Adding 1 day to 2024-11-02 should yield 2024-11-03
260
+ expect(addDays("2024-11-02", 1)).toBe("2024-11-03")
261
+ // Adding 2 days to 2024-11-02 should yield 2024-11-04
262
+ expect(addDays("2024-11-02", 2)).toBe("2024-11-04")
263
+ // Subtracting 1 day from 2024-11-03 should yield 2024-11-02
264
+ expect(addDays("2024-11-03", -1)).toBe("2024-11-02")
265
+ })
266
+ })