Ruby/Rails - изменение часового пояса времени, без изменения значения
У меня есть запись foo
в базе данных с атрибутами :start_time
и :timezone
.
:start_time
- это время в UTC - 2001-01-01 14:20:00
, например.
Например, :timezone
представляет собой строку - America/New_York
.
Я хочу создать новый объект Time со значением :start_time
, но чей часовой пояс указан :timezone
. Я не хочу загружать :start_time
, а затем конвертировать в :timezone
, потому что Rails будет умным и обновит время с UTC, чтобы соответствовать этому часовому поясу.
В настоящее время
t = foo.start_time
=> 2000-01-01 14:20:00 UTC
t.zone
=> "UTC"
t.in_time_zone("America/New_York")
=> Sat, 01 Jan 2000 09:20:00 EST -05:00
Вместо этого я хочу видеть
=> Sat, 01 Jan 2000 14:20:00 EST -05:00
т. Я хочу сделать:
t
=> 2000-01-01 14:20:00 UTC
t.zone = "America/New_York"
=> "America/New_York"
t
=> 2000-01-01 14:20:00 EST
Ответы
Ответ 1
Похоже, что вы хотите что-то вроде строк
ActiveSupport::TimeZone.new('America/New_York').local_to_utc(t)
Это говорит о преобразовании этого локального времени (с использованием зоны) в utc. Если у вас установлен Time.zone
, вы можете, конечно,
Time.zone.local_to_utc(t)
Это не будет использовать часовой пояс, привязанный к t - он предполагает, что он локален в часовом поясе, из которого вы конвертируете.
Один краевой случай для защиты от этого - переходы DST: указанное вами местное время может не существовать или быть неоднозначным.
Ответ 2
Если вы используете Rails, вот еще один метод в соответствии с ответами Эрика Уолша:
def set_in_timezone(time, zone)
Time.use_zone(zone) { time.to_datetime.change(offset: Time.zone.now.strftime("%z")) }
end
Ответ 3
вам нужно добавить временное смещение к своему времени после его преобразования.
Самый простой способ сделать это -
t = Foo.start_time.in_time_zone("America/New_York")
t -= t.utc_offset
не уверен, почему вы хотели бы это сделать.... возможно, лучше всего работать со временем так, как они создаются. Я предполагаю, что некоторые предпосылки о том, почему вам нужно переводить время и время, были бы полезны.
Ответ 4
На самом деле, я думаю, вам нужно вычесть смещение после его преобразования, как в:
1.9.3p194 :042 > utc_time = Time.now.utc
=> 2013-05-29 16:37:36 UTC
1.9.3p194 :043 > local_time = utc_time.in_time_zone('America/New_York')
=> Wed, 29 May 2013 12:37:36 EDT -04:00
1.9.3p194 :044 > desired_time = local_time-local_time.utc_offset
=> Wed, 29 May 2013 16:37:36 EDT -04:00
Ответ 5
Зависит от того, где вы собираетесь использовать это время.
Когда ваше время является атрибутом
Если время используется как атрибут, вы можете использовать тот же date_time_attribute gem:
class Task
include DateTimeAttribute
date_time_attribute :due_at
end
task = Task.new
task.due_at_time_zone = 'Moscow'
task.due_at # => Mon, 03 Feb 2013 22:00:00 MSK +04:00
task.due_at_time_zone = 'London'
task.due_at # => Mon, 03 Feb 2013 22:00:00 GMT +00:00
Когда вы устанавливаете отдельную переменную
Используйте тот же date_time_attribute gem:
my_date_time = DateTimeAttribute::Container.new(Time.zone.now)
my_date_time.date_time # => 2001-02-03 22:00:00 KRAT +0700
my_date_time.time_zone = 'Moscow'
my_date_time.date_time # => 2001-02-03 22:00:00 MSK +0400
Ответ 6
def relative_time_in_time_zone(time, zone)
DateTime.parse(time.strftime("%d %b %Y %H:%M:%S #{time.in_time_zone(zone).formatted_offset}"))
end
Быстрая небольшая функция, с которой я столкнулся, чтобы решить эту задачу. Если у кого-то есть более эффективный способ сделать это, напишите об этом!
Ответ 7
Я потратил значительное время на борьбу с TimeZones, а также после того, как Ruby 1.9.3 понял, что перед конвертированием вам не нужно конвертировать в названный символ часового пояса:
my_time = Time.now
west_coast_time = my_time.in_time_zone(-8) # Pacific Standard Time
east_coast_time = my_time.in_time_zone(-5) # Eastern Standard Time
Что это означает, так это то, что вы можете сосредоточиться на правильной настройке времени в нужном вам регионе, о том, как вы думаете об этом (по крайней мере, в моей голове я разбиваю его таким образом), а затем конвертировать в конец в зону, с которой вы хотите проверить свою бизнес-логику.
Это также работает для Ruby 2.3.1.
Ответ 8
Я столкнулся с той же проблемой, и вот что я буду делать:
t = t.asctime.in_time_zone("America/New York")
Вот документация по asctime
Ответ 9
Я создал несколько вспомогательных методов, один из которых просто делает то же самое, что задал автор оригинала сообщения в Ruby/Rails - изменил часовой пояс Time, без изменение значения.
Кроме того, я зафиксировал несколько особенностей, которые я наблюдал, а также эти помощники содержат методы, позволяющие полностью игнорировать автоматическую экономию дневного света, применимую в то время как преобразования времени, которые недоступны из коробки в Rails:
def utc_offset_of_given_time(time, ignore_dst: false)
# Correcting the utc_offset below
utc_offset = time.utc_offset
if !!ignore_dst && time.dst?
utc_offset_ignoring_dst = utc_offset - 3600 # 3600 seconds = 1 hour
utc_offset = utc_offset_ignoring_dst
end
utc_offset
end
def utc_offset_of_given_time_ignoring_dst(time)
utc_offset_of_given_time(time, ignore_dst: true)
end
def change_offset_in_given_time_to_given_utc_offset(time, utc_offset)
formatted_utc_offset = ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, false)
# change method accepts :offset option only on DateTime instances.
# and also offset option works only when given formatted utc_offset
# like -0500. If giving it number of seconds like -18000 it is not
# taken into account. This is not mentioned clearly in the documentation
# , though.
# Hence the conversion to DateTime instance first using to_datetime.
datetime_with_changed_offset = time.to_datetime.change(offset: formatted_utc_offset)
Time.parse(datetime_with_changed_offset.to_s)
end
def ignore_dst_in_given_time(time)
return time unless time.dst?
utc_offset = time.utc_offset
if utc_offset < 0
dst_ignored_time = time - 1.hour
elsif utc_offset > 0
dst_ignored_time = time + 1.hour
end
utc_offset_ignoring_dst = utc_offset_of_given_time_ignoring_dst(time)
dst_ignored_time_with_corrected_offset =
change_offset_in_given_time_to_given_utc_offset(dst_ignored_time, utc_offset_ignoring_dst)
# A special case for time in timezones observing DST and which are
# ahead of UTC. For e.g. Tehran city whose timezone is Iran Standard Time
# and which observes DST and which is UTC +03:30. But when DST is active
# it becomes UTC +04:30. Thus when a IRDT (Iran Daylight Saving Time)
# is given to this method say '05-04-2016 4:00pm' then this will convert
# it to '05-04-2016 5:00pm' and update its offset to +0330 which is incorrect.
# The updated UTC offset is correct but the hour should retain as 4.
if utc_offset > 0
dst_ignored_time_with_corrected_offset -= 1.hour
end
dst_ignored_time_with_corrected_offset
end
Примеры, которые можно попробовать на консоли rails или ruby script после переноса вышеуказанных методов в класс или модуль:
dd1 = '05-04-2016 4:00pm'
dd2 = '07-11-2016 4:00pm'
utc_zone = ActiveSupport::TimeZone['UTC']
est_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
tehran_zone = ActiveSupport::TimeZone['Tehran']
utc_dd1 = utc_zone.parse(dd1)
est_dd1 = est_zone.parse(dd1)
tehran_dd1 = tehran_zone.parse(dd1)
utc_dd1.dst?
est_dd1.dst?
tehran_dd1.dst?
ignore_dst = true
utc_to_est_time = utc_dd1.in_time_zone(est_zone.name)
if utc_to_est_time.dst? && !!ignore_dst
utc_to_est_time = ignore_dst_in_given_time(utc_to_est_time)
end
puts utc_to_est_time
Надеюсь, что это поможет.