项目

时间处理

处理时间与时区总是充满挑战,尤其是在需要构建一个供不同时区用户使用的全球系统时。

ABP提供了基础架构,使得时间处理变得简单并尽可能实现自动化。本文档涵盖了ABP中与时间和时区相关的服务和系统。

如果您正在创建一个在单一时区运行的本地应用,可能不需要所有这些系统。但即便如此,仍建议使用本文档介绍的IClock服务。

IClock服务

DateTime.Now返回一个包含服务器本地日期和时间DateTime对象。DateTime对象不存储时区信息,因此无法知晓该对象中存储的绝对日期和时间。您只能进行假设,比如假设它是在UTC+5时区创建的。当将此值保存到数据库并在之后读取,或将其发送到不同时区的客户端时,情况会变得尤为复杂。

解决此问题的一种方法是始终使用DateTime.UtcNow,并假设所有DateTime对象均为UTC时间。这样,您可以在需要时将其转换为目标客户端的时区。

IClock在获取当前时间时提供了抽象层,使您能够在应用的单一控制点管理日期时间的类型(UTC或本地)。

示例:获取当前时间

using Volo.Abp.DependencyInjection;
using Volo.Abp.Timing;

namespace AbpDemo
{
    public class MyService : ITransientDependency
    {
        private readonly IClock _clock;

        public MyService(IClock clock)
        {
            _clock = clock;
        }

        public void Foo()
        {
            //获取当前时间!
            var now = _clock.Now;
        }
    }
}
  • 当需要获取当前时间时,注入IClock服务。常见的基类(如ApplicationService)已注入该服务并将其作为基础属性提供,因此您可以直接使用Clock
  • 使用Now属性获取当前时间。

大多数情况下,IClock是您应用中唯一需要了解和使用的服务。

时钟选项

AbpClockOptions是用于设置时钟类型的选项类。

示例:使用UTC时钟

Configure<AbpClockOptions>(options =>
{
    options.Kind = DateTimeKind.Utc;
});

将此代码写入您模块ConfigureServices方法中。

默认的KindUnspecified,这实际上会禁用时钟功能。如果您希望利用时钟系统的优势,请将其设置为UtcLocal

日期时间标准化

IClock的另一个重要功能是标准化DateTime对象。

使用示例:

DateTime dateTime = ...; //从某处获取
var normalizedDateTime = Clock.Normalize(dateTime)

Normalize方法的工作方式如下:

  • 如果当前时钟为UTC且给定的DateTime为本地时间,则将其转换为UTC(使用DateTime.ToUniversalTime()方法)。
  • 如果当前时钟为本地时间且给定的DateTime为UTC,则将其转换为本地时间(使用DateTime.ToLocalTime()方法)。
  • 如果给定的DateTimeKindUnspecified,则将其Kind设置为当前时钟的Kind(使用DateTime.SpecifyKind(...)方法)。

当ABP获取到非由IClock.Now创建且可能与当前时钟类型不兼容的DateTime时,会使用Normalize方法。例如:

禁用日期时间标准化属性

DisableDateTimeNormalization属性可用于禁用特定类或属性的标准化操作。

UTC与用户时区之间的日期时间转换

将给定的UTC转换为用户时区

DateTime ConvertToUserTime(DateTime utcDateTime)DateTimeOffset ConvertToUserTime(DateTimeOffset dateTimeOffset)方法将给定的UTC DateTimeDateTimeOffset转换为用户时区。

如果SupportsMultipleTimezonefalse,或dateTime.Kind不是Utc,或者没有时区设置,则返回给定的DateTimeDateTimeOffset,不做任何更改。

示例:

如果用户的时区设置Europe/Istanbul

// 2025-03-01T05:30:00Z
var utcTime = new DateTime(2025, 3, 1, 5, 30, 0, DateTimeKind.Utc);

var userTime = Clock.ConvertToUserTime(utcTime);

// Europe/Istanbul与UTC有3小时时差。因此,结果将晚3小时。
userTime.Kind.ShouldBe(DateTimeKind.Unspecified);
userTime.ToString("O").ShouldBe("2025-03-01T08:30:00");
// 2025-03-01T05:30:00Z
var utcTime = new DateTimeOffset(new DateTime(2025, 3, 1, 5, 30, 0, DateTimeKind.Utc), TimeSpan.Zero);

var userTime = Clock.ConvertToUserTime(utcTime);

// Europe/Istanbul与UTC有3小时时差。因此,结果将晚3小时。
userTime.Offset.ShouldBe(TimeSpan.FromHours(3));
userTime.ToString("O").ShouldBe("2025-03-01T08:30:00.0000000+03:00");

将给定的用户日期时间转换为UTC

DateTime ConvertToUtc(DateTime dateTime)方法将给定的用户DateTime转换为UTC。

如果SupportsMultipleTimezonefalse,或dateTime.KindUtc,或者没有时区设置,则返回给定的DateTime,不做任何更改。

示例:

如果用户的时区设置Europe/Istanbul

// 2025-03-01T05:30:00
var userTime = new DateTime(2025, 3, 1, 5, 30, 0, DateTimeKind.Unspecified); //与本地相同

var utcTime = Clock.ConvertToUtc(userTime);

// Europe/Istanbul与UTC有3小时时差。因此,结果将早3小时。
utcTime.Kind.ShouldBe(DateTimeKind.Utc);
utcTime.ToString("O").ShouldBe("2025-03-01T02:30:00.0000000Z");

其他IClock属性

除了NowIClock服务还具有以下属性:

  • Kind:返回当前使用的时钟类型的DateTimeKindDateTimeKind.UtcDateTimeKind.LocalDateTimeKind.Unspecified)。
  • SupportsMultipleTimezone:如果当前使用的时钟是UTC,则返回true

时区

本节涵盖ABP中与时区管理相关的基础架构。

时区设置

ABP定义了一个名为Abp.Timing.TimeZone设置,可用于为用户、租户 或全局应用设置和获取时区。默认值为空,这意味着应用将使用服务器的时区。

您可以在 设置管理UI 中更改主机/租户的全局时区。

账户专业模块支持用户在账户设置页面设置自己的时区。

有关设置系统的更多信息,请参阅设置文档

UseAbpTimeZone中间件

app.UseAbpTimeZone()中间件用于为当前请求设置时区。

* 它将从设置中获取时区,顺序为`用户` -> `租户` -> `应用/全局`。
* 如果当前请求是匿名的,它将从请求头/cookie/表单/查询字符串中获取时区,键为`__timezone`。

如果您想获取当前时区,可以注入ICurrentTimezoneProvider服务。 请将此中间件添加在身份验证之后。

ITimezoneProvider

ITimezoneProvider是一个服务,用于简单地将Windows时区ID值转换为Iana时区名称值,反之亦然。它还提供方法获取这些时区的列表,并通过给定名称获取TimeZoneInfo

该服务使用TimeZoneConverter库实现。

在本文档中