Предположим, у меня есть объект Time с часовым поясом (скажем, UTC). Я хочу сбросить часовой пояс на произвольный (то есть не на ваш местный часовой пояс), не изменяя никаких других параметров времени, таких как «час». Ruby версии 3.3, похоже, не предоставляет какого-либо единого метода для этого (см. Официальный документ Time и Ответы на Stackoverflow). Как мне тогда этого добиться?
Отмечу, что для класса DateTime метод change
(по-видимому) работает так же, как в этом ответе [Редактировать: это метод Rails]. Однако,
Класс DateTime считается устаревшим. Используйте класс времени.
как в Ruby 3 и поэтому я хочу знать, как это сделать с помощью класса Time.
Также отмечу, что Rails реализует несколько удобных методов, упомянутых в этих ответах ; но они недоступны в чистом Ruby (и на самом деле in_time_zone в Rails меняет другой параметр времени, hour
).
Как я могу сделать это в чистом Ruby?
Экземпляр 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
, который возвращает точное значение.
@ Стефан Хорошая мысль! Я отредактировал ответ, чтобы он использовал t.subsec
вместо tv_nsec.to_f
. Спасибо!
Что касается «нет простых способов добиться этого» — экземпляры времени в Ruby неизменяемы. Если вы хотите изменить какой-либо атрибут (не только часовой пояс, это также относится к году, месяцу, дню, часу, минуте и т. д.), вам необходимо создать новый экземпляр и передать измененные атрибуты соответствующим образом.
К вашему сведению:
change
также является методом Rails, у встроенного класса Datetime его нет.