Azure / Commercial-Marketplace-SaaS-Accelerator

A reference example with sample code for developers interested publishing transactable, Software as a-Service offers in the Microsoft commercial marketplace.
MIT License
179 stars 276 forks source link

DateTime issues when running CustomerSite locally. #603

Closed jeffw-wherethebitsroam closed 5 months ago

jeffw-wherethebitsroam commented 7 months ago

Describe the bug When running the CustomerSite landing page locally I get the exception:

Error 500 : The UTC time represented when the offset is applied must be between year 0 and 10,000. (Parameter 'offset')

If I remove the try/catch block in HomeController.Index, I get:

fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
      System.ArgumentOutOfRangeException: The UTC time represented when the offset is applied must be between year 0 and 10,000. (Parameter 'offset')
         at System.DateTimeOffset.ValidateDate(DateTime dateTime, TimeSpan offset)
         at System.DateTimeOffset..ctor(DateTime dateTime)
         at System.DateTimeOffset.op_Implicit(DateTime dateTime)
         at Marketplace.SaaS.Accelerator.Services.Services.SubscriptionService.PrepareSubscriptionResponse(Subscriptions subscription, Plans existingPlanDetail) in /Commercial-Marketplace-SaaS-Accelerator/src/Services/Services/SubscriptionService.cs:line 156
         at Marketplace.SaaS.Accelerator.Services.Services.SubscriptionService.GetSubscriptionsBySubscriptionId(Guid subscriptionId, Boolean includeUnsubscribed) in /Commercial-Marketplace-SaaS-Accelerator/src/Services/Services/SubscriptionService.cs:line 134
         at Marketplace.SaaS.Accelerator.CustomerSite.Controllers.HomeController.Index(String token) in /Commercial-Marketplace-SaaS-Accelerator/src/CustomerSite/Controllers/HomeController.cs:line 253

To Reproduce I loaded the landing page with a token. The subscription object returned from the fullfillment API had these dates:

{
  ...
   "TimeStamp":"0001-01-01T00:00:00+00:00",
  ...,
   "term":{
      "endDate":"0001-01-01T00:00:00+00:00",
      "startDate":"0001-01-01T00:00:00+00:00",
      "termUnit":"P1M"
   },
   ...
}

It seems like there is some problem with the timezone conversion. I'm in UTC+1.

In FulfillmentApiService the term endDate and startDate returned from marketplaceClient.Fulfillment.GetSubscriptionAsync are DateTimeOffset?.

The Subscription is converted to a SubscriptionResult via ConversionHelper.subscriptionResult which retains them as DateTimeOffset?.

The HomeController then calls subscriptionService.AddOrUpdatePartnerSubscriptions(subscriptionData) with the SubscriptionResult.

AddOrUpdatePartnerSubscriptions then converts the DateTimeOffset? to DateTime using:

StartDate = subscriptionDetail.Term.StartDate.ToUniversalTime().DateTime

This is problematic since DateTimeOffset.DateTime always return a DateTime with kind DateTimeKind.Unspecified.

A little time later in the HomeController the same subscription is fetched from the repo via subscriptionService.GetSubscriptionsBySubscriptionId. There, in PrepareSubscriptionResponse the error occurs:

            Term = new TermResult
            {
                StartDate = subscription.StartDate.GetValueOrDefault(),
                EndDate = subscription.EndDate.GetValueOrDefault(),
            },

Here it is implicitly converting the DateTime with unspecified kind to DateTimeOffset. This is where I see the exceptions.

Expected behavior Dates to be handled.

Screenshots If applicable, add screenshots to help explain your problem.

Environment (please complete the following information): UTC+1

Additional context Add any other context about the problem here.

jeffw-wherethebitsroam commented 7 months ago

The PR only fixes half of the problem.

When Entity framework fetches DateTime's from the database, they are fetched at DateTimeKind.Unspecified. This is a problem when converting to other formats, for example DateTimeOffset, especially when the value stored is 0001-01-01 00:00:00.0000000. The implicit conversion to DateTimeOffset is a non-UTC timezone can cause an ArgumentOutOfRangeException.

There are 2 solutions to this issue:

  1. Use DateTimeOffset in the entities.
  2. Retrieve (and store) all DateTime as UTC (e.g. https://stackoverflow.com/a/73154546/945878)
santhoshb-msft commented 5 months ago

Hi @jeffw-wherethebitsroam I think this is related to whats coming back from the marketplace API. For the resolve the dates really doest make much sense as they are only sent on activation or renewal. We will be implementing the same.

jeffw-wherethebitsroam commented 5 months ago

I am no longer working on this.