umbraco / Umbraco-CMS

Umbraco is a free and open source .NET content management system helping you deliver delightful digital experiences.
https://umbraco.com
MIT License
4.42k stars 2.67k forks source link

Publish scheduling fails when users language is set to Danish #14306

Closed SonniTost closed 1 year ago

SonniTost commented 1 year ago

Which Umbraco version are you using? (Please write the exact version, example: 10.1.0)

10.5.0

Bug summary

When users try to use the schedule function on a document - then the date format throws an Newtonsoft.Json error when the users press Schedule.

Specifics

It happens when the users' language is set to Danish. It seems that it does not happen when the users' language is set to English.

It could be related to this https://github.com/umbraco/Umbraco-CMS/issues/3075

Steps to reproduce

Open a page Instead of pressing Save and publish - click the arrow and select Schedule Set some dates and press Schedule.

https://github.com/umbraco/Umbraco-CMS/assets/66302306/b38316e1-8a4f-464c-8ce1-d5e567b873b7

Expected result / actual result

Expected

A schedule is created for the page (either publish or expire)

Actual

Throws a Newtonsoft.Json error - Could not convert string to DateTime: 2023-05-30 12.00.00. Path 'variants[0].releaseDate', line 1, position 8119. (Please note the . in the time format)

An error occurred Could not convert string to DateTime: 2023-05-30 12.00.00. Path 'variants[0].releaseDate', line 1, position 8119.

Undtagelsesdetaljer Newtonsoft.Json.JsonReaderException, Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed: Could not convert string to DateTime: 2023-05-30 12.00.00. Path 'variants[0].releaseDate', line 1, position 8119.

Stacktrace at Newtonsoft.Json.JsonReader.ReadDateTimeString(String s) at Newtonsoft.Json.JsonTextReader.FinishReadQuotedStringValue(ReadType readType) at Newtonsoft.Json.JsonTextReader.ReadStringValue(ReadType readType) at Newtonsoft.Json.JsonTextReader.ReadAsDateTime() at Newtonsoft.Json.JsonReader.ReadForType(JsonContract contract, Boolean hasConverter) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateList(IList list, JsonReader reader, JsonArrayContract contract, JsonProperty containerProperty, String id) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent) at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType) at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings) at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings) at Umbraco.Cms.Web.BackOffice.ModelBinders.ContentModelBinderHelper.BindModelFromMultipartRequestAsync[T](IJsonSerializer jsonSerializer, IHostingEnvironment hostingEnvironment, ModelBindingContext bindingContext) at Umbraco.Cms.Web.BackOffice.ModelBinders.ContentItemBinder.BindModelAsync(ModelBindingContext bindingContext) at Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BinderTypeModelBinder.BindModelAsync(ModelBindingContext bindingContext) at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, Object value, Object container) at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>cDisplayClass0_0.<gBind|0>d.MoveNext() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.gAwaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

image

github-actions[bot] commented 1 year ago

Hi there @SonniTost!

Firstly, a big thank you for raising this issue. Every piece of feedback we receive helps us to make Umbraco better.

We really appreciate your patience while we wait for our team to have a look at this but we wanted to let you know that we see this and share with you the plan for what comes next.

We wish we could work with everyone directly and assess your issue immediately but we're in the fortunate position of having lots of contributions to work with and only a few humans who are able to do it. We are making progress though and in the meantime, we will keep you in the loop and let you know when we have any questions.

Thanks, from your friendly Umbraco GitHub bot :robot: :slightly_smiling_face:

elit0451 commented 1 year ago

Hi @SonniTost πŸ‘‹ Thanks for reaching out. I've tried to reproduce your issue, but as you can see in the video below, I don't get the same error as you πŸ€·β€β™€οΈ

Can you help me figure out what I might be doing wrong? πŸ™‚

https://github.com/umbraco/Umbraco-CMS/assets/21998037/54df008f-9a73-4d77-9621-b14ede6a4559

SonniTost commented 1 year ago

Hi @elit0451

It seems that you can create a page and schedule it. But when you go to a scheduled page (after you have visited another page) the error occurs when you try to update the schedule.

I have tried to make a screen cast of the issue below.

https://github.com/umbraco/Umbraco-CMS/assets/66302306/46dea89d-d89b-4f79-8b54-d484a2971c62

