This repository publishes one-event ICS source files and automatically builds aggregate feeds for different scopes and time periods.
For AI-assisted maintenance, start with AGENTS.md, then use
./bin/calendar COMMAND --help for the relevant operation.
Subscription page:
https://beta-sdc.github.io/calendar/
Single-event page:
https://beta-sdc.github.io/calendar/events.html
Calendar view (day, week, month, quarter, year):
https://beta-sdc.github.io/calendar/calendar.html
Web view & Simple way to maintain the Calendar:
https://calendar.betasdc.duckdns.org/calendar/
The FullCalendar page is read-only on Pages and supports authenticated editing when integrated with the beta login service. See 在线日历部署 for roles, API contracts, the publication worker, installation, and rollback.
public/ public one-event ICS sources
internal/ internal one-event ICS sources
web/ subscription page HTML, CSS, and JavaScript
scripts/build_feeds.py validation and feed/site generator
scripts/calendar_cli.py GNU-style AI calendar management CLI
bin/calendar executable CLI entry point
tests/test_build_feeds.py feed and site regression tests
outlook-subscription-guide/ Outlook-specific instructions and assets
docs/ operating guides for calendar links
Generated files are written to site/ locally and are not committed.
Each source file contains exactly one event. Use a readable filename:
<category>/<year>/<month>/<start-date>-<readable-title>.ics
For example:
public/2026/09/2026-09-16-beta-college-autumn-birthday-party.ics
internal/2026/09/2026-09-23-sdc-biweekly-meeting-reminder.ics
The filename is for people; the UID inside the ICS file is the stable
identity used by calendar applications. Keep the UID unchanged when
editing an event.
Events have two independent labels. The source directory defines visibility:
public: public activities, including the Beta College autumn birthday party.internal: internal events and meetings.
The VEVENT STATUS defines planning state:
planned:STATUS:CONFIRMED, or noSTATUSfor existing events.tentative:STATUS:TENTATIVE.
STATUS:CANCELLED remains preserved and belongs to the planned category.
An internal event or any tentative event requires the page access password
betasdc. Selecting all planning states also requires the password, even
for public events. Both web pages filter by visibility and planning state, hide
restricted events while locked, and share the unlock state within the current
browser-tab session. The lock button clears that state.
This is a lightweight frontend gate for non-sensitive information. Source files, generated JSON, and ICS subscription URLs remain publicly readable; the password is not server authentication. The CLI manages all events locally. See 密码访问说明 for the behavior and limitations.
Calendar names, event summaries, locations (when supplied), and reminder
descriptions must be bilingual, using 中文 / English in the same field.
Keep shared identifiers such as room numbers and URLs unchanged. For example:
自习打卡营 / Self-Study Check-in Camp
云谷校区 / Yungu Campus H4-103
云谷校区 / Yungu Campus H4-104
云谷校区 / Yungu Campus H4-121
提醒事项 / Reminder
Use CRLF line endings and fold ICS content lines at 75 UTF-8 octets without
splitting a character. When updating an event's text, increment SEQUENCE
and update DTSTAMP and LAST-MODIFIED, preserving its UID, schedule,
and reminder triggers.
H4-xxx is the building/room identifier on Westlake University's Yungu
Campus. Source files do not include Apple Maps coordinates unless they
have been verified.
The GitHub Actions workflow generates:
BETA-SDC.ics all events
public.ics all public activities
internal.ics all internal events
2026.ics all events in 2026
public/2026.ics public activities in 2026
internal/2026.ics internal events in 2026
2026/09.ics all events in September 2026
public/2026/09.ics public activities in September 2026
internal/2026/09.ics internal events in September 2026
status/planned/public.ics public planned events
status/tentative/public.ics public tentative events, when present
status/planned/internal.ics internal planned events
Status feeds also have year/month paths such as
status/planned/public/2026.ics and status/planned/public/2026/09.ics.
Feed metadata keys use public@planned, public@planned:2026, and
public@planned:2026-09. Selecting all statuses uses the legacy scope keys.
The legacy public, internal, and all-event URLs remain stable.
The locked page offers only public planned subscriptions; after unlocking,
all scopes and states are available. The all-events selection means all
currently accessible events.
The generated files are deployed by the GitHub Actions Pages workflow
from the main branch. They are not committed back into the repository.
The workflow also runs once per day so that scheduled changes can be
published without a source commit.
The subscription page loads generated feed metadata from
calendar-data.json. Its maintained source files live under web/.
The single-event page loads generated event metadata from
events-data.json, groups events by date, and provides download, open
subscription-link, and copy-subscription-link actions.
Each source event is also published at the same relative path on GitHub Pages. For example:
https://beta-sdc.github.io/calendar/public/2026/09/2026-09-29-beta-meet-09-tibet-biodiversity-field-survey.ics
Use an individual event URL for an "Add to calendar" link in an email. Calendar applications decide whether a remote ICS URL is downloaded or subscribed; iPhone may treat the URL as a one-event subscription. Use an aggregate subscription URL when recipients should continue receiving future additions and updates.
For the recommended email wording and Command + K instructions, see
在邮件中添加日历链接.
The subscription page provides selectable webcal:// and HTTPS links.
The stable top-level URLs are:
webcal://beta-sdc.github.io/calendar/BETA-SDC.ics
webcal://beta-sdc.github.io/calendar/public.ics
webcal://beta-sdc.github.io/calendar/internal.ics
Use the HTTPS URL when adding a calendar by URL in Google Calendar or
Outlook. The subscription page also has a direct ICS download link:
public downloads use the default filename BETA.ics, while internal and
all-event downloads use BETA-SDC.ics. Do not download and import the
file if you want automatic updates; use the subscription option instead.
Subscription URLs themselves do not check the password.
Public calendars are named BETA 公开活动 / Public Events, internal calendars
are named BETA-SDC 内部事件 / Internal Events, and all-event calendars are
named BETA-SDC 全部活动 / All Events. Year and month feeds append their period
to the same bilingual name.
Generated feeds preserve the event properties and subcomponents from the
source ICS files, including reminders (VALARM) and attachments
(ATTACH). The build does not remove these fields, so subscribed
calendars can continue to deliver event reminders.
Generated feeds request an hourly subscription refresh with
REFRESH-INTERVAL;VALUE=DURATION:PT1H and X-PUBLISHED-TTL:PT1H.
Calendar applications may allow users to override this suggested interval.
Add, edit, move, or delete one-event ICS files under public/ or
internal/, then commit to main. The workflow validates every source
file and rebuilds all aggregate feeds. Month and year options appear
automatically when their directories contain events.
The source directory must match the event's DTSTART year and month.
Every event must have a non-empty, repository-wide unique UID.
For AI-assisted operations, use the GNU-style CLI:
./bin/calendar prepare https://github.com/BETA-SDC/event-proposal/issues/58 \
--output /tmp/calendar-issue-58.json --json
./bin/calendar add -s public --title '活动 / Event' \
--start 2026-11-01T14:00 --duration-minutes 120 --json
./bin/calendar tentative -s public --title '候选活动 / Candidate Event' \
--start 2026-11-02 --days 1 --json
./bin/calendar confirm <UID> --start 2026-11-03T14:00 \
--duration-minutes 120 --title '正式活动 / Scheduled Event' --dry-run --json
./bin/calendar list --json
./bin/calendar search 'math help' --fields uid,title,start,path --limit 5
./bin/calendar agenda --days 14 --json
./bin/calendar show <UID> --json
./bin/calendar inspect /path/to/event.ics --json
./bin/calendar import /path/to/event.ics -s internal --json
./bin/calendar shift <UID> --days 7 --dry-run --json
./bin/calendar links <UID> --json
./bin/calendar validate --json
./bin/calendar build --jsonprepare reads a GitHub issue through OpenCLI/gh and reports source evidence,
unresolved fields, date candidates, and existing calendar entries. Save its
JSON with --output, then use add/tentative --input FILE with explicit
overrides for missing fields. Exact same-day schedules can be extracted from
the event template; ambiguous dates, years, afternoon-only times, time zones,
and comment changes remain for review. Issue #58 already exists, so its
prepared result should be used to locate that entry rather than create another.
--issue-json supports offline preparation from saved GitHub CLI JSON.
list, search, and agenda support scope/status/date filters, --limit,
and JSON field selection with --fields. agenda includes ongoing events
overlapping its date window. inspect diagnoses external ICS files and
reports existing UIDs before import. Mutating commands support --dry-run
for validation and previews without changing source files. Updating only
the start time preserves the duration; shift moves both endpoints.
Create events with an explicit --end, timed --duration-minutes, or all-day
--days. Use tentative to create candidate events and confirm UID to set
their final fields and transition to planned in one write, preserving the UID,
reminders, attachments, and scope. Omitted fields retain their existing values;
provide --title to replace tentative wording and explicit times when the
stored schedule was a placeholder.
The existing add --status tentative and update UID --status planned commands
remain available. Filter candidates with list --status tentative.
import --status tentative overrides the imported state; omitting the option
preserves it. Existing events default to planned without changing source files.
The full command contract, JSON output format, safety rules, and add/update/ delete examples are in AI 操作日历 CLI.
Install dependencies, run the tests, and build the same output used by GitHub Pages:
python -m pip install -r requirements-server.txt
python -m unittest discover -s tests -v
python scripts/build_feeds.py --output siteTo omit internal and tentative events from every generated JSON and ICS file,
run python scripts/build_feeds.py --output site --public-only or
./bin/calendar build --public-only. The standard Pages build includes all
events and uses the lightweight password gate.
Serve site/ over HTTP when checking the subscription page locally,
because the page loads calendar-data.json with fetch():
python -m http.server 8000 --directory site