Skip to content

Share calendar to users or groups with "show only as busy". #11214

Description

@phlegx

Steps to reproduce

  1. Click on calendar app
  2. Click on share calendar
  3. Select user or group

Expected behaviour

After the steps to reproduce the behavior I want to choose an option "show only as busy". So, that the selected user or group can see my complete calendar but without details. This option is missing.

I know that it is possible for a single event to "show only as busy". But what if I want to have this for a specific user or group for the complete calendar?

Actual behaviour

After the steps to reproduce the behavior there is only an option "can edit" for the chosen user or group.

Server configuration

Nextcloud version: 13.0.6
Nextcloud calendar version: 1.6.1

Activity

  1. nextcloud-bot commented on Sep 14, 2018

    @nextcloud-bot
    Member

    GitMate.io thinks possibly related issues are #8698 (need a group or folder for all user), #7340 (Show circle name in details of a shared file (group user)), #866 (Calendar Share Functionality missing), #10347 (Make shares visible to other users), and #3801 (Adding appointment into shared calendar fails).

  2. MorrisJobke commented on Sep 27, 2018

    @MorrisJobke
    Member
  3. stefanoco commented on Dec 10, 2019

    @stefanoco
  4. georgehrke commented on Dec 10, 2019

    @georgehrke
  5. stefanoco commented on Dec 10, 2019

    @stefanoco
  6. jfrederickson commented on Dec 13, 2019

    @jfrederickson

    Ideally it'd be possible to do this for calendar links as well. (And to be able to generate links for both full access and free/busy simultaneously.) Google Calendar for example only lets you do one or the other at any given time.

  7. nolens commented on Jan 23, 2020

    @nolens

    I badly need this feature and I have just set a bounty on this.
    Bountysource

  8. nolens commented on Jan 24, 2020

    @nolens

    @stefanoco
    Would you like add to my bounty on this? I have a couple of freelance programers who I am working with on other projects, they might be able to contribute on short notice....

    @georgehrke
    Could we setup a group chat somewhere for us and the programers, to discuss scope and for you to give clear directions? Could make life easier, in the end...

  9. added
    0. Needs triagePending check for reproducibility or if it fits our roadmap
    on Aug 20, 2020
  10. brylie commented on Oct 29, 2020

    @brylie

    This would be really important to avoid accidentally publishing event details to a shared calendar. Right now, events seem to default to "when shared, show full event". This may accidentally expose private information when creating an event from a phone or app that doesn't expose the "when shared show" field.

    Is it possible to change the default value for "when shared show" field to "when shared, show only busy"?

  11. biva commented on Mar 2, 2021

    @biva
  12. 47 remaining items

  13. bwurst commented on Aug 27, 2025

    @bwurst

    @AndyScherzinger did you possibly close the wrong bug report?

    This report is about a demanded new feature for sharing a whole calendar as "only show me busy".
    I'd like to have a consumer for my calendar that should never see the details of events but others should.

  14. AndyScherzinger commented on Aug 27, 2025

    @AndyScherzinger
    Member

    did you possibly close the wrong bug report?

    Got auto-closed by merging the referenced PR. @st3iny wrong referenced issue?

  15. st3iny commented on Aug 27, 2025

    @st3iny
    Member

    Yeah, this issue was referenced wrongly.

  16. JlincFM commented on Mar 2, 2026

    @JlincFM

    Question: To support this, would something as simple as the below work?

    • Add a checkbox option to the calendar settings of "When shared show only busy" (default unchecked)
      • When checked:
        • If a new event is created: automatically set the event to "When shared show only busy"
        • If an event is updated: use whatever visibility value comes through
      • When unchecked:
        • Current behavior
  17. kaystrobach commented on Mar 3, 2026

    @kaystrobach

    No

    We need different views for different purposes.

    • Busy / non busy view for people / apps / devices who we don’t trust
    • Normal behavior, where we can decide on event base

    So basically share options are the way to go, something to select how detailed event info is shared like all, minimal (title only), just timeslot

  18. JlincFM commented on Mar 3, 2026

    @JlincFM

    Makes sense. I was trying to think of this in baby steps (small PRs). I.E.:

    • Phase 1: Add the calendar preference for default when new events are added
      • Note: This looks straight forward, solves many of the above use cases, and is a nice to have for phase 2 anyway.
    • Phase 2: Full sharing preferences that override the event settings.
  19. doppelgrau commented on Mar 9, 2026

    @doppelgrau

    I wrote now a small(?) python script that "solves" this problem for me. It "copies" the entries from some calenders into another one. Either just with predefined Title/Summary or copying the original title/summary after the predefined one (e.g. if some details shouldn't be shared that might be in the notes) but it's ok to get an overall idea what the appointment is about.

    from caldav import error, get_davclient
    import calendar
    from datetime import datetime, timedelta
    import icalendar
    
    
    # Calendars with title
    # Name (used in the copied events): URL
    calendars_with_title = {\
            "Private":"https://nextcloud/dav/calendars/user/calendar_name/"
        }
    # Calendars with only "busy"
    # Name (used in the copied events): URL
    calendars_only_busy = {\
            "Work":"https://nextcloud/dav/calendars/user/calendar_name/"
        }
    # Destination calendar
    target_calendar = "https://nextcloud/dav/calendars/user/calendar_name/"
    
    #
    # Helpers
    #
    def replace_calendar_url_by_object(url, calendars):
        for calendar in calendars:
            if url == calendar.url:
                return calendar
    
    def replace_calendar_urls_in_dictionary_by_object(dictionary, calendars):
        for key in dictionary:
            dictionary[key] = replace_calendar_url_by_object(dictionary[key], calendars)
        return dictionary
    
    def delete_old_entries(calendar, min_age):
        old_events = calendar.search(
            start=datetime.now() + timedelta(days= -2 * min_age),
            end=datetime.now() + timedelta(days= -1 * min_age),
            event=True,
            expand=True,
        )
        for event in old_events:
            event.delete()
        
    def get_next_event_from_iterator(iterator):
        try:
            event = iterator.__next__()
            return event
        except:
            return None
        
    def get_event(raw_event):
        for component in raw_event.get_icalendar_instance().subcomponents:
            if isinstance(component, icalendar.cal.Event):
                return component
    
    def sync_calender(source, target, name, copy_summary=False):
        # get events in the relevant intervall
        source_events = source.search(
            start=datetime.now(),
            end=datetime.now() + timedelta(days=270),
            event=True,
            expand=True,
        )
        target_events = target.search(
            start=datetime.now(),
            end=datetime.now() + timedelta(days=270),
            event=True,
            expand=True,
        )
        
        # sort according to start time
        source_events.sort(key=lambda self: calendar.timegm(get_event(self)["DTSTART"].dt.timetuple()))
        target_events.sort(key=lambda self: calendar.timegm(get_event(self)["DTSTART"].dt.timetuple()))
    
        # get iterators
        source_iter = source_events.__iter__()
        target_iter = target_events.__iter__()
        
        # get first event
        current_source_event = get_next_event_from_iterator(source_iter)
        current_target_event = get_next_event_from_iterator(target_iter)
        
        # both calendars still have events
        while current_source_event != None and current_target_event != None:
            source_vevent = get_event(current_source_event)
            target_vevent = get_event(current_target_event)
            # make sure the event in the target calender is for the source, else proceed forward
            if target_vevent["SUMMARY"].find(name) != 0:
                current_target_event = get_next_event_from_iterator(target_iter)
                continue
            # we have two events for the source calender, now lets check if they are sufficient identical
            if copy_summary:
                target_summary = name + ": " + source_vevent["SUMMARY"]
            else:
                target_summary = name
            if target_vevent["SUMMARY"] == target_summary \
               and source_vevent["DTSTART"].dt == target_vevent["DTSTART"].dt \
               and source_vevent["DTEND"].dt == target_vevent["DTEND"].dt:
                # entries are suffienctly identical, proced to the next ones
                current_source_event = get_next_event_from_iterator(source_iter)
                current_target_event = get_next_event_from_iterator(target_iter)
                continue
            # the events are different
            # first case, the one in the target starts earlier (or at the same time) => delete that one
            if source_vevent["DTSTART"].dt >= target_vevent["DTSTART"].dt:
                current_target_event.delete()
                current_target_event = get_next_event_from_iterator(target_iter)
                continue
            # second case, the one in the source starts earlier, => create a new event
            if source_vevent["DTSTART"].dt < target_vevent["DTSTART"].dt:
                target.add_event(
                            dtstart=source_vevent["DTSTART"],
                            dtend=source_vevent["DTEND"],
                            summary=target_summary,)
                current_source_event = get_next_event_from_iterator(source_iter)
                continue
        
        # While loop finished, one or both calendars have no remaining events
        assert current_source_event == None or current_target_event == None
    
        # target still has some events, delete if matching the source
        while current_target_event != None:
            target_vevent = get_event(current_target_event)
            # originally from the source calendar
            if target_vevent["SUMMARY"].find(name) == 0:
                current_target_event.delete()
            # check next
            current_target_event = get_next_event_from_iterator(target_iter)
    
        # source has still some events, add those to the target
        while current_source_event != None:
            source_vevent = get_event(current_source_event)
            # originally from the source calendar
            if copy_summary:
                target_summary = name + ": " + source_vevent["SUMMARY"]
            else:
                target_summary = name
            target.add_event(
                        dtstart=source_vevent["DTSTART"],
                        dtend=source_vevent["DTEND"],
                        summary=target_summary,)
            # check next
            current_source_event = get_next_event_from_iterator(source_iter)
    
    ## The client object can be used as a context manager, like this:
    with get_davclient() as client:
        # Connecting to the caldav server
        my_principal = client.principal()
    
        # The principals calendars can be fetched like this:
        calendars = my_principal.get_calendars()
        
        # replace the URLs by more conventient objects
        target_calendar = replace_calendar_url_by_object(target_calendar, calendars)
        calendars_with_title = replace_calendar_urls_in_dictionary_by_object(calendars_with_title, calendars)
        calendars_only_busy = replace_calendar_urls_in_dictionary_by_object(calendars_only_busy, calendars)
    
        # delete old entries
        delete_old_entries(target_calendar, 90)
        
        # sync "busy"
        for calendar_name in calendars_only_busy:
            sync_calender(calendars_only_busy[calendar_name], target_calendar, calendar_name)
        # sync "summary"
        for calendar_name in calendars_with_title:
            sync_calender(calendars_with_title[calendar_name], target_calendar, calendar_name, True)

    Quite sure it's not perfect, but so far works "good enough". Assumes the CALDAV_USERNAME, CALDAV_PASSWORD and the CALDAV_URL (like CALDAV_URL=https://nextcloud/remote.php/dav) are set as environment variables.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions