Sunday, August 18, 2019

#1 2013-08-04 03:40:23 pm

jfaughnan
Member
From:: MSP
Registered: 2012-07-27
Posts: 9
Website

AppleScript to create list of Events over a specified date range

I came up with this when I wanted to paste an itinerary for an upcoming car trip into a text document. I found some inelegant solutions for Calendar.app (OS X Mountain Lion), but not quite what I wanted. I wanted something like this:

August 18, 2013
Montreal, Canada

August 19, 2013
Burlington, Vermont

and so on. Dates are Event.start date at text is Event.summary (I'm making up syntax here). Output is copied to clipboard or can be saved to plaintext file.

I found some scripts that suggest this is pretty doable (acknowledgements here: http://tech.kateva.org/2013/08/using-ap … ntext.html)

For example, I can get names of calendars (or go with selected?) and I can also see how to pass a date range and get EventIDs back and then use 'summary' and the link to get Event values:

Applescript:

tell application "iCal"
set theCalendars to every calendar
end tell

AND

Applescript:

set {year:y, month:m, day:d} to current date
set str to (m as string) & " " & (d as string) & " " & (y as string)
set today to date str
set tomorrow to today + 60 * 60 * 24

tell application "Calendar"
tell calendar "FL Family Calendar"
set curr to every event whose start date is greater than or equal to today ¬
and start date is less than or equal to tomorrow
end tell
end tell

So all looks very doable. Anyone know someone who has done something even closer to this example?


Filed under: iCal, calendar

Offline

 

#2 2013-08-04 04:13:14 pm

kel1
Member
Registered: 2013-01-11
Posts: 2188

Re: AppleScript to create list of Events over a specified date range

Hi,

Something like this?

Applescript:

tell application "Calendar"
   set the_calendar to calendar "USA Holidays"
   set info_list to {start date, summary} of every event of the_calendar
end tell

It's interesting what you would do with the info from there.

gl,
kel

Last edited by kel1 (2013-08-04 04:19:29 pm)


Os 10.10.3
Mbp

Offline

 

#3 2013-08-04 04:44:13 pm

kel1
Member
Registered: 2013-01-11
Posts: 2188

Re: AppleScript to create list of Events over a specified date range

Here's one way:

Applescript:

tell application "Calendar"
   set the_calendar to calendar "USA Holidays"
   set {start_dates, the_summaries} to {start date, summary} of every event of the_calendar
end tell
set c to count start_dates
set text_list to {}
repeat with i from 1 to c
   set this_date to item i of start_dates
   set this_summary to item i of the_summaries
   set end of text_list to this_date & linefeed & this_summary & return & return
end repeat
set the_text to text_list as string

Edited: why did I do that with the returns:

Applescript:

tell application "Calendar"
   set the_calendar to calendar "USA Holidays"
   set {start_dates, the_summaries} to {start date, summary} of every event of the_calendar
end tell
set c to count start_dates
set text_list to {}
repeat with i from 1 to c
   set this_date to item i of start_dates
   set this_summary to item i of the_summaries
   set end of text_list to this_date & linefeed & this_summary & linefeed & linefeed
end repeat
set the_text to text_list as string

gl,
kel

Last edited by kel1 (2013-08-04 04:48:07 pm)


Os 10.10.3
Mbp

Offline

 

#4 2013-08-04 05:57:23 pm

jfaughnan
Member
From:: MSP
Registered: 2012-07-27
Posts: 9
Website

Re: AppleScript to create list of Events over a specified date range

Thank you, that produced a list in the correct format, though it wasn't sorted by date. I got back about 800 events.

I'd like to be able to produce a similar output, but

1. Be able to specify > 1 calendar (or even all calendars)
2. Be able to specify a start and end date
3. Have results be sorted by date

Your code illustrated something novel to me, that AppleScript appears to support array assignment, as in:

set {start_dates, the_summaries} to {start date, summary} of every event of the_calendar


Filed under: iCal, calendar

Offline

 

#5 2013-08-04 05:59:54 pm

kel1
Member
Registered: 2013-01-11
Posts: 2188

Re: AppleScript to create list of Events over a specified date range

That's good that we separated the dates.


Os 10.10.3
Mbp

Offline

 

#6 2013-08-04 06:02:39 pm

kel1
Member
Registered: 2013-01-11
Posts: 2188

Re: AppleScript to create list of Events over a specified date range

If you had 800 events, then this is a job for minds greater than mine. smile If you want speed that is.

gl,
kel


Os 10.10.3
Mbp

Offline

 

#7 2013-08-04 06:05:19 pm

kel1
Member
Registered: 2013-01-11
Posts: 2188

Re: AppleScript to create list of Events over a specified date range

I told you it was going to be interesting.


Os 10.10.3
Mbp

Offline

 

#8 2013-08-04 06:20:06 pm

kel1
Member
Registered: 2013-01-11
Posts: 2188

Re: AppleScript to create list of Events over a specified date range

Think I've found a way to speed it up. If I can only remember it.


Os 10.10.3
Mbp

Offline

 

#9 2013-08-04 07:40:31 pm

jfaughnan
Member
From:: MSP
Registered: 2012-07-27
Posts: 9
Website

Re: AppleScript to create list of Events over a specified date range

It was actually pretty quick really. Looking at this code
http://stackoverflow.com/questions/5907 … pplescript

Applescript:


set {year:y, month:m, day:d} to current date
set str to (m as string) & " " & (d as string) & " " & (y as string)
set today to date str
set tomorrow to today + 60 * 60 * 24

tell application "iCal"
tell calendar "Lotus Notes"
set curr to every event whose start date is greater than or equal to today ¬
and start date is less than or equal to tomorrow
end tell
end tell

It looked like we could get the events back using a variant of the data criteria, then process them as you did.

I don't know about sorting by date though.

Offline

 

#10 2013-08-05 07:14:32 am

Nigel Garvey
Moderator
From:: Warwickshire, England
Registered: 2002-11-20
Posts: 4940

Re: AppleScript to create list of Events over a specified date range

Hi.

jfaughnan wrote:

It was actually pretty quick really. Looking at this code
http://stackoverflow.com/questions/5907 … pplescript

Applescript:


set {year:y, month:m, day:d} to current date
set str to (m as string) & " " & (d as string) & " " & (y as string)
set today to date str
set tomorrow to today + 60 * 60 * 24


That's rubbish in that it's inefficient and only works on machines where the preferences are set for the US date format. This is better:

Applescript:

set today to (current date)
set today's time to 0
set tomorrow to today + days

If you've got a lot of events in your calendar, a 'whose' filter can take quite a while to execute. (Applications can be very slow when they're asked to think.) A faster way would be to grab the start dates and summaries for all the events in the calendar, as kel's done above, and use vanilla AppleScript to identify which dates and summaries are in required range. The following may get you started. It gets date-range input from the user, narrows down the start dates and summaries to those in the range, sorts the results by start date, composes the text, and makes a TextEdit document containing it:

Applescript:

-- Ask the user for the range of dates to be covered.
on getDateRange()
   set today to (current date)
   set d1 to today's short date string
   set d2 to short date string of (today + 6 * days)
   
   set dateRange to text returned of (display dialog "Enter the required date range:" default answer d1 & " - " & d2)
   set dateRangeStart to date (text from word 1 to word 3 of dateRange)
   set dateRangeEnd to date (text from word -3 to word -1 of dateRange)
   set dateRangeEnd's time to days - 1 -- Sets the last date's time to 23:59:59, the last second of the range.
   
   return {dateRangeStart, dateRangeEnd}
end getDateRange

-- Return the start dates and summaries which are in the given date range.
on filterToDateRange(theStartDates, theSummaries, dateRangeStart, dateRangeEnd)
   set {eventDatesInRange, eventSummariesInRange} to {{}, {}}
   repeat with i from 1 to (count theStartDates)
       set thisStartDate to item i of theStartDates
       if (not ((thisStartDate comes before dateRangeStart) or (thisStartDate comes after dateRangeEnd))) then
           set end of eventDatesInRange to thisStartDate
           set end of eventSummariesInRange to item i of theSummaries
       end if
   end repeat
   
   return {eventDatesInRange, eventSummariesInRange}
end filterToDateRange

-- Sort both the start-date and summary lists by start date.
on sortByDate(eventDatesInRange, eventSummariesInRange)
   -- A sort-customisation object for sorting the summary list in parallel with the date list.
   script custom
       property summaries : eventSummariesInRange
       
       on swap(i, j)
           tell item i of my summaries
               set item i of my summaries to item j of my summaries
               set item j of my summaries to it
           end tell
       end swap
   end script
   
   CustomBubbleSort(eventDatesInRange, 1, -1, {slave:custom})
end sortByDate

-- CustomBubbleSort from "A Dose of Sorts" by Nigel Garvey.
-- The number of items to be sorted here is likely to be small.
on CustomBubbleSort(theList, l, r, customiser)
   script o
       property comparer : me
       property slave : me
       property lst : theList
       
       on bsrt(l, r)
           set l2 to l + 1
           repeat with j from r to l2 by -1
               set a to item l of o's lst
               repeat with i from l2 to j
                   set b to item i of o's lst
                   if (comparer's isGreater(a, b)) then
                       set item (i - 1) of o's lst to b
                       set item i of o's lst to a
                       slave's swap(i - 1, i)
                   else
                       set a to b
                   end if
               end repeat
           end repeat
       end bsrt
       
       -- Default comparison and slave handlers for an ordinary sort.
       on isGreater(a, b)
           (a > b)
       end isGreater
       
       on swap(a, b)
       end swap
   end script
   
   -- Process the input parameters.
   set listLen to (count theList)
   if (listLen > 1) then
       -- Negative and/or transposed range indices.
       if (l < 0) then set l to listLen + l + 1
       if (r < 0) then set r to listLen + r + 1
       if (l > r) then set {l, r} to {r, l}
       
       -- Supplied or default customisation scripts.
       if (customiser's class is record) then set {comparer:o's comparer, slave:o's slave} to (customiser & {comparer:o, slave:o})
       
       -- Do the sort.
       o's bsrt(l, r)
   end if
   
   return -- nothing
end CustomBubbleSort

-- Compose the text from the items in the start-date and summary lists.
on composeText(eventDatesInRange, eventSummariesInRange)
   set txt to ""
   set gap to linefeed & linefeed
   
   repeat with i from 1 to (count eventDatesInRange)
       set txt to txt & (date string of item i of eventDatesInRange) & (linefeed & item i of eventSummariesInRange & gap)
   end repeat
   
   return text 1 thru -3 of txt
end composeText

on main()
   tell application "iCal" to set {theStartDates, theSummaries} to {start date, summary} of events of calendar "FL Family Calendar"
   
   set {dateRangeStart, dateRangeEnd} to getDateRange()
   set {eventDatesInRange, eventSummariesInRange} to filterToDateRange(theStartDates, theSummaries, dateRangeStart, dateRangeEnd)
   sortByDate(eventDatesInRange, eventSummariesInRange)
   set txt to composeText(eventDatesInRange, eventSummariesInRange)
   
   tell application "TextEdit"
       make new document with properties {text:txt}
       activate
   end tell
end main

main()


NG

Offline

 

#11 2013-08-05 07:26:18 am

jfaughnan
Member
From:: MSP
Registered: 2012-07-27
Posts: 9
Website

Re: AppleScript to create list of Events over a specified date range

Wow, that looks awfully good. I should be able to run with that for the moment. I've got a bit of crunch before a family car trip, but then I'll have a chance to study and dissect the script, and tweak it if I need to. I'll document it on a blog post with, of course, full reference to this post and MacScripter.


Filed under: iCal, calendar, agenda, itinerary

Offline

 

#12 2013-08-05 10:22:10 pm

jfaughnan
Member
From:: MSP
Registered: 2012-07-27
Posts: 9
Website

Re: AppleScript to create list of Events over a specified date range

Thanks NG, the new script works very well. It was easy to adjust the text output to be more compressed.

I'll enjoy playing with this further, but it solves my original problem as is.

Offline

 

#13 2016-01-16 03:31:38 pm

jbrigham
Member
Registered: 2005-06-22
Posts: 15

Re: AppleScript to create list of Events over a specified date range

I realize the is a pretty old thread but I came across is because I was wanting to do something very similar in Calendar.  The code works great for what I need but I've also been trying to modify it to do the same thing for several calendars.  I'd still like to get just a single text doc  that has date headers and then listing the events for that date (including which calendar it's on) - something like this (yes I would like the dates in bold if possible):

Monday, January 18, 2016
Calendar1: a thing that's happening
Calendar2: another thing that's happening

Wednesday, January 20, 2016
Calendar1: another day, another thing to do

Can anyone guide me on how to make this script work for several calendars?

Thanks.

Offline

 

#14 2016-01-17 09:39:56 am

Nigel Garvey
Moderator
From:: Warwickshire, England
Registered: 2002-11-20
Posts: 4940

Re: AppleScript to create list of Events over a specified date range

jbrigham wrote:

… I was wanting to do something very similar in Calendar.


I wouldn't describe collating the event data from several calendars by calendar date and then formatting the output text as being particularly similar….  wink

The version below has only been tested in El Capitan. Instead of sorting a single calendar's event summaries in parallel with the associated start dates, it collates the data from all the calendars into a list of lists — each sublist containing the start date, summary, and calendar of an event. This list is then sorted by the sublists' start dates. A new document is then created in TextEdit and text representing the events is output to it as it's derived from the sorted data.

With more calendars involved, there'll usually be more events to process, so I've used script object referencing in the list collation and replaced the bubble sort with a shell sort. However, most of the running time will be the initial wait for Calendar to return the data to the script, which could take up to a minute if there are a lot of events in the calendars. I think there are faster ways to get the data using ASObjC, but I haven't got round to learning them or it yet.

Applescript:

-- Tested with Calendar 8.0 and TextEdit 1.11 (new documents defaulting to RTF) in Mac OS 10.11.2

-- Ask the user for the range of dates to be covered.
on getDateRange()
   set today to (current date)
   set d1 to today's short date string
   set d2 to short date string of (today + 6 * days)
   
   set dateRange to text returned of (display dialog "Enter the required date range:" default answer d1 & " - " & d2)
   set dateRangeStart to date (text from word 1 to word 3 of dateRange)
   set dateRangeEnd to date (text from word -3 to word -1 of dateRange)
   set dateRangeEnd's time to days - 1 -- Sets the last date's time to 23:59:59, the last second of the range.
   
   return {dateRangeStart, dateRangeEnd}
end getDateRange

-- Return the start dates, summaries, and calendar names of events in the given date range.
-- {{start date, summary, calendar name}, {start date, summary, calendar name}, … }
on filterToDateRange(theStartDates, theSummaries, theCalendarNames, dateRangeStart, dateRangeEnd)
   script o
       property sDates : theStartDates
       property summaries : theSummaries
       property cNames : theCalendarNames
       property filteredData : {}
   end script
   
   repeat with i from 1 to (count theCalendarNames)
       set o's sDates to item i of theStartDates
       set o's summaries to item i of theSummaries
       set thisCalendarName to item i of theCalendarNames
       repeat with j from 1 to (count o's sDates)
           set thisStartDate to item j of o's sDates
           if (not ((thisStartDate comes before dateRangeStart) or (thisStartDate comes after dateRangeEnd))) then
               set thisSummary to item j of o's summaries
               set end of o's filteredData to {thisStartDate, thisSummary, thisCalendarName}
           end if
       end repeat
   end repeat
   
   return o's filteredData
end filterToDateRange

-- Sort the filtered data by start date.
on sortByDate(filteredData)
   -- A sort-customisation object which compares the first items of two sublists taken from the list being sorted.
   script custom
       on isGreater(a, b)
           return (beginning of a > beginning of b)
       end isGreater
   end script
   
   CustomShellSort(filteredData, 1, -1, {comparer:custom})
end sortByDate

-- Shell sort. Algorithm: Donald Shell, 1959. AppleScript implementation: Nigel Garvey, 2010.
on CustomShellSort(theList, l, r, customiser)
   script o
       property comparer : me
       property slave : me
       property lst : theList
       
       on shsrt(l, r)
           set step to (r - l + 1) div 2
           repeat while (step > 0)
               slave's setStep(step)
               repeat with j from (l + step) to r
                   set v to item j of o's lst
                   repeat with i from (j - step) to l by -step
                       tell item i of o's lst
                           if (comparer's isGreater(it, v)) then
                               set item (i + step) of o's lst to it
                           else
                               set i to i + step
                               exit repeat
                           end if
                       end tell
                   end repeat
                   set item i of o's lst to v
                   slave's rotate(i, j)
               end repeat
               set step to (step / 2.2) as integer
           end repeat
       end shsrt
       
       -- Default comparison and slave handlers for an ordinary sort.
       on isGreater(a, b)
           (a > b)
       end isGreater
       
       on rotate(a, b)
       end rotate
       
       on setStep(a)
       end setStep
   end script
   
   -- Process the input parameters.
   set listLen to (count theList)
   if (listLen > 1) then
       -- Negative and/or transposed range indices.
       if (l < 0) then set l to listLen + l + 1
       if (r < 0) then set r to listLen + r + 1
       if (l > r) then set {l, r} to {r, l}
       
       -- Supplied or default customisation scripts.
       if (customiser's class is record) then set {comparer:o's comparer, slave:o's slave} to (customiser & {comparer:o, slave:o})
       
       -- Do the sort.
       o's shsrt(l, r)
   end if
   
   return -- nothing.
end CustomShellSort

-- Create a new TextEdit document with text derived from the gathered data.
on composeText(filteredData)
   tell application "TextEdit"
       -- Make a new document, with a minimal text so that we can discover the name of its font.
       set newDoc to (make new document with properties {text:" "})
       set baseFont to font of newDoc's text
       -- This ASSUMES that an equivalent bold font exists and that its name is the same as the plain font with " bold" appended.
       set boldFont to baseFont & " bold"
       -- Dummy text no longer needed.
       set newDoc's text to ""
       activate
   end tell
   
   if (filteredData is {}) then
       -- If no events have been discovered in the date range, print that fact.
       tell application "TextEdit" to make new paragraph at end of newDoc's text with data "No events found in this period." with properties {font:boldFont}
   else
       -- Otherwise print the event details.
       set currentCalendarDate to "" -- The calendar date currently being processed. (None yet.)
       repeat with i from 1 to (count filteredData)
           -- Get the data for an event from the list of filtered data.
           set {{date string:thisCalendarDate, hours:thisStartTimeH, minutes:thisStartTimeM}, thisSummary, thisCalendar} to item i of filteredData
           -- If the calendar date is different from the one we've been processing up till now, output an empty line and the new date string to TextEdit.
           if (thisCalendarDate is not currentCalendarDate) then
               tell application "TextEdit"
                   make new paragraph at end of newDoc's text with data linefeed with properties {font:baseFont}
                   make new paragraph at end of newDoc's text with data (thisCalendarDate & linefeed) with properties {font:boldFont}
               end tell
               -- Make the new date the one currently being processed.
               set currentCalendarDate to thisCalendarDate
           end if
           -- Create a 24-hour time string from the hours and minutes of the start date.
           tell (10000 + thisStartTimeH * 100 + thisStartTimeM) as text to set thisStartTime to text 2 thru 3 & ":" & text 4 thru 5
           -- Output the entry for this event to TextEdit.
           tell application "TextEdit" to make new paragraph at end of newDoc's text with data ("\"" & thisCalendar & "\" calendar: " & thisStartTime & " " & thisSummary & linefeed) with properties {font:baseFont}
       end repeat
   end if
end composeText

on main()
   say "Getting data from Calendar. It may take a while…"
   tell application "Calendar" to set {{theStartDates, theSummaries}, theCalendarNames} to {{start date, summary} of events, name} of calendars
   set {dateRangeStart, dateRangeEnd} to getDateRange()
   set filteredData to filterToDateRange(theStartDates, theSummaries, theCalendarNames, dateRangeStart, dateRangeEnd)
   sortByDate(filteredData)
   composeText(filteredData)
end main

main()

Last edited by Nigel Garvey (2016-01-17 09:49:08 am)


NG

Offline

 

#15 2016-01-17 12:24:44 pm

jbrigham
Member
Registered: 2005-06-22
Posts: 15

Re: AppleScript to create list of Events over a specified date range

Fantastic!  Thank you, Sir NG!  And a more than fair point about my overstatement of the similarity lol

This works perfectly - and, as always, I will learn  a lot by having a close look at it.  Many thanks.

Offline

 

#16 2016-01-17 05:04:42 pm

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5818

Re: AppleScript to create list of Events over a specified date range

Nigel Garvey wrote:

I think there are faster ways to get the data using ASObjC


It's certainly much faster, but it's also more accurate. The basic problem with using Calendar.app is that the start and end dates for repeating events are the start and end dates set when the event was created. So if you have a repeating event that falls within your time frame, scripts using Calendar.app won't catch it (unless it's the first time).

But you don't actually have to write any ASObjC -- all you need is CalendarLib (for 10.9+) or CalendarLib EC (for 10.11 only). So you'd start your script like this:

Applescript:

use script "CalendarLib EC" -- put this at the top of your scripts; use "CalendarLib" for pre-10.11 compatability
use scripting additions

You'd have a handler like this:

Applescript:


on fetchEventsStarting:dateRangeStart ending:dateRangeEnd
   set theStore to fetch store
   set theCals to fetch calendars {} cal type list {} event store theStore -- all calendars
   set theEvents to fetch events starting date dateRangeStart ending date dateRangeEnd searching cals theCals event store theStore
   set theFilteredData to {}
   repeat with anEvent in theEvents
       set theInfo to (event info for event anEvent)
       set end of theFilteredData to {event_start_date of theInfo, event_summary of theInfo, calendar_name of theInfo}
   end repeat
   return theFilteredData
end fetchEventsStarting:ending:

And your main() would be like this:

Applescript:

on main()
   set {dateRangeStart, dateRangeEnd} to getDateRange()
   set filteredData to my fetchEventsStarting:dateRangeStart ending:dateRangeEnd
   composeText(filteredData)
end main

You can then remove filterToDateRange(), sortByDate() and sortByDate().

You can get the CalendarLibs here:

www.macosxautomation.com/applescript/ap … _Libs.html


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/
latenightsw.com

Offline

 

#17 2016-01-17 06:44:03 pm

Nigel Garvey
Moderator
From:: Warwickshire, England
Registered: 2002-11-20
Posts: 4940

Re: AppleScript to create list of Events over a specified date range

Thanks Shane. That's great.  cool

Here's an actual manifestation of your suggestions:

Applescript:

-- Faster version using ASObjC.
-- Requires Shane Stanley's CalendarLib library. (Use the EC version with El Capitan.)
-- <[url=http://www.macosxautomation.com/applescript/apps/Script_Libs.html]www.macosxautomation.com/applescript/apps/Script_Libs.html[/url]>.

-- Doesn't require Calendar.app to be running.
-- Includes the expression dates of repeating events.
-- Tested with CalendarLIb EC and TextEdit 1.11 (new documents defaulting to RTF) in Mac OS 10.11.2.

use script "CalendarLib EC"
use scripting additions

-- Shane's handler to get the event data using his library.
on fetchEventsStarting:dateRangeStart ending:dateRangeEnd
   set theStore to fetch store
   set theCals to fetch calendars {} cal type list {} event store theStore -- all calendars
   set theEvents to fetch events starting date dateRangeStart ending date dateRangeEnd searching cals theCals event store theStore
   set theFilteredData to {}
   repeat with anEvent in theEvents
       set theInfo to (event info for event anEvent)
       set end of theFilteredData to {event_start_date of theInfo, event_summary of theInfo, calendar_name of theInfo}
   end repeat
   return theFilteredData
end fetchEventsStarting:ending:

-- Ask the user for the range of dates to be covered.
on getDateRange()
   set today to (current date)
   set d1 to today's short date string
   set d2 to short date string of (today + 6 * days)
   
   set dateRange to text returned of (display dialog "Enter the required date range:" default answer d1 & " - " & d2)
   set dateRangeStart to date (text from word 1 to word 3 of dateRange)
   set dateRangeEnd to date (text from word -3 to word -1 of dateRange)
   set dateRangeEnd's time to days - 1 -- Sets the last date's time to 23:59:59, the last second of the range.
   
   return {dateRangeStart, dateRangeEnd}
end getDateRange

-- Create a new TextEdit document with text derived from the gathered data.
on composeText(filteredData)
   tell application "TextEdit"
       -- Make a new document, with a minimal text so that we can discover the name of its font.
       set newDoc to (make new document with properties {text:" "})
       set baseFont to font of newDoc's text
       -- This ASSUMES that an equivalent bold font exists and that its name is the same as the plain font with " bold" appended.
       set boldFont to baseFont & " bold"
       -- Dummy text no longer needed.
       set newDoc's text to ""
       activate
   end tell
   
   if (filteredData is {}) then
       -- If no events have been discovered in the date range, print that fact.
       tell application "TextEdit" to make new paragraph at end of newDoc's text with data "No events found in this period." with properties {font:boldFont}
   else
       -- Otherwise print the event details.
       set currentCalendarDate to "" -- The calendar date currently being processed. (None yet.)
       repeat with i from 1 to (count filteredData)
           -- Get the data for an event from the list of filtered data.
           set {{date string:thisCalendarDate, hours:thisStartTimeH, minutes:thisStartTimeM}, thisSummary, thisCalendar} to item i of filteredData
           -- If the calendar date is different from the one we've been processing up till now, output an empty line and the new date string to TextEdit.
           if (thisCalendarDate is not currentCalendarDate) then
               tell application "TextEdit"
                   make new paragraph at end of newDoc's text with data linefeed with properties {font:baseFont}
                   make new paragraph at end of newDoc's text with data (thisCalendarDate & linefeed) with properties {font:boldFont}
               end tell
               -- Make the new date the one currently being processed.
               set currentCalendarDate to thisCalendarDate
           end if
           -- Create a 24-hour time string from the hours and minutes of the start date.
           tell (10000 + thisStartTimeH * 100 + thisStartTimeM) as text to set thisStartTime to text 2 thru 3 & ":" & text 4 thru 5
           -- Output the entry for this event to TextEdit.
           tell application "TextEdit" to make new paragraph at end of newDoc's text with data ("\"" & thisCalendar & "\" calendar: " & thisStartTime & " " & thisSummary & linefeed) with properties {font:baseFont}
       end repeat
   end if
end composeText

on main()
   set {dateRangeStart, dateRangeEnd} to getDateRange()
   say "This script will finish before you can say \"Jack …"
   set filteredData to my fetchEventsStarting:dateRangeStart ending:dateRangeEnd
   composeText(filteredData)
   say "… Robinson\"!"
end main

main()

Last edited by Nigel Garvey (2016-01-17 06:46:05 pm)


NG

Offline

 

#18 2016-01-18 07:16:26 am

Nigel Garvey
Moderator
From:: Warwickshire, England
Registered: 2002-11-20
Posts: 4940

Re: AppleScript to create list of Events over a specified date range

I've noticed this morning that Shane's adaption, as well as returning occurrences of repeating events, returns single events which are in progress during the specified period. I have a January sale noted in one of my calendars which began last Friday (before the default date range if the script's run today) and ends this coming Sunday (after it). Shane's handler includes this event in its result and the script goes on to make an entry for it in TextEdit under last Friday's date. This gives the user the impression of a bug in the script, but it should probably be regarded as a feature not catered for under the script's original specification. A really posh implementation would need to catch multi-day events and indicate them in some specified way in the text output.


NG

Offline

 

#19 2016-01-18 06:31:52 pm

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5818

Re: AppleScript to create list of Events over a specified date range

Nigel,

If you mark your sale as all-day (not technically correct, I know), you can filter it out by inserting this after the fetch events line:

Applescript:

set theEvents to filter events event list theEvents without runs all day


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/
latenightsw.com

Offline

 

#20 2016-01-19 02:48:50 pm

Nigel Garvey
Moderator
From:: Warwickshire, England
Registered: 2002-11-20
Posts: 4940

Re: AppleScript to create list of Events over a specified date range

Thanks Shane. But that of course excludes all all-day events, which may not be desired. As I said, the script needs to be able to recognise multi-day events now and to have a policy about what to do with them. Ideally too, it shouldn't display start times with all-day events.

Here's an attempt to address these points. Unfortunately, I've had to reintroduce sorting handlers — an insertion sort seeming preferable this time:

Applescript:

-- Faster version using ASObjC.
-- Requires Shane Stanley's CalendarLib library. (Use the EC version with El Capitan.)
-- <[url=http://www.macosxautomation.com/applescript/apps/Script_Libs.html]www.macosxautomation.com/applescript/apps/Script_Libs.html[/url]>.

-- Doesn't require Calendar.app to be running.
-- Includes the expression dates of repeating events.
-- Now has a policy for the handling of all-day and multi-day events.
-- Tested with CalendarLIb EC and TextEdit 1.11 (new documents defaulting to RTF) in Mac OS 10.11.2.

use script "CalendarLib EC"
use scripting additions

-- Shane's handler to get the event data using his library, modified for special handling of all-day and multi-day events.
on fetchEventsStarting:dateRangeStart ending:dateRangeEnd
   set theStore to (fetch store)
   set theCals to (fetch calendars {} cal type list {} event store theStore) -- all calendars
   set theEvents to (fetch events starting date dateRangeStart ending date dateRangeEnd searching cals theCals event store theStore)
   set theFilteredData to {}
   repeat with anEvent in theEvents
       -- Besides the start date, summary, and calendar of each event, get its end date and time zone too.
       set {event_start_date:thisStartDate, event_end_date:thisEndDate, event_time_zone:thisTimeZone, event_summary:thisSummary, calendar_name:thisCalendarName} to (event info for event anEvent)
       
       -- If a returned event starts before the date range entered by the user, its start and end dates straddle the beginning of the range. Use the range's start date in this case instead of the returned one.
       set eventAlreadyStarted to (thisStartDate comes before dateRangeStart)
       if (eventAlreadyStarted) then set thisStartDate to dateRangeStart
       -- If the event's an all-day one (no time zone), it ends at 00:00:00 the following day. Change this to 23:59:59 on the event day.
       if (thisTimeZone is missing value) then set thisEndDate to thisEndDate - 1
       -- If the event ends after the specified date range, substitute the last date in the range for the returned end date.
       if (thisEndDate comes after dateRangeEnd) then set thisEndDate to dateRangeEnd
       -- Store the finalised start date, 'already started' flag, time zone, summary, and calendar name. If a multi-day event, make separate entries for each date occupied in the range.
       repeat until (thisStartDate comes after thisEndDate)
           set end of theFilteredData to {thisStartDate, eventAlreadyStarted, thisTimeZone, thisSummary, thisCalendarName}
           set thisStartDate to thisStartDate + days
           set eventAlreadyStarted to true
       end repeat
   end repeat
   -- Since there may afterwards be additional entries for multi-day events, re-sort the entries by start date.
   sortByStartDate(theFilteredData)
   
   return theFilteredData
end fetchEventsStarting:ending:

-- Sort the filtered data by start date.
on sortByStartDate(filteredData)
   -- Comparison object for a customisable sort. Compares the first items of two passed lists.
   script byFirstListItem
       on isGreater(a, b)
           return (beginning of a > beginning of b)
       end isGreater
   end script
   
   CustomInsertionSort(filteredData, 1, -1, {comparer:byFirstListItem})
end sortByStartDate

-- Customisable insertion sort. Algorithm: unknown author. AppleScript implementation: Arthur J. Knapp and Nigel Garvey, 2003. Revised by NG, 2010.
on CustomInsertionSort(theList, l, r, customiser)
   script o
       property comparer : me
       property slave : me
       property lst : theList
       
       on isrt(l, r)
           set u to item l of o's lst
           repeat with j from (l + 1) to r
               set v to item j of o's lst
               if (comparer's isGreater(u, v)) then
                   set here to l
                   set item j of o's lst to u
                   repeat with i from (j - 2) to l by -1
                       tell item i of o's lst
                           if (comparer's isGreater(it, v)) then
                               set item (i + 1) of o's lst to it
                           else
                               set here to i + 1
                               exit repeat
                           end if
                       end tell
                   end repeat
                   set item here of o's lst to v
                   slave's rotate(here, j)
               else
                   set u to v
               end if
           end repeat
       end isrt
       
       on isGreater(a, b)
           (a > b)
       end isGreater
       
       on rotate(a, b)
       end rotate
   end script
   
   set listLen to (count theList)
   if (listLen > 1) then
       if (l < 0) then set l to listLen + l + 1
       if (r < 0) then set r to listLen + r + 1
       if (l > r) then set {l, r} to {r, l}
       
       if (customiser's class is record) then set {comparer:o's comparer, slave:o's slave} to (customiser & {comparer:o, slave:o})
       
       o's isrt(l, r)
   end if
   
   return -- nothing.
end CustomInsertionSort

-- Ask the user for the range of dates to be covered.
on getDateRange()
   set today to (current date)
   set d1 to today's short date string
   set d2 to short date string of (today + 6 * days)
   
   set dateRange to text returned of (display dialog "Enter the required date range:" default answer d1 & " - " & d2)
   set dateRangeStart to date (text from word 1 to word 3 of dateRange)
   set dateRangeEnd to date (text from word -3 to word -1 of dateRange)
   set dateRangeEnd's time to days - 1 -- Sets the last date's time to 23:59:59, the last second of the range.
   
   return {dateRangeStart, dateRangeEnd}
end getDateRange

-- Create a new TextEdit document with text derived from the gathered data.
on composeText(filteredData)
   tell application "TextEdit"
       -- Make a new document, with a minimal text so that we can discover the name of its font.
       set newDoc to (make new document with properties {text:" "})
       set baseFont to font of newDoc's text
       -- This ASSUMES that an equivalent bold font exists and that its name is the same as the plain font with " bold" appended.
       set boldFont to baseFont & " bold"
       -- Dummy text no longer needed.
       set newDoc's text to ""
       activate
   end tell
   
   if (filteredData is {}) then
       -- If no events have been discovered in the date range, print that fact.
       tell application "TextEdit" to make new paragraph at end of newDoc's text with data "No events found in this period." with properties {font:boldFont}
   else
       -- Otherwise print the event details.
       set currentCalendarDate to "" -- The calendar date currently being processed. (None yet.)
       repeat with i from 1 to (count filteredData)
           -- Get the data for an event from the list of filtered data.
           set {{date string:thisCalendarDate, hours:thisStartTimeH, minutes:thisStartTimeM}, eventAlreadyStarted, thisTimeZone, thisSummary, thisCalendar} to item i of filteredData
           -- If the calendar date is different from the one we've been processing till now, output an empty line and the new date string to TextEdit.
           if (thisCalendarDate is not currentCalendarDate) then
               tell application "TextEdit"
                   make new paragraph at end of newDoc's text with data linefeed with properties {font:baseFont}
                   make new paragraph at end of newDoc's text with data (thisCalendarDate & linefeed) with properties {font:boldFont}
               end tell
               -- Make the new date the one currently being processed.
               set currentCalendarDate to thisCalendarDate
           end if
           if (eventAlreadyStarted) then -- Continuation of multi-day event.
               set thisStartTime to " (already started)"
           else if (thisTimeZone is missing value) then -- All-day event.
               set thisStartTime to ""
           else -- Timed event.
               -- Create a 24-hour time string from the hours and minutes of the start date.
               tell (10000 + thisStartTimeH * 100 + thisStartTimeM) as text to set thisStartTime to " at " & text 2 thru 3 & ":" & text 4 thru 5
           end if
           -- Output the entry for this event to TextEdit.
           tell application "TextEdit" to make new paragraph at end of newDoc's text with data ("\"" & thisCalendar & "\" calendar: " & thisSummary & thisStartTime & linefeed) with properties {font:baseFont}
       end repeat
   end if
end composeText

on main()
   set {dateRangeStart, dateRangeEnd} to getDateRange()
   say "This script will finish before you can say \"Jack …"
   set filteredData to my fetchEventsStarting:dateRangeStart ending:dateRangeEnd
   composeText(filteredData)
   say "… Robinson\"!"
end main

main()

Last edited by Nigel Garvey (2016-01-20 05:30:50 am)


NG

Offline

 

#21 2016-01-20 12:07:29 am

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5818

Re: AppleScript to create list of Events over a specified date range

Nigel Garvey wrote:

Applescript:

       -- If a returned event starts before the date range entered by the user, it spans more than one day.


Unfortunately that's not so. Suppose on Monday morning I add a lunch event, and set it to repeat five times. If I run the fetch events part of the script for, say, Wednesday, it will include the lunch event, which I think is what is wanted. But the start date of the lunch will still be on Monday (mercifully, so will the end date).


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/
latenightsw.com

Offline

 

#22 2016-01-20 05:21:48 am

Nigel Garvey
Moderator
From:: Warwickshire, England
Registered: 2002-11-20
Posts: 4940

Re: AppleScript to create list of Events over a specified date range

That's not what I see here. If I create a lunch event on any day and set it to repeat five times (ie. on five consecutive days), your library returns data for each repeat occurring on a day covered by the script, individually dated as per the day covered. Only events (single or repeat instances) whose start and end dates straddle the start of the period are returned as having start dates preceding it. (The comment you quoted is misleadingly phrased. I'll correct it when I post this.)


NG

Offline

 

#23 2016-01-20 04:17:18 pm

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5818

Re: AppleScript to create list of Events over a specified date range

Nigel Garvey wrote:

That's not what I see here.


Ah, I didn't realise that using fetch events also mean the returned events had their start/end dates changed appropriately -- I was looking at that part in Calendar.app, which was still showing the original date.

Don't miss that sale smile


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/
latenightsw.com

Offline

 

#24 2016-01-21 03:40:17 am

Nigel Garvey
Moderator
From:: Warwickshire, England
Registered: 2002-11-20
Posts: 4940

Re: AppleScript to create list of Events over a specified date range

Shane Stanley wrote:

Don't miss that sale smile


I didn't.  wink


NG

Offline

 

#25 2016-06-14 12:15:45 pm

raulium
Member
Registered: 2016-06-14
Posts: 2

Re: AppleScript to create list of Events over a specified date range

After stumbling across this gem of a thread, I began trying to tweak Nigel's first iteration of the script as it was serving my needs rather nicely with the exception that it does not print start/end times.

Really though, I'm not sure which is easier to use (and frankly I'm a bit lost on how to implement it).  My only needs are that it saves to a text file a list of events in a given date range, with their start datetimes and end datetimes (no need for pretty formatting or entries listed under each day).

Offline

 

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)