谷歌日历 API v3 在创建事件时始终返回BadRequest。

huangapple go评论131阅读模式
英文:

Google Calendar API v3 always returns BadRequest when creating events

问题

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 with the Service Account as owner.

Then I noticed

Service Account must manually add the shared calendar

as described here:
link1
link2

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 inserted one event into the calendar (using the 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.

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 inserting event: {}", event)

    val eventResult: Event = calendar.events().insert(calendarId, event).execute()
    logger.info("eventResult = {}", eventResult)
  }

I can see the client truly POST to Google's 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 with a 400 BadRequest, without any further description.

I am using the same calendar instance, and I can successfully call addCalendarToServiceAccount() (as owner) and listEvents().
But what is going 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
  • Kotlin version: 1.4.0

Dependencies:

<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>
英文:

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(&quot;calList1 = {}&quot;, calList1)

    val inserted = calendar.calendarList().insert(CalendarListEntry().setId(calendarId)).execute()
    logger.info(&quot;inserted = {}&quot;, inserted)

    val calList2: CalendarList = calendar.calendarList().list().execute()
    logger.info(&quot;calList2 = {}&quot;, 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(&quot;events = {}&quot;, events)
    events.items.forEachIndexed { index, e -&gt;
      logger.info(&quot;Event [index = {}] , event = {}&quot;, index, e)
    }
  }

It also works.

{
   &quot;accessRole&quot;:&quot;owner&quot;,
   &quot;defaultReminders&quot;:[

   ],
   &quot;etag&quot;:&quot;\&quot;xxx\&quot;&quot;,
   &quot;items&quot;:[
      {
         &quot;created&quot;:&quot;2020-08-17T17:51:21.000Z&quot;,
         &quot;creator&quot;:{
            &quot;email&quot;:&quot;xxx@gmail.com&quot;
         },
         &quot;end&quot;:{
            &quot;date&quot;:&quot;2020-08-20&quot;
         },
         &quot;etag&quot;:&quot;\&quot;xxx\&quot;&quot;,
         &quot;htmlLink&quot;:&quot;https://www.google.com/calendar/event?eid=xxx&quot;,
         &quot;iCalUID&quot;:&quot;xxx@google.com&quot;,
         &quot;id&quot;:&quot;xxx&quot;,
         &quot;kind&quot;:&quot;calendar#event&quot;,
         &quot;organizer&quot;:{
            &quot;displayName&quot;:&quot;xxx&quot;,
            &quot;email&quot;:&quot;xxx@group.calendar.google.com&quot;,
            &quot;self&quot;:true
         },
         &quot;reminders&quot;:{
            &quot;useDefault&quot;:false
         },
         &quot;sequence&quot;:0,
         &quot;start&quot;:{
            &quot;date&quot;:&quot;2020-08-19&quot;
         },
         &quot;status&quot;:&quot;confirmed&quot;,
         &quot;summary&quot;:&quot;xxx  test1&quot;,
         &quot;transparency&quot;:&quot;transparent&quot;,
         &quot;updated&quot;:&quot;2020-08-18T01:07:54.441Z&quot;
      }
   ],
   &quot;kind&quot;:&quot;calendar#events&quot;,
   &quot;nextSyncToken&quot;:&quot;xxx&quot;,
   &quot;summary&quot;:&quot;xxx&quot;,
   &quot;timeZone&quot;:&quot;Asia/Taipei&quot;,
   &quot;updated&quot;:&quot;2020-08-18T01:07:54.688Z&quot;
}

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(&quot;Asia/Taipei&quot;)
    val fromDate = Date.from(now.atZone(zoneId).toInstant())
    val endDate = Date.from(now.plusMinutes(60).atZone(zoneId).toInstant())

    val event = Event()
      .setSummary(&quot;Google I/O 2015&quot;)
      .setLocation(&quot;800 Howard St., San Francisco, CA 94103&quot;)
      .setDescription(&quot;A chance to hear more about Google&#39;s developer products.&quot;)
      .setStart(EventDateTime().setDate(DateTime(fromDate, TimeZone.getTimeZone(zoneId))))
      .setEnd(EventDateTime().setDate(DateTime(endDate, TimeZone.getTimeZone(zoneId))))

    logger.info(&quot;before insert event : {}&quot;, event)

    val eventResult: Event = calendar.events().insert(calendarId, event).execute()
    logger.info(&quot;eventResult = {}&quot;, eventResult)
  }

I can see the client truly POST to google'e endpoint:

