Skip to content

fix: preserve VALUE=DATE in DTSTART for RRULE parsing#432

Merged
jens-maus merged 1 commit intojens-maus:masterfrom
KristjanESPERANTO:preserve
Jan 26, 2026
Merged

fix: preserve VALUE=DATE in DTSTART for RRULE parsing#432
jens-maus merged 1 commit intojens-maus:masterfrom
KristjanESPERANTO:preserve

Conversation

@KristjanESPERANTO
Copy link
Contributor

@KristjanESPERANTO KristjanESPERANTO commented Jan 24, 2026

Problem

When parsing recurring all-day events (VALUE=DATE), the date-only information was never passed to the RRULE library. This became visible when rrule-temporal yesterday added strict RFC 5545 validation (with v1.4.2), causing it to reject DATE-only UNTIL values:

Error: UNTIL rule part MUST have the same value type as DTSTART

Example: Google Calendar birthday events

DTSTART;VALUE=DATE:20160313
RRULE:FREQ=YEARLY;UNTIL=20190312;BYMONTHDAY=13;BYMONTH=3

Both DTSTART and UNTIL are DATE values (correct per RFC 5545), but node-ical didn't preserve VALUE=DATE when passing to rrule-temporal, causing the validation to fail.

Solution

Added check for curr.start.dateOnly to preserve the VALUE=DATE parameter.

Use local date components (not UTC) to stay consistent with how date-only events are parsed.

Testing

  • Yearly/monthly recurring DATE events with UNTIL
  • DATE events with COUNT

Impact

Fixes: Issue reported in MagicMirrorOrg/MagicMirror#4016

Affected: Users parsing recurring all-day events (birthdays, holidays) from Google Calendar and other CalDAV servers

Breaking Changes: None

Summary by CodeRabbit

  • Bug Fixes

    • Date-only recurring events now preserve date-only semantics in recurrence rules and correctly handle UNTIL constraints without type errors, preventing incorrect validation or missing DTSTART behavior.
  • Tests

    • Added end-to-end tests covering date-only recurring events with UNTIL, VALUE=DATE preservation, and COUNT-based recurrences to prevent regressions.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 24, 2026

📝 Walkthrough

Walkthrough

Adds explicit handling for date-only (VALUE=DATE) VEVENTs in RRULE/DTSTART construction, emitting a local YYYYMMDD DTSTART for date-only events and adjusting RRULE assembly; includes new tests validating DATE-only RRULE parsing, UNTIL handling, and RRULE string generation.

Changes

Cohort / File(s) Change Summary
RRULE/DTSTART DATE preservation
ical.js
Added a dedicated branch detecting curr.start.dateOnly to emit DTSTART;VALUE=DATE:YYYYMMDD using local year/month/day before the TZID path; adjusted RRULE extraction to filter out DTSTART segments and VALUE= tokens when building the rruleTemporal input.
DATE-only RRULE test coverage
test/date-only-rrule-until.test.js
New test suite (3 tests) covering: parsing DATE-only VEVENT with FREQ=YEARLY and UNTIL in the past (validates occurrences and no UNTIL type errors), preserving VALUE=DATE in generated RRULE strings, and COUNT-based DATE-only RRULE recurrence behavior.

Sequence Diagram(s)

(No sequence diagrams generated — changes are internal to RRULE/DTSTART formatting and tests and do not introduce multi-component interaction flows that require visualization.)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hop through dates, no zone in sight,
I stitch YYYYMMDD to keep the bite,
RRULEs now sing with VALUE held tight,
Count and Until dance in gentle light,
Hooray—recurrences sleep sound at night! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: preserve VALUE=DATE in DTSTART for RRULE parsing' directly and clearly summarizes the main change: addressing how VALUE=DATE is preserved when constructing RRULE strings for date-only events. The title is specific, concise, and accurately reflects the core problem being fixed.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@ical.js`:
- Around line 748-758: The code appends ";DTSTART;VALUE=DATE:YYYYMMDD" to the
rule string when curr.start.dateOnly is true, but the later filter that builds
rruleOnly only strips segments starting with "DTSTART", leaving an orphaned
"VALUE=DATE:YYYYMMDD" token and producing an invalid RRULE; fix by either not
injecting DTSTART into the rule string (set a separate dtstart variable and
attach it outside the RRULE) or update the filtering that produces rruleOnly to
also remove any standalone "VALUE=DATE:..." segments (e.g., strip segments
matching /^VALUE=DATE:/ or the combined ";VALUE=DATE:..."/"VALUE=DATE:..."),
referencing rule, rruleOnly, and curr.start.dateOnly to locate the change.

