@hbarefoot/engram 1.0.0

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.
@@ -0,0 +1,296 @@
1
+ /**
2
+ * Time utility functions for temporal queries
3
+ */
4
+
5
+ const DAY_MS = 86400000;
6
+ const WEEK_MS = DAY_MS * 7;
7
+ const MONTH_MS = DAY_MS * 30;
8
+ const YEAR_MS = DAY_MS * 365;
9
+
10
+ /**
11
+ * Get start of day for a timestamp
12
+ * @param {number} timestamp - Unix timestamp in ms
13
+ * @returns {number} Start of day timestamp
14
+ */
15
+ function startOfDay(timestamp) {
16
+ const date = new Date(timestamp);
17
+ date.setHours(0, 0, 0, 0);
18
+ return date.getTime();
19
+ }
20
+
21
+ /**
22
+ * Get end of day for a timestamp
23
+ * @param {number} timestamp - Unix timestamp in ms
24
+ * @returns {number} End of day timestamp
25
+ */
26
+ function endOfDay(timestamp) {
27
+ const date = new Date(timestamp);
28
+ date.setHours(23, 59, 59, 999);
29
+ return date.getTime();
30
+ }
31
+
32
+ /**
33
+ * Get start of week (Sunday) for a timestamp
34
+ * @param {number} timestamp - Unix timestamp in ms
35
+ * @returns {number} Start of week timestamp
36
+ */
37
+ function startOfWeek(timestamp) {
38
+ const date = new Date(timestamp);
39
+ const day = date.getDay();
40
+ date.setDate(date.getDate() - day);
41
+ date.setHours(0, 0, 0, 0);
42
+ return date.getTime();
43
+ }
44
+
45
+ /**
46
+ * Get end of week (Saturday) for a timestamp
47
+ * @param {number} timestamp - Unix timestamp in ms
48
+ * @returns {number} End of week timestamp
49
+ */
50
+ function endOfWeek(timestamp) {
51
+ const date = new Date(timestamp);
52
+ const day = date.getDay();
53
+ date.setDate(date.getDate() + (6 - day));
54
+ date.setHours(23, 59, 59, 999);
55
+ return date.getTime();
56
+ }
57
+
58
+ /**
59
+ * Get start of month for a timestamp
60
+ * @param {number} timestamp - Unix timestamp in ms
61
+ * @returns {number} Start of month timestamp
62
+ */
63
+ function startOfMonth(timestamp) {
64
+ const date = new Date(timestamp);
65
+ date.setDate(1);
66
+ date.setHours(0, 0, 0, 0);
67
+ return date.getTime();
68
+ }
69
+
70
+ /**
71
+ * Get end of month for a timestamp
72
+ * @param {number} timestamp - Unix timestamp in ms
73
+ * @returns {number} End of month timestamp
74
+ */
75
+ function endOfMonth(timestamp) {
76
+ const date = new Date(timestamp);
77
+ date.setMonth(date.getMonth() + 1);
78
+ date.setDate(0);
79
+ date.setHours(23, 59, 59, 999);
80
+ return date.getTime();
81
+ }
82
+
83
+ /**
84
+ * Get start of year for a timestamp
85
+ * @param {number} timestamp - Unix timestamp in ms
86
+ * @returns {number} Start of year timestamp
87
+ */
88
+ function startOfYear(timestamp) {
89
+ const date = new Date(timestamp);
90
+ date.setMonth(0, 1);
91
+ date.setHours(0, 0, 0, 0);
92
+ return date.getTime();
93
+ }
94
+
95
+ /**
96
+ * Get end of year for a timestamp
97
+ * @param {number} timestamp - Unix timestamp in ms
98
+ * @returns {number} End of year timestamp
99
+ */
100
+ function endOfYear(timestamp) {
101
+ const date = new Date(timestamp);
102
+ date.setMonth(11, 31);
103
+ date.setHours(23, 59, 59, 999);
104
+ return date.getTime();
105
+ }
106
+
107
+ /**
108
+ * Parse a relative time string into a timestamp
109
+ * @param {string} input - Relative time string (e.g., "3 days ago", "last week")
110
+ * @returns {number|null} Unix timestamp in ms, or null if not parseable
111
+ */
112
+ export function parseRelativeTime(input) {
113
+ if (!input || typeof input !== 'string') {
114
+ return null;
115
+ }
116
+
117
+ const now = Date.now();
118
+ const normalized = input.toLowerCase().trim();
119
+
120
+ // Check for ISO date format first
121
+ if (/^\d{4}-\d{2}-\d{2}/.test(input)) {
122
+ const parsed = new Date(input);
123
+ if (!isNaN(parsed.getTime())) {
124
+ return parsed.getTime();
125
+ }
126
+ }
127
+
128
+ // Special keywords
129
+ const keywords = {
130
+ 'now': now,
131
+ 'today': startOfDay(now),
132
+ 'yesterday': startOfDay(now - DAY_MS),
133
+ 'tomorrow': startOfDay(now + DAY_MS)
134
+ };
135
+
136
+ if (keywords[normalized] !== undefined) {
137
+ return keywords[normalized];
138
+ }
139
+
140
+ // Relative patterns: "X days/weeks/months ago"
141
+ const agoMatch = normalized.match(/^(\d+)\s*(day|days|week|weeks|month|months|year|years)\s*ago$/);
142
+ if (agoMatch) {
143
+ const amount = parseInt(agoMatch[1], 10);
144
+ const unit = agoMatch[2].replace(/s$/, ''); // Remove trailing 's'
145
+
146
+ const unitMs = {
147
+ 'day': DAY_MS,
148
+ 'week': WEEK_MS,
149
+ 'month': MONTH_MS,
150
+ 'year': YEAR_MS
151
+ };
152
+
153
+ return now - (amount * unitMs[unit]);
154
+ }
155
+
156
+ // "last X" patterns
157
+ const lastMatch = normalized.match(/^last\s*(day|week|month|year)$/);
158
+ if (lastMatch) {
159
+ const unit = lastMatch[1];
160
+ switch (unit) {
161
+ case 'day':
162
+ return startOfDay(now - DAY_MS);
163
+ case 'week':
164
+ return startOfWeek(now - WEEK_MS);
165
+ case 'month':
166
+ return startOfMonth(now - MONTH_MS);
167
+ case 'year':
168
+ return startOfYear(now - YEAR_MS);
169
+ }
170
+ }
171
+
172
+ return null;
173
+ }
174
+
175
+ /**
176
+ * Parse a period shorthand into a time range
177
+ * @param {string} period - Period shorthand (e.g., "today", "this_week", "last_month")
178
+ * @returns {Object|null} Object with start and end timestamps, or null if not parseable
179
+ */
180
+ export function parsePeriod(period) {
181
+ if (!period || typeof period !== 'string') {
182
+ return null;
183
+ }
184
+
185
+ const now = Date.now();
186
+
187
+ const periods = {
188
+ 'today': {
189
+ start: startOfDay(now),
190
+ end: endOfDay(now),
191
+ description: 'today'
192
+ },
193
+ 'yesterday': {
194
+ start: startOfDay(now - DAY_MS),
195
+ end: endOfDay(now - DAY_MS),
196
+ description: 'yesterday'
197
+ },
198
+ 'this_week': {
199
+ start: startOfWeek(now),
200
+ end: now,
201
+ description: 'this week'
202
+ },
203
+ 'last_week': {
204
+ start: startOfWeek(now - WEEK_MS),
205
+ end: endOfWeek(now - WEEK_MS),
206
+ description: 'last week'
207
+ },
208
+ 'this_month': {
209
+ start: startOfMonth(now),
210
+ end: now,
211
+ description: 'this month'
212
+ },
213
+ 'last_month': {
214
+ start: startOfMonth(now - MONTH_MS),
215
+ end: endOfMonth(now - MONTH_MS),
216
+ description: 'last month'
217
+ },
218
+ 'this_year': {
219
+ start: startOfYear(now),
220
+ end: now,
221
+ description: 'this year'
222
+ },
223
+ 'last_year': {
224
+ start: startOfYear(now - YEAR_MS),
225
+ end: endOfYear(now - YEAR_MS),
226
+ description: 'last year'
227
+ }
228
+ };
229
+
230
+ return periods[period] || null;
231
+ }
232
+
233
+ /**
234
+ * Parse a time filter object into start/end timestamps
235
+ * @param {Object} timeFilter - Time filter object
236
+ * @param {string} [timeFilter.after] - Start time (ISO date or relative)
237
+ * @param {string} [timeFilter.before] - End time (ISO date or relative)
238
+ * @param {string} [timeFilter.period] - Period shorthand
239
+ * @returns {Object} Object with start, end, and description
240
+ */
241
+ export function parseTimeFilter(timeFilter) {
242
+ if (!timeFilter) {
243
+ return null;
244
+ }
245
+
246
+ const now = Date.now();
247
+
248
+ // If period is specified, use that
249
+ if (timeFilter.period) {
250
+ return parsePeriod(timeFilter.period);
251
+ }
252
+
253
+ // Parse after/before
254
+ let start = null;
255
+ let end = null;
256
+ let description = '';
257
+
258
+ if (timeFilter.after) {
259
+ start = parseRelativeTime(timeFilter.after);
260
+ description = `after ${timeFilter.after}`;
261
+ }
262
+
263
+ if (timeFilter.before) {
264
+ end = parseRelativeTime(timeFilter.before);
265
+ if (description) {
266
+ description += ` and before ${timeFilter.before}`;
267
+ } else {
268
+ description = `before ${timeFilter.before}`;
269
+ }
270
+ }
271
+
272
+ // Default end to now if only after is specified
273
+ if (start && !end) {
274
+ end = now;
275
+ }
276
+
277
+ // Default start to beginning of time if only before is specified
278
+ if (!start && end) {
279
+ start = 0;
280
+ }
281
+
282
+ if (start === null && end === null) {
283
+ return null;
284
+ }
285
+
286
+ return { start, end, description };
287
+ }
288
+
289
+ /**
290
+ * Format a timestamp as ISO string
291
+ * @param {number} timestamp - Unix timestamp in ms
292
+ * @returns {string} ISO date string
293
+ */
294
+ export function formatTimestamp(timestamp) {
295
+ return new Date(timestamp).toISOString();
296
+ }