Fix calendar rec events (#9755)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov
2025-09-01 23:14:10 +05:00
committed by GitHub
parent 0181e865e0
commit d910191fbd
2 changed files with 105 additions and 18 deletions
@@ -161,4 +161,40 @@ describe('generateRecurringValues', () => {
}
expect(() => generateRecurringValues(rule as any, baseDate, from, to)).toThrow('Invalid recurring rule frequency')
})
it('generates simple monthly recurring values (no specific rules)', () => {
const rule: RecurringRule = {
freq: 'MONTHLY',
interval: 1
}
const startDate = new Date('2024-01-15T10:00:00Z').getTime() // 15th of month
const from = new Date('2024-01-01T00:00:00Z').getTime()
const to = new Date('2024-04-30T23:59:59Z').getTime()
const result = generateRecurringValues(rule, startDate, from, to)
// Should generate events on 15th of each month: Jan, Feb, Mar, Apr
expect(result.length).toBe(4)
expect(new Date(result[0]).toISOString().slice(0, 10)).toBe('2024-01-15')
expect(new Date(result[1]).toISOString().slice(0, 10)).toBe('2024-02-15')
expect(new Date(result[2]).toISOString().slice(0, 10)).toBe('2024-03-15')
expect(new Date(result[3]).toISOString().slice(0, 10)).toBe('2024-04-15')
})
it('generates simple yearly recurring values (no specific rules)', () => {
const rule: RecurringRule = {
freq: 'YEARLY',
interval: 1
}
const startDate = new Date('2024-03-10T10:00:00Z').getTime() // March 10th
const from = new Date('2024-01-01T00:00:00Z').getTime()
const to = new Date('2027-12-31T23:59:59Z').getTime()
const result = generateRecurringValues(rule, startDate, from, to)
// Should generate events on March 10th of each year: 2024, 2025, 2026, 2027
expect(result.length).toBe(4)
expect(new Date(result[0]).toISOString().slice(0, 10)).toBe('2024-03-10')
expect(new Date(result[1]).toISOString().slice(0, 10)).toBe('2025-03-10')
expect(new Date(result[2]).toISOString().slice(0, 10)).toBe('2026-03-10')
expect(new Date(result[3]).toISOString().slice(0, 10)).toBe('2027-03-10')
})
})
+69 -18
View File
@@ -154,11 +154,37 @@ function generateMonthlyValues (rule: RecurringRule, currentDate: Date, from: Ti
const end = new Date(new Date(currentDate).setMonth(currentDate.getMonth() + 1, 1))
let date = currentDate
const candidates: Date[] = []
while (date < end) {
if ((byDay == null || matchesByDay(date, byDay)) && (byMonthDay == null || byMonthDay.includes(date.getDate()))) {
candidates.push(new Date(date))
// If no specific rules are set except byMonthDay (simple monthly recurrence)
if (byDay == null && bySetPos == null && byMonthDay != null) {
for (const day of byMonthDay) {
const originalDate = new Date(currentDate.getTime())
const sameDate = new Date(
Date.UTC(
currentDate.getUTCFullYear(),
currentDate.getUTCMonth(),
day,
originalDate.getUTCHours(),
originalDate.getUTCMinutes(),
originalDate.getUTCSeconds(),
originalDate.getUTCMilliseconds()
)
)
if (sameDate.getUTCMonth() === currentDate.getUTCMonth()) {
// Valid day for this month
candidates.push(sameDate)
}
}
} else {
while (date < end) {
if (
(byDay == null || matchesByDay(date, byDay)) &&
(byMonthDay == null || byMonthDay.includes(date.getDate()))
) {
candidates.push(new Date(date))
}
date = new Date(date.setDate(date.getDate() + 1))
}
date = new Date(date.setDate(date.getDate() + 1))
}
let filtered: Date[] = candidates
@@ -197,21 +223,46 @@ function generateYearlyValues (rule: RecurringRule, currentDate: Date, from: Tim
const end = new Date(new Date(currentDate).setFullYear(currentDate.getFullYear() + 1, 0, 1))
let date = currentDate
const candidates: Date[] = []
while (date < end) {
if (
(byDay == null || matchesByDay(date, byDay)) &&
(byMonthDay == null || byMonthDay.includes(date.getDate())) &&
(byYearDay == null || byYearDay.includes(getYearDay(date))) &&
(byWeekNo == null || byWeekNo.includes(getWeekNumber(date))) &&
(byMonth == null || byMonth.includes(date.getMonth()))
) {
const res = date.getTime()
if (res >= from && res <= to) {
candidates.push(new Date(res))
// If no specific rules are set, only generate an event for the same month/day each year
if (
byDay == null &&
byMonthDay == null &&
byYearDay == null &&
byWeekNo == null &&
byMonth == null &&
bySetPos == null
) {
const originalDate = new Date(currentDate.getTime())
const sameDate = new Date(
Date.UTC(
currentDate.getUTCFullYear(),
originalDate.getUTCMonth(),
originalDate.getUTCDate(),
originalDate.getUTCHours(),
originalDate.getUTCMinutes(),
originalDate.getUTCSeconds(),
originalDate.getUTCMilliseconds()
)
)
candidates.push(sameDate)
} else {
while (date < end) {
if (
(byDay == null || matchesByDay(date, byDay)) &&
(byMonthDay == null || byMonthDay.includes(date.getDate())) &&
(byYearDay == null || byYearDay.includes(getYearDay(date))) &&
(byWeekNo == null || byWeekNo.includes(getWeekNumber(date))) &&
(byMonth == null || byMonth.includes(date.getMonth()))
) {
const res = date.getTime()
if (res >= from && res <= to) {
candidates.push(new Date(res))
}
i++
}
i++
date = new Date(date.setDate(date.getDate() + 1))
}
date = new Date(date.setDate(date.getDate() + 1))
}
let filtered: Date[] = candidates
@@ -235,7 +286,7 @@ function generateYearlyValues (rule: RecurringRule, currentDate: Date, from: Tim
}
if (endDate != null && next > endDate) return values
if (next >= to) return values
currentDate = new Date(next)
currentDate = new Date(currentDate.setFullYear(currentDate.getFullYear() + (interval ?? 1)))
}
}