Игнорирование часовых поясов в Rails и PostgreSQL

Я имею дело с датами и временем в Rails и Postgres и сталкивается с этой проблемой:

База данных находится в UTC.

Пользователь задает часовой пояс в приложении Rails, но его можно использовать только при использовании локального времени для сравнения времени.

Пользователь хранит время, скажем 17 марта 2012 года, 19:00. Я не хочу, чтобы изменения в часовом поясе или часовой пояс сохранялись. Я просто хочу, чтобы дата и время были сохранены. Таким образом, если пользователь изменил свой часовой пояс, он все равно покажет 17 марта 2012 года, 19:00.

Я использую только указанный пользователем часовой пояс, чтобы получать записи «до» или «после» текущего времени в локальном часовом поясе пользователей.

В настоящее время я использую «временную метку без часового пояса», но когда я извлекаю записи, rails (?) Преобразуют их в часовой пояс в приложении, чего я не хочу.

Appointment.first.time => Fri, 02 Mar 2012 19:00:00 UTC +00:00 

Поскольку записи в базе данных, как представляется, выходят как UTC, мой взлом – это взять текущее время, удалить часовой пояс с помощью «Date.strptime (str,«% m /% d /% Y »)», а затем сделать запрос с этим:

 .where("time >= ?", date_start) 

Кажется, должен быть более простой способ просто игнорировать часовые пояса вокруг. Есть идеи?

timestamp типа данных – это краткое имя timestamp without time zone .
Другой параметр timestamptz является коротким для timestamp with time zone .

timestamptz является предпочтительным типом в семье даты / времени, буквально. Он имеет typispreferred набор в pg_type , который может иметь значение:

  • Создание временных рядов между двумя датами в PostgreSQL

эпоха

Внутри временные метки сохраняются как счетчик из эпохи . Postgres использует эпоху первого момента первого дня 2000 года в UTC, то есть 2000-01-01T00: 00: 00Z. Восемь октетов используются для хранения номера счета. В зависимости от параметра времени компиляции это число:

  • 8-байтовое целое число (по умолчанию), от 0 до 6 цифр дробной секунды
  • Число с плавающей запятой (устаревшее), от 0 до 10 цифр дробной секунды, где точность быстро ухудшается для значений, удаленных от эпохи.
    В современных установках Postgres используется 8-байтовое целое число.

Обратите внимание, что Postgres не использует время Unix . Эпоха Postgres – это первый момент 2000-01-01, а не Unix ‘1970-01-01. Хотя время Unix имеет разрешение целых секунд, Postgres сохраняет доли секунд.

timestamp

Если вы определяете [without time zone] timestamp типа данных [without time zone] вы сообщаете Postgres: «Я не предоставляю часового пояса явно, вместо этого принимаю текущий часовой пояс. Postgres сохраняет временную метку как естьигнорируя модификатор часового пояса, если вы должны добавить один!

Когда вы позже покажете эту timestamp , вы вернетесь к тому, что вы ввели буквально. При одинаковом настройке часового пояса все в порядке. Если настройка часового пояса для сеанса изменяется, значит, значение timestampзначение остается неизменным.

timestamptz

Обработка timestamp with time zone несколько отличается. Я цитирую здесь инструкцию :

Для timestamp with time zone внутренне сохраненное значение всегда находится в UTC (Universal Coordinated Time …)

Смелый акцент мой. Сам часовой пояс никогда не сохраняется . Это модификатор ввода, используемый для вычисления соответствующей временной метки UTC, которая хранится – и / или модификатор вывода, используемый для вычисления локального времени для отображения – с добавленным смещением часового пояса. Если вы не добавляете смещение для timestamptz на входе, предполагается установка текущего часового пояса сессии. Все вычисления выполняются с отметками времени UTC. Если вам приходится (или, возможно, придется) иметь дело с более чем одним часовым поясом, используйте timestamptz .

