Как изменить часовой пояс времени в Ruby?

Предположим, у меня есть объект Time с часовым поясом (скажем, UTC). Я хочу сбросить часовой пояс на произвольный (то есть не на ваш местный часовой пояс), не изменяя никаких других параметров времени, таких как «час». Ruby версии 3.3, похоже, не предоставляет какого-либо единого метода для этого (см. Официальный документ Time и Ответы на Stackoverflow). Как мне тогда этого добиться?

Отмечу, что для класса DateTime метод change (по-видимому) работает так же, как в этом ответе [Редактировать: это метод Rails]. Однако,

Класс DateTime считается устаревшим. Используйте класс времени.

как в Ruby 3 и поэтому я хочу знать, как это сделать с помощью класса Time.

Также отмечу, что Rails реализует несколько удобных методов, упомянутых в этих ответах ; но они недоступны в чистом Ruby (и на самом деле in_time_zone в Rails меняет другой параметр времени, hour).

Как я могу сделать это в чистом Ruby?

К вашему сведению: change также является методом Rails, у встроенного класса Datetime его нет.

Stefan 25.02.2024 09:57
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
0
1
70
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Экземпляр Time является неизменяемым, то есть невозможно изменить существующий объект Time. Альтернативно, возможно, хотя это и немного неудобно делать в чистом Ruby (см., Официальный документ), создать новый экземпляр Time на основе существующего, в котором вы устанавливаете новый произвольный «часовой пояс […] без изменение любых других параметров Времени", а именно копирование их всех из существующего Времени в новое.

Здесь я перечисляю несколько способов (Примечание: ни одиночные методы или переменные экземпляра не наследуются):

require "time"
t = Time.now(in: "+00:00")
  # => <2024-02-24 01:23:45 +0000>

t1= Time.new(*(t.to_a.reverse[4..-1]), in: "+09:00")
  # => <2024-02-24 01:23:45 +0900>

t2= Time.new(t.year, t.month, t.day, t.hour, t.min, t.sec+t.subsec, in: "+09:00")
  # => <2024-02-24 01:23:45 ... +0900>

ar = t.strftime("%Y %m %d %H %M %S %N").split.map(&:to_i)
t3= Time.new(*(ar[0..4]+[ar[5]+ar[6].quo(1_000_000_000), "+09:00"]))
  # => <2024-02-24 01:23:45 ... +0900>

t4= Time.at(t, in: "+09:00") - 9*3600
  # => <2024-02-24 01:23:45.... +0900>

Здесь t1 считает только до секунды, тогда как остальные считают до наносекунды.

t2 — самый интуитивно понятный, но и самый громоздкий. Здесь Time#subsec возвращает Rational, и, следовательно, формула не содержит ошибок округления с плавающей запятой.

t3 может не иметь преимуществ перед t2. Он не предполагает ошибки округления с плавающей запятой, как в t2.

Хотя t4 менее длинный, тот факт, что он противоречит принципу DRY и что вы должны точно указывать паритет (плюс или минус?), являются существенными недостатками.

t.tv_nsec.to_f/1e9 подвержен ошибкам округления с плавающей запятой. Вместо этого используйте t.subsec, который возвращает точное значение.
Stefan 26.02.2024 09:18

@ Стефан Хорошая мысль! Я отредактировал ответ, чтобы он использовал t.subsec вместо tv_nsec.to_f. Спасибо!

Masa Sakano 26.02.2024 15:25

Что касается «нет простых способов добиться этого» — экземпляры времени в Ruby неизменяемы. Если вы хотите изменить какой-либо атрибут (не только часовой пояс, это также относится к году, месяцу, дню, часу, минуте и т. д.), вам необходимо создать новый экземпляр и передать измененные атрибуты соответствующим образом.

Stefan 27.02.2024 12:06

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