Неожиданное форматирование столбца timestamptz PostgreSQL в функции, возвращающей json

У меня есть функция, возвращающая json, содержащая timestamptz. Когда я выполняю это в консоли Datagrip, я получаю json типа

{..., "time_created":"2024-05-15T11:14:23.384266+00:00",...}.

Но когда я выполняю ту же функцию на C# с помощью ExecuteScalar, возвращаемое значение похоже на

{..., "time_created":"2024-05-15T09:14:23.384266+02:00",...}

Он ведет себя одинаково, даже если функция возвращает json, предварительно приведенное к text. Обратите внимание, что временные метки преобразуются в часовой пояс сервера и смещение времени сервера, если оно добавлено. Это невозможно отобразить в элементах управления Quasar без изменений.

Что заставляет ExecuteScalar выполнять преобразование, даже если text возвращается из функции?

Если функция возвращает таблицу, содержащую timestamptz, значения возвращаются правильно, а сериализация создает значения в форме, например. "time_created":"2024-05-15T11:14:23.384266Z", который можно нормально отобразить при вводе Quasar.

Есть ли способ заставить все, что конвертирует строки json во время чтения с помощью ExecuteScalar, делать это с помощью ...Z?

Функция

create function data_survey_r("Key" integer) returns text language plpgsql as $$ 
BEGIN
   RETURN (SELECT ROW_TO_JSON(t)::text FROM data.survey t WHERE t.id = "Key");
END $$;

или

create function data_survey_r("Key" integer) returns json language plpgsql as $$ 
BEGIN
   RETURN (SELECT ROW_TO_JSON(t) FROM data.survey t WHERE t.id = "Key");
END $$;

Он вызывается из C# как

ret = (string) await connection.ExecuteScalarAsync("data_survey_r", new { Key = key }, commandType: CommandType.StoredProcedure);

Обновлено: (Текст из этого комментария ):

create function test_now () returns json as $$ 
BEGIN   
   return (SELECT json_build_object('now', now())); 
END $$ 
language plpgsql  

С#

NpgsqlConnection con = new NpgsqlConnection(_options.ConnectionString); 
con.Open(); 
var ret = await con.ExecuteScalarAsync("test_now");  

Результат: {"now" : "2024-06-22T12:53:30.515689+00:00"}

в консоли, {"now" : "2024-06-22T14:53:40.079963+02:00"}

Покажите нам определение вашего (PL/pgSQL?) FUNCTION.

Dai 22.06.2024 14:02

поэтому значения времени точно такие же, но когда вы используете pgadmin4, вы получаете значение UTC (+00:00), а в противном случае вы получаете значение, скорректированное для вашего часового пояса (+02:00). Разве это не автоматическая настройка сеанса в pgadmin4?

Ivan Petrov 22.06.2024 14:20

Добавил фрагмент кода в вопрос. pgAdmin использовался как «общее» имя, на самом деле я использую консоль DataGrip. Меня озадачивает то, что какое-то промежуточное программное обеспечение изменяет возвращаемый текст в ExecuteScalar.

Vedran Mornar 22.06.2024 14:32

@VedranMornar, и вы создали простую тестовую функцию «DateTime.Now» (RETURN NOW())… которая ведет себя так, как вы хотите, при одинаковом вызове для двух сценариев из вашего примера?

Ivan Petrov 22.06.2024 14:38

pg: create function test_now () returns json as $$ BEGIN return (SELECT json_build_object('now', now())); END $$ language plpgsql C# NpgsqlConnection con = new NpgsqlConnection(_options.ConnectionString); con.Open(); var ret = await con.ExecuteScalarAsync("test_now"); Результат: {"now" : "2024-06-22T12:53:30.515689+00:00"} в консоли, {"now" : "2024-06-22T14:53:40.079963+02:00"} в C#

Vedran Mornar 22.06.2024 14:56

Я предполагаю, что ваш компьютер локализован в часовом поясе UCT+2. По умолчанию, когда вы делаете это в .NET, он форматирует его в текущем часовом поясе. Чтобы записать дату в формате ISO 8601, вы можете использовать клавишу формата «o»: DateTime.Now.ToString() : Learn.microsoft.com/fr-fr/dotnet/standard/base-types/…

Bisjob 22.06.2024 16:27

