<link rel="stylesheet" id="astra-theme-css-css" href="https://pmo-it.de/wp-content/themes/astra/assets/css/minified/style.min.css?ver=3.7.9" media="all">

Apple Kalender mit Java auslesen

Apple Kalender mit Java auslesen.

Motivation

Apple Kalender-Einträge sollen synchron auf meinem Java-Dashboard sowie auf sämtlichen anderen Geräten im Haus gepflegt werden.
Aktuell bietet Apple jedoch nur die Möglichkeit über eine ICS-Datei Kalenderdaten auszulesen. In diesem Blogeintrag wird eine selbstgeschriebene Bibliothek vorgestellt, mit deren Hilfe Informationen aus der Datei zur einfachen Weiterverarbeitung in einer Datenstruktur abgelegt wird.

Benötigte Komponenten

  1. Apple-ID und vorhandener Kalender
  2. Java ab Version 8

Kalender freigeben

Bevor ein Zugriff von außen auf einen Kalender möglich ist, muss dieser zuerst freigegeben werden. Dies ist über die iCloud (https://www.icloud.com) möglich. Anschließend auf Kalender klicken. Alle vorhandenen Kalender werden nun auf der linken Seite gelistet. Über das „Funksymbol“ kann der Kalender nun freigegeben werden.

Apple Kalender freigeben

Kalender einlesen

Benutzung der AppleCalendar- Class

 AppleCalendar ct = new AppleCalendar("caldav-URL hier eintragen");
 HashMap<String, CalendarObject> xx = ct.readCalendarUrl();

Im Constructor muss zuerst die vorher freigegebene Kalender URL eingetragen werden („webcall“ durch https ersetzen).
Mit der read-Methode lässt sich alles in eine HashMap einlesen.

AppleCalendar- Class

Nachfolgend eine Beschreibung der Calendar-Class. Im ersten Schritt wird alles eingelesen und in ein Calendar-Object gelesen.  Die gelieferten Information aus dem Apple Kalender sehen in etwa so aus:

BEGIN:VEVENT
CREATED:20171122T061139Z
DTEND;VALUE=DATE:20171128
DTSTAMP:20210207T114913Z
DTSTART;VALUE=DATE:20171127
LAST-MODIFIED:20210207T114904Z
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:220BBBAE-EAE8-4470-939
 B-D39704B5490A
RRULE:FREQ=YEARLY;UNTIL=20201127
SEQUENCE:0
SUMMARY:Test Geburtstag
UID:08E483F0-9CA7-415E-B3FE-85C728DAE147
END:VEVENT

Aus diesen Metadaten lassen sich sämmtliche Informationen über den Termin rauslesen. RRULE zeigt z.b. die Häufigkeit oder das Endedatum des Termins. Diese Daten müssen jetzt in dem Datenobjekt abgelegt werden. Dies geschieht mit der buildHashmap-Methode.

private CalendarObject buildHashMap(HashMap<String, CalendarObject> calendarObjects,
    CalendarObject currentCalendarObject, String currentLine) {
    if (currentLine.startsWith("BEGIN:")) {
        currentCalendarObject = new CalendarObject();
    } else if (currentLine.startsWith("END:")) {
        writeToHashmap(calendarObjects, currentCalendarObject);
    } else {
        handleCreated(currentCalendarObject, currentLine);
        handleDtend(currentCalendarObject, currentLine);
        handleDtstamp(currentCalendarObject, currentLine);
        handleDtstart(currentCalendarObject, currentLine);
        handleSummary(currentCalendarObject, currentLine);
        handleRrule(currentCalendarObject, currentLine);
        handleUid(currentCalendarObject, currentLine);
    }
    return currentCalendarObject;
}

private void writeToHashmap(HashMap<String, CalendarObject> calendarObjects, CalendarObject currentCalendarObject) {
    if (currentCalendarObject.getRrule() == null || ! currentCalendarObject.getRrule().contains("UNTIL")) {
        calendarObjects.put(currentCalendarObject.getUid(), currentCalendarObject);
    }
}

private void handleUid(CalendarObject currentCalendarObject, String currentLine) {
    String searchString = "UID:";
    if (currentLine.startsWith(searchString)) {
        currentCalendarObject.setUid(substringSearchStringLength(currentLine, searchString));
    }
}

private void handleRrule(CalendarObject currentCalendarObject, String currentLine) {
    String searchString = "RRULE:";
    if (currentLine.startsWith(searchString)) {
        currentCalendarObject.setRrule(substringSearchStringLength(currentLine, searchString));
    }
}

Die untersten Methoden wurden in diesem Tutorial bewusst weggelassen, da sie gleichartig zu handleRrule sind. Das Einlesen startet mit dem „BEGIN:“-Tag. Hiermit wird ein neues Calendar-Object angelegt und alle Informationen des aktuellen Kalendereintrags dort abgespeichert. Sobald „END:“ gelesen wird, ist der Kalendereintrag zu ende und es erfolgt das Schreiben in die Hashmap.

Die readCalendarUrl-Methode verwendet die URL und baut einen Stream auf. Über einen bufferedReader werden die Zeilen nacheinander eingelesen und zur buildHashMap-Methode weitergegeben.

public HashMap<String, CalendarObject> readCalendarUrl() throws MalformedURLException, IOException {
    HashMap<String, CalendarObject> calendarObjects = new HashMap<String, CalendarObject>();
    CalendarObject currentCalendarObject = null;
    URL calendarURL = new URL(url);
    URLConnection connection = calendarURL.openConnection();
    BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
    while (reader.ready()) {
        String currentLine = reader.readLine();
        currentCalendarObject = buildHashMap(calendarObjects, currentCalendarObject, currentLine);
    }
    return calendarObjects;
}

AppleCalendarOperations- Class

Nachdem nun sämmtliche Informationen in einer Hashmap vorliegen, lassen sich verschiedene Operationen darauf ausführen. Beispielsweise lässt sich der Frage nachgehen „Wann hat xy Geburtstag?“

public CalendarObject searchForSummary(String searchString) {
    for (Map.Entry<String, CalendarObject> e : appleCalendarData.entrySet()) {
        String currentSummary = appleCalendarData.get(e.getKey()).getSummary();
        if (currentSummary != null && currentSummary.trim().equals(searchString)) {
            return appleCalendarData.get(e.getKey());
        }
    }
    return null;
}

Da die Class eine Hashmap entgegen nimmt, könnte der vollständige Anwendungscode in etwa so aussehen:

AppleCalendar ct = new AppleCalendar(
    "https://KALENDERURL");
HashMap<String, CalendarObject> xx = ct.readCalendarUrl();
AppleCalendarOperations co = new AppleCalendarOperations(xx);
 CalendarObject result = co.searchForSummary("test");
result.getDtstart();

Dtstart liefert das Tagesdatum des angelegten Termines, in unserem Fall also den Geburtstag.

Fazit

Das Potential dieser Class ist bei weitem nicht ausgeschöpft, es liefert aktuell nur Basisfunktionen, um Personen zu suchen oder abzufragen, wer an einem bestimmten Datum Geburstag hat. Mit Sicherheit gibt es noch deutlich mehr Anwendungsfälle. Diese lassen sich problemlos in der  Operations-Class hinzufügen. Auch das Anlegen eines Termines ist noch nicht berücksichtigt. Nach und nach werden je nach Anwendungsfall weitere Funktionen hinzukommen.

Den ganzen Code gibts auf Github schaut doch mal rein.