iCal, sum hours in a calendar

I am trying to make a script that takes a month in a calendar (“Work”) and sums the total hours of events in it.

Shouldn’t be too hard, but it seems to be beyond me. This is as far as I have gotten.

The problem is that it does not count recurrence of events.

tell application “iCal”

set the source_calendar to calendar "Work"
copy my month_of((current date)) to {start_day, end_day}
set the returned_events to every event of source_calendar whose start date is greater than or equal to start_day and start date is less than or equal to the end_day

set these_events to {}
repeat with i from 1 to the count of the returned_events
	set this_item to item i of the returned_events
	if the class of this_item is event then
		set the end of these_events to this_item
	end if
end repeat

set the event_count to the count of these_events

set the event_summary to "TOTAL EVENTS: " & (the event_count as string)
display dialog event_summary buttons {"OK"} --Number of events, does not count recurrences.

end tell

on month_of(this_date)
set the target_month to month of this_date
– get beginning of month
– reset to midnight
set the temp_date to (this_date - (time of this_date))
repeat 31 times
if (the day of temp_date) is 1 then
set the start_day to the temp_date
exit repeat
else
set the temp_date to the temp_date - (1 * days)
end if
end repeat
– get end of month
– reset to midnight
set the temp_date to (this_date - (time of this_date))
repeat 31 times
if (the month of temp_date) is not the target_month then
set the end_day to (the temp_date - 1)
exit repeat
else
set the temp_date to the temp_date + (1 * days)
end if
end repeat
return {start_day, end_day}
end month_of

Hi sparrows,

iCal doesn’t create a new event for every recurrence,
the information ist stored in a property “recurrence” of the event

Here is an example subroutine to extract the parameters of recurrence,
it returns false if there is no recurrence, otherwise max. 4 parameters like this:
FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,TU,WE,TH,FR;WKST=MO
that means: every second week on monday thru friday, week starts on monday.

on get_recurr(theEvent)
	set {freq, intv, untl, wkst} to {"", "", "", ""}
	tell application "iCal" to set rec to recurrence of theEvent
	if rec is "" then return false
	set oldDelims to AppleScript's text item delimiters
	set AppleScript's text item delimiters to {";"}
	try
		set freq to text item 1 of rec -- frequency
		set intv to text item 2 of rec -- interval
		set untl to text item 3 of rec -- until
		set wkst to text item 4 of rec -- weekday of first day in week
	end try
	set AppleScript's text item delimiters to oldDelims
	return {freq, intv, untl, wkst}
end get_recurr

The subroutine to calculate first and last day of the month can be much faster
using date math of AppleScript:

on month_of(this_date)
	set start_date to date (short date string of (current date)) -- midnight of current date
	set day of start_date to 1 -- midnight of first day of current month
	copy start_date to end_date
	set month of end_date to ((month of end_date as integer) + 1) -- midnight of first day of next month
	set end_date to end_date - 1 -- 23:59:59 of last day of current month
	return {start_date, end_date}
end month_of

Much faster still, of course:

on month_of(this_date)
	copy this_date to start_date
	tell start_date to set {day, time} to {1, 0}
	copy start_date to end_date
	tell end_date to set {day, day} to {32, 1}
	
	return {start_date, end_date - 1}
end month_of

And even a little faster:

on month_of(this_date)
	tell this_date to set start_date to it - (its day) * days + days - (its time)
	tell start_date to tell it + 32 * days to set end_date to it - (its day) * days + 86399
	
	return {start_date, end_date}
end month_of

Or an in-between version for kai fans: :wink:

on month_of(this_date)
	tell this_date to tell it + (32 - day) * days to tell it - day * days - time + 86399 to return {it - day * days + 1, it}
end month_of

:lol: I guessed, you guys come up easily with an one-liner

:lol: Nice one, Mr G.

This shaves off another sliver or two here:


on |month range| for d
	tell d + 2764800 to tell it - (time + day * days) to {it - days * (day - 1), it + 86399}
end |month range|

|month range| for current date (* or some other date *)


As already demonstrated with this kind of algorithm, a one-liner is unlikely to beat the performance of 2 or 3 similarly optimised lines (even though it can get pretty close). So, for Nigel fans, here’s another variation: :wink:


on |month range| for d
	tell d to set d to it - (time + (day - 1) * days)
	tell d + 2851199 to {d, it - day * days}
end |month range|

|month range| for current date (* or some other date *)


Mssrs G & K;

Truly opaque. :rolleyes:
Interesting that these simple constructs get centuries divisible by 400 correct too. :o