Получить смещение UTC для часового пояса на указанную дату через Ruby/tzinfo?
Это, наверное, тривиально для тех, кто знает tzinfo
API:
Учитывая Timezone
объект из tzinfo
Как я могу получить смещение UTC в данный момент времени (заданное либо по местному времени часового пояса, либо по UTC)?
2 ответа
Вы можете использовать period_for_local
метод. Для этих примеров я использую часовой пояс, в котором я живу (America/Sao_Paulo
), где смещение -03:00
зимой (с марта по октябрь) и -02:00
в летнее время (летнее время):
# Sao Paulo timezone
zone = TZInfo::Timezone.new('America/Sao_Paulo')
# date in January (Brazilia Summer Time - DST)
d = DateTime.new(2017, 1, 1, 10, 0)
period = zone.period_for_local(d)
puts period.offset.utc_total_offset / 3600.0
# date in July (Brazilia Standard Time - not in DST)
d = DateTime.new(2017, 7, 1, 10, 0)
period = zone.period_for_local(d)
puts period.offset.utc_total_offset / 3600.0
Выход:
-2,0
-3,0
utc_total_offset
Метод возвращает смещение в секундах, поэтому я разделил на 3600, чтобы получить значение в часах.
Обратите внимание, что я также использовал 3600.0
заставить результаты быть поплавком. Если я просто использую 3600
, результаты будут округлены и часовые пояса, как Asia/Kolkata
(который имеет смещение +05:30
) даст неверные результаты (5
вместо 5.5
).
Обратите внимание, что вы должны знать об изменениях DST, потому что вы можете иметь разрыв или перекрытие.
В часовом поясе Сан-Паулу летнее время начинается 15 октября 2017 года: в полночь часы переводятся на 1 час ночи (и смещение меняется с -03:00
в -02:00
), поэтому все местное время между 00:00 и 01:00 недопустимо. В этом случае, если вы попытаетесь получить смещение, он получит PeriodNotFound
ошибка:
# DST starts at October 15th, clocks shift from midnight to 1 AM
d = DateTime.new(2017, 10, 15, 0, 30)
period = zone.period_for_local(d) # error: TZInfo::PeriodNotFound
Когда летнее время заканчивается, 18 февраля 2018 года, в полночь часы возвращаются к 23:00 17-го числа (и смещение меняется с -02:00
в -03:00
), поэтому местное время между 23:00 и полночью существует дважды (в обоих смещениях).
В этом случае вы должны указать, какой вы хотите (установив второй параметр period_for_local
), указывая, хотите ли вы смещение для летнего времени или нет:
# DST ends at February 18th, clocks shift from midnight to 11 PM of 17th
d = DateTime.new(2018, 2, 17, 23, 30)
period = zone.period_for_local(d, true) # get DST offset
puts period.offset.utc_total_offset / 3600.0 # -2.0
period = zone.period_for_local(d, false) # get non-DST offset
puts period.offset.utc_total_offset / 3600.0 # -3.0
Если вы не укажете второй параметр, вы получите TZInfo::AmbiguousTime
ошибка:
# error: TZInfo::AmbiguousTime (local time exists twice due do DST overlap)
period = zone.period_for_local(d)
Кажется, в Ruby 1.9.3 есть некоторая хакерская атака (DateTime to Time) с возможной потерей точности, но это мой результат, основанный на ответе @Hugo:
module TZInfo
class Timezone
def utc_to_local_zone(dateTime)
return dateTime.to_time.getlocal(self.period_for_utc(dateTime).utc_total_offset)
end
def offset_to_s(dateTime, format = "%z")
return utc_to_local_zone(dateTime).strftime(format)
end
end
end