elit0451 commented 1 year ago

Thanks for clarifying @SonniTost

I can indeed reproduce. I will mark it as up for grabs as we would like some help with it πŸ™‚

github-actions[bot] commented 1 year ago

Hi @SonniTost,

We're writing to let you know that we would love some help with this issue. We feel that this issue is ideal to flag for a community member to work on it. Once flagged here, folk looking for issues to work on will know to look at yours. Of course, please feel free work on this yourself ;-). If there are any changes to this status, we'll be sure to let you know.

For more information about issues and states, have a look at this blog post.

Thanks muchly, from your friendly Umbraco GitHub bot :-)

Danielh21 commented 1 year ago

Hi.

We are experiencing something related . In the "Info" tab, we can see that the "Created date" is displayed as "invalid date" when the CMS language is Danish here is the video:

Our version of the Umbraco is 11.4

https://github.com/umbraco/Umbraco-CMS/assets/15868547/0279fccc-3291-4da3-ac0e-bf921759274b

emmagarland commented 1 year ago

Just to add, I've tested the scheduled published in the latest Umbraco version, trying out all languages enabled for my user, and the only one I've had this issue with is Danish.

elit0451 commented 1 year ago

Great insight @emmagarland, thanks for that πŸ™Œ

calixuss commented 1 year ago

I am working on it :)

miguelcrpinto commented 1 year ago

This is a frontend issue. Whenever there's already a date/time set for the scheduled publish and that date/time is not modified, the frontend is sending the time with . as unit separator instead of :

See screenshot below: image

JSON posted by the frontend:

{
    "id": 1059,
    "contentTypeAlias": "contentPage",
    "parentId": -1,
    "action": "schedule",
    "variants": [
        {
            "name": "Content created in danish (1)",
            "properties": [
                {
                    "id": 9,
                    "alias": "body",
                    "value": "<p>dummy</p>"
                }
            ],
            "culture": null,
            "segment": null,
            "publish": false,
            "save": true,
            "releaseDate": null,
            "expireDate": "2023-06-23 13.25.00"
        }
    ],
    "expireDate": null,
    "releaseDate": null,
    "templateAlias": null
}

Notice the "13.25.00" in the expireDate vs the expected "13:25:00"

miguelcrpinto commented 1 year ago

After further investigation, it seems that the time is being formatted in the backend in the response of the Get By Id API (/umbraco/backoffice/umbracoapi/content/GetById?id={node id}).

See the attached response examples in PT and in DA. Portuguese response example.txt

Danish response example.txt

miguelcrpinto commented 1 year ago

I figured out the issue.

In the JsonDateTimeFormatAttribute we are adding a IsoDateTimeConverter with a specific time format (the one expected by the frontend) but without specifying the culture. Whenever no culture is provided the IsoDateTimeConverter uses the CurrentCulture ("da-DK" in this case). In .Net (at least since Core 3.1), when you do a DateTime ToString("yyyy-MM-dd HH:mm:ss", new CultureInfo("da-DK")) it seems to replace the time separator symbols with the ones from the provided culture. In the case of the Danish culture it was replacing the : with .

There are a few more cultures that use . as the time separator but they are not available in the backoffice.

miguelcrpinto commented 1 year ago

Created PR 14400 to fix this issue.

emmagarland commented 1 year ago

Created PR 14400 to fix this issue.

Amazing stuff!

calixuss commented 1 year ago

Created PR 14400 to fix this issue.

Awesome work @miguelcrpinto I would need to find other issue to contribute to :)

miguelcrpinto commented 1 year ago

For those interested in knowing more about this behavior, here's the official documentation from Microsoft.

emmagarland commented 1 year ago

Thanks @miguelcrpinto!

The related PR #14400 has now been merged. Thanks @SonniTost for raising this issue.

Best

Emma

SonniTost commented 1 year ago

@nul800sebastiaan this fix is super relevant for our client - but they are running on Umbraco 10. Could this be cherry picked for 10.6?

Zeegaan commented 1 year ago

@SonniTost Yep I've cherry-picked it to the 10.6 release branch, so the fix will make it in πŸš€

Zeegaan commented 1 year ago

Fixed in https://github.com/umbraco/Umbraco-CMS/pull/14400