@forcecalendar/core 0.3.1 → 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.
@@ -294,34 +294,47 @@ export class EventStore {
294
294
  // Use local date string for the query date (in the calendar's timezone)
295
295
  const dateStr = DateUtils.getLocalDateString(date);
296
296
 
297
- // Get all events indexed for this date
298
- const allEvents = [];
297
+ // Collect candidate event IDs from indices
298
+ const candidateIds = new Set();
299
299
 
300
- // Since events might span multiple days in different timezones,
301
- // we need to check events from surrounding dates too
300
+ // Check byDate index for nearby dates (handles most events)
302
301
  const checkDate = new Date(date);
303
302
  for (let offset = -1; offset <= 1; offset++) {
304
303
  const tempDate = new Date(checkDate);
305
304
  tempDate.setDate(tempDate.getDate() + offset);
306
305
  const tempDateStr = DateUtils.getLocalDateString(tempDate);
307
- const eventIds = this.indices.byDate.get(tempDateStr) || new Set();
306
+ const eventIds = this.indices.byDate.get(tempDateStr);
307
+ if (eventIds) {
308
+ eventIds.forEach(id => candidateIds.add(id));
309
+ }
310
+ }
308
311
 
309
- for (const id of eventIds) {
310
- const event = this.events.get(id);
311
- if (event && !allEvents.find(e => e.id === event.id)) {
312
- // Check if event actually occurs on the requested date in the given timezone
313
- const eventStartLocal = event.getStartInTimezone(timezone);
314
- const eventEndLocal = event.getEndInTimezone(timezone);
315
-
316
- const startOfDay = new Date(date);
317
- startOfDay.setHours(0, 0, 0, 0);
318
- const endOfDay = new Date(date);
319
- endOfDay.setHours(23, 59, 59, 999);
320
-
321
- // Event overlaps with this day if it starts before end of day and ends after start of day
322
- if (eventStartLocal <= endOfDay && eventEndLocal >= startOfDay) {
323
- allEvents.push(event);
324
- }
312
+ // Also check byMonth index to catch long-running events that might not be
313
+ // indexed in byDate for this specific date (lazy indexing only indexes
314
+ // first/last week of multi-week events)
315
+ const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
316
+ const monthEventIds = this.indices.byMonth.get(monthKey);
317
+ if (monthEventIds) {
318
+ monthEventIds.forEach(id => candidateIds.add(id));
319
+ }
320
+
321
+ // Filter candidates to events that actually overlap with the requested date
322
+ const allEvents = [];
323
+ const startOfDay = new Date(date);
324
+ startOfDay.setHours(0, 0, 0, 0);
325
+ const endOfDay = new Date(date);
326
+ endOfDay.setHours(23, 59, 59, 999);
327
+
328
+ for (const id of candidateIds) {
329
+ const event = this.events.get(id);
330
+ if (event) {
331
+ // Check if event actually occurs on the requested date in the given timezone
332
+ const eventStartLocal = event.getStartInTimezone(timezone);
333
+ const eventEndLocal = event.getEndInTimezone(timezone);
334
+
335
+ // Event overlaps with this day if it starts before end of day and ends after start of day
336
+ if (eventStartLocal <= endOfDay && eventEndLocal >= startOfDay) {
337
+ allEvents.push(event);
325
338
  }
326
339
  }
327
340
  }
@@ -122,6 +122,7 @@ export class RRuleParser {
122
122
 
123
123
  /**
124
124
  * Parse BYDAY value
125
+ * Returns array of strings like ['MO', '2TU', '-1FR'] for compatibility with RecurrenceEngine
125
126
  * @private
126
127
  */
127
128
  static parseByDay(value) {
@@ -130,14 +131,14 @@ export class RRuleParser {
130
131
  const result = [];
131
132
 
132
133
  for (const day of days) {
133
- const match = day.match(/^([+-]?\d*)([A-Z]{2})$/);
134
+ const trimmed = day.trim().toUpperCase();
135
+ const match = trimmed.match(/^([+-]?\d*)([A-Z]{2})$/);
134
136
  if (match) {
135
137
  const [_, nth, weekday] = match;
136
138
  if (weekDays.includes(weekday)) {
137
- result.push({
138
- weekday,
139
- nth: nth ? parseInt(nth, 10) : null
140
- });
139
+ // Return string format for RecurrenceEngine compatibility
140
+ // e.g., 'MO', '2MO', '-1FR'
141
+ result.push(nth ? `${nth}${weekday}` : weekday);
141
142
  }
142
143
  }
143
144
  }
@@ -108,9 +108,18 @@ export class RecurrenceEngine {
108
108
  case 'WEEKLY':
109
109
  if (rule.byDay && rule.byDay.length > 0) {
110
110
  // Find next day that matches byDay
111
+ // Limit iterations to prevent infinite loop with malformed byDay
112
+ const maxIterations = 8; // 7 days + 1 for safety
113
+ let iterations = 0;
111
114
  next.setDate(next.getDate() + 1);
112
- while (!this.matchesByDay(next, rule.byDay)) {
115
+ while (!this.matchesByDay(next, rule.byDay) && iterations < maxIterations) {
113
116
  next.setDate(next.getDate() + 1);
117
+ iterations++;
118
+ }
119
+ // If no match found, fall back to simple weekly interval
120
+ if (iterations >= maxIterations) {
121
+ console.warn('RecurrenceEngine: Invalid byDay rule, falling back to weekly interval');
122
+ next.setDate(next.getDate() + (7 * rule.interval) - maxIterations);
114
123
  }
115
124
  } else {
116
125
  // Simple weekly recurrence