Рубін/Рэйкі - Змяніць гадзінны пояс Часу, без змены значэння

У мяне ёсць запіс Foo у базе дадзеных, якая мае : start_time і : гадзінны пояс атрыбуты.

<�Код>: start_time гэты час у UTC - 2001-01-01 14:20:00 , напрыклад. <�Код>: гадзінны пояс гэта радок - Амерыка/New_York , напрыклад.

Я хачу стварыць новы аб'ект Time са значэннем : start_time , але чый гадзінны пояс задаецца : гадзінны пояс . Я не хачу, каб загрузіць : start_time , а затым пераўтварыць у : гадзінны пояс , так як 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
77
Я не думаю, што вы карыстаецеся гадзінныя паясы правільна. Калі захаваць яго ў БД, як UTC з лакальнага, што здарылася з яго аналізам з дапамогай свайго мясцовага часу і захавання яго з дапамогай сваёй адноснай UTC?
дададзена аўтар Trip, крыніца
Можа быць, гэта дапаможа: api.rubyonrails.org/classes/Time.html # спосаб-з-use_zone
дададзена аўтар MrYoshiji, крыніца
Я ... Я думаю, што лепш за ўсё атрымаць дапамогу вам можа спатрэбіцца, каб растлумачыць, <�я> чаму </я> вам трэба будзе зрабіць гэта? Чаму вы захоўваеце няправільны час у базу дадзеных, у першую чаргу?
дададзена аўтар nzifnab, крыніца
калі гэтыя дакументы дапамаглі, мы не мелі б StackOverflow :-) Адзін з прыкладаў там, што не паказана, як было ўстаноўлена нічога - тыповая. Мне таксама трэба зрабіць гэта, каб прымусіць яблыкі з яблыкамі параўнання, не зламацца, калі Daylight Savings ног або вонкі.
дададзена аўтар JosephK, крыніца
@MrYoshiji пагадзіўся. гучыць як YAGNI ці заўчаснай аптымізацыі для мяне.
дададзена аўтар engineerDave, крыніца

9 адказы

Падобна на тое, вы хочаце нешта ўздоўж ліній

ActiveSupport::TimeZone.new('America/New_York').local_to_utc(t)

Гэта сведчыць пераўтварыць гэта мясцовае час (з выкарыстаннем зоны) па Грынвічы. Калі ў вас ёсць Time.zone усталяваны, то вы можаце, вядома,

Time.zone.local_to_utc(t)

Гэта не будзе выкарыстоўваць гадзінны пояс, прымацаваны да т - мяркуецца, што гэта лакальнае часовай зона, вы ператвараюцца з.

Адзін крайні выпадак, каб абараніцца ад тут ДСТ пераходы: мясцовае час паказаць не можа існаваць ці можа быць неадназначным.

49
дададзена
Што вы прапануеце супраць летняга часу пераходаў? Выкажам здагадку, што мэтавае час для пераўтварэння пасля пераходу D/ST у той час як Time.now знаходзіцца перад зменай. што будзе працаваць?
дададзена аўтар Cyril Duchon-Doris, крыніца
Спалучэнне local_to_utc і Time.use_zone гэта тое, што мне трэба: Time.use_zone (self.timezone) {Time.zone.local_to_utc (т)} .localtime
дададзена аўтар rwb, крыніца

Калі вы выкарыстоўваеце Rails, вось яшчэ адзін метад па лініі адказу Эрык Уолш:

def set_in_timezone(time, zone)
  Time.use_zone(zone) { time.to_datetime.change(offset: Time.zone.now.strftime("%z")) }
end
17
дададзена
@ Браян Мэрфі-Dye У мяне былі праблемы з Пераход на летні час з дапамогай гэтай функцыі. Вы можаце змяніць сваё пытанне, каб забяспечыць рашэнні, з якімі працуе DST? Можа быць, замяніўшы Time.zone.now да нечага, што бліжэй за ўсё да таго часу, які вы хочаце змяніць будзе працаваць?
дададзена аўтар Cyril Duchon-Doris, крыніца
І, каб пераўтварыць аб'ект DateTime назад да аб'екта TimeWithZone, проста лавіраваць на .in_time_zone да канца.
дададзена аўтар Brian, крыніца

Я толькі што сутыкнуўся з той жа праблемай, і вось што я збіраюся зрабіць:

t = t.asctime.in_time_zone("America/New York")

Here is the documentation on asctime

8
дададзена
Ён проста робіць тое, што я чакаў
дададзена аўтар Kaz, крыніца

Вы павінны дадаць зрушэнне часу ў свой час пасля пераўтварэння яго.

Самы просты спосаб зрабіць гэта:

t = Foo.start_time.in_time_zone("America/New_York")
t -= t.utc_offset

Я не ведаю, чаму вы хацелі б зрабіць гэта, хоць гэта, верагодна, лепш за ўсё на самой справе працаваць з часам, як яны пабудаваны. Я мяркую, што некаторыя даведачныя аб тым, чаму вам трэба перанесці час і вартавыя пояса былі б карысныя.

6
дададзена
Гэта працуе для мяне. Гэта павінна застацца правільна падчас дзённага зберажэнні зрухаў, пры ўмове, што велічыня зрушэння ўсталёўваюцца пры выкарыстанні. Пры выкарыстанні аб'ектаў DateTime, можна дадаць ці адняць «offset.seconds» ад яго.
дададзена аўтар JosephK, крыніца

На самай справе, я думаю, вам трэба адняць зрушэнне пасля пераўтварэння яго, як:

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 
4
дададзена

У залежнасці ад таго, дзе вы збіраецеся выкарыстаць гэты час.

<�Моцны> Калі ваш час з'яўляецца атрыбутам

Калі час выкарыстоўваецца ў якасці атрыбуту, вы можаце выкарыстоўваць адзін і той жа date_time_attribute гем :

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 гем :

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
3
дададзена

Я выдаткаваў шмат часу, змагаючыся з TimeZones, а таксама і пасля таго, як майстраваць з Рубі 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

Што гэта азначае, што вы можаце засяродзіцца на атрыманні адпаведнай налады часу першага ў рэгіёне вы хочаце, як вы б думаць пра гэта (прынамсі, у маёй галаве, я разбіць яго такім чынам), а затым пераўтварыць у канцы ў зону вы хочаце праверыць свой бізнэс-логіку с.

Гэта таксама працуе для Рубі 2.3.1.

1
дададзена
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

Хуткая маленькая функцыяй я прыдумаў, каб вырашыць гэтую працу. Калі хто-то ёсць больш эфектыўны спосаб зрабіць гэта, калі ласка, адправіць яго!

1
дададзена

Я стварыў некалькі дапаможных метадаў, адзін з якіх проста робіць тое ж самае, як прасіў яго аўтар паста ў Ruby/Rails - Змяніць гадзінны пояс з часу, не змяняючы значэнні .

Акрамя таго, я дакументальна некалькі асаблівасцяў, якія я назіраў, а таксама гэтыя хелперы змяшчае метады, каб цалкам ігнараваць аўтаматычны дзённае святло эканоміі дастасавальна у той час як часовыя пераўтварэнні, якія не даступны па-за скрынкі ў рамках 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

У якасці прыкладаў можна паспрабаваць на рэйкі кансолі або сцэнар лалавага пасля абкручванні вышэй метадаў у класе або модулі:

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

Спадзяюся, што гэта дапамагае.

0
дададзена