Ruby / Rails - Değeri değiştirmeden bir Zamanın saat dilimini değiştirin


108

fooVeritabanında :start_timeve :timezoneözniteliklerine sahip bir kaydım var .

:start_time- UTC bir Zamanı 2001-01-01 14:20:00örneğin. :timezoneBir dizedir - America/New_Yorkörneğin.

Değeri olan :start_timeancak saat dilimi ile belirtilen yeni bir Time nesnesi oluşturmak istiyorum :timezone. Rails akıllı olacak ve bu saat dilimiyle tutarlı olması için saati UTC'den güncelleyeceği :start_timeiçin :timezone, yüklemek ve sonra dönüştürmek istemiyorum .

Şu anda,

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

Bunun yerine görmek istiyorum

=> Sat, 01 Jan 2000 14:20:00 EST -05:00

yani. Ben yapmak istiyorum:

t
=> 2000-01-01 14:20:00 UTC
t.zone = "America/New_York"
=> "America/New_York"
t
=> 2000-01-01 14:20:00 EST


3
Saat dilimlerini doğru kullandığınızı sanmıyorum. Bunu db'nize yerelden UTC olarak kaydederseniz, yerel saati aracılığıyla ayrıştırıp, göreceli utc ile kaydetmenin ne yanlışı olur?
Gezi

1
Ya ... Bence en iyi yardımı almak için bunu neden yapmanız gerektiğini açıklamanız gerekebilir? Neden ilk etapta veritabanına yanlış zamanı depoladınız?
nzifnab

@MrYoshiji kabul etti. bana YAGNI veya erken optimizasyon gibi geliyor.
mühendisDave

1
bu belgeler yardımcı olsaydı, StackOverflow'a ihtiyacımız olmazdı :-) Oradaki bir örnek, herhangi bir şeyin nasıl ayarlandığını göstermez - tipik. Bunu, Gün Işığından Yararlanma devreye girdiğinde veya çıktığında bozulmayan Elmalar-Elmalar karşılaştırmasını zorlamak için de yapmam gerekiyor.
JosephK

Yanıtlar:


72

Çizgisinde bir şey istiyor gibisin

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

Bu yerel saati (bölgeyi kullanarak) utc'ye dönüştür diyor. Eğer varsa Time.zoneo zaman set sen elbette can

Time.zone.local_to_utc(t)

Bu, t'ye ekli saat dilimini kullanmaz - dönüştürme yaptığınız saat dilimine göre yerel olduğunu varsayar.

Burada önlem alınması gereken bir uç durum, DST geçişleridir: belirlediğiniz yerel saat mevcut olmayabilir veya belirsiz olabilir.


17
Local_to_utc ve Time.use_zone kombinasyonu ihtiyacım olan şeydi:Time.use_zone(self.timezone) { Time.zone.local_to_utc(t) }.localtime
rwb

DST geçişlerine karşı ne önerirsiniz? Dönüştürülecek hedef zamanın D / ST geçişinden sonra olduğunu ve Time. now'ın değişiklikten önce olduğunu varsayalım. Işe yarar mı ?
Cyril DUCHON-Doris

28

Az önce aynı problemle karşılaştım ve işte yapacağım şey:

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

İşte asctime ile ilgili belgeler


2
Sadece beklediğim şeyi yapıyor
Kaz

Bu harika, teşekkürler! Cevabımı buna göre basitleştirdim . Bunun bir dezavantajı, asctimesaniyenin altındaki değerleri düşürmesidir (cevabım bunu korur).
Henrik N

22

Rails kullanıyorsanız, işte Eric Walsh'un cevabına göre başka bir yöntem:

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

1
Ve DateTime nesnesini bir TimeWithZone nesnesine dönüştürmek .in_time_zoneiçin, sonuna kadar tutun .
Brian

@Brian Murphy-Dye Bu işlevi kullanırken Yaz Saati Uygulaması ile ilgili sorunlar yaşadım. DST ile çalışan bir çözüm sağlamak için sorunuzu düzenleyebilir misiniz? Belki de değiştirmek Time.zone.nowistediğiniz zamana en yakın olan bir şeyi değiştirmek işe yarar?
Cyril DUCHON-Doris

6

Zaman farkını dönüştürdükten sonra saatinize eklemeniz gerekir.

Bunu yapmanın en kolay yolu şudur:

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

Bunu neden yapmak isteyeceğinizden emin değilim, ancak muhtemelen inşa edildikleri gibi zamanlarda çalışmak en iyisidir. Sanırım neden zaman ve saat dilimlerini kaydırmanız gerektiğine dair bir arka plan faydalı olacaktır.


Bu benim için çalıştı. Bu, gün ışığından yararlanma vardiyalarında, ofset değerinin kullanıldığında ayarlanması koşuluyla, doğru kalmalıdır. DateTime nesneleri kullanılıyorsa, "offset.seconds" eklenebilir veya buradan çıkarılabilir.
JosephK

Rails'in dışındaysanız, Time.in_time_zoneactive_support'un doğru bölümlerini zorunlu kılarak kullanabilirsiniz:require 'active_support/core_ext/time'
jevon

Bu, hangi yöne gittiğinize bağlı olarak yanlış bir şey yapıyor gibi görünüyor ve her zaman güvenilir görünmüyor. Örneğin, şimdi bir Stockholm saatini alıp Londra saatine dönüştürürsem, ofseti ekler (çıkarmazsa) işe yarar. Ama Stockholm'u Helsinki'ye çevirirsem, toplama veya çıkarma yapmam yanlış.
Henrik N

5

Aslında, aşağıdaki gibi, dönüştürdükten sonra ofseti çıkarmanız gerektiğini düşünüyorum:

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

Bu Zamanı nerede kullanacağına bağlı.

Zamanınız bir nitelik olduğunda

Bir öznitelik olarak zaman kullanılıyorsa, aynı date_time_attribute gemini kullanabilirsiniz :

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

Ayrı bir değişken belirlediğinizde

Aynı date_time_attribute cevherini kullanın :

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

İşi çözmek için bulduğum küçük hızlı işlev. Birisinin bunu yapmanın daha verimli bir yolu varsa, lütfen bunu gönderin!


1
t.change(zone: 'America/New_York')

OP'nin önermesi yanlış: "Başlangıç_saatini yüklemek ve sonra saat dilimine dönüştürmek istemiyorum, çünkü Rails akıllı olacak ve saati UTC'den bu saat dilimiyle tutarlı olacak şekilde güncelleyecektir." Burada verilen yanıtın da gösterdiği gibi, bu mutlaka doğru değildir.


1
Bu soruya bir cevap vermiyor. Bir yazarı eleştirmek veya açıklama istemek için yazının altına bir yorum bırakın. - Yorumdan
Aksen P

Cevabım, bunun soruya neden geçerli bir cevap olduğunu ve sorunun neden hatalı bir varsayıma dayandığını açıklığa kavuşturmak için güncellendi.
kkurian

Bu hiçbir şey yapmıyor gibi görünüyor. Testlerim onun noop olduğunu gösteriyor.
Itay Grudev

@ItayGrudev Daha t.zoneönce koştuğunuzda t.changene görüyorsunuz? Peki ya t.zonepeşinden koştuğun zaman t.change? Ve t.changetam olarak hangi parametrelere geçiyorsunuz?
kkurian

0

Ruby / Rails'deki gönderinin orijinal yazarı tarafından sorulanla aynı şeyi yapan birkaç yardımcı yöntem oluşturdum - Değeri değiştirmeden bir Zamanın saat dilimini değiştirin .

Ayrıca gözlemlediğim birkaç özelliği belgeledim ve ayrıca bu yardımcılar, Rails çerçevesinde kullanıma hazır olmayan zaman dönüşümleri sırasında uygulanabilir otomatik gün ışığı tasarruflarını tamamen göz ardı etmek için yöntemler içerir:

  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

Yukarıdaki yöntemleri bir sınıfa veya modüle sardıktan sonra raylar konsolunda veya bir ruby ​​betiğinde denenebilecek örnekler:

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

Bu yardımcı olur umarım.


0

İşte benim için mevcut cevaplardan daha iyi çalışan başka bir versiyon:

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" kullanarak nanosaniye üzerinde taşır. Başka bir kesinlik istiyorsanız, bu strftime referansına bakın .


-1

Zaman Dilimleri ile de uğraşırken önemli ölçüde zaman harcadım ve Ruby 1.9.3 ile uğraştıktan sonra, dönüştürmeden önce adlandırılmış bir saat dilimi sembolüne dönüştürmeniz gerekmediğini fark ettim:

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

Bunun anlamı, önce istediğiniz bölgede, sizin düşündüğünüz şekilde (en azından kafamda bunu bu şekilde bölüyorum) uygun zaman kurulumunu elde etmeye odaklanıp sonunda bölgeye dönüştürebileceğinizdir. ile iş mantığınızı doğrulamak istiyorsunuz.

Bu aynı zamanda Ruby 2.3.1 için de geçerlidir.


1
Bazen Doğu ofseti -4'tür, her iki durumda da otomatik olarak işlemek istediklerini düşünüyorum.
OpenCoderX
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.