When parsing recurring events with VALUE=DATE (all-day events),
preserve the VALUE=DATE parameter in the DTSTART string passed to
rrule-temporal. This ensures that rrule-temporal can correctly
validate that UNTIL values have the same type as DTSTART.

Previously, date-only information was lost when building the RRULE
string, causing rrule-temporal to incorrectly assume DTSTART was
DATE-TIME and reject valid DATE-only UNTIL values with the error:
"UNTIL rule part MUST have the same value type as DTSTART"

The fix uses local date components (getFullYear, getMonth, getDate)
which is consistent with how dateOnly dates are created in the
dateParameter function using new Date(year, month, day).

Fixes issue reported in MagicMirrorOrg/MagicMirror#4016 where
Google Calendar birthday events with yearly RRULE and DATE-only
UNTIL in the past failed to parse.

Test coverage:
- Yearly recurring DATE events with UNTIL
- Monthly recurring DATE events with UNTIL
- DATE events with COUNT instead of UNTIL
- Edge case: VALUE=DATE with TZID (correctly ignores TZID)
@KristjanESPERANTO
Copy link
Contributor Author

coderabbit seems to be satisfied here too 😃 @jens-maus, if you have the time, it would be nice if you have a look.

@jens-maus
Copy link
Owner

Thanks @KristjanESPERANTO for the reminder and big thanks for this PR and all your efforts. Will of course merge it immediately.

@jens-maus jens-maus merged commit df1c2fc into jens-maus:master Jan 26, 2026
16 checks passed
@KristjanESPERANTO KristjanESPERANTO deleted the preserve branch January 28, 2026 09:59
KristjanESPERANTO added a commit to KristjanESPERANTO/node-ical that referenced this pull request Jan 28, 2026
…al 1.4.2+

PR jens-maus#432 added initial support for VALUE=DATE preservation, but some
edge cases still failed with rrule-temporal 1.4.2+ strict RFC5545
validation.

Additional fixes:
- Strip time component from UNTIL when DTSTART is DATE-only
- Ensure UNTIL has UTC 'Z' suffix for DATE-TIME events
- Convert non-UTC UNTIL to UTC when DTSTART has TZID
- Embed DTSTART;VALUE=DATE in rruleString for proper validation
KristjanESPERANTO added a commit to KristjanESPERANTO/node-ical that referenced this pull request Jan 28, 2026
…al 1.4.2+

PR jens-maus#432 added initial support for VALUE=DATE preservation, but some
edge cases still failed with rrule-temporal 1.4.2+ strict RFC5545
validation.

Additional fixes:
- Strip time component from UNTIL when DTSTART is DATE-only
- Ensure UNTIL has UTC 'Z' suffix for DATE-TIME events
- Convert non-UTC UNTIL to UTC when DTSTART has TZID
- Embed DTSTART;VALUE=DATE in rruleString for proper validation
jens-maus pushed a commit that referenced this pull request Jan 31, 2026
…al 1.4.2+ (#433)

* chore: update rrule-temporal to v1.4.5

* fix: complete UNTIL/DTSTART value type normalization for rrule-temporal 1.4.2+

PR #432 added initial support for VALUE=DATE preservation, but some
edge cases still failed with rrule-temporal 1.4.2+ strict RFC5545
validation.

Additional fixes:
- Strip time component from UNTIL when DTSTART is DATE-only
- Ensure UNTIL has UTC 'Z' suffix for DATE-TIME events
- Convert non-UTC UNTIL to UTC when DTSTART has TZID
- Embed DTSTART;VALUE=DATE in rruleString for proper validation

* chore: filter orphaned TZID segments when extracting RRULE string

When the internal rule string contains DTSTART;TZID=..., splitting on
semicolons produces an orphaned TZID=... segment. This segment is now
filtered out along with DTSTART and VALUE= for cleaner RRULE syntax.

Note: rrule-temporal tolerates these extra segments, so this is a
code quality improvement rather than a bug fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants