自版本 6.0 起,如果尝试保存具有非零偏移量的
DateTimeOffset
字段,NpgSQL 会引发异常。
例如:
public class User
{
public Guid Id { get; private set; }
public DateTimeOffset? LockedUntil { get; private set; }
public void Lock()
{
LockedUntil = DateTimeOffset.Now.Add(TimeSpan.FromDays(1));
}
}
// ...
builder.Property(u => u.Id);
builder.Property(u => u.Login);
// ...
var user = dbContext.Users.First(u => u.Id == someId);
user.Lock();
dbContext.SaveChanges();
这会导致异常:
System.ArgumentException: ... only offset 0 (UTC) is supported
我很好奇为什么?
我的意思是我确实知道这是有记录的行为。
同时,当类型为 timestamptz
时,Postgres 似乎可以完美地处理时区。
SELECT '2024-04-10T15:00:00.000Z'::timestamptz = '2024-04-10T13:00:00.000-02:00'::timestamptz;
--> true
create table test_timestamptz(some_field timestamptz);
INSERT INTO test_timestamptz(some_field)
VALUES ('2024-04-10T15:00:00.000Z'), ('2024-04-10T13:00:00.000-02:00'), ('2024-04-10T20:00:00.000+05:00');
SELECT COUNT(*) FROM test_timestamptz WHERE some_field = '2024-04-10T15:00:00.000Z';
--> 3
SELECT COUNT(*) FROM test_timestamptz WHERE some_field = '2024-04-10T16:00:00.000+01:00';
--> 3
在我的项目中,我希望能够使用具有不同时区的时间值(特别是比较它们)。
DateTimeOffset
给了我这个并隐藏了 TZ 数学。
毕竟我最终得到了这种转换器:
public class DateTimeOffsetToDateTimeConverter : ValueConverter<DateTimeOffset, DateTime>
{
public DateTimeOffsetToDateTimeConverter() : base(
v => v.UtcDateTime,
v => new DateTimeOffset(v))
{
}
}
这可能是某种技术限制吗?或者我误解了什么(C# 不是我的主要 PL)
这在 Npgsql 文档中有介绍:
用户常见的错误是认为 PostgreSQL
类型将时区存储在数据库中。情况并非如此:仅存储 UTC 时间戳。没有一个 PostgreSQL 类型可以像 .NET DateTimeOffset 那样同时存储日期/时间和时区。要在数据库中存储时区,请添加包含时区 ID 的单独文本列。timestamp with time zone
来自 PostgreSQL 文档:
对于
,内部存储的值始终采用 UTC(通用协调时间,传统上称为格林威治标准时间,GMT)。指定了显式时区的输入值将使用该时区的适当偏移量转换为 UTC。如果输入字符串中未指定时区,则假定其处于系统 TimeZone 参数指示的时区,并使用时区的偏移量将其转换为 UTC。timestamp with time zone
当输出带有时区值的时间戳时,它始终从 UTC 转换为当前时区,并显示为该区域的本地时间。要查看另一个时区的时间,请更改时区或使用 AT TIME ZONE 构造(请参阅第 9.9.4 节)。
还有
我们不建议使用
(尽管 PostgreSQL 支持旧版应用程序并符合 SQL 标准)。对于仅包含日期或时间的任何类型,PostgreSQL 都会假定您的本地时区。type time with time zone
因此 Npgsql 从 6.0 开始引入的这种行为是为了避免潜在的混乱。如果您愿意,可以使用开关启用旧版驱动程序行为:
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);