Клиенты, такие как psql или pgAdmin или любое приложение, сообщающее через libpq (например, Ruby с pg gem), имеют отметку времени плюс смещение для текущего часового пояса или в соответствии с запрошенным часовым поясом (см. Ниже). Это всегда один и тот же момент времени , меняется только формат отображения. Или, как говорится в руководстве :

Все даты и время, относящиеся к времени, хранятся внутри UTC. Они преобразуются в локальное время в зоне, заданной параметром конфигурации TimeZone, перед тем, как показывать клиенту.

Рассмотрим этот простой пример (в psql):

 db = # SELECT timestamptz '2012-03-05 20:00 +03 ';
       timestamptz
 ------------------------
  2012-03-05 18:00:00 +01

Смелый акцент мой. Что здесь случилось?
Я выбрал произвольное временное смещение +3 для входного литерала. Для Postgres это всего лишь один из многих способов ввода временной метки UTC. 2012-03-05 17:00:00 . Результат запроса отображается для текущего часового пояса в Вене / Австрии в моем тесте, который имеет смещение +1 во время зимы и +2 в летнее время: 2012-03-05 18:00:00+01 , потому что это попадает в зимнее время.

Postgres уже забыл, как это значение было введено. Все, что он помнит, – это значение и тип данных. Точно так же, как с десятичным числом. numeric '003.4' , numeric '3.40' или numeric '+3.4' – все это приводит к точному внутреннему значению.

AT TIME ZONE

Как только вы поймете эту логику, вы можете делать все, что хотите. Все, что сейчас отсутствует, – это инструмент для интерпретации или представления литератур timestamp в соответствии с определенным часовым поясом. Вот в чем заключена конструкция AT TIME ZONE . Существуют два разных варианта использования. timestamptz преобразуется в timestamp и наоборот.

Чтобы ввести UTC timestamptz 2012-03-05 17:00:00+0 :

 SELECT timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC' 

… что эквивалентно:

 SELECT timestamptz '2012-03-05 17:00:00 UTC' 

Чтобы отобразить тот же момент времени, что и timestamp EST (Восточное стандартное время):

 SELECT timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC' AT TIME ZONE 'EST' 

Правильно, в AT TIME ZONE 'UTC' дважды . Первая интерпретирует значение timestamp как (данное) UTC timestamp, возвращающее тип timestamptz . Второй преобразует timestamptz в timestamp в данном часовом поясе «EST» – то, что часы в часовом поясе EST отображаются в этот уникальный момент времени.