@Bisjob postgres выполняет форматирование часового пояса в json, смотрите мой ответ.

Ivan Petrov 22.06.2024 18:29

Пожалуйста, отредактируйте заголовок вопроса, чтобы описать реальную проблему, с которой вы столкнулись, или вопрос, который вы задаете. Ваш нынешний заголовок — это прежде всего бесполезное повторение тегов. Ваш заголовок должен быть достаточно ясным и информативным, чтобы будущий пользователь сайта, просматривая список результатов поиска, мог понять, что он содержит. Твой титул сейчас просто бесполезный шум.

Ken White 25.06.2024 05:47

Измененный. Надеюсь, теперь это менее бесполезно.

Vedran Mornar 27.06.2024 14:39
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
9
126
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Думаю, нам удалось добраться до виновника в обсуждении комментариев с помощью диагностической функции test_now (определение и результаты которой вы позже добавили в вопрос)

Он демонстрирует, что postgres отвечает за генерацию форматирования timespantz в json на основе часового пояса, установленного для вашего соединения/сеанса (который, предположительно, для Npgsql — это часовой пояс вашего компьютера +02:00, возможно, установленный на +00:00 UTC). для DataGrip)

Эту логику postgres можно быстро протестировать с помощью psql:

select test_now();
                  test_now
---------------------------------------------
 {"now" : "2024-06-22T19:17:45.53308+03:00"}
(1 row)

SET TIME ZONE 'UTC';
SET 

select test_now();
                   test_now
----------------------------------------------
 {"now" : "2024-06-22T16:18:11.827584+00:00"}
(1 row)

ExecuteScalar() не играет роли, предложенной в вашем вопросе

Что заставляет ExecuteScalar выполнять преобразование, даже если текст вернулся из функции?

но подтверждает ваше первоначальное наблюдение:

Он ведет себя одинаково, даже если функция возвращает json, преобразованный в текст. заранее.

Мы могли бы сделать SET TIME ZONE из нашего кода С#, как предложено здесь , но мы все равно получим время UTC в формате, который вам не нужен (...+00:00 против ...Z), как указано в вашем исходном вопросе. :

Есть ли способ заставить все, что конвертирует строки json, во время чтение с помощью ExecuteScalar, чтобы сделать это с помощью ...Z?

Я не знаком с тем, как Dapper добавить несколько перехватчиков при выполнении ExecuteScalar и изменить сгенерированную строку json с помощью выбранного нами формата даты, что было бы идеальным решением. Аналогичный относительно недавний пример использования с некоторыми решениями, а также с комментарием Марка Гравелла здесь.

Дэппер не хочет иметь ничего общего с вашими причудливыми махинациями с сериализацией. :) В принципе нет: читаем из базы как строку, потом десериализовать.

Если это невозможно, у нас есть два варианта:

  1. делаю это в postgres или
  2. найти способ конвертировать в нужный формат после того, как мы получим нежелательную отформатированную строку json из ExecuteScalar

Ваша исходная функция

create function data_survey_r("Key" integer) returns text language plpgsql as $$ 
BEGIN
   RETURN (SELECT ROW_TO_JSON(t)::text FROM data.survey t WHERE t.id = "Key");
END $$;

использует ROW_TO_JSON, что не позволяет предлагать форматирование для timespantz, однако в этом ответе есть предложение, как обойти эту проблему:

Невозможно повлиять на формат, используемый row_to_json.

Вы можете определить представление для своей таблицы и использовать

to_char(row_added_dttm, 'ГГГГ-ММ-ДД"Т"ЧЧ24:МИ:СС.МС"Z"')

чтобы отформатировать временную метку как строку.

Затем вы можете использовать row_to_json в этом представлении, чтобы получить желаемый результат.

также обратите внимание на комментарий, который предлагает альтернативный подход.

Очень понятный ответ, спасибо большое. Я не знал, что моя среда (я использую DataGrip) работает так, как если бы был установлен SET TIMEZONE 'UTC';, поэтому я ошибочно обвинил промежуточное программное обеспечение. В pgAdmin по умолчанию все работает иначе. В любом случае, это +00.00 или что-то еще не подходит для Javascript, поэтому я воспользуюсь представлением или какой-нибудь функцией SELECT format_date(date_field),....

Vedran Mornar 23.06.2024 16:43

Другие вопросы по теме