Google Calendar per Rest-API programmieren, Teil 2

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.

Ein ganztägiger Termin im Google Calendar

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).

Ausdrücke zum Auslesen von Termineigenschaften

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

 

Schreibe einen Kommentar