@forcecalendar/core 2.1.21 → 2.1.24
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/core/calendar/DateUtils.js +5 -2
- package/core/events/EventStore.js +26 -3
- package/core/ics/ICSParser.js +17 -4
- package/package.json +1 -1
|
@@ -256,8 +256,11 @@ export class DateUtils {
|
|
|
256
256
|
* @returns {number}
|
|
257
257
|
*/
|
|
258
258
|
static differenceInDays(date1, date2) {
|
|
259
|
-
|
|
260
|
-
|
|
259
|
+
// Normalize to midnight to avoid DST-related hour differences
|
|
260
|
+
const d1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
|
|
261
|
+
const d2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
|
|
262
|
+
const diff = d1.getTime() - d2.getTime();
|
|
263
|
+
return Math.round(diff / (1000 * 60 * 60 * 24));
|
|
261
264
|
}
|
|
262
265
|
|
|
263
266
|
/**
|
|
@@ -234,9 +234,30 @@ export class EventStore {
|
|
|
234
234
|
|
|
235
235
|
// Filter by month
|
|
236
236
|
if (filters.month && filters.year) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
237
|
+
// Collect candidates from target month AND adjacent months to handle
|
|
238
|
+
// timezone boundary issues (events indexed in the event's own timezone
|
|
239
|
+
// may fall in a different month than the query month)
|
|
240
|
+
const candidateIds = new Set();
|
|
241
|
+
for (let offset = -1; offset <= 1; offset++) {
|
|
242
|
+
let m = filters.month + offset;
|
|
243
|
+
let y = filters.year;
|
|
244
|
+
if (m < 1) { m = 12; y--; }
|
|
245
|
+
if (m > 12) { m = 1; y++; }
|
|
246
|
+
const key = `${y}-${String(m).padStart(2, '0')}`;
|
|
247
|
+
const ids = this.indices.byMonth.get(key);
|
|
248
|
+
if (ids) {
|
|
249
|
+
ids.forEach(id => candidateIds.add(id));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Post-filter: only include events that actually overlap with the requested month
|
|
254
|
+
const monthStart = new Date(filters.year, filters.month - 1, 1);
|
|
255
|
+
const monthEnd = new Date(filters.year, filters.month, 0, 23, 59, 59, 999);
|
|
256
|
+
|
|
257
|
+
results = results.filter(event => {
|
|
258
|
+
if (!candidateIds.has(event.id)) return false;
|
|
259
|
+
return event.start <= monthEnd && event.end >= monthStart;
|
|
260
|
+
});
|
|
240
261
|
}
|
|
241
262
|
|
|
242
263
|
// Filter by all-day events
|
|
@@ -618,6 +639,8 @@ export class EventStore {
|
|
|
618
639
|
this.indices.byDate.clear();
|
|
619
640
|
this.indices.byMonth.clear();
|
|
620
641
|
this.indices.recurring.clear();
|
|
642
|
+
this.indices.byCategory.clear();
|
|
643
|
+
this.indices.byStatus.clear();
|
|
621
644
|
|
|
622
645
|
this._notifyChange({
|
|
623
646
|
type: 'clear',
|
package/core/ics/ICSParser.js
CHANGED
|
@@ -10,7 +10,10 @@ export class ICSParser {
|
|
|
10
10
|
static MAX_LINES = 100000; // 100k lines
|
|
11
11
|
static MAX_EVENTS = 10000; // 10k events
|
|
12
12
|
|
|
13
|
-
constructor() {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
// Configurable max file size (defaults to static limit)
|
|
15
|
+
this.maxFileSize = options.maxFileSize || ICSParser.MAX_INPUT_SIZE;
|
|
16
|
+
|
|
14
17
|
// ICS line folding max width
|
|
15
18
|
this.maxLineLength = 75;
|
|
16
19
|
|
|
@@ -38,10 +41,13 @@ export class ICSParser {
|
|
|
38
41
|
* @returns {Array} Array of event objects
|
|
39
42
|
*/
|
|
40
43
|
parse(icsString) {
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
if (typeof icsString !== 'string') {
|
|
45
|
+
throw new Error('ICS input must be a string');
|
|
46
|
+
}
|
|
47
|
+
// Enforce input size limit (uses instance config, falling back to static default)
|
|
48
|
+
if (icsString.length > this.maxFileSize) {
|
|
43
49
|
throw new Error(
|
|
44
|
-
`ICS input exceeds maximum size of ${
|
|
50
|
+
`ICS input exceeds maximum size of ${this.maxFileSize / (1024 * 1024)}MB`
|
|
45
51
|
);
|
|
46
52
|
}
|
|
47
53
|
|
|
@@ -306,6 +312,13 @@ export class ICSParser {
|
|
|
306
312
|
event.recurrenceRule = value;
|
|
307
313
|
event.recurring = true;
|
|
308
314
|
break;
|
|
315
|
+
|
|
316
|
+
case 'EXDATE': {
|
|
317
|
+
if (!event.excludeDates) event.excludeDates = [];
|
|
318
|
+
const dates = value.split(',').map(d => this.parseDate(d.trim(), property));
|
|
319
|
+
event.excludeDates.push(...dates.filter(d => d !== null));
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
309
322
|
}
|
|
310
323
|
}
|
|
311
324
|
|