@miso.ai/server-commons 0.6.5-beta.15 → 0.6.5-beta.16
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 +123 -38
- package/test/date.test.js +24 -20
package/package.json
CHANGED
package/src/date.js
CHANGED
|
@@ -36,57 +36,142 @@ 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);
|
|
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
|
-
case '
|
|
72
|
-
return
|
|
73
|
-
case '
|
|
74
|
-
return
|
|
75
|
-
case '
|
|
76
|
-
|
|
77
|
-
|
|
97
|
+
const [_unit, ts] = parseDateExpr(expr);
|
|
98
|
+
return ceilTimestamp(ts, unit || _unit);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function ceilTimestamp(ts, unit) {
|
|
102
|
+
switch (unit) {
|
|
103
|
+
case 'millisecond':
|
|
104
|
+
return ts;
|
|
105
|
+
case 'second':
|
|
106
|
+
return ts + TS_PER_UNIT.s - ts % TS_PER_UNIT.s;
|
|
107
|
+
case 'minute':
|
|
108
|
+
return ts + TS_PER_UNIT.m - ts % TS_PER_UNIT.m;
|
|
109
|
+
case 'hour':
|
|
110
|
+
return ts + TS_PER_UNIT.h - ts % TS_PER_UNIT.h;
|
|
78
111
|
case 'day':
|
|
79
|
-
return
|
|
112
|
+
return ts + TS_PER_UNIT.d - ts % TS_PER_UNIT.d;
|
|
113
|
+
}
|
|
114
|
+
const d = new Date(ts);
|
|
115
|
+
const year = d.getUTCFullYear();
|
|
116
|
+
const month = d.getUTCMonth() + 1;
|
|
117
|
+
const day = d.getUTCDate();
|
|
118
|
+
switch (unit) {
|
|
119
|
+
case 'week':
|
|
120
|
+
return day > 21 ? Date.UTC(year, month, 1) : Date.UTC(year, month - 1, day - (day - 1) % 7 + 7);
|
|
121
|
+
case 'month':
|
|
122
|
+
return Date.UTC(year, month, 1);
|
|
123
|
+
case 'quarter':
|
|
124
|
+
return Date.UTC(year, month - month % 3 + 3);
|
|
125
|
+
case 'year':
|
|
126
|
+
return Date.UTC(year + 1);
|
|
80
127
|
default:
|
|
81
|
-
throw new Error(`Unrecognized
|
|
128
|
+
throw new Error(`Unrecognized unit: ${unit}`);
|
|
82
129
|
}
|
|
83
130
|
}
|
|
84
131
|
|
|
85
|
-
export function
|
|
132
|
+
export function nextDate(expr, unit) {
|
|
133
|
+
validateUnit(unit);
|
|
134
|
+
if (expr === undefined) {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
const [_unit, ts] = parseDateExpr(expr);
|
|
138
|
+
return ceilTimestamp(ts + 1, unit || _unit);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function prevDate(expr, unit) {
|
|
142
|
+
validateUnit(unit);
|
|
86
143
|
if (expr === undefined) {
|
|
87
144
|
return undefined;
|
|
88
145
|
}
|
|
89
|
-
|
|
146
|
+
const [_unit, ts] = parseDateExpr(expr);
|
|
147
|
+
return floorTimestamp(ts - 1, unit || _unit);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
const UNIT_GRANULARITY = {
|
|
153
|
+
'millisecond': 10,
|
|
154
|
+
'second': 9,
|
|
155
|
+
'minute': 8,
|
|
156
|
+
'hour': 7,
|
|
157
|
+
'day': 6,
|
|
158
|
+
'week': 5,
|
|
159
|
+
'month': 4,
|
|
160
|
+
'quarter': 3,
|
|
161
|
+
'year': 2,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
function validateUnit(unit) {
|
|
165
|
+
if (unit === undefined) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (typeof unit !== 'string' || !UNIT_GRANULARITY[unit]) {
|
|
169
|
+
throw new Error(`Unrecognized unit: ${unit}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function isGranular(unit0, unit1) {
|
|
174
|
+
return UNIT_GRANULARITY[unit0] > UNIT_GRANULARITY[unit1];
|
|
90
175
|
}
|
|
91
176
|
|
|
92
177
|
function parseDateExpr(expr) {
|
|
@@ -95,7 +180,7 @@ function parseDateExpr(expr) {
|
|
|
95
180
|
if (expr < 100) {
|
|
96
181
|
throw new Error(`Unrecognized date: ${expr}`);
|
|
97
182
|
}
|
|
98
|
-
return expr < 10000 ?
|
|
183
|
+
return expr < 10000 ? ['year', Date.UTC(expr)] : ['millisecond', expr];
|
|
99
184
|
case 'string':
|
|
100
185
|
expr = expr.trim();
|
|
101
186
|
const len = expr.length;
|
|
@@ -107,28 +192,28 @@ function parseDateExpr(expr) {
|
|
|
107
192
|
throw new Error(`Unrecognized date: ${expr}`);
|
|
108
193
|
}
|
|
109
194
|
if (len === 4) {
|
|
110
|
-
return
|
|
195
|
+
return ['year', Date.UTC(year)];
|
|
111
196
|
}
|
|
112
197
|
if (len === 7 && (expr.charAt(5) === 'Q' || expr.charAt(5) === 'q')) {
|
|
113
198
|
const quarter = parseInt(expr.charAt(6), 10);
|
|
114
199
|
if (isNaN(quarter) || quarter < 1 || quarter > 4) {
|
|
115
200
|
throw new Error(`Unrecognized date: ${expr}`);
|
|
116
201
|
}
|
|
117
|
-
return
|
|
202
|
+
return ['quarter', Date.UTC(year, (quarter - 1) * 3)];
|
|
118
203
|
}
|
|
119
204
|
const month = parseInt(expr.slice(5, 7), 10);
|
|
120
205
|
if (isNaN(month) || month < 1 || month > 12) {
|
|
121
206
|
throw new Error(`Unrecognized date: ${expr}`);
|
|
122
207
|
}
|
|
123
208
|
if (len === 7) {
|
|
124
|
-
return
|
|
209
|
+
return ['month', Date.UTC(year, month - 1)];
|
|
125
210
|
}
|
|
126
211
|
if (expr.charAt(8) === 'W' || expr.charAt(8) === 'w') {
|
|
127
212
|
const week = parseInt(expr.charAt(9), 10);
|
|
128
213
|
if (isNaN(week) || week < 1 || week > 4) {
|
|
129
214
|
throw new Error(`Unrecognized date: ${expr}`);
|
|
130
215
|
}
|
|
131
|
-
return
|
|
216
|
+
return ['week', Date.UTC(year, month - 1, week * 7 - 6)];
|
|
132
217
|
}
|
|
133
218
|
let date;
|
|
134
219
|
try {
|
|
@@ -137,7 +222,7 @@ function parseDateExpr(expr) {
|
|
|
137
222
|
} catch (_) {
|
|
138
223
|
throw new Error(`Unrecognized date: ${expr}`);
|
|
139
224
|
}
|
|
140
|
-
return
|
|
225
|
+
return ['day', Date.UTC(year, month - 1, date.getUTCDate())];
|
|
141
226
|
default:
|
|
142
227
|
throw new Error(`Unrecognized date: ${expr}`);
|
|
143
228
|
}
|
package/test/date.test.js
CHANGED
|
@@ -2,30 +2,34 @@ import { test } from 'uvu';
|
|
|
2
2
|
import * as assert from 'uvu/assert';
|
|
3
3
|
import { startOfDate, endOfDate } from '../src/index.js';
|
|
4
4
|
|
|
5
|
+
function testDateFn(fn, input, output) {
|
|
6
|
+
assert.equal(new Date(fn(input)).toISOString(), output, `${fn.name}(${input})`);
|
|
7
|
+
}
|
|
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');
|
|
29
33
|
});
|
|
30
34
|
|
|
31
35
|
test.run();
|