Skip to content

Commit 506d3b3

Browse files
kjacZeegaan
authored andcommitted
Fix date conversion on the server-side (#16841)
(cherry picked from commit ceddf86)
1 parent c288d03 commit 506d3b3

File tree

3 files changed

+87
-11
lines changed

3 files changed

+87
-11
lines changed

src/Umbraco.Core/Extensions/ObjectExtensions.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,24 @@ public static Attempt<T> TryConvertTo<T>(this object? input)
219219
}
220220
}
221221

222+
if (target == typeof(DateTime) && input is DateTimeOffset dateTimeOffset)
223+
{
224+
// IMPORTANT: for compatability with various editors, we must discard any Offset information and assume UTC time here
225+
return Attempt.Succeed((object?)new DateTime(
226+
new DateOnly(dateTimeOffset.Year, dateTimeOffset.Month, dateTimeOffset.Day),
227+
new TimeOnly(dateTimeOffset.Hour, dateTimeOffset.Minute, dateTimeOffset.Second, dateTimeOffset.Millisecond, dateTimeOffset.Microsecond),
228+
DateTimeKind.Utc));
229+
}
230+
231+
if (target == typeof(DateTimeOffset) && input is DateTime dateTime)
232+
{
233+
// IMPORTANT: for compatability with various editors, we must discard any DateTimeKind information and assume UTC time here
234+
return Attempt.Succeed((object?)new DateTimeOffset(
235+
new DateOnly(dateTime.Year, dateTime.Month, dateTime.Day),
236+
new TimeOnly(dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond, dateTime.Microsecond),
237+
TimeSpan.Zero));
238+
}
239+
222240
TypeConverter? inputConverter = GetCachedSourceTypeConverter(inputType, target);
223241
if (inputConverter != null)
224242
{

src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,19 @@ public override object ConvertSourceToIntermediate(IPublishedElement owner, IPub
2020

2121
internal static DateTime ParseDateTimeValue(object? source)
2222
{
23-
if (source == null)
23+
if (source is null)
2424
{
2525
return DateTime.MinValue;
2626
}
2727

28-
// in XML a DateTime is: string - format "yyyy-MM-ddTHH:mm:ss"
29-
// Actually, not always sometimes it is formatted in UTC style with 'Z' suffixed on the end but that is due to this bug:
30-
// http://issues.umbraco.org/issue/U4-4145, http://issues.umbraco.org/issue/U4-3894
31-
// We should just be using TryConvertTo instead.
32-
if (source is string sourceString)
28+
if (source is DateTime dateTimeValue)
3329
{
34-
Attempt<DateTime> attempt = sourceString.TryConvertTo<DateTime>();
35-
return attempt.Success == false ? DateTime.MinValue : attempt.Result;
30+
return dateTimeValue;
3631
}
3732

38-
// in the database a DateTime is: DateTime
39-
// default value is: DateTime.MinValue
40-
return source is DateTime dateTimeValue ? dateTimeValue : DateTime.MinValue;
33+
Attempt<DateTime> attempt = source.TryConvertTo<DateTime>();
34+
return attempt.Success
35+
? attempt.Result
36+
: DateTime.MinValue;
4137
}
4238
}

tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/ObjectExtensionsTests.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Umbraco.Cms.Core.PropertyEditors;
1212
using Umbraco.Cms.Tests.Common.TestHelpers;
1313
using Umbraco.Extensions;
14+
using DateTimeOffset = System.DateTimeOffset;
1415

1516
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.CoreThings;
1617

@@ -332,6 +333,50 @@ public void CanConvertStringValuesToString()
332333
Assert.AreEqual("This is a string", conv.Result);
333334
}
334335

336+
[Test]
337+
public void CanConvertDateTimeOffsetToDateTime()
338+
{
339+
var dateTimeOffset = new DateTimeOffset(new DateOnly(2024, 07, 05), new TimeOnly(12, 30, 01, 02, 03), TimeSpan.Zero);
340+
var result = dateTimeOffset.TryConvertTo<DateTime>();
341+
Assert.IsTrue(result.Success);
342+
Assert.Multiple(() =>
343+
{
344+
Assert.AreEqual(new DateTime(new DateOnly(2024, 07, 05), new TimeOnly(12, 30, 01, 02, 03)), result.Result);
345+
Assert.AreEqual(DateTimeKind.Utc, result.Result.Kind);
346+
});
347+
}
348+
349+
[Test]
350+
public void CanConvertDateTimeToDateTimeOffset()
351+
{
352+
var dateTime = new DateTime(new DateOnly(2024, 07, 05), new TimeOnly(12, 30, 01, 02, 03), DateTimeKind.Utc);
353+
var result = dateTime.TryConvertTo<DateTimeOffset>();
354+
Assert.IsTrue(result.Success);
355+
Assert.AreEqual(new DateTimeOffset(new DateOnly(2024, 07, 05), new TimeOnly(12, 30, 01, 02, 03), TimeSpan.Zero), result.Result);
356+
}
357+
358+
[Test]
359+
public void DiscardsOffsetWhenConvertingDateTimeOffsetToDateTime()
360+
{
361+
var dateTimeOffset = new DateTimeOffset(new DateOnly(2024, 07, 05), new TimeOnly(12, 30, 01, 02, 03), TimeSpan.FromHours(2));
362+
var result = dateTimeOffset.TryConvertTo<DateTime>();
363+
Assert.IsTrue(result.Success);
364+
Assert.Multiple(() =>
365+
{
366+
Assert.AreEqual(new DateTime(new DateOnly(2024, 07, 05), new TimeOnly(12, 30, 01, 02, 03)), result.Result);
367+
Assert.AreEqual(DateTimeKind.Utc, result.Result.Kind);
368+
});
369+
}
370+
371+
[Test]
372+
public void DiscardsDateTimeKindWhenConvertingDateTimeToDateTimeOffset()
373+
{
374+
var dateTime = new DateTime(new DateOnly(2024, 07, 05), new TimeOnly(12, 30, 01, 02, 03), DateTimeKind.Local);
375+
var result = dateTime.TryConvertTo<DateTimeOffset>();
376+
Assert.IsTrue(result.Success);
377+
Assert.AreEqual(new DateTimeOffset(new DateOnly(2024, 07, 05), new TimeOnly(12, 30, 01, 02, 03), TimeSpan.Zero), result.Result);
378+
}
379+
335380
[Test]
336381
public void Value_Editor_Can_Convert_Decimal_To_Decimal_Clr_Type()
337382
{
@@ -342,6 +387,23 @@ public void Value_Editor_Can_Convert_Decimal_To_Decimal_Clr_Type()
342387
Assert.AreEqual(12.34d, result.Result);
343388
}
344389

390+
[Test]
391+
public void Value_Editor_Can_Convert_DateTimeOffset_To_DateTime_Clr_Type()
392+
{
393+
var valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.Date);
394+
395+
var result = valueEditor.TryConvertValueToCrlType(new DateTimeOffset(new DateOnly(2024, 07, 05), new TimeOnly(12, 30), TimeSpan.Zero));
396+
Assert.IsTrue(result.Success);
397+
Assert.IsTrue(result.Result is DateTime);
398+
399+
var dateTime = (DateTime)result.Result;
400+
Assert.Multiple(() =>
401+
{
402+
Assert.AreEqual(new DateTime(new DateOnly(2024, 07, 05), new TimeOnly(12, 30)), dateTime);
403+
Assert.AreEqual(DateTimeKind.Utc, dateTime.Kind);
404+
});
405+
}
406+
345407
private class MyTestObject
346408
{
347409
public override string ToString() => "Hello world";

0 commit comments

Comments
 (0)