chore: release v0.38.2

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 <noreply@anthropic.com>
This commit is contained in:
Ulas Kalayci
2026-04-30 10:51:01 +02:00
parent a6ecf7e9ca
commit 4a050e92a8
5 changed files with 42 additions and 4 deletions
+5
View File
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.38.2] - 2026-04-30
### Fixed
- Recurring calendar events with `FREQ=WEEKLY;INTERVAL=N;BYDAY=...` (N > 1) now correctly skip N1 weeks between occurrences instead of repeating every week
## [0.38.1] - 2026-04-30 ## [0.38.1] - 2026-04-30
### Changed ### Changed
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "oikos", "name": "oikos",
"version": "0.38.1", "version": "0.38.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "oikos", "name": "oikos",
"version": "0.38.1", "version": "0.38.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bcrypt": "^6.0.0", "bcrypt": "^6.0.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "oikos", "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.", "description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.",
"main": "server/index.js", "main": "server/index.js",
"type": "module", "type": "module",
+7 -1
View File
@@ -78,7 +78,13 @@ function nextOccurrence(baseDateStr, rrule) {
}); });
// Tage bis zum nächsten Vorkommen (mind. 1, damit nicht derselbe Tag) // Tage bis zum nächsten Vorkommen (mind. 1, damit nicht derselbe Tag)
let daysUntil = (sorted[0] - currentDay + 7) % 7; 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 MOSO) → interval-1 Wochen extra überspringen
daysUntil += 7 * (interval - 1);
}
next.setUTCDate(next.getUTCDate() + daysUntil); next.setUTCDate(next.getUTCDate() + daysUntil);
} }
+27
View File
@@ -251,6 +251,33 @@ test('Monatsbereich: 42 Tage für Kalenderraster', () => {
assert(to === '2026-04-11', `Erwartet 2026-04-11, erhalten ${to}`); 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 // Ergebnis
// -------------------------------------------------------- // --------------------------------------------------------