@tim-code/my-util 0.7.2 → 0.8.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/time.js +56 -22
- package/src/time.test.js +90 -50
package/package.json
CHANGED
package/src/time.js
CHANGED
|
@@ -1,24 +1,34 @@
|
|
|
1
1
|
import { mod } from "./math.js"
|
|
2
2
|
|
|
3
|
+
// `floorMinute` at first glance seems ugly and something like `floor: "minute"` seems better.
|
|
4
|
+
// However, practically and realistically, it doesn't seem like we need other values for floor besides "second" and "minute".
|
|
5
|
+
// So, then it seems more problematic to rely on an exact string to be passed... probably would need to throw on some other value.
|
|
6
|
+
// We don't really ever want to disable flooring because we want timestamp, date, and time returned to represent the same thing.
|
|
3
7
|
/**
|
|
4
8
|
* Gets various ways of representing the current time in EDT. Floors to nearest second by default.
|
|
5
9
|
* @param {Object} $1
|
|
6
|
-
* @param {
|
|
7
|
-
* @param {
|
|
8
|
-
*
|
|
10
|
+
* @param {number=} $1.timestamp Unix timestamp to use instead of now (in seconds).
|
|
11
|
+
* @param {Date=} $1.dateInstance Alternative to specifying timestamp that is a Date instance.
|
|
12
|
+
* This can be used to specify UTC: `getEasternTime({ dateInstance: new Date(utc) })`.
|
|
13
|
+
* @param {boolean=} $1.floorMinute If true, floors to the nearest minute. If false, floors to the nearest second (default).
|
|
14
|
+
* @param {string=} $1.timeZone Time zone to use instead of Eastern time. A falsy, not-undefined value corresponds to local time.
|
|
9
15
|
* @returns {Object} { timestamp, date: YYYY-MM-DD, time: HH:mm:ss }
|
|
16
|
+
* If invalid timestamp or dateInstance specified: { timestamp: NaN|Infinity, date: "Invalid date", time: undefined }
|
|
10
17
|
*/
|
|
11
18
|
export function getEasternTime({
|
|
19
|
+
dateInstance = undefined,
|
|
20
|
+
timestamp = (dateInstance ?? new Date()).getTime() / 1000,
|
|
12
21
|
floorMinute = false,
|
|
13
|
-
timestamp = undefined,
|
|
14
22
|
timeZone = "America/New_York",
|
|
15
23
|
} = {}) {
|
|
16
|
-
if (
|
|
17
|
-
timestamp =
|
|
24
|
+
if (floorMinute) {
|
|
25
|
+
timestamp = Math.floor(timestamp / 60) * 60
|
|
26
|
+
} else {
|
|
27
|
+
timestamp = Math.floor(timestamp)
|
|
18
28
|
}
|
|
19
|
-
|
|
29
|
+
const flooredDateInstance = new Date(timestamp * 1000)
|
|
20
30
|
// 'en-CA' (English - Canada) formats dates as YYYY-MM-DD and times in 24-hour format by default
|
|
21
|
-
const string =
|
|
31
|
+
const string = flooredDateInstance.toLocaleString("en-CA", {
|
|
22
32
|
...(timeZone ? { timeZone } : {}),
|
|
23
33
|
hour12: false,
|
|
24
34
|
year: "numeric",
|
|
@@ -35,13 +45,26 @@ export function getEasternTime({
|
|
|
35
45
|
/**
|
|
36
46
|
* Gets various ways of representing the current time in local timezone. Floors to nearest second by default.
|
|
37
47
|
* @param {Object} $1
|
|
48
|
+
* @param {number=} $1.timestamp Unix timestamp to use instead of now (in seconds).
|
|
49
|
+
* @param {Date=} $1.dateInstance Alternative to specifying timestamp that is a Date instance.
|
|
50
|
+
* This can be used to specify UTC: `getEasternTime({ dateInstance: new Date(utc) })`.
|
|
38
51
|
* @param {boolean=} $1.floorMinute If true, floors to the nearest minute. If false, floors to the nearest second.
|
|
39
|
-
* @param {
|
|
40
|
-
* @
|
|
41
|
-
*
|
|
52
|
+
* @param {string=} $1.timeZone Time zone to use instead of local time. A falsy value corresponds to local time (default) .
|
|
53
|
+
* @returns {Object} { timestamp, date: YYYY-MM-DD, time: HH:mm:ss }
|
|
54
|
+
* If invalid timestamp or dateInstance specified: { timestamp: NaN|Infinity, date: "Invalid date", time: undefined }
|
|
42
55
|
*/
|
|
43
|
-
export function getTime({
|
|
44
|
-
return getEasternTime({
|
|
56
|
+
export function getTime({ timestamp, dateInstance, floorMinute, timeZone = false } = {}) {
|
|
57
|
+
return getEasternTime({ timestamp, dateInstance, floorMinute, timeZone })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get Unix timestamp for a UTC date string.
|
|
62
|
+
* @param {string} utc UTC date time: "YYYY-MM-DDTHH:mm:ssZ" or with UTC offset
|
|
63
|
+
* @returns {number} Seconds since epoch
|
|
64
|
+
*/
|
|
65
|
+
export function getUnixTimestamp(utc) {
|
|
66
|
+
const timestamp = Math.floor(new Date(utc).getTime() / 1000)
|
|
67
|
+
return timestamp
|
|
45
68
|
}
|
|
46
69
|
|
|
47
70
|
/**
|
|
@@ -97,30 +120,41 @@ export function isDateString(string) {
|
|
|
97
120
|
}
|
|
98
121
|
|
|
99
122
|
/**
|
|
100
|
-
*
|
|
123
|
+
* Checks if the string represents a valid HH:mm:ss time.
|
|
124
|
+
* This will return false for times like "24:00:00".
|
|
125
|
+
* Does not handle milliseconds.
|
|
101
126
|
* @param {string} string
|
|
102
127
|
* @returns {boolean}
|
|
103
128
|
*/
|
|
104
|
-
export function
|
|
105
|
-
return
|
|
129
|
+
export function isTimeString(string) {
|
|
130
|
+
return /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$/u.test(string)
|
|
106
131
|
}
|
|
107
132
|
|
|
108
133
|
/**
|
|
109
|
-
* Checks if the string
|
|
134
|
+
* Checks if the string represents a valid date time string similar to YYYY-MM-DDTHH:mm:ss.
|
|
135
|
+
* Does not handle milliseconds.
|
|
110
136
|
* @param {string} string
|
|
137
|
+
* @param {Object} $1
|
|
138
|
+
* @param {string=} $1.separator Can specify a different string separator between date and time. Defaults to "T".
|
|
111
139
|
* @returns {boolean}
|
|
112
140
|
*/
|
|
113
|
-
export function
|
|
114
|
-
|
|
141
|
+
export function isDateTimeString(string, { separator = "T" } = {}) {
|
|
142
|
+
const [date, time] = string.split(separator)
|
|
143
|
+
return isDateString(date) && isTimeString(time)
|
|
115
144
|
}
|
|
116
145
|
|
|
117
146
|
/**
|
|
118
|
-
*
|
|
147
|
+
* Checks if the string represents a valid UTC date time similar to YYYY-MM-DDTHH:mm:ssZ.
|
|
148
|
+
* Does not handle milliseconds or UTC offsets.
|
|
119
149
|
* @param {string} string
|
|
120
150
|
* @returns {boolean}
|
|
121
151
|
*/
|
|
122
|
-
export function
|
|
123
|
-
|
|
152
|
+
export function isUTCString(string) {
|
|
153
|
+
if (!string.endsWith("Z")) {
|
|
154
|
+
return false
|
|
155
|
+
}
|
|
156
|
+
const datetime = string.slice(0, string.length - 1)
|
|
157
|
+
return isDateTimeString(datetime)
|
|
124
158
|
}
|
|
125
159
|
|
|
126
160
|
/**
|
package/src/time.test.js
CHANGED
|
@@ -9,14 +9,20 @@ import {
|
|
|
9
9
|
getStartOfWeek,
|
|
10
10
|
getTime,
|
|
11
11
|
getTimeRange,
|
|
12
|
-
|
|
12
|
+
getUnixTimestamp,
|
|
13
13
|
isDateString,
|
|
14
|
-
|
|
14
|
+
isDateTimeString,
|
|
15
15
|
isTimeString,
|
|
16
|
+
isUTCString,
|
|
16
17
|
isUnixTimestamp,
|
|
17
18
|
today,
|
|
18
19
|
} from "./time.js"
|
|
19
20
|
|
|
21
|
+
// Exported functions:
|
|
22
|
+
// getEasternTime, getTime, getUnixTimestamp, today, getDayIndexInWeek, getMinute,
|
|
23
|
+
// isDateString, isTimeString, isDateTimeString, isUTCString, isUnixTimestamp,
|
|
24
|
+
// addTime, getTimeRange, addDays, getDateRange, getStartOfWeek
|
|
25
|
+
|
|
20
26
|
describe("getEasternTime", () => {
|
|
21
27
|
test("returns correct structure and types", () => {
|
|
22
28
|
const result = getEasternTime()
|
|
@@ -47,6 +53,22 @@ describe("getEasternTime", () => {
|
|
|
47
53
|
expect(floored.time.endsWith(":00")).toBe(true)
|
|
48
54
|
})
|
|
49
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
|
+
|
|
50
72
|
test("default parameters yield consistent output", () => {
|
|
51
73
|
const def = getEasternTime()
|
|
52
74
|
const explicit = getEasternTime({ floorMinute: false })
|
|
@@ -170,6 +192,14 @@ describe("getTime", () => {
|
|
|
170
192
|
expect(pacific).toEqual(ref)
|
|
171
193
|
})
|
|
172
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
|
+
|
|
173
203
|
test("floors to minute if floorMinute is true", () => {
|
|
174
204
|
const ts = 1717245296
|
|
175
205
|
const floored = getTime({ timestamp: ts, floorMinute: true })
|
|
@@ -178,6 +208,26 @@ describe("getTime", () => {
|
|
|
178
208
|
})
|
|
179
209
|
})
|
|
180
210
|
|
|
211
|
+
describe("getUnixTimestamp", () => {
|
|
212
|
+
test("parses Zulu (UTC) timestamp", () => {
|
|
213
|
+
expect(getUnixTimestamp("2024-06-01T12:34:56Z")).toBe(1717245296)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
test("parses timestamp with positive offset", () => {
|
|
217
|
+
const expected = Date.UTC(2024, 5, 1, 10, 34, 56) / 1000 // 12:34:56+02:00 == 10:34:56Z
|
|
218
|
+
expect(getUnixTimestamp("2024-06-01T12:34:56+02:00")).toBe(expected)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
test("floors fractional seconds", () => {
|
|
222
|
+
// 999ms floors down to the same second
|
|
223
|
+
expect(getUnixTimestamp("1970-01-01T00:00:00.999Z")).toBe(0)
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
test("returns NaN for invalid date string", () => {
|
|
227
|
+
expect(Number.isNaN(getUnixTimestamp("not-a-date"))).toBe(true)
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
|
|
181
231
|
describe("today", () => {
|
|
182
232
|
test("returns today's date in YYYY-MM-DD format", () => {
|
|
183
233
|
const expected = getTime().date
|
|
@@ -214,7 +264,6 @@ describe("getDayIndexInWeek", () => {
|
|
|
214
264
|
})
|
|
215
265
|
})
|
|
216
266
|
|
|
217
|
-
// New tests for getMinute
|
|
218
267
|
describe("getMinute", () => {
|
|
219
268
|
test("extracts minute from HH:mm:ss", () => {
|
|
220
269
|
expect(getMinute("12:34:56")).toBe(34)
|
|
@@ -247,67 +296,51 @@ describe("isDateString", () => {
|
|
|
247
296
|
})
|
|
248
297
|
})
|
|
249
298
|
|
|
250
|
-
describe("
|
|
251
|
-
test("
|
|
252
|
-
expect(
|
|
253
|
-
expect(
|
|
299
|
+
describe("isTimeString", () => {
|
|
300
|
+
test("validates correct times", () => {
|
|
301
|
+
expect(isTimeString("00:00:00")).toBe(true)
|
|
302
|
+
expect(isTimeString("23:59:59")).toBe(true)
|
|
254
303
|
})
|
|
255
304
|
|
|
256
|
-
test("
|
|
257
|
-
expect(
|
|
258
|
-
expect(
|
|
305
|
+
test("rejects invalid times and formats", () => {
|
|
306
|
+
expect(isTimeString("24:00:00")).toBe(false)
|
|
307
|
+
expect(isTimeString("12:34")).toBe(false)
|
|
259
308
|
})
|
|
309
|
+
})
|
|
260
310
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
expect(
|
|
264
|
-
expect(isDate("2024-6-1")).toBe(false)
|
|
265
|
-
expect(isDate("20240601")).toBe(false)
|
|
266
|
-
expect(isDate("abcd-ef-gh")).toBe(false)
|
|
311
|
+
describe("isDateTimeString", () => {
|
|
312
|
+
test("valid with default T separator", () => {
|
|
313
|
+
expect(isDateTimeString("2024-06-01T12:34:56")).toBe(true)
|
|
267
314
|
})
|
|
268
315
|
|
|
269
|
-
test("
|
|
270
|
-
expect(
|
|
271
|
-
expect(
|
|
272
|
-
expect(
|
|
273
|
-
expect(
|
|
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
|
|
274
321
|
})
|
|
275
322
|
|
|
276
|
-
test("
|
|
277
|
-
expect(
|
|
278
|
-
expect(
|
|
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)
|
|
279
326
|
})
|
|
280
327
|
})
|
|
281
328
|
|
|
282
|
-
describe("
|
|
283
|
-
test("validates
|
|
284
|
-
expect(
|
|
285
|
-
expect(isTimeString("23:59:59")).toBe(true)
|
|
329
|
+
describe("isUTCString", () => {
|
|
330
|
+
test("validates proper Zulu UTC format", () => {
|
|
331
|
+
expect(isUTCString("2024-06-01T12:34:56Z")).toBe(true)
|
|
286
332
|
})
|
|
287
333
|
|
|
288
|
-
test("rejects
|
|
289
|
-
expect(
|
|
290
|
-
expect(isTimeString("12:34")).toBe(false)
|
|
334
|
+
test("rejects strings without trailing Z", () => {
|
|
335
|
+
expect(isUTCString("2024-06-01T12:34:56")).toBe(false)
|
|
291
336
|
})
|
|
292
|
-
})
|
|
293
337
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
expect(
|
|
297
|
-
expect(
|
|
298
|
-
expect(
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
test("returns false for invalid times", () => {
|
|
302
|
-
expect(isTime("24:00:00")).toBe(false)
|
|
303
|
-
expect(isTime("12:60:00")).toBe(false)
|
|
304
|
-
expect(isTime("12:00:60")).toBe(false)
|
|
305
|
-
expect(isTime("1:00:00")).toBe(false)
|
|
306
|
-
expect(isTime("12:0:00")).toBe(false)
|
|
307
|
-
expect(isTime("12:00:0")).toBe(false)
|
|
308
|
-
expect(isTime("12:00")).toBe(false)
|
|
309
|
-
expect(isTime("120000")).toBe(false)
|
|
310
|
-
expect(isTime("ab:cd:ef")).toBe(false)
|
|
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
|
|
311
344
|
})
|
|
312
345
|
})
|
|
313
346
|
|
|
@@ -428,6 +461,14 @@ describe("getTimeRange", () => {
|
|
|
428
461
|
test("handles input with no seconds", () => {
|
|
429
462
|
expect(getTimeRange("12:00", "12:02")).toEqual(["12:00:00", "12:01:00", "12:02:00"])
|
|
430
463
|
})
|
|
464
|
+
|
|
465
|
+
// ISSUE: getTimeRange allows a zero step, which would otherwise loop forever; it's capped internally at 1440 iterations.
|
|
466
|
+
test("caps the number of results at MINUTES_IN_DAY when step is zero", () => {
|
|
467
|
+
const result = getTimeRange("12:00:00", "12:00:00", { hours: 0, minutes: 0 })
|
|
468
|
+
expect(result.length).toBe(24 * 60)
|
|
469
|
+
expect(result[0]).toBe("12:00:00")
|
|
470
|
+
expect(result[result.length - 1]).toBe("12:00:00")
|
|
471
|
+
})
|
|
431
472
|
})
|
|
432
473
|
|
|
433
474
|
describe("addDays", () => {
|
|
@@ -519,7 +560,6 @@ describe("getDateRange", () => {
|
|
|
519
560
|
})
|
|
520
561
|
})
|
|
521
562
|
|
|
522
|
-
// Tests for getStartOfWeek (newly exported function)
|
|
523
563
|
describe("getStartOfWeek", () => {
|
|
524
564
|
test("returns same date if input is Sunday", () => {
|
|
525
565
|
expect(getStartOfWeek("2024-06-02")).toBe("2024-06-02") // Sunday
|