{"id":45483,"date":"2024-10-23T11:51:57","date_gmt":"2024-10-23T15:51:57","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=45483"},"modified":"2024-10-28T10:47:30","modified_gmt":"2024-10-28T14:47:30","slug":"the-sad-state-of-mac-calendar-scripting","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2024\/10\/23\/the-sad-state-of-mac-calendar-scripting\/","title":{"rendered":"The Sad State of Mac Calendar Scripting"},"content":{"rendered":"<em><\/em><p>AppleScript support with popular Mac calendar apps is surprisingly limited. With Apple Mail, it&rsquo;s easy to write a script that operates on the selected e-mail messages, and I&rsquo;ve written many such scripts. I&rsquo;d like to do this same with calendar events. For example, sometimes I want to make the same change to one of the fields of all the selected events (since there&rsquo;s no bulk edit inspector). Other times I want to reschedule by shifting all the events by the same amount of time. Sometimes I have a group of past events (e.g. for cooking a Thanksgiving dinner or traveling to a familiar location) and I want to start planning the next iteration by duplicating and shifting them to the future. When traveling east-west, I would like to check that the time zone information is entered consistently.<\/p>\n<p>But this just doesn&rsquo;t seem to be possible to script. With Apple Calendar and <a href=\"https:\/\/www.busymac.com\/busycal\/index.html\">BusyCal<\/a>, the scripting dictionary does not even let you access the selected calendar events. <a href=\"https:\/\/readdle.com\/calendars\">Readdle Calendars<\/a>, which recently <a href=\"https:\/\/9to5mac.com\/2024\/06\/07\/readdle-sunsets-calendars-5-to-focus-on-freemium-version-of-calendars\/\">switched to a subscription<\/a>, is not scriptable at all. With <a href=\"https:\/\/flexibits.com\/fantastical\">Fantastical<\/a>, the dictionary looks promising but mostly doesn&rsquo;t work:<\/p>\n\n<ul>\n<li><p>You <em>can<\/em> get the selected events, but key properties such as the containing calendar and the repetition and alarm info are missing. So I can&rsquo;t just copy by making a new event based on the selected one.<\/p><\/li>\n<li><p>I usually want the copied event to keep the same calendar, so how about just letting <em>Fantastical<\/em> copy an existing event and then edit it with the script? There&rsquo;s a <code>duplicate<\/code> command, but it doesn&rsquo;t work.<\/p><\/li>\n<li><p>OK, how about if I have <em>the script<\/em> pick the calendar and create a new event using the properties of the selected one? It turns out that creating new events via AppleScript doesn&rsquo;t work, either.<\/p><\/li>\n<li><p>How about if I duplicate the events using the menu command and then use AppleScript to edit them? The properties are shown as editable, and you can set their values, but this seems to just mutate a temporary object. The changes are never reflected in the user interface or saved to disk.<\/p><\/li>\n<\/ul>\n\n<p>I contacted support, who said that, despite what the dictionary shows, none of this is supported. All you can do is read (some) of the properties of the selected events and create new events using the <code>parse sentence<\/code> command. They suggested exporting to ICS, processing the file, and re-importing the ICS. The <strong>Export&#8230;<\/strong> command exports an entire calendar and so isn&rsquo;t very useful for this purpose, unless you first copy the events to a temporary calendar. However, you can export individual events via drag and drop.<\/p>\n\n<p>Some other partial solutions that I found:<\/p>\n<ul>\n<li><p>You can click <a href=\"x-fantastical3:\/\/defaults?key=AlwaysShowAll&amp;value=1&amp;type=bool&amp;group=1\">here<\/a> to set a hidden Fantastical preference to make the event editor popover always open in expanded form. This saves a lot of clicks when editing a sequence of events.<\/p><\/li>\n<li><p>You can Option-drag a selection of events to copy them to a particular day. I mostly don&rsquo;t find this useful because the source and destination days don&rsquo;t usually fit on screen at the same time (and it doesn&rsquo;t let me open multiple windows).<\/p><\/li>\n<li><p>Similarly, if you can manage to view and select a bunch of events in the week or day view, you can drag and drop to reschedule them all at once. This is in general rather awkward, but it can work if you&rsquo;re doing something basic like shifting them all an hour later.<\/p><\/li>\n<li><p>Although there is no command to <strong>Duplicate<\/strong> the selected events <em>to<\/em> a particular day, you can <strong>Copy<\/strong> them, then click on the desired start day, and <strong>Paste<\/strong>, and it will preserve the relative times between the events. (This <a href=\"https:\/\/www.reddit.com\/r\/mac\/comments\/13u0jek\/comment\/jlxym2t\/\">also works<\/a> in Apple Calendar.)<\/p><\/li>\n<\/ul>\n\n<p>Previously:<\/p>\n<ul>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/10\/23\/qbix-calendar-apps-and-the-mac-app-store\/\">Qbix Calendar Apps and the Mac App Store<\/a><\/li>\n<\/ul>\n\n<p id=\"the-sad-state-of-mac-calendar-scripting-update-2024-10-28\">Update (2024-10-28): A reader notes that, although Calendar does not provide AppleScript access to the selected events, if an event is in edit mode you can use GUI scripting to extract its information:<\/p>\n<pre>tell application \"System Events\"\n    tell application process \"Calendar\"\n        set parametersOfEvent to description of every button of pop over 1 of window 1\n        --eg...{\"Oct 25, 2024  9&#x202F;AM to 5&#x202F;PM\", \"Alert when I need to leave Alert 15 minutes before start (default)\", \"Edit Attendees\", \"add\", \"Add Notes or URL\"}\n        \n        set nameOfEvent to value of every text field of pop over 1 of window 1\n        \n        set repeatsHow to value of every static text of pop over 1 of window 1\n        --eg..{\"Busy\", \"Repeats every week on Monday, Tuesday, and Thursday\"} \n        --item 1 is the title, item 2 is how it repeats\n    end tell\nend tell<\/pre>\n\n<p>And <a href=\"https:\/\/mjtsai.com\/blog\/2024\/10\/23\/the-sad-state-of-mac-calendar-scripting\/#comment-4192146\">Shane Stanley<\/a>&rsquo;s CalendarLib EC is an AppleScript wrapper for accessing EventKit using AppleScriptObjC.<\/p>","protected":false},"excerpt":{"rendered":"<p>AppleScript support with popular Mac calendar apps is surprisingly limited. With Apple Mail, it&rsquo;s easy to write a script that operates on the selected e-mail messages, and I&rsquo;ve written many such scripts. I&rsquo;d like to do this same with calendar events. For example, sometimes I want to make the same change to one of the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"apple_news_api_created_at":"2024-10-23T15:51:59Z","apple_news_api_id":"a25ad847-b448-4aaa-91e9-8d473f51ff78","apple_news_api_modified_at":"2024-10-28T14:47:32Z","apple_news_api_revision":"AAAAAAAAAAAAAAAAAAAAAg==","apple_news_api_share_url":"https:\/\/apple.news\/AolrYR7RISqqR6Y1HP1H_eA","apple_news_coverimage":0,"apple_news_coverimage_caption":"","apple_news_is_hidden":false,"apple_news_is_paid":false,"apple_news_is_preview":false,"apple_news_is_sponsored":false,"apple_news_maturity_rating":"","apple_news_metadata":"\"\"","apple_news_pullquote":"","apple_news_pullquote_position":"","apple_news_slug":"","apple_news_sections":"\"\"","apple_news_suppress_video_url":false,"apple_news_use_image_component":false,"footnotes":""},"categories":[2],"tags":[159,192,193,649,194,30,32,2598,2675],"class_list":["post-45483","post","type-post","status-publish","format-standard","hentry","category-technology","tag-applescript","tag-busycal","tag-calendar","tag-esoteric-preferences","tag-fantastical","tag-mac","tag-macapp","tag-macos-15-sequoia","tag-readdle-calendars"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/45483","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/comments?post=45483"}],"version-history":[{"count":5,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/45483\/revisions"}],"predecessor-version":[{"id":45523,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/45483\/revisions\/45523"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=45483"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=45483"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=45483"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}