Необходимо глубокое понимание обработки NpgSQL DateTimeOffset

Начиная с версии 6.0, Npgsql выдает исключение при попытке сохранить поле DateTimeOffset с ненулевым смещением.

Например:

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 Мне было интересно, почему? Я имею в виду, что знаю, что это задокументированное поведение. Между тем, похоже, что Postgres прекрасно работает с часовыми поясами, если тип timestamptz.

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 дает мне это и скрывает математику ТЗ. В конце концов у меня получился вот такой конвертер:

public class DateTimeOffsetToDateTimeConverter : ValueConverter<DateTimeOffset, DateTime>
{
  public DateTimeOffsetToDateTimeConverter() : base(
    v => v.UtcDateTime,
    v => new DateTimeOffset(v))
  {
  }
}

Может быть это какое-то техническое ограничение? Или я что-то неправильно понял (C# не мой основной PL)

🤔 А знаете ли вы, что...
C# поддерживает LINQ (Language Integrated Query) для удобного запроса и обработки данных.


1
88
1

Ответ:

Решено

Это описано в документации Npgsql:

Распространенной ошибкой пользователей является мнение, что тип PostgreSQL timestamp with time zone хранит часовой пояс в базе данных. Это не так: сохраняется только временная метка UTC. Не существует единого типа PostgreSQL, который хранил бы одновременно дату/время и часовой пояс, подобно .NET DateTimeOffset. Чтобы сохранить часовой пояс в базе данных, добавьте отдельный текстовый столбец, содержащий идентификатор часового пояса.

И из документации PostgreSQL:

Для timestamp with time zone внутреннее значение всегда находится в формате UTC (всемирное координированное время, традиционно известное как среднее время по Гринвичу, GMT). Входное значение, для которого явно указан часовой пояс, преобразуется в формат UTC с использованием соответствующего смещения для этого часового пояса. Если во входной строке не указан часовой пояс, то предполагается, что он находится в часовом поясе, указанном системным параметром TimeZone, и преобразуется в формат UTC с использованием смещения часового пояса.

Когда выводится временная метка со значением часового пояса, она всегда преобразуется из UTC в текущий часовой пояс и отображается как местное время в этом поясе. Чтобы увидеть время в другом часовом поясе, либо измените часовой пояс, либо используйте конструкцию AT TIME ZONE (см. раздел 9.9.4).

И

Мы не рекомендуем использовать type time with time zone (хотя PostgreSQL поддерживает его для устаревших приложений и для соответствия стандарту SQL). PostgreSQL предполагает ваш местный часовой пояс для любого типа, содержащего только дату или время.

Что можно наблюдать с помощью:

select *
from test_timestamptz;

Что дает мне:

какое-то_поле 2024-04-10 15:00:00.000000 +00:00 2024-04-10 15:00:00.000000 +00:00 2024-04-10 15:00:00.000000 +00:00

Таким образом, поведение, включенное в Npgsql начиная с версии 6.0, предназначено для того, чтобы избежать потенциальной путаницы.

Если желательно поведение версии до 6.0, нет необходимости в специальном конвертере — вы можете включить поведение устаревшего драйвера с помощью переключателя:

AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);