From 4a050e92a8bcc83b1425668240d2977c7d4f473e Mon Sep 17 00:00:00 2001 From: Ulas Kalayci Date: Thu, 30 Apr 2026 10:51:01 +0200 Subject: [PATCH] chore: release v0.38.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix recurring events with FREQ=WEEKLY;INTERVAL=N;BYDAY ignoring the interval when crossing a week boundary (e.g. Friday → Monday). Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 5 +++++ package-lock.json | 4 ++-- package.json | 2 +- server/services/recurrence.js | 8 +++++++- test-calendar.js | 27 +++++++++++++++++++++++++++ 5 files changed, 42 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad690af..92aa057 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.38.2] - 2026-04-30 + +### Fixed +- Recurring calendar events with `FREQ=WEEKLY;INTERVAL=N;BYDAY=...` (N > 1) now correctly skip N−1 weeks between occurrences instead of repeating every week + ## [0.38.1] - 2026-04-30 ### Changed diff --git a/package-lock.json b/package-lock.json index 3ca5b3f..40c5af5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "oikos", - "version": "0.38.1", + "version": "0.38.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "oikos", - "version": "0.38.1", + "version": "0.38.2", "license": "MIT", "dependencies": { "bcrypt": "^6.0.0", diff --git a/package.json b/package.json index 41d5176..d3fa6d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oikos", - "version": "0.38.1", + "version": "0.38.2", "description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.", "main": "server/index.js", "type": "module", diff --git a/server/services/recurrence.js b/server/services/recurrence.js index db3f5c4..30a7129 100644 --- a/server/services/recurrence.js +++ b/server/services/recurrence.js @@ -78,7 +78,13 @@ function nextOccurrence(baseDateStr, rrule) { }); // Tage bis zum nächsten Vorkommen (mind. 1, damit nicht derselbe Tag) let daysUntil = (sorted[0] - currentDay + 7) % 7; - if (daysUntil === 0) daysUntil = 7 * interval; + if (daysUntil === 0) { + // Selber Wochentag → ganzes Intervall überspringen + daysUntil = 7 * interval; + } else if ((sorted[0] + 6) % 7 < (currentDay + 6) % 7) { + // Wochengrenze überschritten (ISO-Woche MO–SO) → interval-1 Wochen extra überspringen + daysUntil += 7 * (interval - 1); + } next.setUTCDate(next.getUTCDate() + daysUntil); } diff --git a/test-calendar.js b/test-calendar.js index 79bd72f..a4e6ded 100644 --- a/test-calendar.js +++ b/test-calendar.js @@ -251,6 +251,33 @@ test('Monatsbereich: 42 Tage für Kalenderraster', () => { assert(to === '2026-04-11', `Erwartet 2026-04-11, erhalten ${to}`); }); +// -------------------------------------------------------- +// nextOccurrence: INTERVAL-Korrektheit mit BYDAY +// -------------------------------------------------------- +import { nextOccurrence } from './server/services/recurrence.js'; + +test('nextOccurrence: WEEKLY BYDAY=MO,TU,WE,TH,FR INTERVAL=2 — kein täglicher Übergang', () => { + const rule = 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;INTERVAL=2'; + // Innerhalb der Woche: Mo→Di (1 Tag, kein Intervallsprung) + assert(nextOccurrence('2026-05-04', rule) === '2026-05-05', 'Mo→Di'); + // Innerhalb der Woche: Di→Mi + assert(nextOccurrence('2026-05-05', rule) === '2026-05-06', 'Di→Mi'); + // Freitag → Montag der übernächsten Woche (3 + 7 = 10 Tage) + assert(nextOccurrence('2026-05-08', rule) === '2026-05-18', 'Fr→Mo (übernächste Woche)'); +}); + +test('nextOccurrence: WEEKLY BYDAY=SA,SU INTERVAL=2 — Wochenend-Pair bleibt zusammen', () => { + const rule = 'FREQ=WEEKLY;BYDAY=SA,SU;INTERVAL=2'; + // Sa→So (1 Tag, gleiche Woche) + assert(nextOccurrence('2026-05-09', rule) === '2026-05-10', 'Sa→So'); + // So→Sa der übernächsten Woche (13 Tage) + assert(nextOccurrence('2026-05-10', rule) === '2026-05-23', 'So→Sa (übernächste Woche)'); +}); + +test('nextOccurrence: WEEKLY BYDAY=MO INTERVAL=2 — klassisch alle 2 Wochen', () => { + assert(nextOccurrence('2026-05-04', 'FREQ=WEEKLY;BYDAY=MO;INTERVAL=2') === '2026-05-18', 'Mo→Mo+14'); +}); + // -------------------------------------------------------- // Ergebnis // --------------------------------------------------------