From 171f7971c4e21e894ba93c2f06a44ae575a7b2cb Mon Sep 17 00:00:00 2001 From: Super Date: Fri, 24 Oct 2025 22:56:18 +0800 Subject: [PATCH 1/4] Redo `AbpDateTimeModelBinder` to align with the current TZ For https://github.com/abpframework/abp/issues/24048. --- .../ModelBinding/AbpDateTimeModelBinder.cs | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) 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..35e1777f4c 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 @@ -20,9 +20,35 @@ public class AbpDateTimeModelBinder : IModelBinder 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 the DateTime has no timezone info (most cases from input) + if (dateTime.Kind == DateTimeKind.Unspecified) + { + // Try to get user's timezone + var userTz = _currentTimezoneProvider.TimeZone; + if (!userTz.IsNullOrWhiteSpace()) + { + try + { + var tzInfo = _timezoneProvider.GetTimeZoneInfo(userTz); + // Treat the input as user's local time and convert to UTC + var utc = TimeZoneInfo.ConvertTimeToUtc(dateTime, tzInfo); + bindingContext.Result = ModelBindingResult.Success(utc); + return; + } + catch + { + // fallback to default clock normalization if invalid TZ + } + } + } + + // fallback: original behavior + bindingContext.Result = ModelBindingResult.Success(_clock.Normalize(dateTime)); } } From 275b181fce9e908750eb9de7382726bf4ba264d6 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 3 Jan 2026 16:51:37 +0800 Subject: [PATCH 2/4] Enhance DateTime handling with timezone support in model binding and JSON converters --- .../ModelBinding/AbpDateTimeModelBinder.cs | 42 +++---- .../AbpDateTimeModelBinderProvider.cs | 5 +- .../Json/Newtonsoft/AbpDateTimeConverter.cs | 42 ++++--- .../JsonConverters/AbpDateTimeConverter.cs | 69 +++-------- .../AbpDateTimeConverterBase.cs | 110 ++++++++++++++++++ .../AbpNullableDateTimeConverter.cs | 67 +++-------- .../ModelBinding/ModelBindingController.cs | 40 +++++++ .../ModelBindingController_Tests.cs | 80 +++++++++++++ 8 files changed, 310 insertions(+), 145 deletions(-) create mode 100644 framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs 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 35e1777f4c..c63c2c582f 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 @@ -10,45 +10,41 @@ public class AbpDateTimeModelBinder : IModelBinder { private readonly DateTimeModelBinder _dateTimeModelBinder; private readonly IClock _clock; + private readonly ICurrentTimezoneProvider _currentTimezoneProvider; + private readonly ITimezoneProvider _timezoneProvider; - public AbpDateTimeModelBinder(IClock clock, DateTimeModelBinder dateTimeModelBinder) + public AbpDateTimeModelBinder(DateTimeModelBinder dateTimeModelBinder, IClock clock, ICurrentTimezoneProvider currentTimezoneProvider, ITimezoneProvider timezoneProvider) { - _clock = clock; _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 not DateTime dateTime) { return; } - - // If the DateTime has no timezone info (most cases from input) - if (dateTime.Kind == DateTimeKind.Unspecified) + + if (dateTime.Kind == DateTimeKind.Unspecified && + _clock.SupportsMultipleTimezone && + !_currentTimezoneProvider.TimeZone.IsNullOrWhiteSpace()) { - // Try to get user's timezone - var userTz = _currentTimezoneProvider.TimeZone; - if (!userTz.IsNullOrWhiteSpace()) + try { - try - { - var tzInfo = _timezoneProvider.GetTimeZoneInfo(userTz); - // Treat the input as user's local time and convert to UTC - var utc = TimeZoneInfo.ConvertTimeToUtc(dateTime, tzInfo); - bindingContext.Result = ModelBindingResult.Success(utc); - return; - } - catch - { - // fallback to default clock normalization if invalid TZ - } + var timezoneInfo = _timezoneProvider.GetTimeZoneInfo(_currentTimezoneProvider.TimeZone); + dateTime = new DateTimeOffset(dateTime, timezoneInfo.BaseUtcOffset).UtcDateTime; + } + catch + { + // ignored } } - - // fallback: original behavior + 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..3be76251c3 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,9 @@ 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 new AbpDateTimeModelBinder(dateTimeModelBinder, + context.Services.GetRequiredService(), + context.Services.GetRequiredService(), + context.Services.GetRequiredService()); } } 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..257a90e84c 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 @@ -19,11 +19,15 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency 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) { _clock = clock; + _currentTimezoneProvider = currentTimezoneProvider; + _timezoneProvider = timezoneProvider; _options = options.Value; } @@ -41,19 +45,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 +107,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 +120,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.BaseUtcOffset).UtcDateTime; + } + catch + { + // ignored + } + 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..2f3557fcbd --- /dev/null +++ b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs @@ -0,0 +1,110 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.Options; +using Volo.Abp.Timing; + +namespace Volo.Abp.Json.SystemTextJson.JsonConverters; + +public abstract class AbpDateTimeConverterBase : JsonConverter +{ + 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) + { + 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.BaseUtcOffset).UtcDateTime; + } + catch + { + // ignored + } + + 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..4093e47011 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,21 @@ 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..0d2820c59c 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 @@ -26,6 +26,28 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase resultAsString.ShouldBe(Kind.ToString().ToLower()); } + [Fact] + public async Task DateTimeKind_WithTimezone_Test() + { + var response = await Client.GetAsync("/api/model-Binding-test/DateTimeKind_WithResult?input=2010-01-01T00:00:00&__timezone=Europe/Istanbul"); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + var resultAsString = await response.Content.ReadAsStringAsync(); + + var dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); + switch(Kind) + { + case DateTimeKind.Utc: + dateTime = new DateTime(2009, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 + break; + case DateTimeKind.Local: + dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Local); + break; + } + + resultAsString.ShouldBe($"{Kind.ToString().ToLower()}_{dateTime.ToString("O").ToLower()}"); + } + [Fact] public async Task NullableDateTimeKind_Test() { @@ -37,6 +59,29 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase resultAsString.ShouldBe(Kind.ToString().ToLower()); } + [Fact] + public async Task NullableDateTimeKind_WithTimezone_Test() + { + var response = + await Client.GetAsync("/api/model-Binding-test/NullableDateTimeKind_WithResult?input=2010-01-01T00:00:00&__timezone=Europe/Istanbul"); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + var resultAsString = await response.Content.ReadAsStringAsync(); + + var dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); + switch(Kind) + { + case DateTimeKind.Utc: + dateTime = new DateTime(2009, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 + break; + case DateTimeKind.Local: + dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Local); + break; + } + + resultAsString.ShouldBe($"{Kind.ToString().ToLower()}_{dateTime.ToString("O").ToLower()}"); + } + [Fact] public async Task DisableDateTimeNormalizationDateTimeKind_Test() { @@ -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("2010-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(2010, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); + switch(Kind) + { + case DateTimeKind.Utc: + dateTime = new DateTime(2009, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 + break; + case DateTimeKind.Local: + dateTime = new DateTime(2010, 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() { From 84cf25f17b1eb068b4071a1f4fe94ecf3d69a1c7 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 3 Jan 2026 18:02:38 +0800 Subject: [PATCH 3/4] Refactor DateTime handling to use GetUtcOffset for accurate timezone adjustments in model binding and JSON converters --- .../ModelBinding/AbpDateTimeModelBinder.cs | 2 +- .../Json/Newtonsoft/AbpDateTimeConverter.cs | 2 +- .../AbpDateTimeConverterBase.cs | 2 +- .../ModelBinding/ModelBindingController.cs | 1 - .../ModelBindingController_Tests.cs | 54 +++++++++---------- 5 files changed, 30 insertions(+), 31 deletions(-) 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 c63c2c582f..109b85fe9e 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 @@ -37,7 +37,7 @@ public class AbpDateTimeModelBinder : IModelBinder try { var timezoneInfo = _timezoneProvider.GetTimeZoneInfo(_currentTimezoneProvider.TimeZone); - dateTime = new DateTimeOffset(dateTime, timezoneInfo.BaseUtcOffset).UtcDateTime; + dateTime = new DateTimeOffset(dateTime, timezoneInfo.GetUtcOffset(dateTime)).UtcDateTime; } catch { 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 257a90e84c..7b9a2cd4d3 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 @@ -130,7 +130,7 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency try { var timezoneInfo = _timezoneProvider.GetTimeZoneInfo(_currentTimezoneProvider.TimeZone); - dateTime = new DateTimeOffset(dateTime, timezoneInfo.BaseUtcOffset).UtcDateTime; + dateTime = new DateTimeOffset(dateTime, timezoneInfo.GetUtcOffset(dateTime)).UtcDateTime; } catch { 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 index 2f3557fcbd..b603353d0c 100644 --- 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 @@ -98,7 +98,7 @@ public abstract class AbpDateTimeConverterBase : JsonConverter try { var timezoneInfo = TimezoneProvider.GetTimeZoneInfo(CurrentTimezoneProvider.TimeZone); - dateTime = new DateTimeOffset(dateTime, timezoneInfo.BaseUtcOffset).UtcDateTime; + dateTime = new DateTimeOffset(dateTime, timezoneInfo.GetUtcOffset(dateTime)).UtcDateTime; } catch { 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 4093e47011..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 @@ -89,7 +89,6 @@ public class ModelBindingController : AbpController 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 0d2820c59c..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,7 +19,7 @@ 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(); @@ -29,19 +29,19 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase [Fact] public async Task DateTimeKind_WithTimezone_Test() { - var response = await Client.GetAsync("/api/model-Binding-test/DateTimeKind_WithResult?input=2010-01-01T00:00:00&__timezone=Europe/Istanbul"); + 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(2010, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); - switch(Kind) + var dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); + switch (Kind) { case DateTimeKind.Utc: - dateTime = new DateTime(2009, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 + dateTime = new DateTime(2019, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 break; case DateTimeKind.Local: - dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Local); + dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Local); break; } @@ -52,7 +52,7 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase 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(); @@ -63,19 +63,19 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase public async Task NullableDateTimeKind_WithTimezone_Test() { var response = - await Client.GetAsync("/api/model-Binding-test/NullableDateTimeKind_WithResult?input=2010-01-01T00:00:00&__timezone=Europe/Istanbul"); + 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(2010, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); - switch(Kind) + var dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); + switch (Kind) { case DateTimeKind.Utc: - dateTime = new DateTime(2009, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 + dateTime = new DateTime(2019, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 break; case DateTimeKind.Local: - dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Local); + dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Local); break; } @@ -87,11 +87,11 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase { 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()); } @@ -101,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()); } @@ -114,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"); } @@ -129,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 @@ -152,7 +152,7 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase [Fact] public async Task ComplexTypeDateTimeKind_JSON_WithTimezone_Test() { - var time = DateTime.Parse("2010-01-01T00:00:00"); + 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 @@ -170,14 +170,14 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase response.StatusCode.ShouldBe(HttpStatusCode.OK); var resultAsString = await response.Content.ReadAsStringAsync(); - var dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); - switch(Kind) + var dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); + switch (Kind) { case DateTimeKind.Utc: - dateTime = new DateTime(2009, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 + dateTime = new DateTime(2019, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 break; case DateTimeKind.Local: - dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Local); + dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Local); break; } From 028362c6728f47331aa1211b4f122225d93ac318 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 3 Jan 2026 18:35:17 +0800 Subject: [PATCH 4/4] Add logging for DateTime conversion failures --- .../Mvc/ModelBinding/AbpDateTimeModelBinder.cs | 12 ++++++++++-- .../ModelBinding/AbpDateTimeModelBinderProvider.cs | 5 +---- .../Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs | 9 ++++++++- .../JsonConverters/AbpDateTimeConverterBase.cs | 8 +++++++- 4 files changed, 26 insertions(+), 8 deletions(-) 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 109b85fe9e..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,19 +2,27 @@ 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(DateTimeModelBinder dateTimeModelBinder, IClock clock, ICurrentTimezoneProvider currentTimezoneProvider, ITimezoneProvider timezoneProvider) + public AbpDateTimeModelBinder( + ILogger logger, + DateTimeModelBinder dateTimeModelBinder, + IClock clock, + ICurrentTimezoneProvider currentTimezoneProvider, + ITimezoneProvider timezoneProvider) { + _logger = logger; _dateTimeModelBinder = dateTimeModelBinder; _clock = clock; _currentTimezoneProvider = currentTimezoneProvider; @@ -41,7 +49,7 @@ public class AbpDateTimeModelBinder : IModelBinder } catch { - // ignored + _logger.LogWarning("Could not convert DateTime with unspecified Kind using timezone '{TimeZone}'.", _currentTimezoneProvider.TimeZone); } } 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 3be76251c3..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,9 +50,6 @@ public class AbpDateTimeModelBinderProvider : IModelBinderProvider { const DateTimeStyles supportedStyles = DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AdjustToUniversal; var dateTimeModelBinder = new DateTimeModelBinder(supportedStyles, context.Services.GetRequiredService()); - return new AbpDateTimeModelBinder(dateTimeModelBinder, - context.Services.GetRequiredService(), - context.Services.GetRequiredService(), - context.Services.GetRequiredService()); + 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 7b9a2cd4d3..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,6 +17,9 @@ 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; @@ -25,6 +30,8 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency public AbpDateTimeConverter(IClock clock, IOptions options, ICurrentTimezoneProvider currentTimezoneProvider, ITimezoneProvider timezoneProvider) { + Logger = NullLogger.Instance; + _clock = clock; _currentTimezoneProvider = currentTimezoneProvider; _timezoneProvider = timezoneProvider; @@ -134,7 +141,7 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency } catch { - // ignored + Logger.LogWarning("Could not convert DateTime with unspecified Kind using timezone '{TimeZone}'.", _currentTimezoneProvider.TimeZone); } return _skipDateTimeNormalization 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 index b603353d0c..ee39a66678 100644 --- 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 @@ -3,6 +3,8 @@ 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; @@ -10,6 +12,8 @@ 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; } @@ -22,6 +26,8 @@ public abstract class AbpDateTimeConverterBase : JsonConverter ICurrentTimezoneProvider currentTimezoneProvider, ITimezoneProvider timezoneProvider) { + Logger = NullLogger>.Instance; + Clock = clock; CurrentTimezoneProvider = currentTimezoneProvider; TimezoneProvider = timezoneProvider; @@ -102,7 +108,7 @@ public abstract class AbpDateTimeConverterBase : JsonConverter } catch { - // ignored + Logger.LogWarning("Could not convert DateTime with unspecified Kind using timezone '{TimeZone}'.", CurrentTimezoneProvider.TimeZone); } return IsSkipDateTimeNormalization ? dateTime : Clock.Normalize(dateTime);