in iCal erst ab einem bestimmten Datum suchen

T

Thomas_xp

Aktives Mitglied
Thread Starter
Dabei seit
05.04.2006
Beiträge
632
Reaktionspunkte
13
Hi Leute,

ich versuche gerade ein Script zu schreiben, welches in einem Kalender in iCal Events mit bestimmten Kriterien sucht. Ein Kriterium ist dabei das Datum, nämlich ein Monat / der letzte Monat. Dummer Weise geht bei Schleifen das script beim ersten Event im Kalender los bis zum Ende und macht seine Fallunterscheidungen auf dem Weg. Dies ist natürlich sehr ineffizient, da man zB. einen 3 Jahre alten Kalender durchgeht, obwohl man nur einen Monat braucht.

Hat jemand eine Idee?

Was ich momentan hab, funktioniert wunderbar, brauch bei meinem Kalender mit ca. 900 Einträgen ca. 32 Sekunden. Das ist ein Kalender von ca. 18 Monaten. Nicht wundern, ich lasse es der Einfachheit halber in unterschiedliche Listen eintragen, weil ich diese dann in eine Numbers-Tabelle eintrage.

Der "kritische" Teil...
PHP:
tell application "Calendar"
	tell calendar "Calendar" -- your calendar here
		set theEvents to every event
		repeat with current_event in theEvents
			if summary of current_event contains "AE" then
				if start date of current_event is greater than or equal to a_date then
		...

HTML:
set a_date to (current date) - (60 * 60 * 24 * 33)
set theList to **}
set theYears to **}
set theMonths to **}
set theDays to **}
set AEs to **}
tell application "Calendar"
	tell calendar "Calendar" -- your calendar here
		set theEvents to every event
		repeat with current_event in theEvents
			if summary of current_event contains "AE" then
				if start date of current_event is greater than or equal to a_date then
					
					set theList to theList & summary of current_event -- Terminname in Liste eintragen
					set theDate to start date of current_event -- Anfangsdatum mit Zeit des Termins
					set theYears to theYears & year of theDate -- Jahr in Liste eintragen
					-- Monat als Nummer ermittlen und eintragen ## Anfang
					-- super mega Voodoo!
					copy theDate to b
					set the month of b to January
					set monthNum to (1 + (theDate - b + 1314864) div 2629728)
					set theMonths to theMonths & monthNum
					-- Monat als Nummer ermittlen und eintragen ## Ende
					set theDays to theDays & day of theDate -- Tag in Liste eintragen
					set theDateEnd to end date of current_event
					set AEs to AEs & (theDateEnd - theDate) / (60 * 60 / 4) -- AEs über Termin Anfang/Ende ermitteln un eintragen
					
				end if
			end if
		end repeat
	end tell
end tell
 
Probier mal

tell app "Calendar" to get every event of calendar "Calendar" whose start date > date "01/01/2013"
 
Hallo,

set theEvents to every event whose start date is greater than or equal to a_date
und weiter dann natürlich ohne
if start date of current_event is greater than or equal to a_date then

Gruß
 
»whose« nicht wirklich eine Option

Hatte parallel zu dieser Diskussion ein paar Mails mit Thomas ausgetauscht.

Es ist schon abenteuerlich, was Calendar bei einer whose-Klausel treibt.

Meine Testsituation: In einen Kalender namens »Test« 700 Events ab heute über 700 Tage eingetragen. Rechner: Mac mini Intel core i5, 2,3 GHz, 8 GB RAM

Code:
tell app "Calendar"
	tell calendar "Test"
		set l to every event
	end tell
end tell
braucht eine gute Sekunde.

Code:
tell app "Calendar"
	tell calendar "Test"
		set l to every event whose start date > date "1.1.2013"
	end tell
end tell

braucht 2160 Sekunden!!! (In Worten: sechsunddreißig Minuten!!!)

(Je über osascript getestet, weil der Script Editor bei solchen Jobs unbenutzbar wird.)

Da scheint die Laufzeit exponentiell von der Datenmenge abzuhängen.

Die gleichen Daten über ein luxuriöses Script (ein AppleScript, das PHP über »do shell script benutzt) direkt aus der SQLite-Cache-Datei von Calendar abgefragt (dabei außer Zeitraum noch auf Namensteile gefiltert) und anschließend aufwändig aufbereitet (Sekunden zu Datum, dann Tag, Monat, Jahr herausziehen etc.): rund 1 Sekunde!

»whose« mit Calendar scheint nicht wirklich eine Option zu sein, wenn der Kalender wirklich benutzt wird.

Gruß, Jürgen
 
  • Gefällt mir
Reaktionen: Pill
Hallo,

könntest du deinen Ansatz bitte posten? Das wäre sicher auch für andere interessant.

Der schnellste Weg in AppleScript ist in der Regel alle einmal in einer Liste zu speichern und dann mit repeat with i from 1 to count ... durchzugehen:

Code:
[B]tell[/B][COLOR=#0433FF][FONT=Verdana][I]application[/I][/FONT][/COLOR][FONT=Verdana] "Calendar"
[/FONT][FONT=Verdana]    [B]tell[/B] [COLOR=#0433ff][I]calendar[/I][/COLOR] "Test"[/FONT]
[FONT=Verdana]        [B]set[/B] [COLOR=#4f8f00]allEvents[/COLOR] [B]to[/B] [COLOR=#812fdc]uid[/COLOR] [B]of[/B] [B]every[/B] [COLOR=#0433ff][I]event[/I][/COLOR][/FONT]
[FONT=Verdana]        [B]set[/B] [COLOR=#4f8f00]allDates[/COLOR] [B]to[/B] [COLOR=#812fdc]start date[/COLOR] [B]of[/B] [B]every[/B] [COLOR=#0433ff][I]event[/I][/COLOR][/FONT]
[FONT=Verdana]        [B]set[/B] [COLOR=#4f8f00]theEvents[/COLOR] [B]to[/B] **}[/FONT]
[FONT=Verdana]        [B]repeat[/B] [B]with[/B] [COLOR=#4f8f00]i[/COLOR] [B]from[/B] 1 [B]to[/B] [COLOR=#0433ff][B]count[/B][/COLOR] [B]of[/B] [COLOR=#4f8f00]allEvents[/COLOR][/FONT]
[FONT=Verdana]            [B]if[/B] [COLOR=#0433ff][I]item[/I][/COLOR] [COLOR=#4f8f00]i[/COLOR] [B]of[/B] [COLOR=#4f8f00]allDates[/COLOR] > [COLOR=#0433ff][I]date[/I][/COLOR] "Dienstag, 1. Januar 2013 00:00:00" [B]then[/B][/FONT]
[FONT=Verdana]                [B]set[/B] [B]end[/B] [B]of[/B] [COLOR=#4f8f00]theEvents[/COLOR] [B]to[/B] [COLOR=#0433ff][I]event[/I][/COLOR] [COLOR=#5d3292]id[/COLOR] ([COLOR=#0433ff][I]item[/I][/COLOR] [COLOR=#4f8f00]i[/COLOR] [B]of[/B] [COLOR=#4f8f00]allEvents[/COLOR])[/FONT]
[FONT=Verdana]            [B]end[/B] [B]if[/B][/FONT]
[FONT=Verdana]        [B]end[/B] [B]repeat[/B][/FONT]
[COLOR=#4F8F00][FONT=Verdana][COLOR=#000000]        [B]get[/B] [/COLOR]theEvents[/FONT][/COLOR]
[FONT=Verdana]    [B]end[/B] [B]tell[/B][/FONT]
[B]end[/B][B]tell[/B]

Das dürfte aber natürlich trotzdem langsamer sein, als direkt die Datenbank zu durchsuchen.
 
Zuletzt bearbeitet:
Zwei Ansätze

Klar kann ich den Ansatz posten. Achtung: Doppelte geschweifte Klammern »{}« werden im CODE-Teil zu »**}«. Bitte suchen und ersetzen.

Hier zuerst die die schnellste Methode, die ich mit reinem AppleScript gefunden habe:

Code:
property start_at : date "Donnerstag, 1. August 2013 00:00:00"
set start_date_list to **}
property cal_name : "Test"
set s to current date
tell application "Calendar"
    tell calendar cal_name
        set l to (properties of every event)
    end tell
    repeat with x from 1 to count l
        set curr to item x of l
        if (start date of curr > start_at) and (summary of curr starts with "Eintr") then
            copy start date of curr to end of start_date_list
        end if
    end repeat
end tell
set e to current date

{e - s, start_date_list}

Wieder die 700 Einträge, die ab dem 30.1.2013 für die aufeinanderfolgenden Tage eingetragen sind und je »Eintrag 1«, »Eintrag 2« … »Eintrag 700« heißen. Der »starts with«-Filter trifft also immer, so hatte ich zum Test in SQLite auch gefiltert.

Bei mir braucht diese Variante gut 3 Sekunden. Sich gleich alle properties abzuholen dauert anscheinend nicht länger, als sich nur eine davon zu holen. Die Variante von Pill, die einmal die UID und einmal das start date abholt, braucht bei mir ziemlich genau doppels so lange.

(Wenn ihr so eine Lösung baut: Ein Ausdruck wie »start date of curr« funktioniert nicht außerhalb des »tell app "Calendar"«. Da ist die Behandlung von Namensräumen in AS schon ziemlich doof.)

======= schnipp ======

Hier der Weg über die Datenbank:

In »~/Library/Calendars« liegt die Date »Calendar Cache«. Das ist eine SQLite-Datenbank.

Benutzt ein Tool wie »SQLite Database Browser«, um hineinzusehen.

Für mich hier wichtig: Tabelle »ZICSELEMENT« enthält alle Einträge aus allen Kalendern. »ZNODE« ist die Tabelle mit den Kalendern. Die Zahl in Spalte »ZCALENDAR« von »ZICSELEMENT« ist der foreign key auf »ZNODE«, Spalte »Z_PK«.

»ZICSELEMENT.ZSTARTDATE« ist das start date, gemessen in Sekunden ab dem 1.1.2001, soweit ich sehen kann, Greenwich-Zeit (UTC).

Für den Zugriff habe ich ein AppleScript/PHP-Tool benutzt, die ich mal vor zwei Jahren gebastelt hatte:

Das Bundle »sqlite_objects_pdo_devel.scptd« muss an der richtigen Stelle liegen, damit der folgende Code funktioniert, nämlich in »/Library/Application Support/js_aux_scripts/« (also im Library-Ordner für alle Benutzer). Der PHP-Code liegt im Bundle.

Hier der Beispielcode, der bei mir um 1 Sekunde braucht (es gibt noch zusätzlich ein Endedatum, der Unterschied dürfte nicht groß sein):

Code:
property cal_name : "Test"
property search_pattern : "Eintr%" -- entries in calendar are found, matching that pattern
property table_name : "ZICSELEMENT" -- name of table with calendar entries
property cal_table : "ZNODE"

set start_date to date "Donnerstag, 1. August 2013 00:00:00"
set end_date to date "Mittwoch, 1. Januar 2020 00:00:00"

-- convert dates to seconds as ussed by iCal database
set sd_secs to start_date - (date "Montag, 1. Januar 2001 00:00:00")
set ed_secs to end_date - (date "Montag, 1. Januar 2001 00:00:00")
set s to current date
-- find calendar id in iCal
tell application "Calendar"
    set cal_uid to uid of calendar cal_name
end tell

-- load library for SQLite access
-- The result is an AS object in lib
property app_support : (path to application support) as text
property lib_path : app_support & "js_aux_scripts:sqlite_objects_pdo_devel.scptd"
set lib to load script file lib_path

-- path to SQLite database
set cache_path to (path to library folder from user domain) as text
property db_local_path : "Calendars:Calendar Cache"
set db_path to cache_path & db_local_path

-- create link object to database
-- The result is an AS object in dbh
set dbh to new_link(db_path) of lib
set raise_errors of dbh to true -- if false, errors are silently reported in the result

-- find internal number of calendar in database
-- ZCALENDAR in CICSELEMENT is foreign key pointing to Z_PK in ZNODE.
-- The UUID is in ZUID of ZNODE
set query to "SELECT Z_PK FROM " & cal_table & " WHERE ZUID = ?"
set temp to (do_select(query, {cal_uid}) of dbh)
set cal_num to item 1 of item 1 of table of temp

-- setup query in calendar items:
--    Date must be bwetween start date and end date,
--    Title must match search pattern
--    Entry must be in the right calendar
set query to "SELECT ZSTARTDATE, ZENDDATE, ZTITLE FROM " & table_name & " WHERE (ZSTARTDATE BETWEEN ? AND ?) AND (ZTITLE LIKE ?) AND (ZCALENDAR = ?)"


--tell dbh to do_select(query, {sd_secs, ed_secs, "AE%"})
set result_record to do_select(query, {sd_secs as text, ed_secs as text, search_pattern, cal_num}) of dbh
set result_table to table of result_record

-- the following code is based on Thomas’ code
set n to 1
set start_dates to **}
set end_dates to **}
set summaries to **}
repeat with n from 1 to count of result_table
    set {start_date, end_date, summary} to item n of result_table
    copy summary to end of summaries
    copy ((date "Montag, 1. Januar 2001 00:00:00") + (start_date as number)) to end of start_dates
    copy ((date "Montag, 1. Januar 2001 00:00:00") + (end_date as number)) to end of end_dates
end repeat
set e to current date

-- next line just for checking the result
{e - s, {start_dates, end_dates, summaries}}

Ich habe über zwei getrennte Abfragen statt über ein JOIN gearbeitet, weil man das nach meinem Geschmack besser übersehen kann, wenn Änderungen anstehen.

============= schnipp ====================

Ich kann auch nur noch einmal betonen (wie Pill): Apple kann solche Datenbank-Strukturen jederzeit ändern. Unter OS X 10.6 habe ich noch nachgeschaut: Die Tabellen heißen anders.

Man riskiert also bei dem Weg, dass man nach dem nächsten Systemupdate umbauen muss. (Und man hat vorher keine Ahnung, wie groß der Umbau ist. Apple könnte sich ja sogar entscheiden, sich ganz von SQLite zu verabschieden. Allerdings könnte Apple sich auch von AppleScript verabschieden. Keine Ahnung, was wahrscheinlicher ist.)

Je mehr Einträge es im Kalender gibt und je enger euer Auswahl-Kriterium ist, desto größer wird der Performance-Vorteil beim Weg über die Datenbank.

============= schnipp ====================

Für Leute, die testen wollen, zum Schluss noch der Code für die 700 Testeinträge:

Code:
property cal_name : "Test"

tell application "Calendar"
    tell calendar cal_name
        set dts to current date
        set time of dts to 12 * hours
        copy dts to dte
        set time of dte to 14 * hours
        set num_count to 700
        repeat with x from 1 to num_count
            set sum to "Eintrag " & x
            make new event with properties {start date:dts, end date:dte, summary:sum}
            set {dts, dte} to {dts + days, dte + days}
        end repeat
    end tell
end tell


Gruß, Jürgen
 
  • Gefällt mir
Reaktionen: Pill
Zurück
Oben Unten