web-dev-qa-db-de.com

Ruby/Rails - Ändern Sie die Zeitzone einer Zeit, ohne den Wert zu ändern

Ich habe einen Datensatz foo in der Datenbank mit den Attributen :start_time und :timezone.

Der :start_time ist eine Zeit in UTC - 2001-01-01 14:20:00, zum Beispiel . Der :timezone ist zum Beispiel eine Zeichenfolge - America/New_York.

Ich möchte ein neues Zeitobjekt mit dem Wert von :start_time erstellen, dessen Zeitzone jedoch von :timezone angegeben wird. Ich möchte den :start_time nicht laden und dann in :timezone konvertieren, da Rails clever ist und die Zeit von UTC aktualisiert, um mit dieser Zeitzone übereinzustimmen.

Zur Zeit,

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

Stattdessen möchte ich sehen

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

dh. Ich will das tun:

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

Klingt, als ob Sie etwas in der Richtung von wollen 

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

Dies bedeutet, diese Ortszeit (mit der Zone) in utc umzuwandeln. Wenn Sie Time.zone eingestellt haben, können Sie dies natürlich tun

Time.zone.local_to_utc(t)

Die Zeitzone, die an t angehängt ist, wird nicht verwendet. Es wird davon ausgegangen, dass es sich um die lokale Zeitzone der Zeitzone handelt, aus der Sie konvertieren.

Ein Randfall, gegen den Sie sich schützen sollten, sind DST-Übergänge: Die von Ihnen angegebene Ortszeit ist möglicherweise nicht vorhanden oder mehrdeutig.

58

Wenn Sie Rails verwenden, haben Sie eine andere Methode, die Eric Walsh's Antwort entspricht:

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

Ich bin gerade mit dem gleichen Problem konfrontiert.

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

Hier ist die Dokumentation zu asctime

13
Ievgen

Sie müssen den Zeitversatz nach der Konvertierung zu Ihrer Zeit hinzufügen.

Der einfachste Weg dies zu tun ist:

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

Ich bin nicht sicher, warum Sie dies tun möchten, obwohl es wahrscheinlich am besten ist, mit den Zeiten zu arbeiten, in denen sie gebaut wurden. Ich denke, ein paar Hintergrundinformationen darüber, warum Sie Zeit und Zeitzonen verschieben müssen, wären hilfreich.

7
j_mcnally

Ich denke, Sie müssen den Offset nach der Konvertierung subtrahieren.

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
Peter Alfvin

Hängt davon ab, wo Sie diese Zeit verwenden werden.

Wenn deine Zeit ein Attribut ist

Wenn time als Attribut verwendet wird, können Sie dasselbe date_time_attribute gem verwenden:

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

Wenn Sie eine separate Variable festlegen

Verwenden Sie dasselbe 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
3
Sergei Zinin

Ich habe viel Zeit damit verbracht, mit TimeZones zu kämpfen, und nachdem ich mit Ruby 1.9.3 gearbeitet hatte, wurde mir klar, dass Sie vor der Konvertierung nicht in ein benanntes Zeitzonen-Symbol konvertieren müssen:

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

Dies bedeutet, dass Sie sich zunächst darauf konzentrieren können, die entsprechende Zeiteinstellung in der gewünschten Region zu erstellen, wie Sie darüber nachdenken würden (zumindest in meinem Kopf partitioniere ich sie so) und konvertiere sie am Ende in die Zone Sie möchten Ihre Geschäftslogik mit überprüfen.

Dies funktioniert auch für Ruby 2.3.1.

1
Torrey Payne
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

Schnelle kleine Funktion, mit der ich die Aufgabe lösen konnte. Wenn jemand eine effizientere Möglichkeit hat, dies zu tun, poste es bitte!

1
Eric Walsh

Ich habe einige Hilfsmethoden erstellt, von denen eine genau dasselbe tut, wie vom ursprünglichen Autor des Beitrags unter Ruby/Rails - Ändern der Zeitzone einer Zeit angegeben, ohne den Wert zu ändern

Ich habe auch einige Besonderheiten dokumentiert, die ich beobachtet habe, und diese Helfer enthalten Methoden, um die automatischen Tageslicht-Einsparungen, die bei Zeitumstellungen gelten können, vollständig zu ignorieren, was im Rails-Framework nicht standardmäßig verfügbar ist:

  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

Beispiele, die auf der Rails-Konsole oder einem Ruby-Skript nach dem Einschließen der obigen Methoden in eine Klasse oder ein Modul ausprobiert werden können:

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

Hoffe das hilft.

0
Jignesh Gohel