谷歌日历 API v3 在创建事件时始终返回BadRequest。

The body is:

{
   &quot;description&quot;:&quot;A chance to hear more about Google&#39;s developer products.&quot;,
   &quot;end&quot;:{
      &quot;date&quot;:&quot;2020-08-18T11:32:00.000+08:00&quot;
   },
   &quot;location&quot;:&quot;800 Howard St., San Francisco, CA 94103&quot;,
   &quot;start&quot;:{
      &quot;date&quot;:&quot;2020-08-18T10:32:00.000+08:00&quot;
   },
   &quot;summary&quot;:&quot;Google I/O 2015&quot;
}

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=&quot;:443&quot;; ma=2592000,h3-27=&quot;:443&quot;; ma=2592000,h3-T050=&quot;:443&quot;; ma=2592000,h3-Q050=&quot;:443&quot;; ma=2592000,h3-Q046=&quot;:443&quot;; ma=2592000,h3-Q043=&quot;:443&quot;; ma=2592000,quic=&quot;:443&quot;; ma=2592000; v=&quot;46,43&quot;
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 - {
 &quot;error&quot;: {
  &quot;errors&quot;: [
   {
    &quot;domain&quot;: &quot;global&quot;,
    &quot;reason&quot;: &quot;badRequest&quot;,
    &quot;message&quot;: &quot;Bad Request&quot;
   }
  ],
  &quot;code&quot;: 400,
  &quot;message&quot;: &quot;Bad Request&quot;
 }
}

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(&quot;${google.calendar.id}&quot;)
  private lateinit var calendarId: String

  @Value(&quot;${google.calendar.apiKey}&quot;)
  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(&quot;/chancer-d1de03c4c25a.json&quot;).use { iStream -&gt;
      ServiceAccountCredentials.fromStream(iStream)
        .createScoped(listOf(
          &quot;https://www.googleapis.com/auth/cloud-platform&quot;,
          *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:

    &lt;java.version&gt;1.8&lt;/java.version&gt;
    &lt;kotlin.version&gt;1.4.0&lt;/kotlin.version&gt;


    &lt;dependency&gt;
      &lt;groupId&gt;com.google.api-client&lt;/groupId&gt;
      &lt;artifactId&gt;google-api-client&lt;/artifactId&gt;
      &lt;version&gt;1.30.10&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;com.google.apis&lt;/groupId&gt;
      &lt;artifactId&gt;google-api-services-calendar&lt;/artifactId&gt;
      &lt;version&gt;v3-rev20200610-1.30.10&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;com.google.auth&lt;/groupId&gt;
      &lt;artifactId&gt;google-auth-library-oauth2-http&lt;/artifactId&gt;
      &lt;version&gt;0.21.1&lt;/version&gt;
    &lt;/dependency&gt;

答案1

得分: 2

回答:

您需要使用 start.dateTimeend.dateTime,而不是 start.dateend.date

修正:

根据文档

> end.date:如果这是全天事件,则格式为“yyyy-mm-dd”的日期。
>
> end.dateTime:时间,作为组合的日期时间值(根据RFC3339格式化)。除非在timeZone中明确指定时区,否则需要时区偏移。
>
> start.date:如果这是全天事件,则格式为“yyyy-mm-dd”的日期。
>
> start.dateTime:时间,作为组合的日期时间值(根据RFC3339格式化)。除非在timeZone中明确指定时区,否则需要时区偏移。

因此,您需要将日期和时间设置方法从:

EventDateTime().setDate(DateTime(fromDate, TimeZone.getTimeZone(zoneId))))))

更改为:

EventDateTime().setDateTime(DateTime(fromDate, TimeZone.getTimeZone(zoneId))))))

这将更改请求正文为:

{
  "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"
}

您可以在此处查看此方法的文档。

参考:

英文:

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:

{
  &quot;description&quot;: &quot;A chance to hear more about Google&#39;s developer products.&quot;,
  &quot;end&quot;: {
      &quot;dateTime&quot;: &quot;2020-08-18T11:32:00.000+08:00&quot; // modified
  },
  &quot;location&quot;: &quot;800 Howard St., San Francisco, CA 94103&quot;,
  &quot;start&quot;: {
    &quot;dateTime&quot;: &quot;2020-08-18T10:32:00.000+08:00&quot; // modified
  },
  &quot;summary&quot;: &quot;Google I/O 2015&quot;
}

You can see the documentation for this method here.

References:

huangapple
  • 本文由 发表于 2020年8月18日 10:45:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/63461135.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定