Lies in den Artikel rein und unten bekommst Du ein unschlagbares Angebot!
Im ersten Teil unserer Artikelserie haben wir grundlegende Funktionen vorgestellt, die es ermöglichen, Termine zwischen Outlook und Google Calendar zu synchronisieren. Jetzt gehen wir einen Schritt weiter und optimieren diese Funktionen an, um die Aktualisierung von Terminen effektiver zu gestalten. Zum Beispiel ist es von Vorteil, die EventID eines im Google Calendar erstellten Termins im entsprechenden Outlook-Termin zu speichern, um Änderungen zwischen beiden Kalendern synchron zu halten. Außerdem fügen wir neue Funktionen zur Aktualisierung von Kalendereinträgen und zum Abrufen von Terminen anhand ihrer EventID hinzu. Darüber hinaus diskutieren wir Herausforderungen, auf die wir gestoßen sind, und präsentieren Lösungen sowie Erweiterungen, die wir implementiert haben.
Anpassungen der bestehenden Funktionen
Die Funktionen aus dem ersten Teil dieser Artikelreihe, Google Calendar per Rest-API programmieren (www.vbentwickler.de/410), haben wir noch ein wenig angepasst, damit wir besser für Exporte und anschließend notwendige Aktualisieren vorbereitet sind.
Wenn wir also einen Termin aus Outlook zum Google Calendar exportieren wollen, ist es sinnvoll, dass wir die EventID für den im Google Calendar angelegten Termin im Outlook-Termin zu speichern. Auf diese Weise können wir Änderungen an diesem Termin in Outlook auf den Termin im Google Calendar übertragen.
Und auch wenn der Termin in Outlook gelöscht wird, wollen wir dies gegebenenfalls im Google Calendar abbilden – also passen wir die Funktion, mit der wir einen Termin im Google Calendar anlegen, so an, dass diese die EventID des neu angelegten Termins zurückgibt.
Diese schreiben wir in der Funktion InsertEvent in den Parameter strEventID (siehe Listing 1). Außerdem haben wir noch weitere Parameter hinzugefügt, mit denen man die Zeitzone für das Start- und das Enddatum übergeben kann. Diese beiden Parameter heißen strStartTimeZone und strEndTimeZone und erhalten den Standardwert Europe/Berlin. Und mit dem Parameter bolAllDayEvent können wir angeben, ob es sich bei dem anzulegenden Termin um einen Ganztagestermin handelt.
Public Function InsertEvent(strToken As String, strCalendarID As String, strSummary As String, strDescription _ As String, datStart As Date, datEnd As Date, Optional bolAllDayEvent As Boolean, Optional strStartTimeZone _ As String = "Europe/Berlin", Optional strEndTimeZone As String = "Europe/Berlin", Optional intColor _ As eColor = colLavendel, Optional strResponse As String, Optional ByRef strEventID As String) As Boolean Dim strURL As String, strMethod As String, strAuthorization As String, objJSON As Object Dim strJSON As String, dictMain As Dictionary, dictStart As Dictionary, dictEnd As Dictionary Set dictMain = New Dictionary Set dictStart = New Dictionary Set dictEnd = New Dictionary dictMain.Add "start", dictStart dictMain.Add "end", dictEnd If Not bolAllDayEvent Then dictStart.Add "dateTime", Format(datStart, "yyyy-mm-ddThh:nn:ss") dictEnd.Add "dateTime", Format(datEnd, "yyyy-mm-ddThh:nn:ss") Else datStart = Int(datStart) datEnd = datStart + 1 dictStart.Add "dateTime", Format(datStart, "yyyy-mm-ddThh:nn:ss") dictEnd.Add "dateTime", Format(datEnd, "yyyy-mm-ddThh:nn:ss") End If dictStart.Add "timeZone", strStartTimeZone dictEnd.Add "timeZone", strEndTimeZone dictMain.Add "summary", strSummary dictMain.Add "description", strDescription dictMain.Add "colorId", intColor strJSON = ConvertToJson(dictMain) strURL = cStrURLBase & "/calendars/" & strCalendarID & "/events" strMethod = "POST" strAuthorization = "Bearer " & strToken If RefreshAccessToken("InsertEvent") Then Select Case HTTPRequest(strURL, strAuthorization, strMethod, , strJSON, strResponse) Case 200 InsertEvent = True Set objJSON = ParseJson(strResponse) strEventID = objJSON.Item("id") Case Else MsgBox strResponse End Select Else MsgBox "Access-Token ungültig oder nicht aktualisierbar." End If End Function
Listing 1: Neue Version der Funktion InsertEvent
Für den Parameter intColor haben wir als Standardwert den Wert colLavendel vorgemerkt.
Die Zeitzonen übergeben wir in der Funktion jeweils für das Element timeZone.
Für die Verarbeitung des Parameters bolAllDayEvent mussten wir eine If…Then-Bedingung zur Funktion hinzufügen. Wenn bolAllDayEvent den Wert False enthält, werden die Werte für Start- und Enddatum wie zuvor hinzugefügt. Im Else-Teil der Bedingung behandeln wir den Fall eines ganztägigen Termins.
Hier ermitteln wir als Startzeit das Datum der mit datStart übergebenen Startzeit und stellen die Uhrzeit auf 00:00 ein, indem wir die Nachkommastellen von datStart mit der Int-Funktion entfernen.
Als Enddatum legen wir in datEnd das Datum des folgenden Tages fest – wieder mit der Uhrzeit 00:00. Die Rest-API von Google Calendar sieht keine spezielle Eigenschaft für einen ganztätigen Termin vor, sodass wir diese Notation verwenden.
In der Benutzeroberfläche sehen wir anschließend allerdings einen Ganztagestermin (siehe Bild 1). Wenn die Funktion HTTPRequest den Wert True zurückliefert, lesen wir aus dem mit strResponse gelieferten JSON-Dokument den Wert id aus und tragen diesen für den Parameter strEventID ein. Diesen kann man dann nach dem Aufruf verarbeiten, wenn dies nötig ist.
Bild 1: Ein ganztägiger Termin im Google Calendar
Rest-API-Funktion zum Abrufen eines Termins per EventID
Bei den im ersten Teil dieses Artikels beschriebenen Funktionen können wir die kompletten Informationen zu einem Termin abrufen. In den Beispielen speichern wir diese Daten in einer Access-Tabelle, darunter auch die EventID, ein eindeutiges Merkmal eines Google Calendar-Termins. Damit können wir wiederum gezielt erneut auf einen solchen Termin zugreifen – beispielsweise um den aktuellen Stand abzufragen, denn ein Termin kann ja auch durch den Benutzer in Google geändert werden.
Dies erledigen wir mit der Funktion GetEventByID (siehe Listing 2). Diese erwartet folgende Parameter:
Public Function GetEventByID(strToken As String, strCalendarID As String, strEventID As String, Optional strJSON _ As String) As Boolean Dim strURL As String Dim strMethod As String Dim strResponse As String Dim strAuthorization As String strURL = cStrURLBase & "/calendars/" & strCalendarID & "/events/" & strEventID strMethod = "GET" strAuthorization = "Bearer " & strToken If RefreshAccessToken("GetEventByID") Then Select Case HTTPRequest(strURL, strAuthorization, strMethod, , , strResponse) Case 200 strJSON = strResponse GetEventByID = True Case Else GetEventByID = False End Select Else MsgBox "Access-Token ungültig oder nicht aktualisierbar." End If End Function
Listing 2: Funktion zum Einlesen eines Termins von Google
- strToken: Access-Token für den Zugriff auf den Google Calendar
- strCalendarID: Bezeichnung des Kalenders, in der Regel die Google-E-Mail-Adresse
- strEventID: ID des zu ermittelnden Termins
- strJSON: Parameter, der das Ergebnis der Abfrage entgegennimmt
Die Funktion stellt die URL für den Zugriff auf die Rest-API zusammen, die beispielsweise so aussieht:
https://www.googleapis.com/calendar/v3/calendars/andre.minhorst@googlemail.com/events/36kis3n1kt1sc5ch82a54k4u5k
Als Methode verwenden wir GET, und mit der Variablen strAuthorization übergeben wir das Token an die Funktion, die den eigentlichen Request ausführt. Zuvor rufen wir noch die Prozedur CheckToken aus, welche die Gültigkeit des Tokens prüft. Ist dieses ungültig, erfolgt erst gar kein Zugriff auf die Rest-API und es erscheint eine Meldung. Danach erfolgt der Aufruf der Funktion HTTPRequest. Das Ergebnis ist ein Status. Lautet dieser 200, war der Zugriff erfolgreich und wir geben die Antwort über den Rückgabeparameter strJSON zurück. Außerdem stellen wir den Rückgabewert der Funktion auf True ein.
Diese Funktion rufen wir beispielsweise wie folgt auf:
Public Sub Test_GetEventByID() Dim strToken As String Dim strCalendarID As String Dim strEventID As String Dim strJSON As String Dim objJSON As Object strToken = GetAppSetting("AccessToken") strCalendarID = GetAppSetting("CalendarID") strEventID = "36kis3n1kt1sc5ch82a54k4u5k" If GetEventByID(strToken, strCalendarID, strEventID, _ strJSON) = True Then Set objJSON = ParseJson(strJSON) Debug.Print GetJSONDOM(strJSON, True) End If End Sub
Die beiden Parameter strToken und strCalendarID lesen wir aus der Registry aus, die EventID haben wir hier fest angegeben. War der Aufruf erfolgreich, lassen wir uns das Zugriffsmodell für das JSON-Dokument im Direktbereich ausgeben. Hier können wir uns dann die Elemente heraussuchen, die wir in der aufrufenden Prozedur weiterverarbeiten wollen (siehe Bild 2).
Bild 2: Ausdrücke zum Auslesen von Termineigenschaften
Rest-API-Funktion zum Aktualisieren eines Kalendereintrags
Wenn wir einen vorhandenen Termin im Google Calendar aktualisieren wollen, haben wir zwei Möglichkeiten. Die erste ist die Patch-Methode. Damit können wir gezielt einzelne Eigenschaften anpassen. Mehr dazu liest Du hier:
https://developers.google.com/calendar/api/v3/reference/events/patch?hl=en
Bei dieser Methode werden nur die Eigenschaften des Termins aktualisiert, die wir im Aufruf der Rest-API an Google übergeben. Die übrigen Eigenschaftswerte werden beibehalten. Die Alternative ist das Update. Damit aktualisieren wir den kompletten Termin:
https://developers.google.com/calendar/api/v3/reference/events/update?hl=en
Damit werden alle Eigenschaften aktualisiert. Wir machen es uns einfach und aktualisieren einfach den kompletten Termin. Die dazu verwendete Funktion UpdateEvent ist der Funktion InsertEvent sehr ähnlich, daher haben wir in nur die Teile abgebildet, wo sich Unterschiede befinden.
Neben dem Funktionsnamen ist dies vor allem die Methode des Aufrufs, hier PUT statt POST. Die URL wird um die EventID aus der Variablen strEventID ergänzt. Außerdem enthält die Zeile, die dem Rückgabewert den Wert True zuweist, den Namen der Funktion (siehe Listing 3).
Public Function UpdateEvent(strToken As String, strCalendarID As String, strSummary As String, strDescription _ As String, datStart As Date, datEnd As Date, bolAllDayEvent, Optional strStartTimeZone _ As String = "Europe/Berlin", Optional strEndTimeZone As String = "Europe/Berlin", Optional _ intColor As eColor = colLavendel, Optional strResponse As String, Optional ByRef strEventID As String) _ As Boolean ''... Identisch mit InsertEvent strURL = cStrURLBase & "/calendars/" & strCalendarID & "/events/" & strEventID strMethod = "PUT" strAuthorization = "Bearer " & strToken If RefreshAccessToken("UpdateEvent") Then Select Case HTTPRequest(strURL, strAuthorization, strMethod, , strJSON, strResponse) Case 200 UpdateEvent = True Set objJSON = ParseJson(strResponse) strEventID = objJSON.Item("id") Case 404 MsgBox "Der Termin mit der EventID ''" & strEventID & "'' konnte nicht gefunden werden." Case Else MsgBox strResponse End Select Else MsgBox "Access-Token ungültig oder nicht aktualisierbar." End If End Function
Listing 3: Funktion zum Aktualisieren eines Termins
Schließlich haben wir die Select Case-Bedingung, mit der wir den Rückgabewert der Funktion HTTPRequest abfragen, um einen Zweig für den Wert 404 erweitert. Dieser wird angesteuert, wenn wir eine EventID angeben, die es nicht gibt, und gibt eine entsprechende Meldung aus.
“Löschen” von Terminen im Google Calender
Um die Funktion UpdateEvent auszuprobieren, haben wir einen Termin mit der Funktion InsertEvent im Google Calendar angelegt. Die Funktion hat daraufhin die EventID zurückgeliefert:
Public Sub Test_InsertEvent() Dim strEventID As String If InsertEvent(GetAppSetting("AccessToken"), _ "andre.minhorst@googlemail.com", "Summary", _ "Description", Now, Now + 1 / 24, , , , , , _ strEventID) = True Then Debug.Print "Termin ''" & strEventID & "'' angelegt." End If End Sub
Danach haben wir diese EventID als letzten Parameter eines Aufrufs der Funktion UpdateEvent genutzt und konnten damit den Termin in Google aktualisieren:
Public Sub Test_UpdateEvent() Dim strEventID As String strEventID = "leka4d24rapsjiu77fqikvturk" If UpdateEvent(GetAppSetting("AccessToken"), _ "andre.minhorst@googlemail.com", _ "Neue Summary", Description", Now, _ Now + 1 / 24, , , , , , _ strEventID) = True Then Debug.Print "Termin ''" & strEventID & "'' geändert." End If End Sub
Dies hat den Betreff des Termins wie gewünscht geändert.
Dann haben wir den Termin durch Markieren im Google Calendar und anschließendes Betätigen der Entfernen-Taste gelöscht.
Danach haben wir erneut die Prozedur Test_UpdateEvent aufgerufen. Dies führte allerdings nicht zu der Meldung, dass es keinen Termin mit dieser EventID gibt, sondern es erschien eine Meldung über die erfolgreiche Aktualisierung.
Außerdem erschien der bereits gelöschte Termin wieder im Google Calendar. Das konnten wir auch reproduzieren, wenn wir die Funktion DeleteEvent verwendet haben, um den Termin zu löschen.
Also haben wir den Termin nochmals gelöscht und uns diesen mit der Funktion GetEventByID erneut geholt, um seinen JSON-Code anzusehen. Dieser sieht wie folgt aus und die entscheidende Eigenschaft heißt status – hier finden wir den Wert cancelled vor:
{ "kind": "calendar#event", "etag": "\"3408935113444000\"", "id": "e4d35j9hsdovo57be5kigrg2f0", "status": "cancelled", ... "eventType": "default" }
Das heißt, dass das Löschen eines Termins lediglich zur Änderung des Status in den Wert cancelled führt.
Mehr Informationen dazu finden wir auf der folgenden Seite, welche die Eigenschaften eines Event-Elements erläutert:
https://developers.google.com/calendar/api/v3/reference/events?hl=en
Hier lesen wir von folgenden Werten für den Status:
- confirmed: Der Termin ist bestätigt.
- tentative: Der Termin ist vorläufig bestätigt.
- cancelled: Der Termin wurde abgesagt (gelöscht).
Termine werden als cancelled markiert und nicht gelöscht, weil er unter Umständen mehrere Teilnehmer hat. Wenn ein Termin, der bereits an Teilnehmer kommuniziert wurde, gelöscht wird, sollten auch alle Teilnehmer über die Absage des Termins informiert werden können. Deshalb wird der Termin nicht direkt gelöscht. Wir können aber mit einem bestimmten Parameter dafür sorgen, dass direkt beim Löschen alle Teilnehmer von der Absage informiert werden.
Kalendereinträge einlesen