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


108

및 속성 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

1
아마도 이것이 도움이 될 것입니다 : api.rubyonrails.org/classes/Time.html#method-c-use_zone
MrYoshiji

3
시간대를 올바르게 사용하고 있다고 생각하지 않습니다. 로컬에서 UTC로 db에 저장하면 로컬 시간을 통해 구문 분석하고 상대 utc를 통해 저장하는 데 문제가 있습니까?
여행

1
Ya .. 도움을 가장 잘 받으려면 이렇게 해야하는지 설명 해야할까요? 애초에 잘못된 시간을 데이터베이스에 저장하는 이유는 무엇입니까?
nzifnab

@MrYoshiji가 동의했습니다. 나에게 YAGNI 또는 조기 최적화처럼 들립니다.
engineerDave

1
해당 문서가 도움이된다면 StackOverflow가 필요하지 않을 것입니다. :-) 거기에 한 가지 예가 있습니다. 또한 일광 절약 시간이 시작되거나 종료 될 때 깨지지 않는 Apples-To-Apples 비교를 강제하기 위해이 작업을 수행해야합니다.
JosephK 2015-10-13

답변:


72

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

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

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

Time.zone.local_to_utc(t)

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

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


17
local_to_utc와 Time.use_zone의 조합이 필요했습니다.Time.use_zone(self.timezone) { Time.zone.local_to_utc(t) }.localtime
rwb

DST 전환에 대해 무엇을 제안합니까? 변환 할 목표 시간이 D / ST 전환 후이고 Time.now가 변경 전이라고 가정합니다. 작동할까요?
Cyril Duchon-Doris

28

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

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

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


2
내가 예상했던대로 작동합니다
Kaz

감사합니다! 그것을 기반으로 내 대답을 단순화 했습니다. 한 가지 단점은 asctime(내 대답이 유지하는) 1 초 미만의 값을 삭제한다는 것입니다.
Henrik N

22

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

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

1
그리고 DateTime 객체를 TimeWithZone 객체로 다시 변환하려면 끝까지 붙 .in_time_zone입니다.
Brian

@Brian Murphy-Dye이 기능을 사용하여 일광 절약 시간제에 문제가 발생했습니다. DST와 함께 작동하는 솔루션을 제공하기 위해 질문을 편집 할 수 있습니까? Time.zone.now변경하려는 시간에 가장 가까운 것으로 교체 하면 효과가 있습니까?
Cyril Duchon-Doris

6

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

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

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

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


이것은 나를 위해 일했습니다. 이는 오프셋 값이 사용될 때 설정되어있는 경우 일광 절약 근무 시간 동안 올바르게 유지되어야합니다. DateTime 객체를 사용하는 경우 "offset.seconds"를 더하거나 뺄 수 있습니다.
JosephK

Rails 외부인 경우 Time.in_time_zoneactive_support의 올바른 부분을 요구하여 사용할 수 있습니다 .require 'active_support/core_ext/time'
jevon

이것은 어떤 방향으로 들어가 느냐에 따라 잘못된 일을하는 것처럼 보이며 항상 신뢰할 수있는 것은 아닙니다. 예를 들어 지금 스톡홀름 시간을 가져와 런던 시간으로 변환하면 오프셋을 더하거나 빼지 않으면 작동합니다. 그러나 스톡홀름을 헬싱키로 변환하면 더하거나 빼는 것이 잘못되었습니다.
Henrik N

5

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

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 

3

이번에 사용할 위치에 따라 다릅니다.

시간이 속성 일 때

시간이 속성으로 사용되는 경우 동일한 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

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
t.change(zone: 'America/New_York')

OP의 전제는 올바르지 않습니다. "나는 Rails가 영리하고 UTC에서 해당 시간대와 일치하도록 시간을 업데이트 할 것이기 때문에 : start_time을로드 한 다음 : timezone으로 변환하고 싶지 않습니다." 여기에 제공된 답변에서 알 수 있듯이 반드시 사실은 아닙니다.


1
이것은 질문에 대한 답을 제공하지 않습니다. 작성자에게 비판이나 설명을 요청하려면 게시물 아래에 댓글을 남겨주세요. - 리뷰에서
Aksen P

이것이 질문에 대한 유효한 대답 인 이유와 질문이 잘못된 가정에 기반한 이유를 명확히하기 위해 내 대답을 업데이트했습니다.
kkurian

이것은 아무것도하지 않는 것 같습니다. 내 테스트는 그것이 멍청이임을 보여줍니다.
Itay Grudev

@ItayGrudev 실행 t.zone하기 전에 t.change무엇을 볼 수 있습니까? 그리고 당신이 t.zone쫓아 가면 t.change어떨까요? 그리고 t.change정확히 어떤 매개 변수에 전달하고 있습니까?
kkurian

0

필자는 Ruby / Rails-Change the timezone of a 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

위의 메소드를 클래스 나 모듈에 래핑 한 후 레일스 콘솔이나 루비 스크립트에서 시도 할 수있는 예 :

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

현재 답변보다 더 잘 작동 한 또 다른 버전이 있습니다.

now = Time.now
# => 2020-04-15 12:07:10 +0200
now.strftime("%F %T.%N").in_time_zone("Europe/London")
# => Wed, 15 Apr 2020 12:07:10 BST +01:00

"% N"을 사용하여 나노초 이상을 전달합니다. 다른 정밀도를 원하면이 strftime 참조를 참조하십시오 .


-1

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에서도 작동합니다.


1
때로는 동부 오프셋이 -4인데, 두 경우 모두 자동으로 처리하기를 원한다고 생각합니다.
OpenCoderX
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.