Existe-t-il un moyen facile d’arrondir le temps à 15 minutes près?
C’est ce que je fais actuellement. Y a-t-il un moyen plus facile de le faire?
t = Time.new rounded_t = Time.local(t.year, t.month, t.day, t.hour, t.min/15*15)
Vous avez dit “arrondi”, donc je ne suis pas sûr que vous cherchiez le tour ou le sol, mais voici le code pour faire les deux. Je pense que quelque chose comme cela se lit très bien si vous ajoutez des méthodes round_off
et floor
à la classe Time. L’avantage supplémentaire est que vous pouvez plus facilement arrondir par n’importe quelle partition de temps.
require 'active_support/core_ext/numeric' # from gem 'activesupport' class Time # Time#round already exists with different meaning in Ruby 1.9 def round_off(seconds = 60) Time.at((self.to_f / seconds).round * seconds).utc end def floor(seconds = 60) Time.at((self.to_f / seconds).floor * seconds).utc end end t = Time.now # => Thu Jan 15 21:26:36 -0500 2009 t.round_off(15.minutes) # => Thu Jan 15 21:30:00 -0500 2009 t.floor(15.minutes) # => Thu Jan 15 21:15:00 -0500 2009
Note: ActiveSupport n’était nécessaire que pour le joli argument de 15.minutes
. Si vous ne voulez pas cette dépendance, utilisez plutôt 15 * 60
.
Je ne connais pas très bien la syntaxe de ruby mais vous pouvez arrondir à 15 minutes près avec modulo. (c.-à-d. x – (x modulo 15)). Je suppose que la syntaxe serait quelque chose comme
t.min - ( t.min % 15)
Cela fera de votre ensemble de valeurs possibles 0, 15, 30 et 45. En supposant que 0 <= t.min <= 59.
Je pensais que je publierais une autre solution qui arrondit au nombre de secondes donné le plus proche. Oh, et cela ne change pas le fuseau horaire comme certaines des autres solutions.
class Time def round(sec=1) down = self - (self.to_i % sec) up = down + sec difference_down = self - down difference_up = up - self if (difference_down < difference_up) return down else return up end end end t = Time.now # => Mon Nov 15 10:18:29 +0200 2010 t.round(15.minutes) # => Mon Nov 15 10:15:00 +0200 2010 t.round(20.minutes) # => Mon Nov 15 10:20:00 +0200 2010 t.round(60.minutes) # => Mon Nov 15 10:00:00 +0200 2010
ActiveSupport a été utilisé dans les exemples de la fonctionnalité x.minutes. Vous pouvez utiliser 15 * 60 à la place.
Les méthodes floor et ceil peuvent être facilement implémentées sur la base de cette solution.
Comme Ruby autorise l’arithmétique (en secondes) sur Times, vous pouvez simplement faire ceci:
t = Time.new rounded_t = tt.sec-t.min%15*60
J’ai écrit le joyau d’arrondi pour gérer ce genre de cas.
Arrondir une heure devient aussi simple que d’appeler floor_to
sur le temps. Arrondir et arrondir au plus proche sont également supportés ( ceil_to
et round_to
).
require "rounding" Time.current.floor_to(15.minutes) # => Thu, 07 May 2015 16:45:00 UTC +00:00 Time.current.ceil_to(15.minutes) # => Thu, 07 May 2015 17:00:00 UTC +00:00 Time.current.round_to(15.minutes) # => Thu, 07 May 2015 16:45:00 UTC +00:00
Le fuseau horaire de l’heure d’origine est conservé (UTC dans ce cas). Vous n’avez pas besoin d’ActiveSupport chargé, vous pouvez écrire floor_to(15*60)
et cela fonctionnera floor_to(15*60)
. Le bijou utilise des nombres rationnels pour éviter les erreurs d’arrondi. Vous pouvez arrondir au lundi le plus proche en fournissant un round_to(1.week, Time.parse("2015-5-4 00:00:00 UTC"))
. Nous l’utilisons en production.
J’ai écrit un article de blog qui explique plus. J’espère que vous trouverez cela utile.
J’ai trouvé une solution très lisible.
Cela va arrondir votre temps à la dernière ronde de 15 minutes. Vous pouvez changer le 15.minutes
à chaque échelle de temps possible.
Time.at(Time.now.to_i - (Time.now.to_i % 15.minutes))
Vous pourriez faire:
Time.at(t.to_i/(15*60)*(15*60))
# this is an extension of Ryan McGeary's solution, specifically for Rails. # Note the use of utc, which is necessary to keep Rails time zone stuff happy. # put this in config/initializers/time_extensions require 'rubygems' require 'active_support' module TimeExtensions %w[ round floor ceil ].each do |_method| define_method _method do |*args| seconds = args.first || 60 Time.at((self.to_f / seconds).send(_method) * seconds).utc end end end Time.send :include, TimeExtensions
Il y a pas mal de solutions ici et j’ai commencé à m’interroger sur leur efficacité (votre efficacité n’est probablement pas l’aspect le plus important dans ce problème). J’en ai pris quelques-uns et en ai jeté quelques-uns. (NB: bien que l’OP ait demandé d’arrondir à 15 minutes au plus, j’ai effectué mes comparaisons et mes échantillons avec seulement 1 minute / 60 pour des échantillons plus simples).
Benchmark.bmbm do |x| x.report("to_f, /, floor, * and Time.at") { 1_000_000.times { Time.at((Time.now.to_f / 60).floor * 60) } } x.report("to_i, /, * and Time.at") { 1_000_000.times { Time.at((Time.now.to_i / 60) * 60) } } x.report("to_i, %, - and Time.at") { 1_000_000.times { t = Time.now.to_i; Time.at(t - (t % 60)) } } x.report("to_i, %, seconds and -") { 1_000_000.times { t = Time.now; t - (t.to_i % 60).seconds } } x.report("to_i, % and -") { 1_000_000.times { t = Time.now; t - (t.to_i % 60) } } end
Rehearsal ----------------------------------------------------------------- to_f, /, floor, * and Time.at 4.380000 0.010000 4.390000 ( 4.393235) to_i, /, * and Time.at 3.270000 0.010000 3.280000 ( 3.277615) to_i, %, - and Time.at 3.220000 0.020000 3.240000 ( 3.233176) to_i, %, seconds and - 10.860000 0.020000 10.880000 ( 10.893103) to_i, % and - 4.450000 0.010000 4.460000 ( 4.460001) ------------------------------------------------------- total: 26.250000sec user system total real to_f, /, floor, * and Time.at 4.400000 0.020000 4.420000 ( 4.419075) to_i, /, * and Time.at 3.220000 0.000000 3.220000 ( 3.226546) to_i, %, - and Time.at 3.270000 0.020000 3.290000 ( 3.275769) to_i, %, seconds and - 10.910000 0.010000 10.920000 ( 10.924287) to_i, % and - 4.500000 0.010000 4.510000 ( 4.513809)
Que faire de ça? Eh bien, quelque chose pourrait fonctionner plus vite ou plus lentement sur votre matériel, alors ne prenez pas le mot informatique pour cela. Comme vous pouvez le voir, une autre chose est que, à moins que nous effectuions ces opérations à l’échelle de millions d’opérations, la méthode utilisée pour le traitement ne fera pas beaucoup de différence. fournir très peu de puissance de traitement et donc des millions peuvent être des centaines ou des dizaines de milliers dans de tels environnements).
En ce sens, en utilisant clairement le plus lent d’entre eux t = Time.now; t - (t.to_i % 60).seconds
t = Time.now; t - (t.to_i % 60).seconds
peut être justifié simplement parce que les .seconds sont tellement cool.
Cependant, comme il n’est pas nécessaire du tout et rend l’opération deux fois plus chère que sans elle, je dois dire que mon choix est le t = Time.now; t - (t.to_i % 60)
t = Time.now; t - (t.to_i % 60)
. À mon avis, il est assez rapide et millions de fois plus lisible que les autres solutions présentées ici. C’est la raison pour laquelle je pense que c’est la meilleure solution pour vos besoins en matière de revêtement de sol, même si elle est nettement plus lente que les trois autres.
La solution la plus votée sur cette page Time.at((Time.now.to_f / 60).floor * 60)
est la plus lente de toutes les solutions de cette page (avant cette réponse) et nettement plus lente que les 2 premières solutions. Utiliser des flotteurs simplement pour pouvoir décoller les décimales semble également très illogique. Pour ce qui est de la partie arrondie, ce serait bien, mais l’arrondi ressemble à un «revêtement de sol» pour moi. Si quoi que ce soit, la contrepartie pourrait être l’arrondi ou le “plafond”, qui serait quelque chose comme t = Time.now; t - (60 - t.to_i % 60) % 60
t = Time.now; t - (60 - t.to_i % 60) % 60
ou Time.at((Time.now.to_f / 60).ceil * 60)
. Le double modulo que la solution to_i a besoin ici est un peu désagréable, alors même si elle est beaucoup plus rapide, je préférerais la méthode ceil. (Benchmarks ajoutés à la toute fin de ce post)
Le lié (les différences sont tellement insignifiantes que vous ne pouvez pas vraiment déclarer un vainqueur) les deux meilleurs interprètes du test où deux variantes to_i qui utilisent une combinaison d’opérations légèrement différente puis convertissent un entier en object Time. Si vous êtes pressé, voici ceux que vous devriez utiliser:
Time.at((Time.now.to_i / 60) * 60) t = Time.now.to_i; Time.at(t - (t % 60))
Benchmark.bmbm do |x| x.report("to_f, /, ceil, * and Time.at") { 1_000_000.times { Time.at((Time.now.to_f / 60).ceil * 60) } } x.report("to_i, %, -, %, + and Time.at") { 1_000_000.times { t = Time.now; t + (60 - t.to_i % 60) % 60 } } end
Rehearsal ---------------------------------------------------------------- to_f, /, ceil, * and Time.at 4.410000 0.040000 4.450000 ( 4.446320) to_i, %, -, %, + and Time.at 3.910000 0.020000 3.930000 ( 3.939048) ------------------------------------------------------- total: 8.380000sec user system total real to_f, /, ceil, * and Time.at 4.420000 0.030000 4.450000 ( 4.454173) to_i, %, -, %, + and Time.at 3.860000 0.010000 3.870000 ( 3.884866)
La réponse de Chuck, bien qu’élégante, vous posera des problèmes si vous essayez de comparer les valeurs ainsi obtenues. les usecs ne sont pas mis à zéro.
La réponse de Shalmanese s’en occupe, ou Chuck’s peut être modifié comme suit:
t = Time.new truncated_t = Time.at(t.to_i - t.sec - t.min % 15 * 60)
La solution de Ryan McGeary n’a pas fonctionné pour les fuseaux horaires qui n’étaient pas sur la demi-heure. Par exemple, Katmandou a +5: 45, arrondir à 30.minutes a eu de mauvais résultats. Cela devrait fonctionner:
class ActiveSupport::TimeWithZone def floor(seconds = 60) return self if seconds.zero? Time.at(((self - self.utc_offset).to_f / seconds).floor * seconds).in_time_zone + self.utc_offset end def ceil(seconds = 60) return self if seconds.zero? Time.at(((self - self.utc_offset).to_f / seconds).ceil * seconds).in_time_zone + self.utc_offset end # returns whichever (out of #floor and #ceil) is closer to the current time def closest(seconds = 60) down, up = floor(seconds), ceil(seconds) ((self - down).abs > (self - up).abs) ? up : down end end
Et des tests:
class TimeHelperTest < ActionDispatch::IntegrationTest test "floor" do t = Time.now.change(min: 14) assert_equal Time.now.change(min: 10), t.floor(5.minutes) assert_equal Time.now.change(min: 0), t.floor(30.minutes) end test "ceil" do t = Time.now.change(min: 16) assert_equal Time.now.change(min: 20), t.ceil(5.minutes) assert_equal Time.now.change(min: 30), t.ceil(30.minutes) end test "closest" do t = Time.now.change(min: 18) assert_equal Time.now.change(min: 20), t.closest(5.minutes) assert_equal Time.now.change(min: 30), t.closest(30.minutes) assert_equal Time.now.change(min: 0), t.closest(60.minutes) end test "works in time zones that are off the half hour" do Time.zone = "Kathmandu" #2.1.0p0 :028 > Time.zone.now # => Tue, 30 Sep 2014 06:46:12 NPT +05:45 # doing .round(30.minutes) here would give 06:45 under the old method t = Time.zone.now.change(min: 30) assert_equal Time.zone.now.change(min: 30), t.closest(30.minutes) t = Time.zone.now.change(min: 0) assert_equal Time.zone.now.change(min: 0), t.closest(30.minutes) end end
Votre évaluation actuelle en utilisant
min / 15 * 15
ne tronque que le min, donc
15 => 15 16 => 15 .. 29 => 15 30 => 30
Ce qui n’est pas un “arrondi”.
Vous pouvez approcher de façon approximative avec
(( min + 7.5 ) / 15).to_i * 15
Ou, en utilisant des internes:
( min.to_f / 15 ).round * 15