@miso.ai/server-commons 0.6.5-beta.15 → 0.6.5-beta.17
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/date.js +125 -38
- package/test/date.test.js +77 -21
package/package.json
CHANGED
package/src/date.js
CHANGED
|
@@ -36,57 +36,144 @@ const RE_DATE_EXPR = /^(?:\d{4})|(?:\d{4}-[Qq\d]\d)|(?:\d{4}-\d{2}-[Ww\d]\d)$/;
|
|
|
36
36
|
// TODO: support hour, minute
|
|
37
37
|
|
|
38
38
|
export function startOfDate(expr) {
|
|
39
|
+
return floorDate(expr);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function endOfDate(expr) {
|
|
39
43
|
if (expr === undefined) {
|
|
40
44
|
return undefined;
|
|
41
45
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
return nextDate(expr) - 1000; // 1 sec
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function floorDate(expr, unit) {
|
|
50
|
+
validateUnit(unit);
|
|
51
|
+
if (expr === undefined) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
const [_unit, ts] = parseDateExpr(expr);
|
|
55
|
+
if (unit === undefined || !isGranular(unit, _unit)) {
|
|
56
|
+
return ts;
|
|
57
|
+
}
|
|
58
|
+
return floorTimestamp(ts, unit);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function floorTimestamp(ts, unit) {
|
|
62
|
+
switch (unit) {
|
|
63
|
+
case 'millisecond':
|
|
64
|
+
return ts;
|
|
65
|
+
case 'second':
|
|
66
|
+
return ts - ts % TS_PER_UNIT.s;
|
|
67
|
+
case 'minute':
|
|
68
|
+
return ts - ts % TS_PER_UNIT.m;
|
|
69
|
+
case 'hour':
|
|
70
|
+
return ts - ts % TS_PER_UNIT.h;
|
|
54
71
|
case 'day':
|
|
55
|
-
return
|
|
72
|
+
return ts - ts % TS_PER_UNIT.d;
|
|
73
|
+
}
|
|
74
|
+
const d = new Date(ts);
|
|
75
|
+
const year = d.getUTCFullYear();
|
|
76
|
+
const month = d.getUTCMonth() + 1;
|
|
77
|
+
const day = d.getUTCDate();
|
|
78
|
+
switch (unit) {
|
|
79
|
+
case 'week':
|
|
80
|
+
return Date.UTC(year, month - 1, day > 21 ? 22 : day - (day - 1) % 7);
|
|
81
|
+
case 'month':
|
|
82
|
+
return Date.UTC(year, month - 1);
|
|
83
|
+
case 'quarter':
|
|
84
|
+
return Date.UTC(year, month - month % 3 - 3);
|
|
85
|
+
case 'year':
|
|
86
|
+
return Date.UTC(year);
|
|
56
87
|
default:
|
|
57
|
-
throw new Error(`Unrecognized
|
|
88
|
+
throw new Error(`Unrecognized unit: ${unit}`);
|
|
58
89
|
}
|
|
59
90
|
}
|
|
60
91
|
|
|
61
|
-
export function
|
|
92
|
+
export function ceilDate(expr, unit) {
|
|
93
|
+
validateUnit(unit);
|
|
62
94
|
if (expr === undefined) {
|
|
63
95
|
return undefined;
|
|
64
96
|
}
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
97
|
+
const [_unit, ts] = parseDateExpr(expr);
|
|
98
|
+
unit = unit || _unit;
|
|
99
|
+
const floored = floorTimestamp(ts, unit);
|
|
100
|
+
return floored === ts ? floored : nextTimestamp(floored, unit);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function nextDate(expr, unit) {
|
|
104
|
+
validateUnit(unit);
|
|
105
|
+
if (expr === undefined) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
const [_unit, ts] = parseDateExpr(expr);
|
|
109
|
+
return nextTimestamp(ts + 1, unit || _unit);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function nextTimestamp(ts, unit) {
|
|
113
|
+
switch (unit) {
|
|
114
|
+
case 'millisecond':
|
|
115
|
+
return ts;
|
|
116
|
+
case 'second':
|
|
117
|
+
return ts + TS_PER_UNIT.s - ts % TS_PER_UNIT.s;
|
|
118
|
+
case 'minute':
|
|
119
|
+
return ts + TS_PER_UNIT.m - ts % TS_PER_UNIT.m;
|
|
120
|
+
case 'hour':
|
|
121
|
+
return ts + TS_PER_UNIT.h - ts % TS_PER_UNIT.h;
|
|
78
122
|
case 'day':
|
|
79
|
-
return
|
|
123
|
+
return ts + TS_PER_UNIT.d - ts % TS_PER_UNIT.d;
|
|
124
|
+
}
|
|
125
|
+
const d = new Date(ts);
|
|
126
|
+
const year = d.getUTCFullYear();
|
|
127
|
+
const month = d.getUTCMonth() + 1;
|
|
128
|
+
const day = d.getUTCDate();
|
|
129
|
+
switch (unit) {
|
|
130
|
+
case 'week':
|
|
131
|
+
return day > 21 ? Date.UTC(year, month, 1) : Date.UTC(year, month - 1, day - (day - 1) % 7 + 7);
|
|
132
|
+
case 'month':
|
|
133
|
+
return Date.UTC(year, month, 1);
|
|
134
|
+
case 'quarter':
|
|
135
|
+
return Date.UTC(year, month - month % 3 + 3);
|
|
136
|
+
case 'year':
|
|
137
|
+
return Date.UTC(year + 1);
|
|
80
138
|
default:
|
|
81
|
-
throw new Error(`Unrecognized
|
|
139
|
+
throw new Error(`Unrecognized unit: ${unit}`);
|
|
82
140
|
}
|
|
83
141
|
}
|
|
84
142
|
|
|
85
|
-
export function
|
|
143
|
+
export function prevDate(expr, unit) {
|
|
144
|
+
validateUnit(unit);
|
|
86
145
|
if (expr === undefined) {
|
|
87
146
|
return undefined;
|
|
88
147
|
}
|
|
89
|
-
|
|
148
|
+
const [_unit, ts] = parseDateExpr(expr);
|
|
149
|
+
return floorTimestamp(ts - 1, unit || _unit);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
const UNIT_GRANULARITY = {
|
|
155
|
+
'millisecond': 10,
|
|
156
|
+
'second': 9,
|
|
157
|
+
'minute': 8,
|
|
158
|
+
'hour': 7,
|
|
159
|
+
'day': 6,
|
|
160
|
+
'week': 5,
|
|
161
|
+
'month': 4,
|
|
162
|
+
'quarter': 3,
|
|
163
|
+
'year': 2,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
function validateUnit(unit) {
|
|
167
|
+
if (unit === undefined) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (typeof unit !== 'string' || !UNIT_GRANULARITY[unit]) {
|
|
171
|
+
throw new Error(`Unrecognized unit: ${unit}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function isGranular(unit0, unit1) {
|
|
176
|
+
return UNIT_GRANULARITY[unit0] > UNIT_GRANULARITY[unit1];
|
|
90
177
|
}
|
|
91
178
|
|
|
92
179
|
function parseDateExpr(expr) {
|
|
@@ -95,7 +182,7 @@ function parseDateExpr(expr) {
|
|
|
95
182
|
if (expr < 100) {
|
|
96
183
|
throw new Error(`Unrecognized date: ${expr}`);
|
|
97
184
|
}
|
|
98
|
-
return expr < 10000 ?
|
|
185
|
+
return expr < 10000 ? ['year', Date.UTC(expr)] : ['millisecond', expr];
|
|
99
186
|
case 'string':
|
|
100
187
|
expr = expr.trim();
|
|
101
188
|
const len = expr.length;
|
|
@@ -107,28 +194,28 @@ function parseDateExpr(expr) {
|
|
|
107
194
|
throw new Error(`Unrecognized date: ${expr}`);
|
|
108
195
|
}
|
|
109
196
|
if (len === 4) {
|
|
110
|
-
return
|
|
197
|
+
return ['year', Date.UTC(year)];
|
|
111
198
|
}
|
|
112
199
|
if (len === 7 && (expr.charAt(5) === 'Q' || expr.charAt(5) === 'q')) {
|
|
113
200
|
const quarter = parseInt(expr.charAt(6), 10);
|
|
114
201
|
if (isNaN(quarter) || quarter < 1 || quarter > 4) {
|
|
115
202
|
throw new Error(`Unrecognized date: ${expr}`);
|
|
116
203
|
}
|
|
117
|
-
return
|
|
204
|
+
return ['quarter', Date.UTC(year, (quarter - 1) * 3)];
|
|
118
205
|
}
|
|
119
206
|
const month = parseInt(expr.slice(5, 7), 10);
|
|
120
207
|
if (isNaN(month) || month < 1 || month > 12) {
|
|
121
208
|
throw new Error(`Unrecognized date: ${expr}`);
|
|
122
209
|
}
|
|
123
210
|
if (len === 7) {
|
|
124
|
-
return
|
|
211
|
+
return ['month', Date.UTC(year, month - 1)];
|
|
125
212
|
}
|
|
126
213
|
if (expr.charAt(8) === 'W' || expr.charAt(8) === 'w') {
|
|
127
214
|
const week = parseInt(expr.charAt(9), 10);
|
|
128
215
|
if (isNaN(week) || week < 1 || week > 4) {
|
|
129
216
|
throw new Error(`Unrecognized date: ${expr}`);
|
|
130
217
|
}
|
|
131
|
-
return
|
|
218
|
+
return ['week', Date.UTC(year, month - 1, week * 7 - 6)];
|
|
132
219
|
}
|
|
133
220
|
let date;
|
|
134
221
|
try {
|
|
@@ -137,7 +224,7 @@ function parseDateExpr(expr) {
|
|
|
137
224
|
} catch (_) {
|
|
138
225
|
throw new Error(`Unrecognized date: ${expr}`);
|
|
139
226
|
}
|
|
140
|
-
return
|
|
227
|
+
return ['day', Date.UTC(year, month - 1, date.getUTCDate())];
|
|
141
228
|
default:
|
|
142
229
|
throw new Error(`Unrecognized date: ${expr}`);
|
|
143
230
|
}
|
package/test/date.test.js
CHANGED
|
@@ -1,31 +1,87 @@
|
|
|
1
1
|
import { test } from 'uvu';
|
|
2
2
|
import * as assert from 'uvu/assert';
|
|
3
|
-
import { startOfDate, endOfDate } from '../src/index.js';
|
|
3
|
+
import { floorDate, ceilDate, nextDate, prevDate, startOfDate, endOfDate } from '../src/index.js';
|
|
4
|
+
|
|
5
|
+
function testDateFn(fn, input, output) {
|
|
6
|
+
assert.equal(new Date(fn(input)).toISOString(), output, `${fn.name}(${input})`);
|
|
7
|
+
}
|
|
4
8
|
|
|
5
9
|
test('startOfDate', () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
testDateFn(startOfDate, '2025', '2025-01-01T00:00:00.000Z');
|
|
11
|
+
testDateFn(startOfDate, '2025-Q1', '2025-01-01T00:00:00.000Z');
|
|
12
|
+
testDateFn(startOfDate, '2025-q2', '2025-04-01T00:00:00.000Z');
|
|
13
|
+
testDateFn(startOfDate, '2025-Q3', '2025-07-01T00:00:00.000Z');
|
|
14
|
+
testDateFn(startOfDate, '2025-q4', '2025-10-01T00:00:00.000Z');
|
|
15
|
+
testDateFn(startOfDate, '2025-02', '2025-02-01T00:00:00.000Z');
|
|
16
|
+
testDateFn(startOfDate, '2025-02-W1', '2025-02-01T00:00:00.000Z');
|
|
17
|
+
testDateFn(startOfDate, '2025-02-w2', '2025-02-08T00:00:00.000Z');
|
|
18
|
+
testDateFn(startOfDate, '2025-02-03', '2025-02-03T00:00:00.000Z');
|
|
15
19
|
});
|
|
16
20
|
|
|
17
21
|
test('endOfDate', () => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
testDateFn(endOfDate, '2025', '2025-12-31T23:59:59.000Z');
|
|
23
|
+
testDateFn(endOfDate, '2025-Q1', '2025-03-31T23:59:59.000Z');
|
|
24
|
+
testDateFn(endOfDate, '2025-q2', '2025-06-30T23:59:59.000Z');
|
|
25
|
+
testDateFn(endOfDate, '2025-Q3', '2025-09-30T23:59:59.000Z');
|
|
26
|
+
testDateFn(endOfDate, '2025-q4', '2025-12-31T23:59:59.000Z');
|
|
27
|
+
testDateFn(endOfDate, '2025-02', '2025-02-28T23:59:59.000Z');
|
|
28
|
+
testDateFn(endOfDate, '2025-03-W1', '2025-03-07T23:59:59.000Z');
|
|
29
|
+
testDateFn(endOfDate, '2025-03-w2', '2025-03-14T23:59:59.000Z');
|
|
30
|
+
testDateFn(endOfDate, '2025-03-W3', '2025-03-21T23:59:59.000Z');
|
|
31
|
+
testDateFn(endOfDate, '2025-03-w4', '2025-03-31T23:59:59.000Z');
|
|
32
|
+
testDateFn(endOfDate, '2025-03-03', '2025-03-03T23:59:59.000Z');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('floorDate', () => {
|
|
36
|
+
testDateFn(floorDate, '2025', '2025-01-01T00:00:00.000Z');
|
|
37
|
+
testDateFn(floorDate, '2025-Q1', '2025-01-01T00:00:00.000Z');
|
|
38
|
+
testDateFn(floorDate, '2025-q2', '2025-04-01T00:00:00.000Z');
|
|
39
|
+
testDateFn(floorDate, '2025-Q3', '2025-07-01T00:00:00.000Z');
|
|
40
|
+
testDateFn(floorDate, '2025-q4', '2025-10-01T00:00:00.000Z');
|
|
41
|
+
testDateFn(floorDate, '2025-02', '2025-02-01T00:00:00.000Z');
|
|
42
|
+
testDateFn(floorDate, '2025-02-W1', '2025-02-01T00:00:00.000Z');
|
|
43
|
+
testDateFn(floorDate, '2025-02-w2', '2025-02-08T00:00:00.000Z');
|
|
44
|
+
testDateFn(floorDate, '2025-02-03', '2025-02-03T00:00:00.000Z');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('ceilDate', () => {
|
|
48
|
+
testDateFn(ceilDate, '2025', '2025-01-01T00:00:00.000Z');
|
|
49
|
+
testDateFn(ceilDate, '2025-Q1', '2025-01-01T00:00:00.000Z');
|
|
50
|
+
testDateFn(ceilDate, '2025-q2', '2025-04-01T00:00:00.000Z');
|
|
51
|
+
testDateFn(ceilDate, '2025-Q3', '2025-07-01T00:00:00.000Z');
|
|
52
|
+
testDateFn(ceilDate, '2025-q4', '2025-10-01T00:00:00.000Z');
|
|
53
|
+
testDateFn(ceilDate, '2025-02', '2025-02-01T00:00:00.000Z');
|
|
54
|
+
testDateFn(ceilDate, '2025-02-W1', '2025-02-01T00:00:00.000Z');
|
|
55
|
+
testDateFn(ceilDate, '2025-02-w2', '2025-02-08T00:00:00.000Z');
|
|
56
|
+
testDateFn(ceilDate, '2025-02-03', '2025-02-03T00:00:00.000Z');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('nextDate', () => {
|
|
60
|
+
testDateFn(nextDate, '2025', '2026-01-01T00:00:00.000Z');
|
|
61
|
+
testDateFn(nextDate, '2025-Q1', '2025-04-01T00:00:00.000Z');
|
|
62
|
+
testDateFn(nextDate, '2025-q2', '2025-07-01T00:00:00.000Z');
|
|
63
|
+
testDateFn(nextDate, '2025-Q3', '2025-10-01T00:00:00.000Z');
|
|
64
|
+
testDateFn(nextDate, '2025-q4', '2026-01-01T00:00:00.000Z');
|
|
65
|
+
testDateFn(nextDate, '2025-02', '2025-03-01T00:00:00.000Z');
|
|
66
|
+
testDateFn(nextDate, '2025-02-W1', '2025-02-08T00:00:00.000Z');
|
|
67
|
+
testDateFn(nextDate, '2025-02-w2', '2025-02-15T00:00:00.000Z');
|
|
68
|
+
testDateFn(nextDate, '2025-02-W3', '2025-02-22T00:00:00.000Z');
|
|
69
|
+
testDateFn(nextDate, '2025-02-w4', '2025-03-01T00:00:00.000Z');
|
|
70
|
+
testDateFn(nextDate, '2025-02-03', '2025-02-04T00:00:00.000Z');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('prevDate', () => {
|
|
74
|
+
testDateFn(prevDate, '2025', '2024-01-01T00:00:00.000Z');
|
|
75
|
+
testDateFn(prevDate, '2025-Q1', '2024-10-01T00:00:00.000Z');
|
|
76
|
+
testDateFn(prevDate, '2025-q2', '2025-01-01T00:00:00.000Z');
|
|
77
|
+
testDateFn(prevDate, '2025-Q3', '2025-04-01T00:00:00.000Z');
|
|
78
|
+
testDateFn(prevDate, '2025-q4', '2025-07-01T00:00:00.000Z');
|
|
79
|
+
testDateFn(prevDate, '2025-02', '2025-01-01T00:00:00.000Z');
|
|
80
|
+
testDateFn(prevDate, '2025-02-W1', '2025-01-22T00:00:00.000Z');
|
|
81
|
+
testDateFn(prevDate, '2025-02-w2', '2025-02-01T00:00:00.000Z');
|
|
82
|
+
testDateFn(prevDate, '2025-02-W3', '2025-02-08T00:00:00.000Z');
|
|
83
|
+
testDateFn(prevDate, '2025-02-w4', '2025-02-15T00:00:00.000Z');
|
|
84
|
+
testDateFn(prevDate, '2025-02-03', '2025-02-02T00:00:00.000Z');
|
|
29
85
|
});
|
|
30
86
|
|
|
31
87
|
test.run();
|