Enhanced the recurring event functionality (#9475)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov
2025-07-07 13:49:14 +07:00
committed by GitHub
parent b886b48d61
commit 4e90bd83be
19 changed files with 745 additions and 105 deletions
+77 -88
View File
@@ -24,40 +24,29 @@ function getInstance (event: ReccuringEvent, date: Timestamp): ReccuringInstance
}
}
function generateRecurringValues (
export function generateRecurringValues (
rule: RecurringRule,
startDate: Timestamp,
from: Timestamp,
to: Timestamp
): Timestamp[] {
const values: Timestamp[] = []
const currentDate = new Date(startDate)
switch (rule.freq) {
case 'DAILY':
generateDailyValues(rule, currentDate, values, from, to)
break
return generateDailyValues(rule, currentDate, from, to)
case 'WEEKLY':
generateWeeklyValues(rule, currentDate, values, from, to)
break
return generateWeeklyValues(rule, currentDate, from, to)
case 'MONTHLY':
generateMonthlyValues(rule, currentDate, values, from, to)
break
return generateMonthlyValues(rule, currentDate, from, to)
case 'YEARLY':
generateYearlyValues(rule, currentDate, values, from, to)
break
return generateYearlyValues(rule, currentDate, from, to)
default:
throw new Error('Invalid recurring rule frequency')
}
return values
}
function generateDailyValues (
rule: RecurringRule,
currentDate: Date,
values: Timestamp[],
from: Timestamp,
to: Timestamp
): void {
function generateDailyValues (rule: RecurringRule, currentDate: Date, from: Timestamp, to: Timestamp): Timestamp[] {
const values: Timestamp[] = []
const { count, endDate, interval } = rule
const { bySetPos } = rule
let i = 0
@@ -76,15 +65,11 @@ function generateDailyValues (
currentDate.setDate(currentDate.getDate() + (interval ?? 1))
if (count !== undefined && i === count) break
}
return values
}
function generateWeeklyValues (
rule: RecurringRule,
currentDate: Date,
values: Timestamp[],
from: Timestamp,
to: Timestamp
): void {
function generateWeeklyValues (rule: RecurringRule, currentDate: Date, from: Timestamp, to: Timestamp): Timestamp[] {
const values: Timestamp[] = []
const { count, endDate, interval } = rule
let { byDay, bySetPos } = rule
let i = 0
@@ -98,8 +83,8 @@ function generateWeeklyValues (
const end = new Date(new Date(currentDate).setDate(currentDate.getDate() + 7))
let date = currentDate
while (date < end) {
if (endDate != null && date.getTime() > endDate) return
if (date.getTime() > to) return
if (endDate != null && date.getTime() > endDate) return values
if (date.getTime() > to) return values
if ((byDay == null || matchesByDay(date, byDay)) && (bySetPos == null || bySetPos.includes(getSetPos(date)))) {
const res = date.getTime()
if (res >= from && res <= to) {
@@ -108,7 +93,7 @@ function generateWeeklyValues (
i++
}
date = new Date(date.setDate(date.getDate() + 1))
if (count !== undefined && i === count) return
if (count !== undefined && i === count) return values
}
currentDate = new Date(next)
@@ -154,13 +139,8 @@ function getNegativePosition (date: Date, weekday: string, pos: number): number
throw new Error(`Unable to calculate negative position ${pos}`)
}
function generateMonthlyValues (
rule: RecurringRule,
currentDate: Date,
values: Timestamp[],
from: Timestamp,
to: Timestamp
): void {
function generateMonthlyValues (rule: RecurringRule, currentDate: Date, from: Timestamp, to: Timestamp): Timestamp[] {
const values: Timestamp[] = []
const { count, endDate, interval } = rule
let { byDay, byMonthDay, bySetPos } = rule
let i = 0
@@ -170,82 +150,91 @@ function generateMonthlyValues (
}
while (true) {
const next = new Date(currentDate).setMonth(currentDate.getMonth() + (interval ?? 1))
const end = new Date(new Date(currentDate).setMonth(currentDate.getMonth() + 1))
const next = new Date(currentDate).setMonth(currentDate.getMonth() + (interval ?? 1), 1)
const end = new Date(new Date(currentDate).setMonth(currentDate.getMonth() + 1, 1))
let date = currentDate
const candidates: Date[] = []
while (date < end) {
if (endDate != null && date.getTime() > endDate) return
if (date.getTime() >= to) return
if (
(byDay == null || matchesByDay(date, byDay)) &&
(byMonthDay == null || byMonthDay.includes(new Date(currentDate).getDate())) &&
(bySetPos == null || bySetPos.includes(getSetPos(currentDate)))
) {
const res = currentDate.getTime()
if (res >= from && res <= to) {
values.push(res)
}
i++
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))
if (count !== undefined && i === count) return
}
let filtered: Date[] = candidates
if (bySetPos != null) {
filtered = bySetPos
.map((pos) => {
if (pos === 0) return null // invalid
const index = pos > 0 ? pos - 1 : candidates.length + pos
return candidates[index] ?? null
})
.filter((d): d is Date => d != null)
}
for (const d of filtered) {
const res = d.getTime()
if (res >= from && res <= to) {
values.push(res)
i++
if (count !== undefined && i === count) return values
}
}
if (endDate != null && next > endDate) return values
if (next >= to) return values
currentDate = new Date(next)
}
}
function generateYearlyValues (
rule: RecurringRule,
currentDate: Date,
values: Timestamp[],
from: Timestamp,
to: Timestamp
): void {
function generateYearlyValues (rule: RecurringRule, currentDate: Date, from: Timestamp, to: Timestamp): Timestamp[] {
const values: Timestamp[] = []
const { count, endDate, interval } = rule
const { byDay, byMonthDay, byYearDay, byWeekNo, byMonth, bySetPos } = rule
let i = 0
while (true) {
const next = new Date(currentDate).setFullYear(currentDate.getFullYear() + (interval ?? 1))
const end = new Date(new Date(currentDate).setFullYear(currentDate.getFullYear() + 1))
const next = new Date(currentDate).setFullYear(currentDate.getFullYear() + (interval ?? 1), 0, 1)
const end = new Date(new Date(currentDate).setFullYear(currentDate.getFullYear() + 1, 0, 1))
let date = currentDate
const candidates: Date[] = []
while (date < end) {
if (endDate != null && date.getTime() > endDate) return
if (date.getTime() > to) return
if (
byDay == null &&
byMonthDay == null &&
byYearDay == null &&
byWeekNo == null &&
byMonth == null &&
bySetPos == null
(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()))
) {
date = new Date(next)
const res = currentDate.getTime()
const res = date.getTime()
if (res >= from && res <= to) {
values.push(res)
candidates.push(new Date(res))
}
i++
} else {
if (
(byDay == null || matchesByDay(date, byDay)) &&
(byMonthDay == null || byMonthDay.includes(currentDate.getDate())) &&
(byYearDay == null || byYearDay.includes(getYearDay(currentDate))) &&
(byWeekNo == null || byWeekNo.includes(getWeekNumber(currentDate))) &&
(byMonth == null || byMonth.includes(currentDate.getMonth())) &&
(bySetPos == null || bySetPos.includes(getSetPos(currentDate)))
) {
const res = currentDate.getTime()
if (res >= from && res <= to) {
values.push(res)
}
i++
}
date = new Date(date.setDate(date.getDate() + 1))
}
if (count !== undefined && i === count) return
date = new Date(date.setDate(date.getDate() + 1))
}
let filtered: Date[] = candidates
if (bySetPos != null) {
filtered = bySetPos
.map((pos) => {
if (pos === 0) return null // invalid
const index = pos > 0 ? pos - 1 : candidates.length + pos
return candidates[index] ?? null
})
.filter((d): d is Date => d != null)
}
for (const d of filtered) {
const res = d.getTime()
if (res >= from && res <= to) {
values.push(res)
i++
if (count !== undefined && i === count) return values
}
}
if (endDate != null && next > endDate) return values
if (next >= to) return values
currentDate = new Date(next)
}
}