Search code examples
c#asp.net-coregoogle-calendar-api

Google Calendar API ASP.NET Core 3.1 C# DateTime System.FormatException


I am trying to create Google Calendar event using ASP.NET Core 3.1. When I see in my calendar, it shows new event was created. But in the backend, I get an error:

System.FormatException: String '2023-12-01T10:00:00+05:30' was not recognized as a valid DateTime.

at System.DateTimeParse.ParseExact(ReadOnlySpan1 s, ReadOnlySpan1 format, DateTimeFormatInfo dtfi, DateTimeStyles style, TimeSpan& offset)
at System.DateTimeOffset.ParseExact(String input, String format, IFormatProvider formatProvider, DateTimeStyles styles)
at Google.Apis.Util.Utilities.GetDateTimeOffsetFromString(String raw)
at Google.Apis.Calendar.v3.Data.EventDateTime.get_DateTimeDateTimeOffset()
at System.Text.Json.JsonPropertyInfoNullable`2.OnWrite(WriteStackFrame& current, Utf8JsonWriter writer)
at System.Text.Json.JsonPropertyInfo.Write(WriteStack& state, Utf8JsonWriter writer)
at System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.JsonSerializer.WriteAsyncCore(Stream utf8Json, Object value, Type inputType, JsonSerializerOptions options, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|21_0(ResourceInvoker invoker, IActionResult result) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker) at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

HEADERS

Accept: application/json
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 158
Content-Type: application/json
Host: localhost:44363
User-Agent: PostmanRuntime/7.34.0
Postman-Token: a4e9cbd1-c04a-4457-ac54-26f14e0a5f3f

My implementation:

using Google.Apis.Auth.OAuth2;
using Google.Apis.Calendar.v3;
using Google.Apis.Calendar.v3.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Google_Calendar.Helper
{
    public class GoogleCalendarHelper
    {
        protected GoogleCalendarHelper()
        {
        }

        [Obsolete]
        public static async Task<Event> CreateGoogleCalendar(GoogleCalender request)
        {
            try
            {
                string[] Scopes = { CalendarService.Scope.Calendar };
                string ApplicationName = "Google Calendar";

                UserCredential credential;

                using (var stream = new FileStream(Path.Combine(Directory.GetCurrentDirectory(), "Cre", "cre.json"), FileMode.Open, FileAccess.Read))
                {
                    string credPath = "token.json";
                    credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                        GoogleClientSecrets.Load(stream).Secrets,
                        Scopes,
                        "user",
                        CancellationToken.None,
                        new FileDataStore(credPath, true)).Result;
                }
  
                var services = new CalendarService(new BaseClientService.Initializer()
                {
                    HttpClientInitializer = credential,
                    ApplicationName = ApplicationName,
                });

                var @event = new Event
                {
                    Summary = "Akila",
                    Location = "Sri Lanka",
                    Description = "Hello",
                    Start = new EventDateTime()
                    {
                        DateTime = new DateTime(2023, 12, 1, 9, 0, 0),
                    },
                    End = new EventDateTime()
                    {
                        DateTime = new DateTime(2023, 12, 1, 10, 0, 0),
                    },
                };

                var eventRequest = await services.Events.Insert(@event, "primary").ExecuteAsync();
                return eventRequest;
            }
            catch(Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
    }
}

When I am debugging the error, it appears from this eventRequest response:

eventRequest.End.DateTimeDateTimeOffset = 'eventRequest.End.DateTimeDateTimeOffset' threw an exception of type 'System.FormatException'

eventRequest.Start.DateTimeDateTimeOffset = 'eventRequest.Start.DateTimeDateTimeOffset' threw an exception of type 'System.FormatException'

Google Event is created successfully in calendar. But this error occurs.

I need an answer for this. I tried different ways of time inputs but not working for me. Please help me solve this issue.


Solution

  • This is a bug in the Google API client libraries right now - I believe due to a documentation issue with the Discovery format. The documented format for "date-time" is always in UTC, to millisecond precision. Calendar is providing (and I suspect always has provided) a value including a UTC offset, and our current utility code fails to parse that.

    We have a similar issue filed in GitHub, and it's probably best to subscribe to that issue for updates. We're actively working on the issue (as I write this answer, I happen to have Visual Studio open drafting a solution...) but for now, I'd recommend parsing the raw string property, where you can.

    It looks like in your particular case, the problem occurs when you're serializing the calendar event model as JSON to return it. I'd recommend not doing that - or if you must do it, only doing so by explicitly using the serializer provided in the client library... we have various properties (including the DateTimeOffset-based ones) which are configured with [JsonIgnore] to avoid them besing serialized, but that's using Json.NET, not System.Text.Json.

    Even once we've fixed the parsing bug, you still wouldn't want the result of serializing the data model using System.Text.Json - you'd end up with multiple properties effectively representing the same data.

    Two options:

    • Call services.Serializer.Serialize(eventRequest) to retrieve the JSON, and then return that from your method, taking care not to double-escape it.
    • Create your own model (that can use whatever JSON format you want) and convert eventRequest into an instance of that, and return that from your method.

    Personally I'd recommend the second option. It'll give you complete control over how much information is returned and the format in which it's returned.

    (I'd also recommend renaming eventRequest - it actually represents the response from the Calendar service, not the request.)