Ruby / Rails: modifica il fuso orario di un tempo, senza modificare il valore

Ho un record foo nel database che ha :start_time e :timezone attributi :timezone .

Il :start_time è un tempo in UTC – 2001-01-01 14:20:00 , per esempio. The :timezone è una stringa: America/New_York , ad esempio.

Voglio creare un nuovo object Time con il valore di :start_time ma il cui fuso orario è specificato da :timezone . Non voglio caricare :start_time e poi convertire in :timezone , perché Rails sarà intelligente e aggiornerà il tempo da UTC per essere coerente con quel fuso orario.

Attualmente,

 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 

Invece, voglio vedere

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

vale a dire. Voglio fare:

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

Sembra che tu voglia qualcosa sulla falsariga di

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

Questo dice convertire l’ora locale (usando la zona) in utc. Se hai impostato Time.zone , puoi ovviamente farlo

 Time.zone.local_to_utc(t) 

Questo non utilizzerà il fuso orario associato a t – presuppone che sia locale al fuso orario da cui stai convertendo.

Un caso limite da proteggere qui è le transizioni DST: l’ora locale specificata potrebbe non esistere o essere ambigua.

Se stai usando Rails, ecco un altro metodo sulla falsariga della risposta di Eric Walsh:

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

Ho appena affrontato lo stesso problema ed ecco cosa ho intenzione di fare:

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

Ecco la documentazione su asctime

È necessario aggiungere l’offset dell’ora al proprio tempo dopo averlo convertito.

Il modo più semplice per farlo è:

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

Non sono sicuro del perché vorresti farlo, anche se probabilmente è meglio lavorare effettivamente con il modo in cui sono costruiti. Immagino un po ‘di background sul perché è necessario spostare il tempo e il fuso orario sarebbe utile.

In realtà, penso che devi sottrarre l’offset dopo averlo convertito, come in:

 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 

Dipende da dove userai questo tempo.

Quando il tuo tempo è un attributo

Se il tempo viene utilizzato come attributo, puoi utilizzare lo stesso gem 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 

Quando imposti una variabile separata

Utilizza la stessa gem 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 
 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 

Rapida piccola funzione che ho trovato per risolvere il problema. Se qualcuno ha un modo più efficiente per farlo, per favore pubblicalo!

Ho passato molto tempo a lottare con i timezone e, dopo aver armeggiato con Ruby 1.9.3, ho capito che non è necessario convertire in un simbolo di fuso orario con nome prima della conversione:

 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 

Ciò che questo implica è che puoi concentrarti su come impostare l’ora appropriata prima nella regione che desideri, nel modo in cui ci penserai (almeno nella mia testa la partiziono in questo modo), e poi convertirai alla fine nella zona vuoi verificare la tua logica di business con.

Questo funziona anche per Ruby 2.3.1.

Ho creato alcuni metodi di supporto, uno dei quali fa esattamente la stessa cosa che viene chiesto dall’autore originale del post a Ruby / Rails – Modifica il fuso orario di un tempo, senza modificare il valore .

Inoltre ho documentato alcune particolarità che ho osservato e anche questi aiutanti contengono metodi per ignorare completamente i risparmi automatici di luce diurna applicabili mentre le conversioni temporali che non sono disponibili immediatamente nella struttura di 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 eg 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 

Esempi che possono essere provati su console rails o uno script ruby ​​dopo aver completato i metodi sopra elencati in una class o un modulo:

 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 

Spero che questo ti aiuti.