mirror of https://github.com/abpframework/abp.git
Browse Source
Add an ITimezoneProvider.ConvertUnspecifiedToUtc helper that computes the UTC ticks and keeps boundary values unchanged instead of throwing, then use it in the System.Text.Json and Newtonsoft converters and the MVC model binder. Normalize the Volo.Docs commit date filters to UTC for consistency.pull/25703/head
7 changed files with 113 additions and 26 deletions
@ -0,0 +1,37 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Timing; |
|||
|
|||
public static class TimezoneProviderExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Interprets <paramref name="dateTime"/> as local time in <paramref name="windowsOrIanaTimeZoneId"/>
|
|||
/// and converts it to its UTC equivalent. The caller is expected to pass a
|
|||
/// <see cref="DateTimeKind.Unspecified"/> value; the kind is not inspected, so a value is always
|
|||
/// treated as wall-clock time in the given timezone regardless of its kind.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Returns <paramref name="dateTime"/> unchanged when applying the timezone offset would move it
|
|||
/// outside the supported <see cref="DateTime"/> range. This happens for values within the offset
|
|||
/// distance of <see cref="DateTime.MinValue"/>/<see cref="DateTime.MaxValue"/>, typically the
|
|||
/// <see cref="DateTime.MinValue"/> placeholder that does not represent a real instant. Computing the
|
|||
/// UTC ticks directly avoids the <see cref="ArgumentOutOfRangeException"/> that
|
|||
/// <c>new DateTimeOffset(dateTime, offset)</c> would throw for such values.
|
|||
/// </remarks>
|
|||
public static DateTime ConvertUnspecifiedToUtc( |
|||
this ITimezoneProvider timezoneProvider, |
|||
DateTime dateTime, |
|||
string windowsOrIanaTimeZoneId) |
|||
{ |
|||
Check.NotNull(timezoneProvider, nameof(timezoneProvider)); |
|||
|
|||
var timezoneInfo = timezoneProvider.GetTimeZoneInfo(windowsOrIanaTimeZoneId); |
|||
var utcTicks = dateTime.Ticks - timezoneInfo.GetUtcOffset(dateTime).Ticks; |
|||
if (utcTicks < DateTime.MinValue.Ticks || utcTicks > DateTime.MaxValue.Ticks) |
|||
{ |
|||
return dateTime; |
|||
} |
|||
|
|||
return new DateTime(utcTicks, DateTimeKind.Utc); |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
using System; |
|||
using Shouldly; |
|||
using Volo.Abp.Testing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Timing; |
|||
|
|||
public class TimezoneProviderExtensions_Tests : AbpIntegratedTest<AbpTimingTestModule> |
|||
{ |
|||
private readonly ITimezoneProvider _timezoneProvider; |
|||
|
|||
public TimezoneProviderExtensions_Tests() |
|||
{ |
|||
_timezoneProvider = GetRequiredService<ITimezoneProvider>(); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData("Asia/Shanghai")] // +08:00
|
|||
[InlineData("Europe/Brussels")] // +01:00 / +02:00
|
|||
public void Should_Keep_MinValue_Unchanged_Under_Positive_Offset(string timeZoneId) |
|||
{ |
|||
// A positive offset would push DateTime.MinValue below the supported range; keep it as-is.
|
|||
var result = _timezoneProvider.ConvertUnspecifiedToUtc(DateTime.MinValue, timeZoneId); |
|||
|
|||
result.ShouldBe(DateTime.MinValue); |
|||
result.Kind.ShouldBe(DateTimeKind.Unspecified); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Keep_Value_Near_MinValue_Unchanged_Under_Positive_Offset() |
|||
{ |
|||
var nearMin = DateTime.MinValue.AddHours(3); // 0001-01-01T03:00:00 - 08:00 underflows
|
|||
|
|||
var result = _timezoneProvider.ConvertUnspecifiedToUtc(nearMin, "Asia/Shanghai"); |
|||
|
|||
result.ShouldBe(nearMin); |
|||
result.Kind.ShouldBe(DateTimeKind.Unspecified); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Keep_MaxValue_Unchanged_Under_Negative_Offset() |
|||
{ |
|||
var result = _timezoneProvider.ConvertUnspecifiedToUtc(DateTime.MaxValue, "America/New_York"); |
|||
|
|||
result.ShouldBe(DateTime.MaxValue); |
|||
result.Kind.ShouldBe(DateTimeKind.Unspecified); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Convert_Unspecified_Value_To_Utc_Using_Offset() |
|||
{ |
|||
var unspecified = new DateTime(2026, 6, 27, 18, 0, 0, DateTimeKind.Unspecified); |
|||
|
|||
var result = _timezoneProvider.ConvertUnspecifiedToUtc(unspecified, "Asia/Shanghai"); // +08:00
|
|||
|
|||
// 18:00 in +08:00 == 10:00 UTC.
|
|||
result.ShouldBe(new DateTime(2026, 6, 27, 10, 0, 0, DateTimeKind.Utc)); |
|||
result.Kind.ShouldBe(DateTimeKind.Utc); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue