J'ai un enregistrement foo
dans la base de données qui a les attributs :start_time
et :timezone
.
Le :start_time
est une heure en UTC - 2001-01-01 14:20:00
, par exemple . Le :timezone
est une chaîne - America/New_York
, par exemple.
Je souhaite créer un nouvel objet Time avec la valeur :start_time
mais dont le fuseau horaire est spécifié par :timezone
. Je ne veux pas charger le :start_time
et ensuite le convertir en :timezone
, car Rails sera intelligent et mettra à jour l'heure à partir de l'heure UTC pour qu'elle soit cohérente avec ce fuseau horaire.
Actuellement,
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
Au lieu de cela, je veux voir
=> Sat, 01 Jan 2000 14:20:00 EST -05:00
c'est à dire. Je veux faire:
t
=> 2000-01-01 14:20:00 UTC
t.zone = "America/New_York"
=> "America/New_York"
t
=> 2000-01-01 14:20:00 EST
On dirait que vous voulez quelque chose du genre
ActiveSupport::TimeZone.new('America/New_York').local_to_utc(t)
Ceci dit convertit cette heure locale (en utilisant la zone) en utc. Si vous avez défini Time.zone
, vous pouvez bien sûr
Time.zone.local_to_utc(t)
Cela n'utilisera pas le fuseau horaire associé à t - il suppose qu'il est local au fuseau horaire à partir duquel vous effectuez la conversion.
Les transitions DST sont un cas à éviter ici: l’heure locale spécifiée peut ne pas exister ou être ambiguë.
Si vous utilisez Rails, voici une autre méthode dans le sens de la réponse d'Eric Walsh:
def set_in_timezone(time, zone)
Time.use_zone(zone) { time.to_datetime.change(offset: Time.zone.now.strftime("%z")) }
end
Je viens de faire face au même problème et voici ce que je vais faire:
t = t.asctime.in_time_zone("America/New York")
Voici la documentation sur asctime
Vous devez ajouter le décalage horaire à votre temps après l'avoir converti.
Le moyen le plus simple de procéder est:
t = Foo.start_time.in_time_zone("America/New_York")
t -= t.utc_offset
Je ne sais pas pourquoi vous voudriez le faire, bien qu'il soit probablement préférable de travailler avec les époques telles qu'elles sont construites. J'imagine qu'il serait utile de connaître les raisons pour lesquelles vous devez changer l'heure et les fuseaux horaires.
En fait, je pense que vous devez soustraire le décalage après l'avoir converti, comme dans:
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
Cela dépend de l'endroit où vous allez utiliser ce temps.
Quand votre temps est un attribut
Si le temps est utilisé comme attribut, vous pouvez utiliser le même 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
Lorsque vous définissez une variable distincte
Utilisez la même 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
J'ai passé beaucoup de temps à lutter avec TimeZones également, et après avoir bricolé Ruby 1.9.3, j'ai réalisé qu'il n'était pas nécessaire de convertir un symbole de fuseau horaire nommé avant de convertir:
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
Cela signifie que vous pouvez vous concentrer sur la configuration de l'heure appropriée en premier dans la région de votre choix, comme vous le pensez (du moins dans ma tête, je la partitionne de cette façon), puis à la fin, la conversion en zone. vous voulez vérifier votre logique métier avec.
Cela fonctionne aussi pour Ruby 2.3.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
Petite fonction rapide, je suis venu avec pour résoudre le travail. Si quelqu'un a un moyen plus efficace de le faire, postez-le!
J'ai créé quelques méthodes d'assistance, dont l'une fait exactement la même chose que celle demandée par l'auteur original du message sur Ruby/Rails - Modifie le fuseau horaire d'une heure, sans modifier la valeur .
J'ai également documenté quelques particularités que j'ai observées et ces aides contiennent des méthodes pour ignorer complètement les économies automatiques de la lumière du jour applicables lors des conversions d'heure qui ne sont pas disponibles prêtes à l'emploi dans le cadre 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
Exemples pouvant être essayés sur la console Rails ou un script Ruby après avoir encapsulé les méthodes ci-dessus dans une classe ou un 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
J'espère que cela t'aides.