diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs index 35a06c9c1d..ac6e44ed6c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs @@ -2,27 +2,57 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; +using Microsoft.Extensions.Logging; using Volo.Abp.Timing; namespace Volo.Abp.AspNetCore.Mvc.ModelBinding; public class AbpDateTimeModelBinder : IModelBinder { + private readonly ILogger _logger; private readonly DateTimeModelBinder _dateTimeModelBinder; private readonly IClock _clock; + private readonly ICurrentTimezoneProvider _currentTimezoneProvider; + private readonly ITimezoneProvider _timezoneProvider; - public AbpDateTimeModelBinder(IClock clock, DateTimeModelBinder dateTimeModelBinder) + public AbpDateTimeModelBinder( + ILogger logger, + DateTimeModelBinder dateTimeModelBinder, + IClock clock, + ICurrentTimezoneProvider currentTimezoneProvider, + ITimezoneProvider timezoneProvider) { - _clock = clock; + _logger = logger; _dateTimeModelBinder = dateTimeModelBinder; + _clock = clock; + _currentTimezoneProvider = currentTimezoneProvider; + _timezoneProvider = timezoneProvider; } public async Task BindModelAsync(ModelBindingContext bindingContext) { await _dateTimeModelBinder.BindModelAsync(bindingContext); - if (bindingContext.Result.IsModelSet && bindingContext.Result.Model is DateTime dateTime) + + if (!bindingContext.Result.IsModelSet || bindingContext.Result.Model is not DateTime dateTime) { - bindingContext.Result = ModelBindingResult.Success(_clock.Normalize(dateTime)); + return; } + + if (dateTime.Kind == DateTimeKind.Unspecified && + _clock.SupportsMultipleTimezone && + !_currentTimezoneProvider.TimeZone.IsNullOrWhiteSpace()) + { + try + { + var timezoneInfo = _timezoneProvider.GetTimeZoneInfo(_currentTimezoneProvider.TimeZone); + dateTime = new DateTimeOffset(dateTime, timezoneInfo.GetUtcOffset(dateTime)).UtcDateTime; + } + catch + { + _logger.LogWarning("Could not convert DateTime with unspecified Kind using timezone '{TimeZone}'.", _currentTimezoneProvider.TimeZone); + } + } + + bindingContext.Result = ModelBindingResult.Success(_clock.Normalize(dateTime)); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinderProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinderProvider.cs index a4c3ee2288..a836838cd1 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinderProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinderProvider.cs @@ -50,6 +50,6 @@ public class AbpDateTimeModelBinderProvider : IModelBinderProvider { const DateTimeStyles supportedStyles = DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AdjustToUniversal; var dateTimeModelBinder = new DateTimeModelBinder(supportedStyles, context.Services.GetRequiredService()); - return new AbpDateTimeModelBinder(context.Services.GetRequiredService(), dateTimeModelBinder); + return ActivatorUtilities.CreateInstance(context.Services, dateTimeModelBinder); } } diff --git a/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs b/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs index c95660e484..55c270d14e 100644 --- a/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs +++ b/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs @@ -2,6 +2,8 @@ using System.Globalization; using System.Linq; using System.Reflection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -15,15 +17,24 @@ namespace Volo.Abp.Json.Newtonsoft; public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency { private const string DefaultDateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK"; + + public ILogger Logger { get; set; } + private readonly DateTimeStyles _dateTimeStyles = DateTimeStyles.RoundtripKind; private readonly CultureInfo _culture = CultureInfo.InvariantCulture; private readonly IClock _clock; private readonly AbpJsonOptions _options; + private readonly ICurrentTimezoneProvider _currentTimezoneProvider; + private readonly ITimezoneProvider _timezoneProvider; private bool _skipDateTimeNormalization; - public AbpDateTimeConverter(IClock clock, IOptions options) + public AbpDateTimeConverter(IClock clock, IOptions options, ICurrentTimezoneProvider currentTimezoneProvider, ITimezoneProvider timezoneProvider) { + Logger = NullLogger.Instance; + _clock = clock; + _currentTimezoneProvider = currentTimezoneProvider; + _timezoneProvider = timezoneProvider; _options = options.Value; } @@ -41,19 +52,14 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { var nullable = Nullable.GetUnderlyingType(objectType) != null; - if (reader.TokenType == JsonToken.Null) + switch (reader.TokenType) { - if (!nullable) - { + case JsonToken.Null when !nullable: throw new JsonSerializationException($"Cannot convert null value to {objectType.FullName}."); - } - - return null; - } - - if (reader.TokenType == JsonToken.Date) - { - return Normalize(reader.Value!.To()); + case JsonToken.Null: + return null; + case JsonToken.Date: + return Normalize(reader.Value!.To()); } if (reader.TokenType != JsonToken.String) @@ -108,7 +114,7 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency } } - static internal bool ShouldNormalize(MemberInfo member, JsonProperty property) + internal static bool ShouldNormalize(MemberInfo member, JsonProperty property) { if (property.PropertyType != typeof(DateTime) && property.PropertyType != typeof(DateTime?)) @@ -121,6 +127,23 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency protected virtual DateTime Normalize(DateTime dateTime) { + if (dateTime.Kind != DateTimeKind.Unspecified || + !_clock.SupportsMultipleTimezone || + _currentTimezoneProvider.TimeZone.IsNullOrWhiteSpace()) + { + return _skipDateTimeNormalization ? dateTime : _clock.Normalize(dateTime); + } + + try + { + var timezoneInfo = _timezoneProvider.GetTimeZoneInfo(_currentTimezoneProvider.TimeZone); + dateTime = new DateTimeOffset(dateTime, timezoneInfo.GetUtcOffset(dateTime)).UtcDateTime; + } + catch + { + Logger.LogWarning("Could not convert DateTime with unspecified Kind using timezone '{TimeZone}'.", _currentTimezoneProvider.TimeZone); + } + return _skipDateTimeNormalization ? dateTime : _clock.Normalize(dateTime); diff --git a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs index 3feea8d299..d796c3183f 100644 --- a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs +++ b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs @@ -1,84 +1,43 @@ using System; -using System.Globalization; using System.Linq; using System.Text.Json; -using System.Text.Json.Serialization; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.Timing; namespace Volo.Abp.Json.SystemTextJson.JsonConverters; -public class AbpDateTimeConverter : JsonConverter, ITransientDependency +public class AbpDateTimeConverter : AbpDateTimeConverterBase, ITransientDependency { - private readonly IClock _clock; - private readonly AbpJsonOptions _options; - private bool _skipDateTimeNormalization; - - public AbpDateTimeConverter(IClock clock, IOptions abpJsonOptions) + public AbpDateTimeConverter( + IClock clock, + IOptions abpJsonOptions, + ICurrentTimezoneProvider currentTimezoneProvider, + ITimezoneProvider timezoneProvider) + : base(clock, abpJsonOptions, currentTimezoneProvider, timezoneProvider) { - _clock = clock; - _options = abpJsonOptions.Value; } public virtual AbpDateTimeConverter SkipDateTimeNormalization() { - _skipDateTimeNormalization = true; + IsSkipDateTimeNormalization = true; return this; } public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (_options.InputDateTimeFormats.Any()) - { - if (reader.TokenType == JsonTokenType.String) - { - foreach (var format in _options.InputDateTimeFormats) - { - var s = reader.GetString(); - if (DateTime.TryParseExact(s, format, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d1)) - { - return Normalize(d1); - } - } - } - else - { - throw new JsonException("Reader's TokenType is not String!"); - } - } - - if (reader.TryGetDateTime(out var d3)) - { - return Normalize(d3); - } - - var dateText = reader.GetString(); - if (!dateText.IsNullOrWhiteSpace()) + if (Options.InputDateTimeFormats.Any() && reader.TokenType != JsonTokenType.String) { - if (DateTime.TryParse(dateText, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d4)) - { - return Normalize(d4); - } + throw new JsonException("Reader's TokenType is not String!"); } - throw new JsonException("Can't get datetime from the reader!"); + return TryReadDateTime(ref reader, out var result) + ? result + : throw new JsonException("Can't get datetime from the reader!"); } public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { - if (_options.OutputDateTimeFormat.IsNullOrWhiteSpace()) - { - writer.WriteStringValue(Normalize(value)); - } - else - { - writer.WriteStringValue(Normalize(value).ToString(_options.OutputDateTimeFormat, CultureInfo.CurrentUICulture)); - } - } - - protected virtual DateTime Normalize(DateTime dateTime) - { - return _skipDateTimeNormalization ? dateTime : _clock.Normalize(dateTime); + WriteDateTime(writer, value); } } diff --git a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs new file mode 100644 index 0000000000..ee39a66678 --- /dev/null +++ b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs @@ -0,0 +1,116 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Volo.Abp.Timing; + +namespace Volo.Abp.Json.SystemTextJson.JsonConverters; + +public abstract class AbpDateTimeConverterBase : JsonConverter +{ + public ILogger> Logger { get; set; } + + protected IClock Clock { get; } + protected AbpJsonOptions Options { get; } + protected ICurrentTimezoneProvider CurrentTimezoneProvider { get; } + protected ITimezoneProvider TimezoneProvider { get; } + protected bool IsSkipDateTimeNormalization { get; set; } + + protected AbpDateTimeConverterBase( + IClock clock, + IOptions abpJsonOptions, + ICurrentTimezoneProvider currentTimezoneProvider, + ITimezoneProvider timezoneProvider) + { + Logger = NullLogger>.Instance; + + Clock = clock; + CurrentTimezoneProvider = currentTimezoneProvider; + TimezoneProvider = timezoneProvider; + Options = abpJsonOptions.Value; + } + + protected bool TryReadDateTime(ref Utf8JsonReader reader, out DateTime value) + { + value = default; + + if (Options.InputDateTimeFormats.Any()) + { + if (reader.TokenType != JsonTokenType.String) + { + return false; + } + + var s = reader.GetString(); + foreach (var format in Options.InputDateTimeFormats) + { + if (!DateTime.TryParseExact(s, format, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d1)) + { + continue; + } + + value = Normalize(d1); + return true; + } + } + + if (reader.TryGetDateTime(out var d2)) + { + value = Normalize(d2); + return true; + } + + var dateText = reader.GetString(); + if (dateText.IsNullOrWhiteSpace()) + { + return false; + } + + if (!DateTime.TryParse(dateText, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d3)) + { + return false; + } + + value = Normalize(d3); + return true; + + } + + protected void WriteDateTime(Utf8JsonWriter writer, DateTime value) + { + if (Options.OutputDateTimeFormat.IsNullOrWhiteSpace()) + { + writer.WriteStringValue(Normalize(value)); + } + else + { + writer.WriteStringValue(Normalize(value).ToString(Options.OutputDateTimeFormat, CultureInfo.CurrentUICulture)); + } + } + + protected virtual DateTime Normalize(DateTime dateTime) + { + if (dateTime.Kind != DateTimeKind.Unspecified || + !Clock.SupportsMultipleTimezone || + CurrentTimezoneProvider.TimeZone.IsNullOrWhiteSpace()) + { + return IsSkipDateTimeNormalization ? dateTime : Clock.Normalize(dateTime); + } + + try + { + var timezoneInfo = TimezoneProvider.GetTimeZoneInfo(CurrentTimezoneProvider.TimeZone); + dateTime = new DateTimeOffset(dateTime, timezoneInfo.GetUtcOffset(dateTime)).UtcDateTime; + } + catch + { + Logger.LogWarning("Could not convert DateTime with unspecified Kind using timezone '{TimeZone}'.", CurrentTimezoneProvider.TimeZone); + } + + return IsSkipDateTimeNormalization ? dateTime : Clock.Normalize(dateTime); + } +} diff --git a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs index e73f39d097..e1125b5040 100644 --- a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs +++ b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs @@ -1,65 +1,39 @@ using System; -using System.Globalization; using System.Linq; using System.Text.Json; -using System.Text.Json.Serialization; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.Timing; namespace Volo.Abp.Json.SystemTextJson.JsonConverters; -public class AbpNullableDateTimeConverter : JsonConverter, ITransientDependency +public class AbpNullableDateTimeConverter : AbpDateTimeConverterBase, ITransientDependency { - private readonly IClock _clock; - private readonly AbpJsonOptions _options; - private bool _skipDateTimeNormalization; - - public AbpNullableDateTimeConverter(IClock clock, IOptions abpJsonOptions) + public AbpNullableDateTimeConverter( + IClock clock, + IOptions abpJsonOptions, + ICurrentTimezoneProvider currentTimezoneProvider, + ITimezoneProvider timezoneProvider) + : base(clock, abpJsonOptions, currentTimezoneProvider, timezoneProvider) { - _clock = clock; - _options = abpJsonOptions.Value; } public virtual AbpNullableDateTimeConverter SkipDateTimeNormalization() { - _skipDateTimeNormalization = true; + IsSkipDateTimeNormalization = true; return this; } public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (_options.InputDateTimeFormats.Any()) - { - if (reader.TokenType == JsonTokenType.String) - { - foreach (var format in _options.InputDateTimeFormats) - { - var s = reader.GetString(); - if (DateTime.TryParseExact(s, format, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d1)) - { - return Normalize(d1); - } - } - } - else - { - throw new JsonException("Reader's TokenType is not String!"); - } - } - - if (reader.TryGetDateTime(out var d2)) + if (Options.InputDateTimeFormats.Any() && reader.TokenType != JsonTokenType.String) { - return Normalize(d2); + throw new JsonException("Reader's TokenType is not String!"); } - var dateText = reader.GetString(); - if (!dateText.IsNullOrWhiteSpace()) + if (TryReadDateTime(ref reader, out var result)) { - if (DateTime.TryParse(dateText, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d3)) - { - return Normalize(d3); - } + return result; } return null; @@ -70,22 +44,9 @@ public class AbpNullableDateTimeConverter : JsonConverter, ITransient if (value == null) { writer.WriteNullValue(); + return; } - else - { - if (_options.OutputDateTimeFormat.IsNullOrWhiteSpace()) - { - writer.WriteStringValue(Normalize(value.Value)); - } - else - { - writer.WriteStringValue(Normalize(value.Value).ToString(_options.OutputDateTimeFormat, CultureInfo.CurrentUICulture)); - } - } - } - protected virtual DateTime Normalize(DateTime dateTime) - { - return _skipDateTimeNormalization ? dateTime : _clock.Normalize(dateTime); + WriteDateTime(writer, value.Value); } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs index 6dfe3a7281..ceddc0083e 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs @@ -13,12 +13,24 @@ public class ModelBindingController : AbpController return input.Kind.ToString().ToLower(); } + [HttpGet("DateTimeKind_WithResult")] + public string DateTimeKind_WithResult(DateTime input) + { + return input.Kind.ToString().ToLower() + "_" + input.ToString("O").ToLower(); + } + [HttpGet("NullableDateTimeKind")] public string NullableDateTimeKind(DateTime? input) { return input.Value.Kind.ToString().ToLower(); } + [HttpGet("NullableDateTimeKind_WithResult")] + public string NullableDateTimeKind_WithResult(DateTime? input) + { + return input.Value.Kind.ToString().ToLower() + "_" + input.Value.ToString("O").ToLower(); + } + [HttpGet("DisableDateTimeNormalizationDateTimeKind")] public string DisableDateTimeNormalizationDateTimeKind([DisableDateTimeNormalization] DateTime input) { @@ -40,6 +52,19 @@ public class ModelBindingController : AbpController input.InnerModel.Time4.Kind.ToString().ToLower(); } + [HttpGet("ComplexTypeDateTimeKind_WithResult")] + public string ComplexTypeDateTimeKind_WithResult(GetDateTimeKindModel input) + { + return input.Time1.Kind.ToString().ToLower() + "_" + + input.Time1.ToString("O").ToLower() + "_" + + input.Time2.Kind.ToString().ToLower() + "_" + + input.Time2.ToString("O").ToLower() + "_" + + input.Time3.Value.Kind.ToString().ToLower() + "_" + + input.Time3.Value.ToString("O").ToLower() + "_" + + input.InnerModel.Time4.Kind.ToString().ToLower() + "_" + + input.InnerModel.Time4.ToString("O").ToLower(); + } + //JSON input and output. [HttpPost("ComplexTypeDateTimeKind_JSON")] public string ComplexTypeDateTimeKind_JSON([FromBody] GetDateTimeKindModel input) @@ -50,6 +75,20 @@ public class ModelBindingController : AbpController input.InnerModel.Time4.Kind.ToString().ToLower(); } + //JSON input and output. + [HttpPost("ComplexTypeDateTimeKind_JSON_WithResult")] + public string ComplexTypeDateTimeKind_JSON_WithResult([FromBody] GetDateTimeKindModel input) + { + return input.Time1.Kind.ToString().ToLower() + "_" + + input.Time1.ToString("O").ToLower() + "_" + + input.Time2.Kind.ToString().ToLower() + "_" + + input.Time2.ToString("O").ToLower() + "_" + + input.Time3.Value.Kind.ToString().ToLower() + "_" + + input.Time3.Value.ToString("O").ToLower() + "_" + + input.InnerModel.Time4.Kind.ToString().ToLower() + "_" + + input.InnerModel.Time4.ToString("O").ToLower(); + } + [HttpPost("Guid_Json_Test")] public GuidJsonModel Guid_Json_Test([FromBody] GuidJsonModel input) { diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs index edfdc4ff73..2fd1130474 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs @@ -19,34 +19,79 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase [Fact] public async Task DateTimeKind_Test() { - var response = await Client.GetAsync("/api/model-Binding-test/DateTimeKind?input=2010-01-01T00:00:00Z"); + var response = await Client.GetAsync("/api/model-Binding-test/DateTimeKind?input=2020-01-01T00:00:00Z"); response.StatusCode.ShouldBe(HttpStatusCode.OK); var resultAsString = await response.Content.ReadAsStringAsync(); resultAsString.ShouldBe(Kind.ToString().ToLower()); } + [Fact] + public async Task DateTimeKind_WithTimezone_Test() + { + var response = await Client.GetAsync("/api/model-Binding-test/DateTimeKind_WithResult?input=2020-01-01T00:00:00&__timezone=Europe/Istanbul"); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + var resultAsString = await response.Content.ReadAsStringAsync(); + + var dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); + switch (Kind) + { + case DateTimeKind.Utc: + dateTime = new DateTime(2019, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 + break; + case DateTimeKind.Local: + dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Local); + break; + } + + resultAsString.ShouldBe($"{Kind.ToString().ToLower()}_{dateTime.ToString("O").ToLower()}"); + } + [Fact] public async Task NullableDateTimeKind_Test() { var response = - await Client.GetAsync("/api/model-Binding-test/NullableDateTimeKind?input=2010-01-01T00:00:00Z"); + await Client.GetAsync("/api/model-Binding-test/NullableDateTimeKind?input=2020-01-01T00:00:00Z"); response.StatusCode.ShouldBe(HttpStatusCode.OK); var resultAsString = await response.Content.ReadAsStringAsync(); resultAsString.ShouldBe(Kind.ToString().ToLower()); } + [Fact] + public async Task NullableDateTimeKind_WithTimezone_Test() + { + var response = + await Client.GetAsync("/api/model-Binding-test/NullableDateTimeKind_WithResult?input=2020-01-01T00:00:00&__timezone=Europe/Istanbul"); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + var resultAsString = await response.Content.ReadAsStringAsync(); + + var dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); + switch (Kind) + { + case DateTimeKind.Utc: + dateTime = new DateTime(2019, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 + break; + case DateTimeKind.Local: + dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Local); + break; + } + + resultAsString.ShouldBe($"{Kind.ToString().ToLower()}_{dateTime.ToString("O").ToLower()}"); + } + [Fact] public async Task DisableDateTimeNormalizationDateTimeKind_Test() { var response = await Client.GetAsync( - "/api/model-Binding-test/DisableDateTimeNormalizationDateTimeKind?input=2010-01-01T00:00:00Z"); + "/api/model-Binding-test/DisableDateTimeNormalizationDateTimeKind?input=2020-01-01T00:00:00Z"); response.StatusCode.ShouldBe(HttpStatusCode.OK); var resultAsString = await response.Content.ReadAsStringAsync(); - //Time parameter(2010-01-01T00:00:00Z) with time zone information, so the default Kind is UTC + //Time parameter(2020-01-01T00:00:00Z) with time zone information, so the default Kind is UTC //https://docs.microsoft.com/en-us/aspnet/core/migration/31-to-50?view=aspnetcore-3.1&tabs=visual-studio#datetime-values-are-model-bound-as-utc-times resultAsString.ShouldBe(DateTimeKind.Utc.ToString().ToLower()); } @@ -56,11 +101,11 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase { var response = await Client.GetAsync( - "/api/model-Binding-test/DisableDateTimeNormalizationNullableDateTimeKind?input=2010-01-01T00:00:00Z"); + "/api/model-Binding-test/DisableDateTimeNormalizationNullableDateTimeKind?input=2020-01-01T00:00:00Z"); response.StatusCode.ShouldBe(HttpStatusCode.OK); var resultAsString = await response.Content.ReadAsStringAsync(); - //Time parameter(2010-01-01T00:00:00Z) with time zone information, so the default Kind is UTC + //Time parameter(2020-01-01T00:00:00Z) with time zone information, so the default Kind is UTC //https://docs.microsoft.com/en-us/aspnet/core/migration/31-to-50?view=aspnetcore-3.1&tabs=visual-studio#datetime-values-are-model-bound-as-utc-times resultAsString.ShouldBe(DateTimeKind.Utc.ToString().ToLower()); } @@ -69,14 +114,14 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase public async Task ComplexTypeDateTimeKind_Test() { var response = await Client.GetAsync("/api/model-Binding-test/ComplexTypeDateTimeKind?" + - "Time1=2010-01-01T00:00:00Z&" + - "Time2=2010-01-01T00:00:00Z&" + - "Time3=2010-01-01T00:00:00Z&" + - "InnerModel.Time4=2010-01-01T00:00:00Z"); + "Time1=2020-01-01T00:00:00Z&" + + "Time2=2020-01-01T00:00:00Z&" + + "Time3=2020-01-01T00:00:00Z&" + + "InnerModel.Time4=2020-01-01T00:00:00Z"); response.StatusCode.ShouldBe(HttpStatusCode.OK); var resultAsString = await response.Content.ReadAsStringAsync(); - //Time parameter(2010-01-01T00:00:00Z) with time zone information, so the default Kind is UTC + //Time parameter(2020-01-01T00:00:00Z) with time zone information, so the default Kind is UTC //https://docs.microsoft.com/en-us/aspnet/core/migration/31-to-50?view=aspnetcore-3.1&tabs=visual-studio#datetime-values-are-model-bound-as-utc-times resultAsString.ShouldBe($"utc_{Kind.ToString().ToLower()}_{Kind.ToString().ToLower()}_utc"); } @@ -84,7 +129,7 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase [Fact] public async Task ComplexTypeDateTimeKind_JSON_Test() { - var time = DateTime.Parse("2010-01-01T00:00:00Z"); + var time = DateTime.Parse("2020-01-01T00:00:00Z"); var response = await Client.PostAsync("/api/model-Binding-test/ComplexTypeDateTimeKind_JSON", new StringContent(JsonSerializer.Serialize( new GetDateTimeKindModel @@ -104,6 +149,41 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase resultAsString.ShouldBe($"local_{Kind.ToString().ToLower()}_{Kind.ToString().ToLower()}_local"); } + [Fact] + public async Task ComplexTypeDateTimeKind_JSON_WithTimezone_Test() + { + var time = DateTime.Parse("2020-01-01T00:00:00"); + var response = await Client.PostAsync("/api/model-Binding-test/ComplexTypeDateTimeKind_JSON_WithResult?__timezone=Europe/Istanbul", + new StringContent(JsonSerializer.Serialize( + new GetDateTimeKindModel + { + Time1 = time, + Time2 = time, + Time3 = time, + InnerModel = new GetDateTimeKindModel.GetDateTimeKindInnerModel + { + Time4 = time + } + } + ), Encoding.UTF8, MimeTypes.Application.Json)); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + var resultAsString = await response.Content.ReadAsStringAsync(); + + var dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); + switch (Kind) + { + case DateTimeKind.Utc: + dateTime = new DateTime(2019, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 + break; + case DateTimeKind.Local: + dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Local); + break; + } + + resultAsString.ShouldBe($"unspecified_{time.ToString("O").ToLower()}_{Kind.ToString().ToLower()}_{dateTime.ToString("O").ToLower()}_{Kind.ToString().ToLower()}_{dateTime.ToString("O").ToLower()}_unspecified_{time.ToString("O").ToLower()}"); + } + [Fact] public async Task Guid_Json_Test() {