Fix all-day events showing a day early, and expand recurring events - #152
Closed
cosmosified wants to merge 2 commits into
Closed
cosmosified wants to merge 2 commits into
cosmosified wants to merge 2 commits into
Conversation
added 2 commits
September 3, 2026 11:41
…ents An all-day event is a date, not an instant. Both the Apple and ICS sync paths resolved date-only values against the *server's* local midnight and cached the resulting instant, which baked the backend's timezone into the row. A backend on America/New_York therefore handed a display on America/Chicago an all-day marker that rendered at 11:00 PM on the previous day: "NO PRACTICE" on Sep 5 showed up under Friday Sep 4, on nearly every day of a school-sports feed. Two things made it worse. Team feeds (SportsEngine and friends) write their day markers as a timed midnight-to-midnight span rather than VALUE=DATE, so all_day was false and the widget drew a clock time instead of an all-day banner. And icsToEvents read DTSTART off each VEVENT without expanding RRULE, so a recurring series appeared once, at the date it began — a yearly birthday stranded in 2022, a weekly practice only in the week it started, and a shared iCloud calendar syncing 17 events across two years. All-day events are now anchored to UTC midnight of the calendar date the source named, on every sync path (Google already did this), and the widget rebuilds them as local midnight from that date, so they hold their day on any display. Midnight-to-midnight spans are recognised as all-day. Recurring series are expanded over the same +/-13-month window the rest of the sync uses, honouring EXDATE and applying RECURRENCE-ID overrides to their own occurrence only. Verified: server and client suites pass, and a subprocess test runs the same ICS payload under America/New_York, Asia/Tokyo, and UTC and requires byte-identical output.
The CalDAV source type (a Nextcloud/Baikal/Radicale export URL, or any private ICS behind basic auth) hand-rolled its own ical.js parse, so it carried both of the bugs the Apple path just lost: a recurring event was cached once, at the date its series began, and an all-day event was anchored to the server's local midnight, which lands it on the previous day on a display in another timezone. Moved the reader to utils/icsEvents.js — an iCloud calendar, a subscription's upstream feed and a plain authenticated ICS URL all want the same one, and it was only living in appleCalDAV.js because that is where it was written. appleCalDAV re-exports it so existing callers and tests keep one import. Also stop guessing at the response: a non-200, or a 200 whose body is a login page or the XML a collection URL returns when it wanted a REPORT, now reports what happened instead of surfacing an ical.js parse error. Tests cover the path end to end against a local ICS host: the basic-auth header is built from the decrypted password, a series is expanded, an all-day date is anchored to UTC midnight, and both failure shapes report clearly.
Contributor
Author
|
Closing — opened against upstream by mistake; this belongs on my fork for now. Will re-open here if it's wanted upstream. |
Contributor
|
@cosmosified did you implement "store the date in the DB" and handle the work around it? Lengthy discussion here if you are interested #159 Sounds like maybe you did much of the work for this - if not or you don't like the path you took and don't want to ... lmk and I'll pick it up correctly which I think means store the date in the DB without a time or timezone. |
Contributor
Author
|
Ah I don’t remember let me look. I don’t think I adjusted the date time field but generally storing it as gmt or utc then letting frontend apply locale is the appropriate solution
On Sep 8, 2026, at 15:35, mrramam ***@***.***> wrote:
[https://avatars.githubusercontent.com/u/5881944?s=20&v=4]mrramam left a comment (jherforth/HomeGlow#152)<#152 (comment)>
@cosmosified<https://github.com/cosmosified> did you implement "store the date in the DB" and handle the work around it? Lengthy discussion here if you are interested #159<#159> Sounds like maybe you did much of the work for this - if not or you don't like the path you took and don't want to ... lmk and I'll pick it up correctly which I think means store the date in the DB without a time or timezone.
—
Reply to this email directly, view it on GitHub<#152?email_source=notifications&email_token=AAJNDZWSR3XVDNVGXIRTUKL5OBUPBA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNJZGE2TAMBVGU2KM4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5591500554>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AAJNDZX5DKWNSH4M4YX6HNT5OBUPBAVCNFSNUABGKJSXA33TNF2G64TZHMYTAMJRGUZDKMZSGI5US43TOVSTWNJTGM4TEMRUGI4DFILWAI>.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
An all-day event is a date, not an instant. Both sync paths resolved date-only
values against the server's local midnight and cached the resulting instant,
which baked the backend's timezone into the row. A backend on
America/New_Yorktherefore handed a display on
America/Chicagoan all-day marker that renderedat 11:00 PM on the previous day:
Two things compounded it:
timed midnight-to-midnight span rather than
VALUE=DATE, soall_daywasfalse and the widget drew a clock time instead of an all-day banner.
icsToEventsreadDTSTARToff each VEVENT without expandingRRULE, so arecurring series appeared once, at the date it began — a yearly birthday
stranded in 2022, a weekly practice only in the week it started, and a shared
iCloud calendar syncing 17 events across two years.
The change
named, on every sync path (
server/utils/calendarDates.js). Google's syncalready did this; Apple and ICS now match. The widget rebuilds them as local
midnight from that date, so they hold their day on any display.
00:00to
00:00shift is indistinguishable from a one-day all-day event iniCalendar, and is treated as all-day.
sync uses, honouring
EXDATEand applyingRECURRENCE-IDoverrides to theirown occurrence only, with an iteration cap so an open-ended daily
RRULEcannot spin.
server/utils/icsEvents.js: an iCloud calendar, asubscription's upstream feed and a generic CalDAV URL all need the same one,
and it was only in
appleCalDAV.jsbecause that is where it was written.appleCalDAVre-exportsicsToEventsso callers keep a single import.CalDAVsource type now uses that reader instead of its ownhand-rolled parse, so it loses the same two bugs, and reports a non-200 or a
non-iCalendar body (a login page, or the XML a collection URL returns when it
wanted a
REPORT) instead of surfacing anical.jsparse error.Also fixes, incidentally: the event edit dialog showed the previous day for
Google all-day events, and cross-calendar dedup can now match an all-day event
between a Google and an Apple calendar, since both anchor it identically.
Testing
Server 234/234, client 165/165,
npm run buildclean.The key test runs the same ICS payload in three subprocesses under
America/New_York,Asia/TokyoandUTCand requires byte-identical output —which the old code could never do. Alongside it: date-only and
midnight-to-midnight anchoring, multi-day spans,
EXDATE,RECURRENCE-IDoverrides (including an orphan whose master is absent), window bounding of an
open-ended series, and a malformed VEVENT not sinking the rest of the payload.
The CalDAV path is covered end-to-end against a local ICS host: the basic-auth
header is asserted from what the server actually received, a series is expanded,
an all-day date is anchored, and both failure shapes report clearly.
Existing
appleCalDAVfixtures were pinned to March 2026; they are now relativeto today, since the ±13-month window would otherwise age them out and start
failing the suite.
Notes
America/New_Yorkwith aCentral display — the reported symptom. Expect noticeably more (correct)
events after the first sync, since recurring series were previously collapsed
to one occurrence.
TZcorrectly on the backend is still worth doing: all-day events nolonger care, but a feed that sends a timed value with no timezone at all is
still resolved against the server's clock.
GETof an ICS document, nota real
REPORT. That works for export URLs (Nextcloud, Baikal) but not for acollection URL that requires a
REPORT— now a clear error rather than aparse failure.