I created a shared calendar and want to add events to the calendar.
I created a project and set up a Service Account xxx@xxx.iam.gserviceaccount.com
.
Then I shared the calendar to the Service Account as owner.
Then I noticed
Service Account must manually add shared calendar
as described here https://stackoverflow.com/a/62232361/298430 and https://issuetracker.google.com/issues/148804709
So I wrote a code:
@Test fun addCalendarToServiceAccount() { val calList1: CalendarList = calendar.calendarList().list().execute() logger.info("calList1 = {}", calList1) val inserted = calendar.calendarList().insert(CalendarListEntry().setId(calendarId)).execute() logger.info("inserted = {}", inserted) val calList2: CalendarList = calendar.calendarList().list().execute() logger.info("calList2 = {}", calList2) }
It works perfectly. When first called, I can see calList1
is empty, and calList2
contains something.
Then I manually insert one event to the calendar (with google calendar WEB UI), I want to check if I can retrieve the event:
@Test fun listEvents() { val events: Events = calendar.events().list(calendarId).execute() logger.info("events = {}", events) events.items.forEachIndexed { index, e -> logger.info("Event [index = {}] , event = {}", index, e) } }
It also works.
{ "accessRole":"owner", "defaultReminders":[ ], "etag":""xxx"", "items":[ { "created":"2020-08-17T17:51:21.000Z", "creator":{ "email":"xxx@gmail.com" }, "end":{ "date":"2020-08-20" }, "etag":""xxx"", "htmlLink":"https://www.google.com/calendar/event?eid=xxx", "iCalUID":"xxx@google.com", "id":"xxx", "kind":"calendar#event", "organizer":{ "displayName":"xxx", "email":"xxx@group.calendar.google.com", "self":true }, "reminders":{ "useDefault":false }, "sequence":0, "start":{ "date":"2020-08-19" }, "status":"confirmed", "summary":"xxx test1", "transparency":"transparent", "updated":"2020-08-18T01:07:54.441Z" } ], "kind":"calendar#events", "nextSyncToken":"xxx", "summary":"xxx", "timeZone":"Asia/Taipei", "updated":"2020-08-18T01:07:54.688Z" }
Then I want to programmatically insert something, like the API example shows:
@Test fun testInsertEvent() { val now = LocalDateTime.now().withSecond(0).withNano(0) val zoneId = ZoneId.of("Asia/Taipei") val fromDate = Date.from(now.atZone(zoneId).toInstant()) val endDate = Date.from(now.plusMinutes(60).atZone(zoneId).toInstant()) val event = Event() .setSummary("Google I/O 2015") .setLocation("800 Howard St., San Francisco, CA 94103") .setDescription("A chance to hear more about Google's developer products.") .setStart(EventDateTime().setDate(DateTime(fromDate, TimeZone.getTimeZone(zoneId)))) .setEnd(EventDateTime().setDate(DateTime(endDate, TimeZone.getTimeZone(zoneId)))) logger.info("before insert event : {}", event) val eventResult: Event = calendar.events().insert(calendarId, event).execute() logger.info("eventResult = {}", eventResult) }
I can see the client truly POST to google’e endpoint:
The body is:
{ "description":"A chance to hear more about Google's developer products.", "end":{ "date":"2020-08-18T11:32:00.000+08:00" }, "location":"800 Howard St., San Francisco, CA 94103", "start":{ "date":"2020-08-18T10:32:00.000+08:00" }, "summary":"Google I/O 2015" }
But google just replied 400 BadRequest, without any further description:
2020-08-18 10:32:15.974 [main] INFO c.g.a.c.h.HttpResponse - -------------- RESPONSE -------------- HTTP/1.1 400 Bad Request Transfer-Encoding: chunked Alt-Svc: h3-29=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-T050=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43" Server: ESF X-Content-Type-Options: nosniff Pragma: no-cache Date: Tue, 18 Aug 2020 02:32:15 GMT X-Frame-Options: SAMEORIGIN Cache-Control: no-cache, no-store, max-age=0, must-revalidate Content-Encoding: gzip Vary: Referer Vary: X-Origin Vary: Origin Expires: Mon, 01 Jan 1990 00:00:00 GMT X-XSS-Protection: 0 Content-Type: application/json; charset=UTF-8 2020-08-18 10:32:15.980 [main] INFO c.g.a.c.u.LoggingByteArrayOutputStream - Total: 171 bytes 2020-08-18 10:32:15.980 [main] INFO c.g.a.c.u.LoggingByteArrayOutputStream - { "error": { "errors": [ { "domain": "global", "reason": "badRequest", "message": "Bad Request" } ], "code": 400, "message": "Bad Request" } }
I am using the same calendar instance, can successfully addCalendarToServiceAccount()
(as owner
) and listEvents()
.
But what goes wrong when inserting an event? Did I miss anything?
Other fields are initialized as follows:
@Value("${google.calendar.id}") private lateinit var calendarId: String @Value("${google.calendar.apiKey}") private lateinit var apiKey : String private val httpTransport: HttpTransport by lazy { GoogleNetHttpTransport.newTrustedTransport() } private val jacksonFactory: JsonFactory by lazy { JacksonFactory.getDefaultInstance() } private val saCredentials: GoogleCredentials by lazy { javaClass.getResourceAsStream("/chancer-d1de03c4c25a.json").use { iStream -> ServiceAccountCredentials.fromStream(iStream) .createScoped(listOf( "https://www.googleapis.com/auth/cloud-platform", *CalendarScopes.all().toTypedArray() )) }.apply { refreshIfExpired() } } private val requestInitializer: HttpRequestInitializer by lazy { HttpCredentialsAdapter(saCredentials) } private val calendar: Calendar by lazy { Calendar.Builder(httpTransport, jacksonFactory, requestInitializer) .build() }
Environments:
<java.version>1.8</java.version> <kotlin.version>1.4.0</kotlin.version> <dependency> <groupId>com.google.api-client</groupId> <artifactId>google-api-client</artifactId> <version>1.30.10</version> </dependency> <dependency> <groupId>com.google.apis</groupId> <artifactId>google-api-services-calendar</artifactId> <version>v3-rev20200610-1.30.10</version> </dependency> <dependency> <groupId>com.google.auth</groupId> <artifactId>google-auth-library-oauth2-http</artifactId> <version>0.21.1</version> </dependency>
Advertisement
Answer
Answer:
You need to use start.dateTime
and end.dateTime
rather than start.date
and end.date
Fix:
As per the documentation:
end.date
: The date, in the format “yyyy-mm-dd”, if this is an all-day event.
end.dateTime
: The time, as a combined date-time value (formatted according to RFC3339). A time zone offset is required unless a time zone is explicitly specified in timeZone.
start.date
: The date, in the format “yyyy-mm-dd”, if this is an all-day event.
start.dateTime
: The time, as a combined date-time value (formatted according to RFC3339). A time zone offset is required unless a time zone is explicitly specified in timeZone.
Therefore, you need to change your date & time setting method from:
EventDateTime().setDate(DateTime(fromDate, TimeZone.getTimeZone(zoneId))))
to:
EventDateTime().setDateTime(DateTime(fromDate, TimeZone.getTimeZone(zoneId))))
Which will change the request body to:
{ "description": "A chance to hear more about Google's developer products.", "end": { "dateTime": "2020-08-18T11:32:00.000+08:00" // modified }, "location": "800 Howard St., San Francisco, CA 94103", "start": { "dateTime": "2020-08-18T10:32:00.000+08:00" // modified }, "summary": "Google I/O 2015" }
You can see the documentation for this method here.