@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 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.14"
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
- if (typeof expr === 'number') {
41
- if (expr < 10000) {
42
- expr = startOfYearByYearNum(expr);
43
- }
44
- return expr;
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 = Date.parse(expr);
47
- if (isNaN(ts)) {
48
- throw new Error(`Unrecognized date: ${expr}`);
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
- const RE_YEAR = /^\d{4}$/g;
54
- const RE_MONTH = /^\d{4}-\d{2}$/g;
55
- const RE_DATE = /^\d{4}-\d{2}-\d{2}$/g;
56
-
57
- // TODO: support end of hour, minute
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 endOfDate(expr) {
92
+ export function ceilDate(expr, unit) {
93
+ validateUnit(unit);
60
94
  if (expr === undefined) {
61
95
  return undefined;
62
96
  }
63
- if (typeof expr === 'number') {
64
- if (expr < 10000) {
65
- expr = endOfYearByYearNum(expr);
66
- }
67
- return expr;
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
- let ts = Date.parse(expr);
70
- if (isNaN(ts)) {
71
- throw new Error(`Unrecognized date: ${expr}`);
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 endOfYear(ts) {
79
- return endOfYearByYearNum(new Date(ts).getFullYear());
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 startOfYearByYearNum(fullYear) {
83
- return new Date(fullYear, 0, 1).getTime();
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
- function endOfMonth(ts) {
91
- const date = new Date(ts);
92
- const year = date.getFullYear();
93
- const month = date.getMonth();
94
- return (month == 11 ? new Date(year + 1, 0, 1) : new Date(year, month + 1, 1)).getTime() - 1000;
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
- const MS_PER_DAY = 1000 * 60 * 60 * 24;
173
+ function isGranular(unit0, unit1) {
174
+ return UNIT_GRANULARITY[unit0] > UNIT_GRANULARITY[unit1];
175
+ }
98
176
 
99
- function endOfDay(ts) {
100
- return (Math.floor(ts / MS_PER_DAY) + 1) * MS_PER_DAY - 1000;
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();