Примеры

 SELECT ts AT TIME ZONE 'UTC' FROM ( VALUES (1, timestamptz '2012-03-05 17:00:00+0') , (2, timestamptz '2012-03-05 18:00:00+1') , (3, timestamptz '2012-03-05 17:00:00 UTC') , (4, timestamp '2012-03-05 11:00:00' AT TIME ZONE '+6') , (5, timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC') , (6, timestamp '2012-03-05 07:00:00' AT TIME ZONE 'US/Hawaii') -- ① , (7, timestamptz '2012-03-05 07:00:00 US/Hawaii') -- ① , (8, timestamp '2012-03-05 07:00:00' AT TIME ZONE 'HST') -- ① , (9, timestamp '2012-03-05 18:00:00+1') -- ② loaded footgun! ) t(id, ts); 

Возвращает 8 (или 9) одинаковых строк с столбцами timestamptz с одной и той же меткой времени UTC. 2012-03-05 17:00:00 . 9-й ряд вроде бы работает в моем часовом поясе, но является злой ловушкой. Смотри ниже.

① Строки 6-8 с указанием названия часового пояса и сокращением времени часового пояса для времени Гавайев подлежат летнему времени (DST) и могут отличаться, хотя в настоящее время они не установлены. Название часового пояса, такое как 'US/Hawaii' , знает правила DST и все исторические смены автоматически, а аббревиатура типа HST – это просто тупой код для фиксированного смещения. Возможно, вам придется добавить другую аббревиатуру для летнего / стандартного времени. Имя правильно интерпретирует любую метку времени в данном часовом поясе. Аббревиатура дешевая, но она должна быть правильной для данной отметки времени:

  • Названия часовых поясов с одинаковыми свойствами дают разные результаты при применении к метке времени

Переход на летнее время не входит в число самых ярких идей, с которыми когда-либо приходило человечество.

② Ряд 9, отмеченный как загруженный ногой, работает для меня , но только по совпадению. Если вы явно передали литеральное значение в timestamp [without time zone] , любое смещение часового пояса игнорируется ! Используется только голая timestamp. Затем значение автоматически привязывается к timestamptz в примере, соответствующем типу столбца. Для этого шага предполагается установка timezone текущего сеанса, которая в моем случае является одной и той же часовой зоной +1 (Европа / Вена). Но, вероятно, не в вашем случае – что приведет к другому значению. Короче говоря: не бросайте timestamptz литералы в timestamp или вы теряете смещение часового пояса.

Ваши вопросы

Пользователь хранит время, скажем 17 марта 2012 года, 19:00. Я не хочу, чтобы изменения в часовом поясе или часовой пояс сохранялись.

Сам часовой пояс никогда не сохраняется. Используйте один из методов выше, чтобы ввести временную метку UTC.

Я использую только указанный пользователем часовой пояс, чтобы получать записи «до» или «после» текущего времени в локальном часовом поясе пользователей.

Вы можете использовать один запрос для всех клиентов в разных часовых поясах.
Для абсолютного глобального времени:

 SELECT * FROM tbl WHERE time_col > (now() AT TIME ZONE 'UTC')::time 

Для времени в соответствии с местными часами:

 SELECT * FROM tbl WHERE time_col > now()::time 

Еще не устали от справочной информации? В руководстве больше.

Если вы хотите иметь дело с UTC по умолчанию:

В config/application.rb добавьте:

 config.time_zone = 'UTC' 

Затем, если вы сохраняете текущее имя часового пояса пользователя current_user.timezone вы можете сказать.

 post.created_at.in_time_zone(current_user.timezone) 

current_user.timezone должен быть действительным именем часового пояса, иначе вы получите ArgumentError: Invalid Timezone , см. полный список .

  • Как перевести между часовыми поясами Windows и IANA?
  • Установите системный часовой пояс из .NET.
  • Как элегантно работать с часовыми поясами
  • Сокращения временной зоны
  • linux конвертировать время (для разных часовых поясов) в UTC
  • .NET TimeZoneInfo из часового пояса Олсона
  • Как хранить дату и время и временные метки в часовом поясе UTC с JPA и Hibernate
  • Получение системных часовых поясов на разных языках
  • Веб-сервис - текущий часовой пояс для города?
  • Как конвертировать дату с одного часового пояса в другой часовой пояс
  • Как получить текущий часовой пояс MySQL?
  • Interesting Posts

    Сильное пароли regex

    эффективно генерировать случайную выборку времени и дат между двумя датами

    Как мне выполнить резервное копирование своих данных перед переустановкой Windows? Опущенные файлы при копировании диска C

    Как сделать USB-пристыкованный жесткий диск «отключенным» от подключенной системы в режиме ожидания?

    Как я могу перечислить все процессы с повышенными разрешениями в Windows 7 или выше?

    Как я могу запускать тесты NUnit параллельно?

    Как переместить раздел в конец в gparted?

    Как выбрать дату из столбца datetime?

    Как установить настройки звука Windows Sound для устройства?

    Как вычислить число 3D Morton (чередуйте биты 3 ints)

    Есть что-то вроде Command Substitution в WIndows CLI?

    Сетевое копирование на Windows 7 Общий доступ к файлам не работает и убивает сетевое подключение

    Как получить представление ActionBar?

    Использование плагина jQuery в TypeScript

    Гистограмма с использованием gnuplot?

    Давайте будем гением компьютера.