@miso.ai/server-commons 0.6.5-beta.14 → 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 +4 -2
- package/src/date.js +171 -43
- package/test/date.test.js +35 -0
package/package.json
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
9
|
-
"scripts": {
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "npx uvu test -i 'ignored'"
|
|
11
|
+
},
|
|
10
12
|
"repository": "MisoAI/miso-server-js-sdk",
|
|
11
13
|
"license": "MIT",
|
|
12
14
|
"contributors": [
|
|
@@ -21,5 +23,5 @@
|
|
|
21
23
|
"uuid": "^9.0.0",
|
|
22
24
|
"yargs": "^17.5.1"
|
|
23
25
|
},
|
|
24
|
-
"version": "0.6.5-beta.
|
|
26
|
+
"version": "0.6.5-beta.16"
|
|
25
27
|
}
|
package/src/date.js
CHANGED
|
@@ -7,8 +7,6 @@ TS_PER_UNIT.m = TS_PER_UNIT.s * 60;
|
|
|
7
7
|
TS_PER_UNIT.h = TS_PER_UNIT.m * 60;
|
|
8
8
|
TS_PER_UNIT.d = TS_PER_UNIT.h * 24;
|
|
9
9
|
TS_PER_UNIT.w = TS_PER_UNIT.d * 7;
|
|
10
|
-
//TS_PER_UNIT.M = TS_PER_UNIT.d * 31;
|
|
11
|
-
//TS_PER_UNIT.y = TS_PER_UNIT.d * 366;
|
|
12
10
|
|
|
13
11
|
export function parseDuration(expr, unit) {
|
|
14
12
|
if (expr === undefined || typeof expr === 'number') {
|
|
@@ -33,69 +31,199 @@ export function getYear(dateStr) {
|
|
|
33
31
|
return new Date(dateStr).getFullYear();
|
|
34
32
|
}
|
|
35
33
|
|
|
34
|
+
const RE_DATE_EXPR = /^(?:\d{4})|(?:\d{4}-[Qq\d]\d)|(?:\d{4}-\d{2}-[Ww\d]\d)$/;
|
|
35
|
+
|
|
36
|
+
// TODO: support hour, minute
|
|
37
|
+
|
|
36
38
|
export function startOfDate(expr) {
|
|
39
|
+
return floorDate(expr);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function endOfDate(expr) {
|
|
37
43
|
if (expr === undefined) {
|
|
38
44
|
return undefined;
|
|
39
45
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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;
|
|
45
53
|
}
|
|
46
|
-
const ts =
|
|
47
|
-
if (
|
|
48
|
-
|
|
54
|
+
const [_unit, ts] = parseDateExpr(expr);
|
|
55
|
+
if (unit === undefined || !isGranular(unit, _unit)) {
|
|
56
|
+
return ts;
|
|
49
57
|
}
|
|
50
|
-
return ts;
|
|
58
|
+
return floorTimestamp(ts, unit);
|
|
51
59
|
}
|
|
52
60
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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;
|
|
71
|
+
case 'day':
|
|
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);
|
|
87
|
+
default:
|
|
88
|
+
throw new Error(`Unrecognized unit: ${unit}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
58
91
|
|
|
59
|
-
export function
|
|
92
|
+
export function ceilDate(expr, unit) {
|
|
93
|
+
validateUnit(unit);
|
|
60
94
|
if (expr === undefined) {
|
|
61
95
|
return undefined;
|
|
62
96
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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;
|
|
111
|
+
case 'day':
|
|
112
|
+
return ts + TS_PER_UNIT.d - ts % TS_PER_UNIT.d;
|
|
68
113
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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);
|
|
127
|
+
default:
|
|
128
|
+
throw new Error(`Unrecognized unit: ${unit}`);
|
|
72
129
|
}
|
|
73
|
-
return expr.match(RE_YEAR) ? endOfYear(ts) :
|
|
74
|
-
expr.match(RE_MONTH) ? endOfMonth(ts) :
|
|
75
|
-
expr.match(RE_DATE) ? endOfDay(ts) : ts;
|
|
76
130
|
}
|
|
77
131
|
|
|
78
|
-
function
|
|
79
|
-
|
|
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);
|
|
80
139
|
}
|
|
81
140
|
|
|
82
|
-
function
|
|
83
|
-
|
|
141
|
+
export function prevDate(expr, unit) {
|
|
142
|
+
validateUnit(unit);
|
|
143
|
+
if (expr === undefined) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
const [_unit, ts] = parseDateExpr(expr);
|
|
147
|
+
return floorTimestamp(ts - 1, unit || _unit);
|
|
84
148
|
}
|
|
85
149
|
|
|
86
|
-
function endOfYearByYearNum(fullYear) {
|
|
87
|
-
return new Date(fullYear + 1, 0, 1).getTime() - 1000;
|
|
88
|
-
}
|
|
89
150
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
+
}
|
|
95
171
|
}
|
|
96
172
|
|
|
97
|
-
|
|
173
|
+
function isGranular(unit0, unit1) {
|
|
174
|
+
return UNIT_GRANULARITY[unit0] > UNIT_GRANULARITY[unit1];
|
|
175
|
+
}
|
|
98
176
|
|
|
99
|
-
function
|
|
100
|
-
|
|
177
|
+
function parseDateExpr(expr) {
|
|
178
|
+
switch (typeof expr) {
|
|
179
|
+
case 'number':
|
|
180
|
+
if (expr < 100) {
|
|
181
|
+
throw new Error(`Unrecognized date: ${expr}`);
|
|
182
|
+
}
|
|
183
|
+
return expr < 10000 ? ['year', Date.UTC(expr)] : ['millisecond', expr];
|
|
184
|
+
case 'string':
|
|
185
|
+
expr = expr.trim();
|
|
186
|
+
const len = expr.length;
|
|
187
|
+
if (!RE_DATE_EXPR.test(expr)) {
|
|
188
|
+
throw new Error(`Unrecognized date: ${expr}`);
|
|
189
|
+
}
|
|
190
|
+
const year = parseInt(expr.slice(0, 4), 10);
|
|
191
|
+
if (isNaN(year)) {
|
|
192
|
+
throw new Error(`Unrecognized date: ${expr}`);
|
|
193
|
+
}
|
|
194
|
+
if (len === 4) {
|
|
195
|
+
return ['year', Date.UTC(year)];
|
|
196
|
+
}
|
|
197
|
+
if (len === 7 && (expr.charAt(5) === 'Q' || expr.charAt(5) === 'q')) {
|
|
198
|
+
const quarter = parseInt(expr.charAt(6), 10);
|
|
199
|
+
if (isNaN(quarter) || quarter < 1 || quarter > 4) {
|
|
200
|
+
throw new Error(`Unrecognized date: ${expr}`);
|
|
201
|
+
}
|
|
202
|
+
return ['quarter', Date.UTC(year, (quarter - 1) * 3)];
|
|
203
|
+
}
|
|
204
|
+
const month = parseInt(expr.slice(5, 7), 10);
|
|
205
|
+
if (isNaN(month) || month < 1 || month > 12) {
|
|
206
|
+
throw new Error(`Unrecognized date: ${expr}`);
|
|
207
|
+
}
|
|
208
|
+
if (len === 7) {
|
|
209
|
+
return ['month', Date.UTC(year, month - 1)];
|
|
210
|
+
}
|
|
211
|
+
if (expr.charAt(8) === 'W' || expr.charAt(8) === 'w') {
|
|
212
|
+
const week = parseInt(expr.charAt(9), 10);
|
|
213
|
+
if (isNaN(week) || week < 1 || week > 4) {
|
|
214
|
+
throw new Error(`Unrecognized date: ${expr}`);
|
|
215
|
+
}
|
|
216
|
+
return ['week', Date.UTC(year, month - 1, week * 7 - 6)];
|
|
217
|
+
}
|
|
218
|
+
let date;
|
|
219
|
+
try {
|
|
220
|
+
// parse so we can validate the day part
|
|
221
|
+
date = new Date(Date.parse(expr));
|
|
222
|
+
} catch (_) {
|
|
223
|
+
throw new Error(`Unrecognized date: ${expr}`);
|
|
224
|
+
}
|
|
225
|
+
return ['day', Date.UTC(year, month - 1, date.getUTCDate())];
|
|
226
|
+
default:
|
|
227
|
+
throw new Error(`Unrecognized date: ${expr}`);
|
|
228
|
+
}
|
|
101
229
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { test } from 'uvu';
|
|
2
|
+
import * as assert from 'uvu/assert';
|
|
3
|
+
import { 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
|
+
}
|
|
8
|
+
|
|
9
|
+
test('startOfDate', () => {
|
|
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');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('endOfDate', () => {
|
|
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.run();
|