Получить смещение 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
Другие вопросы по тегам