@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 CHANGED
@@ -23,5 +23,5 @@
23
23
  "uuid": "^9.0.0",
24
24
  "yargs": "^17.5.1"
25
25
  },
26
- "version": "0.6.5-beta.15"
26
+ "version": "0.6.5-beta.17"
27
27
  }
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
- const parsed = parseDateExpr(expr);
43
- switch (parsed.type) {
44
- case 'ts':
45
- return parsed.ts;
46
- case 'year':
47
- return Date.UTC(parsed.year);
48
- case 'quarter':
49
- return Date.UTC(parsed.year, (parsed.quarter - 1) * 3);
50
- case 'month':
51
- return Date.UTC(parsed.year, parsed.month - 1);
52
- case 'week':
53
- return Date.UTC(parsed.year, parsed.month - 1, (parsed.week - 1) * 7 + 1);
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 Date.UTC(parsed.year, parsed.month - 1, parsed.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 - 3);
85
+ case 'year':
86
+ return Date.UTC(year);
56
87
  default:
57
- throw new Error(`Unrecognized date: ${expr}`);
88
+ throw new Error(`Unrecognized unit: ${unit}`);
58
89
  }
59
90
  }
60
91
 
61
- export function nextOfDate(expr) {
92
+ export function ceilDate(expr, unit) {
93
+ validateUnit(unit);
62
94
  if (expr === undefined) {
63
95
  return undefined;
64
96
  }
65
- const parsed = parseDateExpr(expr);
66
- switch (parsed.type) {
67
- case 'ts':
68
- return parsed.ts + 1;
69
- case 'year':
70
- return Date.UTC(parsed.year + 1);
71
- case 'quarter':
72
- return Date.UTC(parsed.year, parsed.quarter * 3);
73
- case 'month':
74
- return Date.UTC(parsed.year, parsed.month);
75
- case 'week':
76
- // roll over to 1st of next month if week is 4
77
- return Date.UTC(parsed.year, parsed.week < 4 ? parsed.month - 1 : parsed.month, (parsed.week % 4) * 7 + 1);
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 Date.UTC(parsed.year, parsed.month - 1, parsed.day + 1);
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 date: ${expr}`);
139
+ throw new Error(`Unrecognized unit: ${unit}`);
82
140
  }
83
141
  }
84
142
 
85
- export function endOfDate(expr) {
143
+ export function prevDate(expr, unit) {
144
+ validateUnit(unit);
86
145
  if (expr === undefined) {
87
146
  return undefined;
88
147
  }
89
- return nextOfDate(expr) - 1000; // 1 sec
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 ? { type: 'year', year: expr } : { type: 'ts', ts: expr };
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 { type: 'year', year };
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 { type: 'quarter', year, quarter };
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 { type: 'month', year, month };
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 { type: 'week', year, month, week };
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 { type: 'day', year, month, day: date.getUTCDate() };
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
- assert.equal(startOfDate('2025'), Date.parse('2025-01-01T00:00:00Z'));
7
- assert.equal(startOfDate('2025-Q1'), Date.parse('2025-01-01T00:00:00Z'));
8
- assert.equal(startOfDate('2025-q2'), Date.parse('2025-04-01T00:00:00Z'));
9
- assert.equal(startOfDate('2025-Q3'), Date.parse('2025-07-01T00:00:00Z'));
10
- assert.equal(startOfDate('2025-q4'), Date.parse('2025-10-01T00:00:00Z'));
11
- assert.equal(startOfDate('2025-02'), Date.parse('2025-02-01T00:00:00Z'));
12
- assert.equal(startOfDate('2025-02-W1'), Date.parse('2025-02-01T00:00:00Z'));
13
- assert.equal(startOfDate('2025-02-w2'), Date.parse('2025-02-08T00:00:00Z'));
14
- assert.equal(startOfDate('2025-02-03'), Date.parse('2025-02-03T00:00:00Z'));
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
- assert.equal(endOfDate('2025'), Date.parse('2025-12-31T23:59:59Z'));
19
- assert.equal(endOfDate('2025-Q1'), Date.parse('2025-03-31T23:59:59Z'));
20
- assert.equal(endOfDate('2025-q2'), Date.parse('2025-06-30T23:59:59Z'));
21
- assert.equal(endOfDate('2025-Q3'), Date.parse('2025-09-30T23:59:59Z'));
22
- assert.equal(endOfDate('2025-q4'), Date.parse('2025-12-31T23:59:59Z'));
23
- assert.equal(endOfDate('2025-02'), Date.parse('2025-02-28T23:59:59Z'));
24
- assert.equal(endOfDate('2025-03-W1'), Date.parse('2025-03-07T23:59:59Z'));
25
- assert.equal(endOfDate('2025-03-w2'), Date.parse('2025-03-14T23:59:59Z'));
26
- assert.equal(endOfDate('2025-03-W3'), Date.parse('2025-03-21T23:59:59Z'));
27
- assert.equal(endOfDate('2025-03-w4'), Date.parse('2025-03-31T23:59:59Z'));
28
- assert.equal(endOfDate('2025-03-03'), Date.parse('2025-03-03T23:59:59Z'));
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();