@ditojs/utils 2.99.0 → 3.0.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 +2 -8
- package/src/__snapshots__/index.test.js.snap +2 -0
- package/src/array/shuffle.js +1 -1
- package/src/object/equals.test.js +11 -2
- package/src/string/formatPlainDate.js +14 -0
- package/src/string/formatPlainDate.test.js +13 -0
- package/src/string/index.js +2 -0
- package/src/string/isDomain.js +10 -3
- package/src/string/isDomain.test.js +9 -0
- package/src/string/parsePlainDate.js +18 -0
- package/src/string/parsePlainDate.test.js +20 -0
- package/types/index.d.ts +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ditojs/utils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Dito.js Utility Functions – Dito.js is a declarative and modern web framework, based on Objection.js, Koa.js and Vue.js",
|
|
6
6
|
"repository": "https://github.com/ditojs/dito/tree/main/packages/utils",
|
|
@@ -19,9 +19,6 @@
|
|
|
19
19
|
"src/",
|
|
20
20
|
"types/"
|
|
21
21
|
],
|
|
22
|
-
"scripts": {
|
|
23
|
-
"types": "tsc --noEmit types/index.d.ts"
|
|
24
|
-
},
|
|
25
22
|
"engines": {
|
|
26
23
|
"node": ">= 24.0.0"
|
|
27
24
|
},
|
|
@@ -32,11 +29,8 @@
|
|
|
32
29
|
"ie >= 11",
|
|
33
30
|
"ie_mob >= 11"
|
|
34
31
|
],
|
|
35
|
-
"dependencies": {
|
|
36
|
-
"punycode": "^2.3.1"
|
|
37
|
-
},
|
|
38
32
|
"devDependencies": {
|
|
39
33
|
"typescript": "^6.0.3"
|
|
40
34
|
},
|
|
41
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "399f683a6ca227a03cfc0978fee9055383e6b3d8"
|
|
42
36
|
}
|
|
@@ -35,6 +35,7 @@ exports[`index > exports all symbols 1`] = `
|
|
|
35
35
|
"flatten": [Function],
|
|
36
36
|
"format": [Function],
|
|
37
37
|
"formatDate": [Function],
|
|
38
|
+
"formatPlainDate": [Function],
|
|
38
39
|
"getCommonOffset": [Function],
|
|
39
40
|
"getCommonPrefix": [Function],
|
|
40
41
|
"getEntriesAtDataPath": [Function],
|
|
@@ -73,6 +74,7 @@ exports[`index > exports all symbols 1`] = `
|
|
|
73
74
|
"mixin": [Function],
|
|
74
75
|
"normalizeDataPath": [Function],
|
|
75
76
|
"parseDataPath": [Function],
|
|
77
|
+
"parsePlainDate": [Function],
|
|
76
78
|
"pick": [Function],
|
|
77
79
|
"pickBy": [Function],
|
|
78
80
|
"setDataPathEntries": [Function],
|
package/src/array/shuffle.js
CHANGED
|
@@ -40,8 +40,11 @@ describe('equals()', () => {
|
|
|
40
40
|
[false, '', false],
|
|
41
41
|
|
|
42
42
|
[symbol1, symbol1, true],
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
// TODO: Uncomment once vitest can format boxed symbols in `%o` titles,
|
|
44
|
+
// and remove 'should compare boxed symbols' below:
|
|
45
|
+
// https://github.com/vitest-dev/vitest/issues/11445
|
|
46
|
+
// [symbol1, Object(symbol1), true],
|
|
47
|
+
// [Object(symbol1), Object(symbol1), true],
|
|
45
48
|
[symbol1, symbol2, false],
|
|
46
49
|
|
|
47
50
|
[null, null, true],
|
|
@@ -60,6 +63,12 @@ describe('equals()', () => {
|
|
|
60
63
|
}
|
|
61
64
|
)
|
|
62
65
|
|
|
66
|
+
// TODO: Remove once the boxed symbol rows above are uncommented.
|
|
67
|
+
it('should compare boxed symbols', () => {
|
|
68
|
+
expect(equals(symbol1, Object(symbol1))).toBe(true)
|
|
69
|
+
expect(equals(Object(symbol1), Object(symbol1))).toBe(true)
|
|
70
|
+
})
|
|
71
|
+
|
|
63
72
|
it('should compare arrays', () => {
|
|
64
73
|
let array1 = [true, null, 1, 'a', undefined]
|
|
65
74
|
let array2 = [true, null, 1, 'a', undefined]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats a date as its local plain date, e.g. `'2026-05-14'`, without the
|
|
3
|
+
* time and time zone of `toISOString()`, which can shift it to another day.
|
|
4
|
+
*/
|
|
5
|
+
export function formatPlainDate(date) {
|
|
6
|
+
// Use the date parts, as shifting by `getTimezoneOffset()` misses offsets
|
|
7
|
+
// with seconds, e.g. local mean time before 1894 in Europe/Zurich.
|
|
8
|
+
const pad = (number, length = 2) => String(number).padStart(length, '0')
|
|
9
|
+
return [
|
|
10
|
+
pad(date.getFullYear(), 4),
|
|
11
|
+
pad(date.getMonth() + 1),
|
|
12
|
+
pad(date.getDate())
|
|
13
|
+
].join('-')
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { formatPlainDate } from './formatPlainDate.js'
|
|
2
|
+
|
|
3
|
+
describe('formatPlainDate()', () => {
|
|
4
|
+
it('should format the local plain date', () => {
|
|
5
|
+
expect(formatPlainDate(new Date(2026, 4, 14))).toBe('2026-05-14')
|
|
6
|
+
expect(formatPlainDate(new Date(2026, 4, 14, 23, 59))).toBe(
|
|
7
|
+
'2026-05-14'
|
|
8
|
+
)
|
|
9
|
+
expect(formatPlainDate(new Date(987, 0, 1))).toBe('0987-01-01')
|
|
10
|
+
// Local mean time, with an offset in seconds in some timezones:
|
|
11
|
+
expect(formatPlainDate(new Date(1850, 6, 1))).toBe('1850-07-01')
|
|
12
|
+
})
|
|
13
|
+
})
|
package/src/string/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export * from './decamelize.js'
|
|
|
4
4
|
export * from './deindent.js'
|
|
5
5
|
export * from './escapeRegexp.js'
|
|
6
6
|
export * from './format.js'
|
|
7
|
+
export * from './formatPlainDate.js'
|
|
7
8
|
export * from './formatDate.js'
|
|
8
9
|
export * from './getCommonPrefix.js'
|
|
9
10
|
export * from './isAbsoluteUrl.js'
|
|
@@ -13,3 +14,4 @@ export * from './isEmail.js'
|
|
|
13
14
|
export * from './isHostname.js'
|
|
14
15
|
export * from './isUrl.js'
|
|
15
16
|
export * from './labelize.js'
|
|
17
|
+
export * from './parsePlainDate.js'
|
package/src/string/isDomain.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import punycode from 'punycode/punycode.js'
|
|
2
|
-
|
|
3
1
|
// Best effort approach, allowing Internationalized domain name (with punycode)
|
|
4
2
|
const domainRegExp =
|
|
5
3
|
/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i
|
|
6
4
|
|
|
7
5
|
export function isDomain(str) {
|
|
8
|
-
|
|
6
|
+
// Reject characters that `URL` would treat as delimiters or decode, so that
|
|
7
|
+
// only the domain itself is converted to its ASCII (punycode) form.
|
|
8
|
+
if (!str || /[\s/\\?#@:%]/.test(str)) {
|
|
9
|
+
return false
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
return domainRegExp.test(new URL(`http://${str}`).hostname)
|
|
13
|
+
} catch {
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
9
16
|
}
|
|
@@ -12,4 +12,13 @@ describe('isDomain()', () => {
|
|
|
12
12
|
expect(isDomain('line_to.com')).toBe(false)
|
|
13
13
|
expect(isDomain('lünéto.com')).toBe(true)
|
|
14
14
|
})
|
|
15
|
+
|
|
16
|
+
it('should return false for anything beyond a domain', () => {
|
|
17
|
+
expect(isDomain('')).toBe(false)
|
|
18
|
+
expect(isDomain('lineto.com:80')).toBe(false)
|
|
19
|
+
expect(isDomain('lineto.com/path')).toBe(false)
|
|
20
|
+
expect(isDomain('user@lineto.com')).toBe(false)
|
|
21
|
+
expect(isDomain('line%74o.com')).toBe(false)
|
|
22
|
+
expect(isDomain('line to.com')).toBe(false)
|
|
23
|
+
})
|
|
15
24
|
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { formatPlainDate } from './formatPlainDate.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parses a plain date like `'2026-05-14'` as local midnight, unlike
|
|
5
|
+
* `new Date()`, which parses it as UTC midnight. Returns `null` for anything
|
|
6
|
+
* else, including invalid dates like `'2026-02-30'`.
|
|
7
|
+
*/
|
|
8
|
+
export function parsePlainDate(string) {
|
|
9
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(string)) {
|
|
10
|
+
// Date-time strings without an offset are parsed as local time.
|
|
11
|
+
const date = new Date(`${string}T00:00`)
|
|
12
|
+
// Reject invalid days, which `Date` rolls over to the next month.
|
|
13
|
+
if (formatPlainDate(date) === string) {
|
|
14
|
+
return date
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { parsePlainDate } from './parsePlainDate.js'
|
|
2
|
+
|
|
3
|
+
describe('parsePlainDate()', () => {
|
|
4
|
+
it('should parse plain dates as local midnight', () => {
|
|
5
|
+
expect(parsePlainDate('2026-05-14')).toEqual(new Date(2026, 4, 14))
|
|
6
|
+
expect(parsePlainDate('1850-07-01')).toEqual(new Date(1850, 6, 1))
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it('should return null for other values', () => {
|
|
10
|
+
expect(parsePlainDate(null)).toBe(null)
|
|
11
|
+
expect(parsePlainDate('')).toBe(null)
|
|
12
|
+
expect(parsePlainDate('2026-05-14T00:00:00.000Z')).toBe(null)
|
|
13
|
+
expect(parsePlainDate('14.05.2026')).toBe(null)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should return null for invalid dates', () => {
|
|
17
|
+
expect(parsePlainDate('2026-02-30')).toBe(null)
|
|
18
|
+
expect(parsePlainDate('2026-13-01')).toBe(null)
|
|
19
|
+
})
|
|
20
|
+
})
|
package/types/index.d.ts
CHANGED
|
@@ -518,6 +518,19 @@ export function format(
|
|
|
518
518
|
}
|
|
519
519
|
}
|
|
520
520
|
): string | null | undefined
|
|
521
|
+
/**
|
|
522
|
+
* Formats a date as its local plain date, e.g. `'2026-05-14'`, without the
|
|
523
|
+
* time and time zone of `toISOString()`, which can shift it to another day.
|
|
524
|
+
*/
|
|
525
|
+
export function formatPlainDate(date: Date): string
|
|
526
|
+
/**
|
|
527
|
+
* Parses a plain date like `'2026-05-14'` as local midnight, unlike
|
|
528
|
+
* `new Date()`, which parses it as UTC midnight. Returns `null` for anything
|
|
529
|
+
* else, including invalid dates like `'2026-02-30'`.
|
|
530
|
+
*/
|
|
531
|
+
export function parsePlainDate(
|
|
532
|
+
string: string | null | undefined
|
|
533
|
+
): Date | null
|
|
521
534
|
/**
|
|
522
535
|
* Formats a date value as a string. If the value is not a Date,
|
|
523
536
|
* attempts to convert it to a Date first.
|