3

If I wanted to look through my OS X Calendar (formerly "iCal") to find events whose summary was "Lunch", I could do something like this:

var eventsContainer = Application('Calendar').calendars[0].events
for (var i = 0; i < eventsContainer.length; i++) {
    var thisEvent = eventsContainer[i];
    if (thisEvent.summary() == 'Lunch') { doSomething() }
} 

even taking into account the fact that this only searches the first calendar, it's very, very, very slow, since each iCal event needs to be translated to a Javascript object to run. Here's a formulation that is substantially faster:

var foundEvents = Application('Calendar').calendars.events.whose({summary: 'Lunch'});

This works great for an exact match summary == 'Lunch'. But what about comparisons such as endDate: > new Date() or summary: /lunch/i ? Is it possible to pass native (ObjC) code into a whose() selector? Is there any documentation anywhere for whose() that would help?

  • This related question: http://stackoverflow.com/questions/27072977/using-whose-on-arrays-in-javascript-for-automation was primarily about getting the simple form of `whose()` to work in the initial, buggy implementation of JXA. It's not a duplicate Q. – Michael Scott Asato Cuthbert Jun 25 '16 at 00:17

1 Answers1

4

The relevant documentation turned out to be in the Release Notes for Interapplication Communication for OS X 10.10: https://developer.apple.com/library/mac/releasenotes/InterapplicationCommunication/RN-JavaScriptForAutomation/Articles/OSX10-10.html

The right-hand side of the object in the whose() argument can take another one-element object where the left side gives an operator and the right side gives a value. For instance:

.calendars.events.whose({summary: {_beginsWith: 'Lunch'}});

or in my case, to find events starting today:

    var startOfDay = new Date();
    startOfDay.setHours(0);
    startOfDay.setMinutes(0);
    startOfDay.setSeconds(0);
    startOfDay.setMilliseconds(0);
    var endOfDay = new Date();
    endOfDay.setHours(23);
    endOfDay.setMinutes(59);
    endOfDay.setSeconds(59);
    endOfDay.setMilliseconds(999);

    var events = Application('Calendar').calendars.events.whose({
        _and: [
            { startDate: { _greaterThan: startOfDay }},
            { startDate: { _lessThan: endOfDay }}
        ]
    });
    var convertedEvents = events();
    for (var cal of convertedEvents) {
        for (var ev of cal) { 
            console.log(ev.summary());
        }
    }  
  • 1
    Thanks for sharing a great question and answer! – JMichaelTX Jun 26 '16 at 04:19
  • Thanks! I'll leave the "Accepted Answer" open to see if someone can improve upon the syntax/speed. The query takes 20 seconds on my computer. – Michael Scott Asato Cuthbert Jun 26 '16 at 17:07
  • 1
    "The query takes 20 seconds on my computer" -- that surprises me. It would be interesting to time just through the "var events = . . ." statement. I would do it but I don't use Apple Calendar. I was more interested in the general approach and use of the whose() function. – JMichaelTX Jun 26 '16 at 21:29
  • The `var events = ... ` statement takes no time at all, because it's sort of the equivalent of an SQL prepare statement. It's the `var convertedEvents = events()` statement that takes all the time, but it takes the same amount of time (approximately) no matter how many events are retrieved. It accounts for > 90% of execution time. Presumably it's O(n) on the number of events in my calendars which I think is somewhere around 2,000 (50 x month * 4 years). Obviously iCal has a faster system. – Michael Scott Asato Cuthbert Jun 27 '16 at 03:21
  • @MichaelScottCuthbert Where did you run your code? ScriptEditor does not need console.log() – OrigamiEye Mar 18 '20 at 09:12
  • The console.log() is for demoing what you might do with the event object. It's a stand in for the actual application logic. – Michael Scott Asato Cuthbert Mar 21 '20 at 16:23
  • 1
    Is there a way to limit the number of returned results? Often I expect only a single match (e.g., `whose` with an ID), and for performance reasons I'd prefer `.whose({id: myId}, {limit: 1})[0]` to `.whose({id: myId})[0]`. But I can't figure out the syntax to enable the former, if it exists. – BallpointBen Dec 06 '20 at 22:31
  • 1
    @BallpointBen you can substitute `whose` with `byId(myId)` in your case. – Nick Sep 18 '22 at 23:21