datetime-lube 1.0.0 → 1.0.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/CHANGELOG.md +9 -0
- package/package.json +2 -5
- package/src/add.js +36 -19
- package/src/calendar.js +3 -1
- package/src/date_to_string.js +16 -6
- package/src/diff.js +31 -12
- package/src/end_of.js +24 -11
- package/src/start_of.js +28 -12
- package/src/string_to_date.js +68 -26
- package/src/zone.js +67 -31
- package/types/add.d.ts.map +1 -1
- package/types/calendar.d.ts.map +1 -1
- package/types/date_to_string.d.ts.map +1 -1
- package/types/diff.d.ts.map +1 -1
- package/types/end_of.d.ts.map +1 -1
- package/types/start_of.d.ts.map +1 -1
- package/types/string_to_date.d.ts.map +1 -1
- package/types/zone.d.ts +1 -0
- package/types/zone.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.1
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- `startOf` returned a day, week, month or year that began exactly where a forward jump of the clock
|
|
8
|
+
lands 1 ms late.
|
|
9
|
+
- `stringToDate("-000000-01-01")` returned year 0 instead of an invalid `Date`, which ISO 8601 and
|
|
10
|
+
ECMAScript give `-000000`; it is invalid with and without a time zone.
|
|
11
|
+
|
|
3
12
|
## 1.0.0
|
|
4
13
|
|
|
5
14
|
### Breaking
|
package/package.json
CHANGED
|
@@ -4,9 +4,6 @@
|
|
|
4
4
|
"url": "https://github.com/artxe/lube-series/issues"
|
|
5
5
|
},
|
|
6
6
|
"description": "Simplify Date Manipulation with Time Zones in JavaScript",
|
|
7
|
-
"devDependencies": {
|
|
8
|
-
"@typescript/native": "npm:typescript@^7.0.2"
|
|
9
|
-
},
|
|
10
7
|
"engines": {
|
|
11
8
|
"node": ">=18"
|
|
12
9
|
},
|
|
@@ -55,10 +52,10 @@
|
|
|
55
52
|
]
|
|
56
53
|
}
|
|
57
54
|
},
|
|
58
|
-
"version": "1.0.
|
|
55
|
+
"version": "1.0.1",
|
|
59
56
|
"scripts": {
|
|
60
57
|
"dts": "node ../../scripts/tsc_with_clean.js datetime-lube",
|
|
61
58
|
"lint": "pnpm -w lint",
|
|
62
|
-
"test": "pnpm -w exec vitest run
|
|
59
|
+
"test": "pnpm -w exec vitest run test/datetime-lube"
|
|
63
60
|
}
|
|
64
61
|
}
|
package/src/add.js
CHANGED
|
@@ -51,19 +51,30 @@ function add_months(date, months, zone) {
|
|
|
51
51
|
*/
|
|
52
52
|
function compile(sum) {
|
|
53
53
|
let steps = durations.get(sum)
|
|
54
|
-
if (steps)
|
|
54
|
+
if (steps) {
|
|
55
|
+
return steps
|
|
56
|
+
}
|
|
55
57
|
steps = []
|
|
56
58
|
if (!/\S/.test(sum) || /\S/.test(
|
|
57
59
|
sum.replace(duration_regex, "")
|
|
58
|
-
))
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
)) {
|
|
61
|
+
throw new RangeError(
|
|
62
|
+
`add: Invalid duration "${String(sum)}"`
|
|
63
|
+
)
|
|
64
|
+
}
|
|
61
65
|
for (const [ , value = "", unit = "" ] of sum.matchAll(duration_regex)) {
|
|
62
|
-
if (!+value)
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
if (!+value) {
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
if (unit == "W") {
|
|
70
|
+
steps.push("D", +value * 7)
|
|
71
|
+
} else {
|
|
72
|
+
steps.push(unit, +value)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (durations.size >= 1000) {
|
|
76
|
+
durations.clear()
|
|
65
77
|
}
|
|
66
|
-
if (durations.size >= 1000) durations.clear()
|
|
67
78
|
durations.set(sum, steps)
|
|
68
79
|
return steps
|
|
69
80
|
}
|
|
@@ -104,22 +115,28 @@ export default function(date, sum, zone) {
|
|
|
104
115
|
const steps = compile(sum)
|
|
105
116
|
check_zone("add", zone)
|
|
106
117
|
const result = new Date(time)
|
|
107
|
-
if (isNaN(time))
|
|
118
|
+
if (isNaN(time)) {
|
|
119
|
+
return result
|
|
120
|
+
}
|
|
108
121
|
if (zone === undefined) {
|
|
109
122
|
for (let i = 0; i < steps.length; i += 2) {
|
|
110
123
|
const unit = /** @type {string} */(steps[i])/**/
|
|
111
124
|
const value = /** @type {number} */(steps[i + 1])/**/
|
|
112
125
|
const ms = unit_ms[unit]
|
|
113
|
-
if (ms)
|
|
114
|
-
|
|
115
|
-
else
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
126
|
+
if (ms) {
|
|
127
|
+
result.setTime(result.getTime() + value * ms)
|
|
128
|
+
} else if (unit == "D") {
|
|
129
|
+
result.setDate(result.getDate() + value)
|
|
130
|
+
} else {
|
|
131
|
+
result.setTime(
|
|
132
|
+
add_months(
|
|
133
|
+
result,
|
|
134
|
+
unit == "Y"
|
|
135
|
+
? value * 12
|
|
136
|
+
: value
|
|
137
|
+
).getTime()
|
|
138
|
+
)
|
|
139
|
+
}
|
|
123
140
|
}
|
|
124
141
|
return result
|
|
125
142
|
}
|
package/src/calendar.js
CHANGED
|
@@ -10,7 +10,9 @@ function days_of(year, month, day) {
|
|
|
10
10
|
year += Math.floor(month / 12)
|
|
11
11
|
month = (month % 12 + 12) % 12
|
|
12
12
|
}
|
|
13
|
-
if (month < 2)
|
|
13
|
+
if (month < 2) {
|
|
14
|
+
year--
|
|
15
|
+
}
|
|
14
16
|
const era = Math.floor(year / 400)
|
|
15
17
|
const yoe = year - era * 400
|
|
16
18
|
return era * 146097
|
package/src/date_to_string.js
CHANGED
|
@@ -20,18 +20,26 @@ const tokens = {
|
|
|
20
20
|
*/
|
|
21
21
|
function compile(format) {
|
|
22
22
|
let parts = formats.get(format)
|
|
23
|
-
if (parts)
|
|
23
|
+
if (parts) {
|
|
24
|
+
return parts
|
|
25
|
+
}
|
|
24
26
|
parts = []
|
|
25
27
|
let last = 0
|
|
26
28
|
for (const { 0: token, index = 0 } of format.matchAll(token_regex)) {
|
|
27
|
-
if (index > last)
|
|
29
|
+
if (index > last) {
|
|
30
|
+
parts.push(format.slice(last, index))
|
|
31
|
+
}
|
|
28
32
|
parts.push(
|
|
29
33
|
/** @type {number[]} */(tokens[token])/**/
|
|
30
34
|
)
|
|
31
35
|
last = index + token.length
|
|
32
36
|
}
|
|
33
|
-
if (last < format.length)
|
|
34
|
-
|
|
37
|
+
if (last < format.length) {
|
|
38
|
+
parts.push(format.slice(last))
|
|
39
|
+
}
|
|
40
|
+
if (formats.size >= 1000) {
|
|
41
|
+
formats.clear()
|
|
42
|
+
}
|
|
35
43
|
formats.set(format, parts)
|
|
36
44
|
return parts
|
|
37
45
|
}
|
|
@@ -86,7 +94,9 @@ export default function(
|
|
|
86
94
|
) {
|
|
87
95
|
const time = time_of("dateToString", date)
|
|
88
96
|
check_zone("dateToString", zone)
|
|
89
|
-
if (isNaN(time))
|
|
97
|
+
if (isNaN(time)) {
|
|
98
|
+
return "Invalid Date"
|
|
99
|
+
}
|
|
90
100
|
const fields = fields_of(date, zone)
|
|
91
101
|
const parts = compile(format)
|
|
92
102
|
let result = ""
|
|
@@ -98,7 +108,7 @@ export default function(
|
|
|
98
108
|
/** @type {number} */(fields[0])/**/
|
|
99
109
|
)
|
|
100
110
|
: pad(
|
|
101
|
-
|
|
111
|
+
/** @type {number} */(fields[/** @type {number} */(part[0])/**/])/**/ + /** @type {number} */(part[2])/**/,
|
|
102
112
|
/** @type {number} */(part[1])/**/
|
|
103
113
|
)
|
|
104
114
|
}
|
package/src/diff.js
CHANGED
|
@@ -42,7 +42,9 @@ function anchor_after(
|
|
|
42
42
|
/** @type {number} */(from_fields[6])/**/
|
|
43
43
|
]
|
|
44
44
|
const distance = wall_of_fields(fields) - to_wall
|
|
45
|
-
if (Math.abs(distance) >= 172800000)
|
|
45
|
+
if (Math.abs(distance) >= 172800000) {
|
|
46
|
+
return distance
|
|
47
|
+
}
|
|
46
48
|
const time = date_of(fields, zone).getTime()
|
|
47
49
|
return isNaN(time)
|
|
48
50
|
? distance
|
|
@@ -71,13 +73,19 @@ function anchor_after(
|
|
|
71
73
|
export default function(from, to, unit, zone) {
|
|
72
74
|
const start = time_of("diff", from)
|
|
73
75
|
const end = time_of("diff", to)
|
|
74
|
-
if (!units_of.has(unit))
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
if (!units_of.has(unit)) {
|
|
77
|
+
throw new RangeError(
|
|
78
|
+
`diff: Invalid unit "${String(unit)}"`
|
|
79
|
+
)
|
|
80
|
+
}
|
|
77
81
|
check_zone("diff", zone)
|
|
78
|
-
if (isNaN(start) || isNaN(end))
|
|
82
|
+
if (isNaN(start) || isNaN(end)) {
|
|
83
|
+
return NaN
|
|
84
|
+
}
|
|
79
85
|
const ms = unit_ms[unit]
|
|
80
|
-
if (ms)
|
|
86
|
+
if (ms) {
|
|
87
|
+
return Math.trunc((end - start) / ms) + 0
|
|
88
|
+
}
|
|
81
89
|
const from_fields = fields_of(from, zone)
|
|
82
90
|
const to_fields = fields_of(to, zone)
|
|
83
91
|
if (unit == "D" || unit == "W") {
|
|
@@ -99,8 +107,11 @@ export default function(from, to, unit, zone) {
|
|
|
99
107
|
const to_day = /** @type {number} */(to_fields[2])/**/
|
|
100
108
|
const from_day = /** @type {number} */(from_fields[2])/**/
|
|
101
109
|
if (Math.abs(to_day - from_day) > 2 && to_day < 27 && from_day < 27) {
|
|
102
|
-
if (months > 0 && to_day < from_day)
|
|
103
|
-
|
|
110
|
+
if (months > 0 && to_day < from_day) {
|
|
111
|
+
months--
|
|
112
|
+
} else if (months < 0 && to_day > from_day) {
|
|
113
|
+
months++
|
|
114
|
+
}
|
|
104
115
|
} else {
|
|
105
116
|
const to_wall = wall_of_fields(to_fields)
|
|
106
117
|
if (end >= start) {
|
|
@@ -111,14 +122,18 @@ export default function(from, to, unit, zone) {
|
|
|
111
122
|
to_wall,
|
|
112
123
|
end,
|
|
113
124
|
zone
|
|
114
|
-
) > 0)
|
|
125
|
+
) > 0) {
|
|
126
|
+
months--
|
|
127
|
+
}
|
|
115
128
|
if (anchor_after(
|
|
116
129
|
from_fields,
|
|
117
130
|
months + 1,
|
|
118
131
|
to_wall,
|
|
119
132
|
end,
|
|
120
133
|
zone
|
|
121
|
-
) <= 0)
|
|
134
|
+
) <= 0) {
|
|
135
|
+
months++
|
|
136
|
+
}
|
|
122
137
|
} else {
|
|
123
138
|
months = Math.min(months, 0)
|
|
124
139
|
while (months && anchor_after(
|
|
@@ -127,14 +142,18 @@ export default function(from, to, unit, zone) {
|
|
|
127
142
|
to_wall,
|
|
128
143
|
end,
|
|
129
144
|
zone
|
|
130
|
-
) < 0)
|
|
145
|
+
) < 0) {
|
|
146
|
+
months++
|
|
147
|
+
}
|
|
131
148
|
if (anchor_after(
|
|
132
149
|
from_fields,
|
|
133
150
|
months - 1,
|
|
134
151
|
to_wall,
|
|
135
152
|
end,
|
|
136
153
|
zone
|
|
137
|
-
) >= 0)
|
|
154
|
+
) >= 0) {
|
|
155
|
+
months--
|
|
156
|
+
}
|
|
138
157
|
}
|
|
139
158
|
}
|
|
140
159
|
return unit == "Y"
|
package/src/end_of.js
CHANGED
|
@@ -40,11 +40,15 @@ const units_of = new Set(
|
|
|
40
40
|
*/
|
|
41
41
|
export default function(date, unit, zone) {
|
|
42
42
|
const time = time_of("endOf", date)
|
|
43
|
-
if (!units_of.has(unit))
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
if (!units_of.has(unit)) {
|
|
44
|
+
throw new RangeError(
|
|
45
|
+
`endOf: Invalid unit "${String(unit)}"`
|
|
46
|
+
)
|
|
47
|
+
}
|
|
46
48
|
check_zone("endOf", zone)
|
|
47
|
-
if (isNaN(time))
|
|
49
|
+
if (isNaN(time)) {
|
|
50
|
+
return new Date(NaN)
|
|
51
|
+
}
|
|
48
52
|
const ms = unit_ms[unit]
|
|
49
53
|
if (ms) {
|
|
50
54
|
if (zone !== undefined) {
|
|
@@ -66,8 +70,10 @@ export default function(date, unit, zone) {
|
|
|
66
70
|
const result = new Date(time + rest - 1)
|
|
67
71
|
if (
|
|
68
72
|
result.getTimezoneOffset() == date.getTimezoneOffset()
|
|
69
|
-
|
|
70
|
-
)
|
|
73
|
+
&& result.getSeconds() == (ms > 1000 ? 59 : date.getSeconds())
|
|
74
|
+
) {
|
|
75
|
+
return result
|
|
76
|
+
}
|
|
71
77
|
result.setTime(unit_end(time, ms))
|
|
72
78
|
return result
|
|
73
79
|
}
|
|
@@ -102,16 +108,23 @@ export default function(date, unit, zone) {
|
|
|
102
108
|
}
|
|
103
109
|
const offset = offset_in(time, zone)
|
|
104
110
|
const fields = fields_of_wall(time + offset)
|
|
105
|
-
if (unit == "W")
|
|
106
|
-
|
|
107
|
-
|
|
111
|
+
if (unit == "W") {
|
|
112
|
+
fields[2] = /** @type {number} */(fields[2])/**/ + 7 - (/** @type {number} */(fields[7])/**/ + 6) % 7
|
|
113
|
+
} else {
|
|
114
|
+
fields[from - 1] = /** @type {number} */(fields[from - 1])/**/ + 1
|
|
115
|
+
}
|
|
116
|
+
for (let i = from; i < 7; i++) {
|
|
117
|
+
fields[i] = i == 2 ? 1 : 0
|
|
118
|
+
}
|
|
108
119
|
const wall = wall_of_fields(fields)
|
|
109
120
|
if (zone !== undefined) {
|
|
110
121
|
const next = offset_of(wall - offset, zone)
|
|
111
122
|
if (
|
|
112
123
|
next == offset
|
|
113
|
-
|
|
114
|
-
)
|
|
124
|
+
|| wall - next > time && offset_of(wall - next, zone) == next
|
|
125
|
+
) {
|
|
126
|
+
return new Date(wall - next - 1)
|
|
127
|
+
}
|
|
115
128
|
end = instant_of(wall, zone) - 1
|
|
116
129
|
}
|
|
117
130
|
return new Date(end_at(wall, end, time, zone))
|
package/src/start_of.js
CHANGED
|
@@ -36,11 +36,15 @@ const units_of = new Set(
|
|
|
36
36
|
*/
|
|
37
37
|
export default function(date, unit, zone) {
|
|
38
38
|
const time = time_of("startOf", date)
|
|
39
|
-
if (!units_of.has(unit))
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
if (!units_of.has(unit)) {
|
|
40
|
+
throw new RangeError(
|
|
41
|
+
`startOf: Invalid unit "${String(unit)}"`
|
|
42
|
+
)
|
|
43
|
+
}
|
|
42
44
|
check_zone("startOf", zone)
|
|
43
|
-
if (isNaN(time))
|
|
45
|
+
if (isNaN(time)) {
|
|
46
|
+
return new Date(NaN)
|
|
47
|
+
}
|
|
44
48
|
const ms = unit_ms[unit]
|
|
45
49
|
if (ms) {
|
|
46
50
|
if (zone !== undefined) {
|
|
@@ -60,8 +64,10 @@ export default function(date, unit, zone) {
|
|
|
60
64
|
const result = new Date(time - rest)
|
|
61
65
|
if (
|
|
62
66
|
result.getTimezoneOffset() == date.getTimezoneOffset()
|
|
63
|
-
|
|
64
|
-
)
|
|
67
|
+
&& result.getSeconds() == (ms > 1000 ? 0 : date.getSeconds())
|
|
68
|
+
) {
|
|
69
|
+
return result
|
|
70
|
+
}
|
|
65
71
|
result.setTime(unit_start(time, ms))
|
|
66
72
|
return result
|
|
67
73
|
}
|
|
@@ -84,15 +90,23 @@ export default function(date, unit, zone) {
|
|
|
84
90
|
0,
|
|
85
91
|
0
|
|
86
92
|
)
|
|
87
|
-
if (result.getTime() <= time && !(result.getHours() || result.getMinutes() || result.getSeconds()))
|
|
93
|
+
if (result.getTime() <= time && !(result.getHours() || result.getMinutes() || result.getSeconds())) {
|
|
94
|
+
return result
|
|
95
|
+
}
|
|
88
96
|
}
|
|
89
97
|
const fields = fields_of(date, zone)
|
|
90
|
-
if (unit == "W")
|
|
91
|
-
|
|
98
|
+
if (unit == "W") {
|
|
99
|
+
fields[2] = /** @type {number} */(fields[2])/**/ - (/** @type {number} */(fields[7])/**/ + 6) % 7
|
|
100
|
+
}
|
|
101
|
+
for (let i = from; i < 7; i++) {
|
|
102
|
+
fields[i] = i == 2 ? 1 : 0
|
|
103
|
+
}
|
|
92
104
|
const wall = wall_of_fields(fields)
|
|
93
105
|
if (zone !== undefined) {
|
|
94
106
|
const start = instant_of(wall, zone, true)
|
|
95
|
-
if (start <= time)
|
|
107
|
+
if (start <= time) {
|
|
108
|
+
return new Date(start)
|
|
109
|
+
}
|
|
96
110
|
}
|
|
97
111
|
const before = offset_in(wall - 86400000, zone)
|
|
98
112
|
const guess = wall - before
|
|
@@ -100,10 +114,12 @@ export default function(date, unit, zone) {
|
|
|
100
114
|
const earliest = after == before
|
|
101
115
|
? wall - offset_in(time, zone)
|
|
102
116
|
: wall - after
|
|
103
|
-
if (!(earliest >= -8.64e15))
|
|
117
|
+
if (!(earliest >= -8.64e15)) {
|
|
118
|
+
return new Date(NaN)
|
|
119
|
+
}
|
|
104
120
|
return new Date(
|
|
105
121
|
transition_of(
|
|
106
|
-
earliest,
|
|
122
|
+
earliest - 1,
|
|
107
123
|
after == before || guess > time
|
|
108
124
|
? time
|
|
109
125
|
: guess,
|
package/src/string_to_date.js
CHANGED
|
@@ -22,22 +22,30 @@ const zone_regex = /(?<![a-z])(?:[cemp][ds]t|gmt|utc?|z)(?![a-z])|(?:\s|[ap]m|\d
|
|
|
22
22
|
*/
|
|
23
23
|
function compile(format) {
|
|
24
24
|
let parts = formats.get(format)
|
|
25
|
-
if (parts)
|
|
25
|
+
if (parts) {
|
|
26
|
+
return parts
|
|
27
|
+
}
|
|
26
28
|
parts = []
|
|
27
29
|
let last = 0
|
|
28
30
|
for (const { 0: token, index = 0 } of format.matchAll(token_regex)) {
|
|
29
|
-
if (index > last)
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
if (index > last) {
|
|
32
|
+
parts.push(
|
|
33
|
+
[ ...format.slice(last, index) ].length
|
|
34
|
+
)
|
|
35
|
+
}
|
|
32
36
|
parts.push(
|
|
33
37
|
/** @type {number[]} */(tokens[token])/**/
|
|
34
38
|
)
|
|
35
39
|
last = index + token.length
|
|
36
40
|
}
|
|
37
|
-
if (last < format.length)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
if (last < format.length) {
|
|
42
|
+
parts.push(
|
|
43
|
+
[ ...format.slice(last) ].length
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
if (formats.size >= 1000) {
|
|
47
|
+
formats.clear()
|
|
48
|
+
}
|
|
41
49
|
formats.set(format, parts)
|
|
42
50
|
return parts
|
|
43
51
|
}
|
|
@@ -47,9 +55,14 @@ function compile(format) {
|
|
|
47
55
|
*/
|
|
48
56
|
function out_of_range(match) {
|
|
49
57
|
const [ , year, month, day, hour, minute, second ] = match
|
|
50
|
-
if (month === undefined)
|
|
58
|
+
if (month === undefined) {
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
51
61
|
const index = +month - 1
|
|
52
|
-
return index < 0 || index > 11 || day !== undefined && (+day < 1 || +day > last_day(
|
|
62
|
+
return index < 0 || index > 11 || day !== undefined && (+day < 1 || +day > last_day(
|
|
63
|
+
+/** @type {string} */(year)/**/,
|
|
64
|
+
index
|
|
65
|
+
)) || +(hour ?? 0) > (+(minute ?? 0) || +(second ?? 0) || +(match[7] ?? 0)
|
|
53
66
|
? 23
|
|
54
67
|
: 24) || +(minute ?? 0) > 59 || +(second ?? 0) > 59
|
|
55
68
|
}
|
|
@@ -88,26 +101,39 @@ function out_of_range(match) {
|
|
|
88
101
|
* @example stringToDate("2024-07-15 09:00", "YYYY-MM-DD HH:mm", "Asia/Seoul")
|
|
89
102
|
*/
|
|
90
103
|
export default function(date, format, zone) {
|
|
91
|
-
if (typeof date != "string")
|
|
92
|
-
|
|
93
|
-
|
|
104
|
+
if (typeof date != "string") {
|
|
105
|
+
throw new TypeError(
|
|
106
|
+
`stringToDate: Expected a string, got ${date === null ? "null" : typeof date}`
|
|
107
|
+
)
|
|
108
|
+
}
|
|
94
109
|
check_zone("stringToDate", zone)
|
|
95
110
|
if (format === undefined) {
|
|
96
111
|
const native = new Date(date)
|
|
97
112
|
const valid = !isNaN(native.getTime())
|
|
98
|
-
if (!valid && zone === undefined)
|
|
113
|
+
if (!valid && zone === undefined) {
|
|
114
|
+
return native
|
|
115
|
+
}
|
|
99
116
|
const match = iso_regex.exec(date)
|
|
100
|
-
if (match && out_of_range(match))
|
|
117
|
+
if (match && out_of_range(match)) {
|
|
118
|
+
return new Date(NaN)
|
|
119
|
+
}
|
|
101
120
|
const parsed = match?.[8] === ""
|
|
102
121
|
? match
|
|
103
122
|
: null
|
|
104
123
|
if (parsed) {
|
|
105
|
-
if (
|
|
106
|
-
|
|
124
|
+
if (parsed[1] == "-000000") {
|
|
125
|
+
return new Date(NaN)
|
|
126
|
+
}
|
|
127
|
+
if (zone === undefined && parsed[4] !== undefined) {
|
|
128
|
+
return native
|
|
129
|
+
}
|
|
130
|
+
if (!valid && isNaN(Date.parse(`${date}Z`))) {
|
|
131
|
+
return native
|
|
132
|
+
}
|
|
107
133
|
const fraction = parsed[7]
|
|
108
134
|
return date_of(
|
|
109
135
|
[
|
|
110
|
-
|
|
136
|
+
+/** @type {string} */(parsed[1])/**/,
|
|
111
137
|
+(parsed[2] ?? 1) - 1,
|
|
112
138
|
+(parsed[3] ?? 1),
|
|
113
139
|
+(parsed[4] ?? 0),
|
|
@@ -120,11 +146,17 @@ export default function(date, format, zone) {
|
|
|
120
146
|
zone
|
|
121
147
|
)
|
|
122
148
|
}
|
|
123
|
-
if (zone === undefined)
|
|
149
|
+
if (zone === undefined) {
|
|
150
|
+
return native
|
|
151
|
+
}
|
|
124
152
|
const text = date.replace(comment_regex, "")
|
|
125
|
-
if (zone_regex.test(text))
|
|
153
|
+
if (zone_regex.test(text)) {
|
|
154
|
+
return native
|
|
155
|
+
}
|
|
126
156
|
const wall = new Date(`${text} GMT`)
|
|
127
|
-
if (!valid && isNaN(wall.getTime()))
|
|
157
|
+
if (!valid && isNaN(wall.getTime())) {
|
|
158
|
+
return native
|
|
159
|
+
}
|
|
128
160
|
return date_of(
|
|
129
161
|
isNaN(wall.getTime())
|
|
130
162
|
? fields_of(native)
|
|
@@ -157,15 +189,23 @@ export default function(date, format, zone) {
|
|
|
157
189
|
length = 6
|
|
158
190
|
min = -271821
|
|
159
191
|
}
|
|
160
|
-
while (text.length < length && date.charCodeAt(index) > 47 && date.charCodeAt(index) < 58)
|
|
161
|
-
|
|
192
|
+
while (text.length < length && date.charCodeAt(index) > 47 && date.charCodeAt(index) < 58) {
|
|
193
|
+
text += date[index++]
|
|
194
|
+
}
|
|
195
|
+
if (!text || sign && (text.length < 6 || sign == "-" && !+text)) {
|
|
196
|
+
return new Date(NaN)
|
|
197
|
+
}
|
|
162
198
|
if (part[0] == 6) {
|
|
163
|
-
while (date.charCodeAt(index) > 47 && date.charCodeAt(index) < 58)
|
|
199
|
+
while (date.charCodeAt(index) > 47 && date.charCodeAt(index) < 58) {
|
|
200
|
+
index++
|
|
201
|
+
}
|
|
164
202
|
}
|
|
165
203
|
const value = part[0] == 6
|
|
166
204
|
? +text.padEnd(3, "0")
|
|
167
205
|
: +(sign + text)
|
|
168
|
-
if (value > /** @type {number} */(part[3])/**/ || value < min)
|
|
206
|
+
if (value > /** @type {number} */(part[3])/**/ || value < min) {
|
|
207
|
+
return new Date(NaN)
|
|
208
|
+
}
|
|
169
209
|
fields[/** @type {number} */(part[0])/**/] = /** @type {number} */(part[0])/**/ == 1
|
|
170
210
|
? value - 1
|
|
171
211
|
: value
|
|
@@ -173,6 +213,8 @@ export default function(date, format, zone) {
|
|
|
173
213
|
if (index != date.length || /** @type {number} */(fields[2])/**/ > last_day(
|
|
174
214
|
/** @type {number} */(fields[0])/**/,
|
|
175
215
|
/** @type {number} */(fields[1])/**/
|
|
176
|
-
))
|
|
216
|
+
)) {
|
|
217
|
+
return new Date(NaN)
|
|
218
|
+
}
|
|
177
219
|
return date_of(fields, zone)
|
|
178
220
|
}
|
package/src/zone.js
CHANGED
|
@@ -7,9 +7,12 @@ const formatters = new Map()
|
|
|
7
7
|
/**
|
|
8
8
|
* @param {string} name
|
|
9
9
|
* @param {string=} zone
|
|
10
|
+
* @returns {void}
|
|
10
11
|
*/
|
|
11
12
|
function check_zone(name, zone) {
|
|
12
|
-
if (zone === undefined || formatters.has(zone))
|
|
13
|
+
if (zone === undefined || formatters.has(zone)) {
|
|
14
|
+
return
|
|
15
|
+
}
|
|
13
16
|
try {
|
|
14
17
|
formatter_of(zone)
|
|
15
18
|
} catch (error) {
|
|
@@ -25,9 +28,11 @@ function check_zone(name, zone) {
|
|
|
25
28
|
* @returns {Date}
|
|
26
29
|
*/
|
|
27
30
|
function date_of(fields, zone) {
|
|
28
|
-
if (zone !== undefined)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
if (zone !== undefined) {
|
|
32
|
+
return new Date(
|
|
33
|
+
instant_of(wall_of_fields(fields), zone)
|
|
34
|
+
)
|
|
35
|
+
}
|
|
31
36
|
const year = /** @type {number} */(fields[0])/**/
|
|
32
37
|
if (year >= 0 && year < 100) {
|
|
33
38
|
const date = new Date(0)
|
|
@@ -58,10 +63,14 @@ function date_of(fields, zone) {
|
|
|
58
63
|
* @returns {number}
|
|
59
64
|
*/
|
|
60
65
|
function end_at(wall, end, time, zone) {
|
|
61
|
-
if (end < time)
|
|
66
|
+
if (end < time) {
|
|
67
|
+
return wall - offset_in(time, zone) - 1
|
|
68
|
+
}
|
|
62
69
|
const next = end + 1
|
|
63
70
|
const offset = offset_in(next, zone)
|
|
64
|
-
if (next + offset == wall || offset_in(end, zone) != offset)
|
|
71
|
+
if (next + offset == wall || offset_in(end, zone) != offset) {
|
|
72
|
+
return end
|
|
73
|
+
}
|
|
65
74
|
const transition = transition_of(time, next, zone)
|
|
66
75
|
return transition + offset_in(transition, zone) >= wall
|
|
67
76
|
? transition - 1
|
|
@@ -73,16 +82,18 @@ function end_at(wall, end, time, zone) {
|
|
|
73
82
|
* @returns {number[]}
|
|
74
83
|
*/
|
|
75
84
|
function fields_of(date, zone) {
|
|
76
|
-
if (zone === undefined)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
if (zone === undefined) {
|
|
86
|
+
return [
|
|
87
|
+
date.getFullYear(),
|
|
88
|
+
date.getMonth(),
|
|
89
|
+
date.getDate(),
|
|
90
|
+
date.getHours(),
|
|
91
|
+
date.getMinutes(),
|
|
92
|
+
date.getSeconds(),
|
|
93
|
+
date.getMilliseconds(),
|
|
94
|
+
date.getDay()
|
|
95
|
+
]
|
|
96
|
+
}
|
|
86
97
|
const time = date.getTime()
|
|
87
98
|
return fields_of_wall(time + offset_of(time, zone))
|
|
88
99
|
}
|
|
@@ -92,7 +103,9 @@ function fields_of(date, zone) {
|
|
|
92
103
|
*/
|
|
93
104
|
function formatter_of(zone) {
|
|
94
105
|
let formatter = formatters.get(zone)
|
|
95
|
-
if (formatter)
|
|
106
|
+
if (formatter) {
|
|
107
|
+
return formatter
|
|
108
|
+
}
|
|
96
109
|
formatter = new Intl.DateTimeFormat(
|
|
97
110
|
"en-US",
|
|
98
111
|
{
|
|
@@ -105,10 +118,14 @@ function formatter_of(zone) {
|
|
|
105
118
|
if (known) {
|
|
106
119
|
formatter = known
|
|
107
120
|
} else {
|
|
108
|
-
if (canonical.size >= 1000)
|
|
121
|
+
if (canonical.size >= 1000) {
|
|
122
|
+
canonical.clear()
|
|
123
|
+
}
|
|
109
124
|
canonical.set(name, formatter)
|
|
110
125
|
}
|
|
111
|
-
if (formatters.size >= 1000)
|
|
126
|
+
if (formatters.size >= 1000) {
|
|
127
|
+
formatters.clear()
|
|
128
|
+
}
|
|
112
129
|
formatters.set(zone, formatter)
|
|
113
130
|
return formatter
|
|
114
131
|
}
|
|
@@ -122,7 +139,9 @@ function instant_of(wall, zone, exact) {
|
|
|
122
139
|
const before = offset_of(wall - 86400000, zone)
|
|
123
140
|
const guess = wall - before
|
|
124
141
|
const offset = offset_of(guess, zone)
|
|
125
|
-
if (offset == before)
|
|
142
|
+
if (offset == before) {
|
|
143
|
+
return guess
|
|
144
|
+
}
|
|
126
145
|
const candidate = wall - offset
|
|
127
146
|
return offset_of(candidate, zone) == offset
|
|
128
147
|
? candidate
|
|
@@ -155,10 +174,12 @@ function offset_of(time, zone) {
|
|
|
155
174
|
: 8.64e15
|
|
156
175
|
)
|
|
157
176
|
)
|
|
158
|
-
if (!match)
|
|
177
|
+
if (!match) {
|
|
178
|
+
return 0
|
|
179
|
+
}
|
|
159
180
|
return (match[1] == "-" ? -1 : 1) * (
|
|
160
|
-
(
|
|
161
|
-
|
|
181
|
+
(+/** @type {string} */(match[2])/**/ * 60 + +/** @type {string} */(match[3])/**/) * 60000
|
|
182
|
+
+ +(match[4] ?? 0) * 1000
|
|
162
183
|
)
|
|
163
184
|
}
|
|
164
185
|
/**
|
|
@@ -171,8 +192,11 @@ function transition_of(from, to, zone) {
|
|
|
171
192
|
const offset = offset_in(to, zone)
|
|
172
193
|
while (to - from > 1) {
|
|
173
194
|
const middle = Math.floor((from + to) / 2)
|
|
174
|
-
if (offset_in(middle, zone) == offset)
|
|
175
|
-
|
|
195
|
+
if (offset_in(middle, zone) == offset) {
|
|
196
|
+
to = middle
|
|
197
|
+
} else {
|
|
198
|
+
from = middle
|
|
199
|
+
}
|
|
176
200
|
}
|
|
177
201
|
return to
|
|
178
202
|
}
|
|
@@ -190,11 +214,17 @@ function unit_end(time, ms, zone) {
|
|
|
190
214
|
for (;;) {
|
|
191
215
|
const offset = offset_in(at, zone)
|
|
192
216
|
const end = (bucket + 1) * ms - offset - 1
|
|
193
|
-
if (!(end <= 8.64e15))
|
|
194
|
-
|
|
217
|
+
if (!(end <= 8.64e15)) {
|
|
218
|
+
return NaN
|
|
219
|
+
}
|
|
220
|
+
if (offset_in(end, zone) == offset) {
|
|
221
|
+
return end
|
|
222
|
+
}
|
|
195
223
|
const transition = transition_of(at, end, zone)
|
|
196
224
|
const wall = transition + offset_in(transition, zone)
|
|
197
|
-
if (Math.floor(wall / ms) != bucket || wall <= transition - 1 + offset)
|
|
225
|
+
if (Math.floor(wall / ms) != bucket || wall <= transition - 1 + offset) {
|
|
226
|
+
return transition - 1
|
|
227
|
+
}
|
|
198
228
|
at = transition
|
|
199
229
|
}
|
|
200
230
|
}
|
|
@@ -212,12 +242,18 @@ function unit_start(time, ms, zone) {
|
|
|
212
242
|
for (;;) {
|
|
213
243
|
const offset = offset_in(at, zone)
|
|
214
244
|
const start = bucket * ms - offset
|
|
215
|
-
if (!(start >= -8.64e15))
|
|
216
|
-
|
|
245
|
+
if (!(start >= -8.64e15)) {
|
|
246
|
+
return NaN
|
|
247
|
+
}
|
|
248
|
+
if (offset_in(start, zone) == offset) {
|
|
249
|
+
return start
|
|
250
|
+
}
|
|
217
251
|
const transition = transition_of(start, at, zone)
|
|
218
252
|
const before = transition - 1
|
|
219
253
|
const wall = before + offset_in(before, zone)
|
|
220
|
-
if (Math.floor(wall / ms) != bucket || wall >= transition + offset)
|
|
254
|
+
if (Math.floor(wall / ms) != bucket || wall >= transition + offset) {
|
|
255
|
+
return transition
|
|
256
|
+
}
|
|
221
257
|
at = before
|
|
222
258
|
}
|
|
223
259
|
}
|
package/types/add.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../src/add.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../src/add.js"],"names":[],"mappings":"AAgFA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,CAAC,OAAO,WAAU,IAAI,EAzBjB,IAyBiB,EAAE,GAAG,EAxBtB,MAwBsB,EAAE,IAAI,AAVpC,CACA,EADQ,OAAO,cAAc,EAAE,QAAQ,YAUH,GAT1B,IAAI,CA6DhB"}
|
package/types/calendar.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../src/calendar.js"],"names":[],"mappings":"AACA;;;;;GAKG;AACH,iBAAS,OAAO,CAAC,IAAI,EALV,MAKU,EAAE,KAAK,EAJjB,MAIiB,EAAE,GAAG,EAHtB,MAGsB,GAFpB,MAAM,
|
|
1
|
+
{"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../src/calendar.js"],"names":[],"mappings":"AACA;;;;;GAKG;AACH,iBAAS,OAAO,CAAC,IAAI,EALV,MAKU,EAAE,KAAK,EAJjB,MAIiB,EAAE,GAAG,EAHtB,MAGsB,GAFpB,MAAM,CAqBlB;AACD;;;GAGG;AACH,iBAAS,cAAc,CAAC,IAAI,EAHjB,MAGiB,GAFf,MAAM,EAAE,CA6BpB;AACD;;;;GAIG;AACH,iBAAS,QAAQ,CAAC,IAAI,EAJX,MAIW,EAAE,KAAK,EAHlB,MAGkB,GAFhB,MAAM,CAMlB;AACD;;;GAGG;AACH,iBAAS,cAAc,CAAC,MAAM,EAHnB,MAAM,EAGa,GAFjB,MAAM,CAYlB;AACD,OAAO,EACN,OAAO,EACP,cAAc,EACd,QAAQ,EACR,cAAc,EACd,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"date_to_string.d.ts","sourceRoot":"","sources":["../src/date_to_string.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"date_to_string.d.ts","sourceRoot":"","sources":["../src/date_to_string.js"],"names":[],"mappings":"AA8DA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,OAAO,WACb,IAAI,EAxBM,IAwBN,EACJ,MAAM,AAxBJ,CAaA,EAbQ,MAAM,YAwBkB,EAClC,IAAI,AAZF,CACA,EADQ,OAAO,cAAc,EAAE,QAAQ,YAYrC,GAXQ,MAAM,CAkClB"}
|
package/types/diff.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../src/diff.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../src/diff.js"],"names":[],"mappings":"AAoDA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,OAAO,WAAU,IAAI,EAhBjB,IAgBiB,EAAE,EAAE,EAfrB,IAeqB,EAAE,IAAI,EAd3B,OAAO,cAAc,EAAE,IAcI,EAAE,IAAI,AAVzC,CACA,EADQ,OAAO,cAAc,EAAE,QAAQ,YAUE,GAT/B,MAAM,CAkGlB"}
|
package/types/end_of.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"end_of.d.ts","sourceRoot":"","sources":["../src/end_of.js"],"names":[],"mappings":"AAsBA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,OAAO,WAAU,IAAI,EAfjB,IAeiB,EAAE,IAAI,EAdvB,OAAO,CAAC,OAAO,cAAc,EAAE,IAAI,EAAE,KAAK,CAcnB,EAAE,IAAI,AAVrC,CACA,EADQ,OAAO,cAAc,EAAE,QAAQ,YAUF,GAT3B,IAAI,
|
|
1
|
+
{"version":3,"file":"end_of.d.ts","sourceRoot":"","sources":["../src/end_of.js"],"names":[],"mappings":"AAsBA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,OAAO,WAAU,IAAI,EAfjB,IAeiB,EAAE,IAAI,EAdvB,OAAO,CAAC,OAAO,cAAc,EAAE,IAAI,EAAE,KAAK,CAcnB,EAAE,IAAI,AAVrC,CACA,EADQ,OAAO,cAAc,EAAE,QAAQ,YAUF,GAT3B,IAAI,CAmGhB"}
|
package/types/start_of.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start_of.d.ts","sourceRoot":"","sources":["../src/start_of.js"],"names":[],"mappings":"AAkBA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,OAAO,WAAU,IAAI,EAfjB,IAeiB,EAAE,IAAI,EAdvB,OAAO,CAAC,OAAO,cAAc,EAAE,IAAI,EAAE,KAAK,CAcnB,EAAE,IAAI,AAVrC,CACA,EADQ,OAAO,cAAc,EAAE,QAAQ,YAUF,GAT3B,IAAI,
|
|
1
|
+
{"version":3,"file":"start_of.d.ts","sourceRoot":"","sources":["../src/start_of.js"],"names":[],"mappings":"AAkBA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,OAAO,WAAU,IAAI,EAfjB,IAeiB,EAAE,IAAI,EAdvB,OAAO,CAAC,OAAO,cAAc,EAAE,IAAI,EAAE,KAAK,CAcnB,EAAE,IAAI,AAVrC,CACA,EADQ,OAAO,cAAc,EAAE,QAAQ,YAUF,GAT3B,IAAI,CAqGhB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"string_to_date.d.ts","sourceRoot":"","sources":["../src/string_to_date.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"string_to_date.d.ts","sourceRoot":"","sources":["../src/string_to_date.js"],"names":[],"mappings":"AAoEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,CAAC,OAAO,WAAU,IAAI,EAvBjB,MAuBiB,EAAE,MAAM,AAtBjC,CAaA,EAbQ,MAAM,YAsBmB,EAAE,IAAI,AATvC,CACA,EADQ,OAAO,cAAc,EAAE,QAAQ,YASA,GAR7B,IAAI,CA6HhB"}
|
package/types/zone.d.ts
CHANGED
package/types/zone.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zone.d.ts","sourceRoot":"","sources":["../src/zone.js"],"names":[],"mappings":"AAMA
|
|
1
|
+
{"version":3,"file":"zone.d.ts","sourceRoot":"","sources":["../src/zone.js"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,iBAAS,UAAU,CAAC,IAAI,EAJb,MAIa,EAAE,IAAI,AAH3B,CACA,EADQ,MAAM,YAGa,GAFjB,IAAI,CAchB;AACD;;;;GAIG;AACH,iBAAS,OAAO,CAAC,MAAM,EAJZ,MAAM,EAIM,EAAE,IAAI,AAH1B,CACA,EADQ,MAAM,YAGY,GAFhB,IAAI,CA6BhB;AACD;;;;;;GAMG;AACH,iBAAS,MAAM,CAAC,IAAI,EANT,MAMS,EAAE,GAAG,EALd,MAKc,EAAE,IAAI,EAJpB,MAIoB,EAAE,IAAI,AAHlC,CACA,EADQ,MAAM,YAGoB,GAFxB,MAAM,CAelB;AACD;;;;GAIG;AACH,iBAAS,SAAS,CAAC,IAAI,EAJZ,IAIY,EAAE,IAAI,AAH1B,CACA,EADQ,MAAM,YAGY,GAFhB,MAAM,EAAE,CAiBpB;AAiCD;;;;;GAKG;AACH,iBAAS,UAAU,CAAC,IAAI,EALb,MAKa,EAAE,IAAI,EAJnB,MAImB,EAAE,KAAK,AAHlC,CACA,EADQ,OAAO,YAGmB,GAFxB,MAAM,CAelB;AACD;;;;GAIG;AACH,iBAAS,SAAS,CAAC,IAAI,EAJZ,MAIY,EAAE,IAAI,AAH1B,CACA,EADQ,MAAM,YAGY,GAFhB,MAAM,CAMlB;AACD;;;;GAIG;AACH,iBAAS,SAAS,CAAC,IAAI,EAJZ,MAIY,EAAE,IAAI,EAHlB,MAGkB,GAFhB,MAAM,CAmBlB;AACD;;;;;GAKG;AACH,iBAAS,aAAa,CAAC,IAAI,EALhB,MAKgB,EAAE,EAAE,EAJpB,MAIoB,EAAE,IAAI,AAHlC,CACA,EADQ,MAAM,YAGoB,GAFxB,MAAM,CAalB;AACD;;;;;GAKG;AACH,iBAAS,QAAQ,CAAC,IAAI,EALX,MAKW,EAAE,EAAE,EAJf,MAIe,EAAE,IAAI,AAH7B,CACA,EADQ,MAAM,YAGe,GAFnB,MAAM,CAuBlB;AACD;;;;;GAKG;AACH,iBAAS,UAAU,CAAC,IAAI,EALb,MAKa,EAAE,EAAE,EAJjB,MAIiB,EAAE,IAAI,AAH/B,CACA,EADQ,MAAM,YAGiB,GAFrB,MAAM,CAwBlB;AACD,OAAO,EACN,UAAU,EACV,OAAO,EACP,MAAM,EACN,SAAS,EACT,UAAU,EACV,SAAS,EACT,SAAS,EACT,aAAa,EACb,QAAQ,EACR,UAAU,EACV,CAAA"}
|