IT story

Ruby / Rails-값을 변경하지 않고 시간의 시간대 변경

hot-time 2020. 8. 30. 19:48
반응형

Ruby / Rails-값을 변경하지 않고 시간의 시간대 변경


속성 foo이있는 데이터베이스에 기록 이 있습니다 .:start_time:timezone

:start_time- UTC에서 시간입니다 2001-01-01 14:20:00예를 들어. :timezone문자열입니다 - America/New_York예를 들면.

값이 :start_time이지만 시간대가로 지정된 새 Time 개체를 만들고 싶습니다 :timezone. Rails가 영리하고 UTC에서 해당 시간대와 일치하도록 시간을 업데이트하기 때문에 를로드 한 :start_time다음으로 변환 하고 싶지 않습니다 :timezone.

현재

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

라인을 따라 무언가를 원하는 것 같습니다.

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

이것은이 현지 시간 (영역 사용)을 utc로 변환하는 것을 말합니다. Time.zone설정 한 경우 물론 다음을 수행 할 수 있습니다.

Time.zone.local_to_utc(t)

이것은 t에 첨부 된 시간대를 사용하지 않습니다. 변환하려는 시간대의 현지 시간대라고 가정합니다.

여기서 경계해야 할 한 가지 중요한 경우는 DST 전환입니다. 지정한 현지 시간이 존재하지 않거나 모호 할 수 있습니다.


나는 방금 같은 문제에 직면했고 여기에 내가 할 일이 있습니다.

t = t.asctime.in_time_zone("America/New_York")

다음은 asctime에 대한 문서입니다.


Rails를 사용하는 경우 Eric Walsh의 답변에 따른 또 다른 방법이 있습니다.

def set_in_timezone(time, zone)
  Time.use_zone(zone) { time.to_datetime.change(offset: Time.zone.now.strftime("%z")) }
end

시간을 변환 한 후 시간 오프셋을 추가해야합니다.

이를 수행하는 가장 쉬운 방법은 다음과 같습니다.

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

왜 그렇게 하려는지 잘 모르겠지만, 실제로 시간이 만들어지는 방식으로 작업하는 것이 가장 좋습니다. 시간과 시간대를 변경해야하는 이유에 대한 배경 지식이 도움이 될 것 같습니다.


실제로 다음과 같이 변환 한 후에 오프셋을 빼야한다고 생각합니다.

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 

Depends on where you are going to use this Time.

When your time is an attribute

If time is used as an attribute, you can use the same 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

When you set a separate variable

Use the same 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

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

Quick little function I came up with to solve the job. If someone has a more efficient way of doing this please post it!


I have created few helper methods one of which just does the same thing as is asked by the original author of the post at Ruby / Rails - Change the timezone of a Time, without changing the value.

Also I have documented few peculiarities I observed and also these helpers contains methods to completely ignore automatic day-light savings applicable while time-conversions which is not available out-of-the-box in Rails framework:

  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

Examples which can be tried on rails console or a ruby script after wrapping the above methods in a class or module:

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

Hope this helps.


I spent significant time struggling with TimeZones as well, and after tinkering with Ruby 1.9.3 realized that you don't need to convert to a named timezone symbol before converting:

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

What this implies is that you can focus on getting the appropriate time setup first in the region you want, the way you would think about it (at least in my head I partition it this way), and then convert at the end to the zone you want to verify your business logic with.

This also works for Ruby 2.3.1.

참고URL : https://stackoverflow.com/questions/16818180/ruby-rails-change-the-timezone-of-a-time-without-changing-the-value

반응형