Converting DateTime.MinValue to sqlDateTime's minimum value (#20019)
* Converting DateTime.MinValue to sqlDateTime's minimum value * Changing code to be a bit less hacky * Changing hard coded value to a variable based on SqlDateTime * Removing unused code * Moving date converter logic to DateTimePropertyEditor.cs * Replacing tests with proper version * Removing unused import * Removing unused imports again * Creating new logic, to ensure formatting is more precise. * Rewriting tests to be more precise and include testing on odd format separators * Used parsing to determine timeonly date picker data type configuration format. Fixed casing on key for data type configuration format. --------- Co-authored-by: Andy Butland <abutland73@gmail.com>
This commit is contained in:
@@ -1,8 +1,16 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.Data.SqlTypes;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
@@ -25,8 +33,91 @@ public class DateTimePropertyEditor : DataEditor
|
||||
/// <inheritdoc />
|
||||
protected override IDataValueEditor CreateValueEditor()
|
||||
{
|
||||
IDataValueEditor editor = base.CreateValueEditor();
|
||||
DateTimePropertyValueEditor editor = DataValueEditorFactory.Create<DateTimePropertyValueEditor>(Attribute!);
|
||||
editor.Validators.Add(new DateTimeValidator());
|
||||
return editor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides a value editor for the datetime property editor.
|
||||
/// </summary>
|
||||
internal sealed class DateTimePropertyValueEditor : DataValueEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The key used to retrieve the date format from the data type configuration.
|
||||
/// </summary>
|
||||
internal const string DateTypeConfigurationFormatKey = "format";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DateTimePropertyValueEditor"/> class.
|
||||
/// </summary>
|
||||
public DateTimePropertyValueEditor(
|
||||
IShortStringHelper shortStringHelper,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper,
|
||||
DataEditorAttribute attribute)
|
||||
: base(shortStringHelper, jsonSerializer, ioHelper, attribute)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object? FromEditor(ContentPropertyData editorValue, object? currentValue)
|
||||
{
|
||||
if (editorValue.Value is null)
|
||||
{
|
||||
return base.FromEditor(editorValue, currentValue);
|
||||
}
|
||||
|
||||
if (TryGetConfiguredDateTimeFormat(editorValue, out string? format) is false)
|
||||
{
|
||||
return base.FromEditor(editorValue, currentValue);
|
||||
}
|
||||
|
||||
if (IsTimeOnlyFormat(format) is false)
|
||||
{
|
||||
return base.FromEditor(editorValue, currentValue);
|
||||
}
|
||||
|
||||
// We have a time-only format, so we need to ensure the date part is valid for SQL Server, so we can persist
|
||||
// without error.
|
||||
// If we have a date part that is less than the minimum date, we need to adjust it to be the minimum date.
|
||||
Attempt<object?> dateConvertAttempt = editorValue.Value.TryConvertTo(typeof(DateTime?));
|
||||
if (dateConvertAttempt.Success is false || dateConvertAttempt.Result is null)
|
||||
{
|
||||
return base.FromEditor(editorValue, currentValue);
|
||||
}
|
||||
|
||||
var dateTimeValue = (DateTime)dateConvertAttempt.Result;
|
||||
int yearValue = dateTimeValue.Year > SqlDateTime.MinValue.Value.Year
|
||||
? dateTimeValue.Year
|
||||
: SqlDateTime.MinValue.Value.Year;
|
||||
return new DateTime(yearValue, dateTimeValue.Month, dateTimeValue.Day, dateTimeValue.Hour, dateTimeValue.Minute, dateTimeValue.Second);
|
||||
}
|
||||
|
||||
private static bool TryGetConfiguredDateTimeFormat(ContentPropertyData editorValue, [NotNullWhen(true)] out string? format)
|
||||
{
|
||||
if (editorValue.DataTypeConfiguration is not Dictionary<string, object> dataTypeConfigurationDictionary)
|
||||
{
|
||||
format = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
KeyValuePair<string, object> keyValuePair = dataTypeConfigurationDictionary
|
||||
.FirstOrDefault(kvp => kvp.Key is "format");
|
||||
format = keyValuePair.Value as string;
|
||||
return string.IsNullOrWhiteSpace(format) is false;
|
||||
}
|
||||
|
||||
private static bool IsTimeOnlyFormat(string format)
|
||||
{
|
||||
DateTime testDate = DateTime.UtcNow;
|
||||
var testDateFormatted = testDate.ToString(format);
|
||||
if (DateTime.TryParseExact(testDateFormatted, format, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out DateTime parsedDate) is false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return parsedDate.Year == 1 && parsedDate.Month == 1 && parsedDate.Day == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PropertyEditors;
|
||||
|
||||
public class DateTimePropertyEditorTests
|
||||
{
|
||||
// Various time formats with years below the minimum, so we expect to increase the date to the minimum supported by SQL Server.
|
||||
[TestCase("01/01/0001 10:00", "01/01/1753 10:00", "hh:mm")]
|
||||
[TestCase("01/01/0001 10:00", "01/01/1753 10:00", "HH:mm")]
|
||||
[TestCase("01/01/0001 10:00", "01/01/1753 10:00", "hh mm")]
|
||||
[TestCase("10/10/1000 10:00", "10/10/1753 10:00", "hh:mm:ss")]
|
||||
[TestCase("10/10/1000 10:00", "10/10/1753 10:00", "hh-mm-ss")]
|
||||
|
||||
// Time format with year above the minimum, so we expect to not convert.
|
||||
[TestCase("01/01/2000 10:00", "01/01/2000 10:00", "HH:mm")]
|
||||
|
||||
// Date formats, so we don't convert even if the year is below the minimum.
|
||||
[TestCase("01/01/0001 10:00", "01/01/0001 10:00", "dd-MM-yyyy hh:mm")]
|
||||
[TestCase("01/01/0001 10:00", "01/01/0001 10:00", "dd-MM-yyyy")]
|
||||
[TestCase("01/01/0001 10:00", "01/01/0001 10:00", "yyyy-MM-d")]
|
||||
public void Time_Only_Format_Ensures_DateTime_Can_Be_Persisted(DateTime actualDateTime, DateTime expectedDateTime, string format)
|
||||
{
|
||||
var dateTimePropertyEditor = new DateTimePropertyEditor.DateTimePropertyValueEditor(
|
||||
Mock.Of<IShortStringHelper>(),
|
||||
Mock.Of<IJsonSerializer>(),
|
||||
Mock.Of<IIOHelper>(),
|
||||
new DataEditorAttribute("Alias") { ValueType = ValueTypes.DateTime.ToString() });
|
||||
|
||||
Dictionary<string, object> dictionary = new Dictionary<string, object> { { DateTimePropertyEditor.DateTimePropertyValueEditor.DateTypeConfigurationFormatKey, format } };
|
||||
ContentPropertyData propertyData = new ContentPropertyData(actualDateTime, dictionary);
|
||||
var value = (DateTime)dateTimePropertyEditor.FromEditor(propertyData, null);
|
||||
|
||||
Assert.AreEqual(expectedDateTime, value);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user