diff --git a/src/routes/(docs)/docs/content/v4/maintenances/rrule-patterns.md b/src/routes/(docs)/docs/content/v4/maintenances/rrule-patterns.md index b96f9c4e..6513fcd1 100644 --- a/src/routes/(docs)/docs/content/v4/maintenances/rrule-patterns.md +++ b/src/routes/(docs)/docs/content/v4/maintenances/rrule-patterns.md @@ -28,10 +28,11 @@ FREQ=frequency[;INTERVAL=n][;BYDAY=days][;BYMONTHDAY=day][;COUNT=n][;UNTIL=date] - Default: 1 - Integer value: 2 = every other, 3 = every third, etc. -**BYDAY** (Optional, for WEEKLY) +**BYDAY** (Optional, for WEEKLY or MONTHLY with an ordinal prefix) - Day codes: `MO`, `TU`, `WE`, `TH`, `FR`, `SA`, `SU` - Multiple days: `MO,WE,FR` +- With MONTHLY, prefix a day code with an ordinal to mean "the Nth such weekday of the month": `1WE` (first Wednesday), `1SU` (first Sunday) **BYMONTHDAY** (Optional, for MONTHLY) @@ -240,6 +241,29 @@ Occurrences: - Continues monthly... ``` +#### Nth Day-of-Week of Month (Ordinal BYDAY) {#first-weekday-of-month} + +**RRULE:** `FREQ=MONTHLY;BYDAY=1WE` + +**Description:** Maintenance occurs on the first Wednesday of each month + +**Use Case:** Monthly patch windows tied to a specific weekday + +**Example:** + +``` +Title: Monthly Patch Window +Start: June 3, 2026 at 2:30 PM +RRULE: FREQ=MONTHLY;BYDAY=1WE +Duration: 90 minutes + +Occurrences: +- Jun 3, 2026 at 2:30 PM +- Jul 1, 2026 at 2:30 PM +- Aug 5, 2026 at 2:30 PM +- Continues monthly... +``` + #### Middle of Month {#middle-of-month} **RRULE:** `FREQ=MONTHLY;BYMONTHDAY=15` @@ -362,15 +386,16 @@ Affected Monitors: Title: Monthly Security Updates Description: Critical security patches and system updates Start Date: Sunday, June 4, 2026 at 2:00 AM -RRULE: FREQ=MONTHLY;BYMONTHDAY=1 +RRULE: FREQ=MONTHLY;BYDAY=1SU Duration: 3600 seconds (1 hour) Affected Monitors: - All Servers: MAINTENANCE ``` -**Note:** Since RRULE doesn't support "first Sunday", use BYMONTHDAY=1 and choose first occurrence that falls on Sunday, or use BYDAY with ordinal (not supported in Kener UI currently). +**Note:** Use ordinal weekday syntax in `BYDAY` for "first X weekday" patterns: -**Workaround:** Create as weekly and manage manually or use BYMONTHDAY for specific date. +- First Sunday: `BYDAY=1SU` +- First Wednesday: `BYDAY=1WE` ### Example 4: Business Hours Backup {#example-backup} @@ -469,6 +494,7 @@ Start: 2:00 AM UTC ✅ `FREQ=WEEKLY;BYDAY=MO,WE,FR` ✅ `FREQ=WEEKLY;INTERVAL=2;BYDAY=SU` ✅ `FREQ=MONTHLY;BYMONTHDAY=1` +✅ `FREQ=MONTHLY;BYDAY=1WE` ✅ `FREQ=MINUTELY;COUNT=1` ### Invalid RRULEs {#invalid-rrules} @@ -602,6 +628,7 @@ Description: | | Every Sunday | `FREQ=WEEKLY;BYDAY=SU` | | Bi-weekly Mondays | `FREQ=WEEKLY;INTERVAL=2;BYDAY=MO` | | First of month | `FREQ=MONTHLY;BYMONTHDAY=1` | +| First Wednesday | `FREQ=MONTHLY;BYDAY=1WE` | | 15th of month | `FREQ=MONTHLY;BYMONTHDAY=15` | | Quarterly | `FREQ=MONTHLY;INTERVAL=3;BYMONTHDAY=1` | @@ -619,4 +646,4 @@ Description: | - [Creating and Managing Maintenances](/docs/v4/maintenances/creating-managing) - Apply these patterns in the dashboard - [Maintenance Events](/docs/v4/maintenances/events) - Understand how RRULE generates events -- [Maintenance Overview](/docs/v4/maintenances/overview) - Learn maintenance fundamentals +- [Maintenance Overview](/docs/v4/maintenances/overview) - Learn maintenance fundamentals \ No newline at end of file diff --git a/src/routes/(manage)/manage/app/maintenances/[id]/+page.svelte b/src/routes/(manage)/manage/app/maintenances/[id]/+page.svelte index e2275403..ca9a03b8 100644 --- a/src/routes/(manage)/manage/app/maintenances/[id]/+page.svelte +++ b/src/routes/(manage)/manage/app/maintenances/[id]/+page.svelte @@ -28,7 +28,7 @@ import { onMount } from "svelte"; import { goto } from "$app/navigation"; import { toast } from "svelte-sonner"; - import { format, formatDistanceToNow, isPast, isFuture, isWithinInterval, addDays } from "date-fns"; + import { format, formatDistanceToNow, isPast, isFuture, isWithinInterval } from "date-fns"; import { rrulestr } from "rrule"; import { resolve } from "$app/paths"; import clientResolver from "$lib/client/resolver.js"; @@ -193,9 +193,16 @@ const dtstart = new Date(startDateTimeLocal); const fullRrule = `DTSTART:${dtstart.toISOString().replace(/[-:]/g, "").split(".")[0]}Z\nRRULE:${customRrule}`; const rule = rrulestr(fullRrule); - const now = new Date(); - const windowEnd = addDays(now, 30); - const occurrences = rule.between(now, windowEnd, true).slice(0, 5); + const occurrences: Date[] = []; + let searchFrom = new Date(); + + for (let i = 0; i < 5; i++) { + const next = rule.after(searchFrom, i === 0); + if (!next) break; + occurrences.push(next); + searchFrom = next; + } + return occurrences.map((d) => format(d, "EEE, MMM d, yyyy 'at' h:mm a")); } catch (e) { return []; @@ -980,4 +987,4 @@ - + \ No newline at end of file