parent
b81abd9bfe
commit
c656c2aa67
3 changed files with 108 additions and 39 deletions
|
|
@ -152,18 +152,21 @@ async function calculateNextIterations(task, startFromDate, userTimezone) {
|
|||
const weekdays = Array.isArray(task.recurrence_weekdays)
|
||||
? task.recurrence_weekdays
|
||||
: JSON.parse(task.recurrence_weekdays);
|
||||
let found = false;
|
||||
for (let daysAhead = 1; daysAhead <= 7; daysAhead++) {
|
||||
const testDate = new Date(nextDate);
|
||||
testDate.setUTCDate(testDate.getUTCDate() + daysAhead);
|
||||
if (weekdays.includes(testDate.getUTCDay())) {
|
||||
nextDate = testDate;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
nextDate.setUTCDate(nextDate.getUTCDate() + interval * 7);
|
||||
const sorted = [...weekdays].sort((a, b) => a - b);
|
||||
const currentDay = nextDate.getUTCDay();
|
||||
const laterInWeek = sorted.filter((d) => d > currentDay);
|
||||
if (laterInWeek.length > 0) {
|
||||
nextDate.setUTCDate(
|
||||
nextDate.getUTCDate() + (laterInWeek[0] - currentDay)
|
||||
);
|
||||
} else {
|
||||
const daysToNextFirst =
|
||||
(7 - currentDay + sorted[0]) % 7 || 7;
|
||||
nextDate.setUTCDate(
|
||||
nextDate.getUTCDate() +
|
||||
daysToNextFirst +
|
||||
(interval - 1) * 7
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
task.recurrence_weekday !== null &&
|
||||
|
|
@ -213,28 +216,26 @@ async function calculateNextIterations(task, startFromDate, userTimezone) {
|
|||
|
||||
// Handle multiple weekdays
|
||||
if (task.recurrence_weekdays) {
|
||||
// Sequelize getter already parses JSON, so it's already an array
|
||||
const weekdays = Array.isArray(task.recurrence_weekdays)
|
||||
? task.recurrence_weekdays
|
||||
: JSON.parse(task.recurrence_weekdays);
|
||||
const interval = task.recurrence_interval || 1;
|
||||
const sorted = [...weekdays].sort((a, b) => a - b);
|
||||
const currentDay = nextDate.getUTCDay();
|
||||
const laterInWeek = sorted.filter((d) => d > currentDay);
|
||||
|
||||
// Find next matching weekday
|
||||
let found = false;
|
||||
for (let daysAhead = 1; daysAhead <= 7; daysAhead++) {
|
||||
const testDate = new Date(nextDate);
|
||||
testDate.setUTCDate(testDate.getUTCDate() + daysAhead);
|
||||
const testWeekday = testDate.getUTCDay();
|
||||
|
||||
if (weekdays.includes(testWeekday)) {
|
||||
nextDate = testDate;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// Fallback: add 7 days
|
||||
nextDate.setUTCDate(nextDate.getUTCDate() + 7);
|
||||
if (laterInWeek.length > 0) {
|
||||
nextDate.setUTCDate(
|
||||
nextDate.getUTCDate() + (laterInWeek[0] - currentDay)
|
||||
);
|
||||
} else {
|
||||
const daysToNextFirst =
|
||||
(7 - currentDay + sorted[0]) % 7 || 7;
|
||||
nextDate.setUTCDate(
|
||||
nextDate.getUTCDate() +
|
||||
daysToNextFirst +
|
||||
(interval - 1) * 7
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Old behavior for single weekday
|
||||
|
|
|
|||
|
|
@ -68,16 +68,22 @@ const calculateWeeklyRecurrence = (fromDate, interval, weekday, weekdays) => {
|
|||
: null;
|
||||
|
||||
if (parsedWeekdays && parsedWeekdays.length > 0) {
|
||||
// Find the next matching weekday from tomorrow onward
|
||||
for (let daysAhead = 1; daysAhead <= 7; daysAhead++) {
|
||||
const testDate = new Date(nextDate);
|
||||
testDate.setUTCDate(testDate.getUTCDate() + daysAhead);
|
||||
if (parsedWeekdays.includes(testDate.getUTCDay())) {
|
||||
return testDate;
|
||||
}
|
||||
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
|
||||
);
|
||||
}
|
||||
// Fallback: advance by interval weeks
|
||||
nextDate.setUTCDate(nextDate.getUTCDate() + interval * 7);
|
||||
} else if (weekday !== null && weekday !== undefined) {
|
||||
const currentWeekday = nextDate.getUTCDay();
|
||||
const daysUntilTarget = (weekday - currentWeekday + 7) % 7;
|
||||
|
|
|
|||
|
|
@ -295,6 +295,68 @@ describe('Recurring Tasks', () => {
|
|||
expect(nextDueDate.getUTCDay()).toBe(2); // Tuesday
|
||||
});
|
||||
|
||||
it('should respect bi-weekly interval with multiple weekdays (Issue #844)', async () => {
|
||||
// Use a fixed Tuesday: 2026-02-10
|
||||
const tuesday = new Date(Date.UTC(2026, 1, 10, 0, 0, 0, 0));
|
||||
expect(tuesday.getUTCDay()).toBe(2); // Sanity: Tuesday
|
||||
|
||||
const task = await Task.create({
|
||||
name: 'Bi-weekly Tue/Thu Task',
|
||||
recurrence_type: 'weekly',
|
||||
recurrence_interval: 2,
|
||||
recurrence_weekdays: [2, 4], // Tuesday, Thursday
|
||||
due_date: tuesday,
|
||||
user_id: user.id,
|
||||
status: Task.STATUS.NOT_STARTED,
|
||||
});
|
||||
|
||||
// Tuesday -> Thursday (same cycle week)
|
||||
const next1 = calculateNextDueDate(task, tuesday);
|
||||
expect(next1.getUTCDay()).toBe(4); // Thursday
|
||||
expect(next1.toISOString().split('T')[0]).toBe('2026-02-12');
|
||||
|
||||
// Thursday -> next cycle Tuesday (skip 1 week, land on week 3's Tuesday)
|
||||
const next2 = calculateNextDueDate(task, next1);
|
||||
expect(next2.getUTCDay()).toBe(2); // Tuesday
|
||||
expect(next2.toISOString().split('T')[0]).toBe('2026-02-24');
|
||||
|
||||
// Tuesday (week 3) -> Thursday (week 3, same cycle)
|
||||
const next3 = calculateNextDueDate(task, next2);
|
||||
expect(next3.getUTCDay()).toBe(4); // Thursday
|
||||
expect(next3.toISOString().split('T')[0]).toBe('2026-02-26');
|
||||
|
||||
// Thursday (week 3) -> Tuesday (week 5)
|
||||
const next4 = calculateNextDueDate(task, next3);
|
||||
expect(next4.getUTCDay()).toBe(2); // Tuesday
|
||||
expect(next4.toISOString().split('T')[0]).toBe('2026-03-10');
|
||||
});
|
||||
|
||||
it('should respect tri-weekly interval with single weekday via weekdays array (Issue #844)', async () => {
|
||||
// Use a fixed Monday: 2026-02-09
|
||||
const monday = new Date(Date.UTC(2026, 1, 9, 0, 0, 0, 0));
|
||||
expect(monday.getUTCDay()).toBe(1);
|
||||
|
||||
const task = await Task.create({
|
||||
name: 'Every 3 weeks on Monday',
|
||||
recurrence_type: 'weekly',
|
||||
recurrence_interval: 3,
|
||||
recurrence_weekdays: [1], // Monday only
|
||||
due_date: monday,
|
||||
user_id: user.id,
|
||||
status: Task.STATUS.NOT_STARTED,
|
||||
});
|
||||
|
||||
// Monday Feb 9 -> Monday Mar 2 (3 weeks later)
|
||||
const next1 = calculateNextDueDate(task, monday);
|
||||
expect(next1.getUTCDay()).toBe(1);
|
||||
expect(next1.toISOString().split('T')[0]).toBe('2026-03-02');
|
||||
|
||||
// Monday Mar 2 -> Monday Mar 23 (3 weeks later)
|
||||
const next2 = calculateNextDueDate(task, next1);
|
||||
expect(next2.getUTCDay()).toBe(1);
|
||||
expect(next2.toISOString().split('T')[0]).toBe('2026-03-23');
|
||||
});
|
||||
|
||||
it('should handle three weekdays (Mon/Wed/Fri)', async () => {
|
||||
// Use a fixed Monday: 2026-02-09
|
||||
const monday = new Date(Date.UTC(2026, 1, 9, 0, 0, 0, 0));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue