Fix: Bi-weekly recurring task scheduling for multi-day patterns (#1005)

* fix: correct bi-weekly recurring task scheduling for multi-day patterns

Fixes #1004

Previously, when a recurring task was set to repeat every N weeks (where N > 1)
on multiple weekdays that span a week boundary (e.g., Saturday + Sunday), the
algorithm incorrectly calculated the next occurrence dates.

The issue was in the calculateWeeklyRecurrence function, which didn't properly
determine when to add the interval skip for multi-weekday patterns. It would:
- Correctly handle Sat -> Sun (adjacent days, same cycle)
- Incorrectly handle Sun -> Sat (should skip weeks, but didn't)

This fix improves the logic to:
1. Detect when the current day is the last weekday in the pattern cycle
2. Account for Sunday (day 0) wrapping around in the day-of-week numbering
3. Only add interval skips when truly moving to a new cycle, not when moving
   between weekdays within the same cycle

Test coverage added for:
- Bi-weekly Saturday + Sunday pattern (the reported bug)
- Starting from different days in the pattern
- Bi-weekly Tuesday + Thursday pattern
- Tri-weekly Friday + Saturday + Sunday pattern

* docs: update MEMORY.md with GitHub template requirements

- Add detailed PR template requirements and structure
- Expand bug report template requirements with all fields
- Update last modified date
This commit is contained in:
Chris 2026-04-12 08:52:13 +03:00 committed by GitHub
parent e8c7eed226
commit 01a84c3598
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 195 additions and 22 deletions

View file

@ -71,19 +71,53 @@ const calculateWeeklyRecurrence = (fromDate, interval, weekday, weekdays) => {
const sorted = [...parsedWeekdays].sort((a, b) => a - b);
const currentDay = nextDate.getUTCDay();
// Find next weekday later in the current week
const laterInWeek = sorted.filter((d) => d > currentDay);
if (laterInWeek.length > 0) {
nextDate.setUTCDate(
nextDate.getUTCDate() + (laterInWeek[0] - currentDay)
);
} else {
// Wrap to first weekday of next cycle (interval weeks ahead)
const daysToNextFirst = (7 - currentDay + sorted[0]) % 7 || 7;
nextDate.setUTCDate(
nextDate.getUTCDate() + daysToNextFirst + (interval - 1) * 7
);
// Find next weekday in calendar order (accounting for week wrap)
let nextWeekday = null;
let daysToNext = null;
for (let i = 1; i <= 7; i++) {
const testDay = (currentDay + i) % 7;
if (sorted.includes(testDay)) {
nextWeekday = testDay;
daysToNext = i;
break;
}
}
if (daysToNext === null) {
// No weekday found (shouldn't happen), fallback
daysToNext = interval * 7;
} else if (daysToNext < 7) {
// Next weekday is within current 7-day period
// Determine if we should skip weeks based on interval
const isAdjacentDay = daysToNext === 1;
// Determine if current day is the last weekday in the pattern
// (considering calendar order, where the highest day number is last,
// except Sunday(0) which wraps around)
const maxWeekday = Math.max(...sorted);
const isOnLastWeekday = currentDay === maxWeekday;
// Special case: if pattern includes Sunday(0), check if we're on it
// and all other weekdays are higher (meaning we're at the end of the pattern)
const hasSunday = sorted.includes(0);
const isOnSundayWithHigherDays =
currentDay === 0 && sorted.every((d) => d === 0 || d > 0);
if (
interval > 1 &&
!isAdjacentDay &&
(isOnLastWeekday || isOnSundayWithHigherDays)
) {
// We're on the last weekday of the pattern cycle, add interval skip
daysToNext += (interval - 1) * 7;
}
} else {
// Next occurrence is 7 days away, add interval skip
daysToNext += (interval - 1) * 7;
}
nextDate.setUTCDate(nextDate.getUTCDate() + daysToNext);
} else if (weekday !== null && weekday !== undefined) {
const currentWeekday = nextDate.getUTCDay();
const daysUntilTarget = (weekday - currentWeekday + 7) % 7;