可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Can Ruby do something like this?
irb(main):001:0> start = Time.now
=> Thu Nov 05 01:02:54 -0800 2009
irb(main):002:0> Time.now - start
=> 25.239
irb(main):003:0> (Time.now - start).duration
=> "25 seconds"
(the duration method doesn't exist now)... and similarly, report
23 minutes and 35 seconds
1 hour and 33 minutes
2 days and 3 hours
(either report the whole duration, up to how many seconds, or report up to 2 numbers and units (if day and hour is reported, then no need to tell how many minutes))
回答1:
Here's a quick and simple way to implement this. Set predefined measurements for seconds, minutes, hours and days. Then depending on the size of the number, output the appropriate string with the those units. We'll extend Numeric
so that you can invoke the method on any numeric class (Fixnum
, Bignum
, or in your case Float
).
class Numeric
def duration
secs = self.to_int
mins = secs / 60
hours = mins / 60
days = hours / 24
if days > 0
"#{days} days and #{hours % 24} hours"
elsif hours > 0
"#{hours} hours and #{mins % 60} minutes"
elsif mins > 0
"#{mins} minutes and #{secs % 60} seconds"
elsif secs >= 0
"#{secs} seconds"
end
end
end
回答2:
Have a look at the Rails DateHelper.distance_of_time_in_words method. It will give you a great starting place. Despite being loaded with magic numbers, the approach should work for you.
回答3:
There is a gem available https://rubygems.org/gems/time_diff
Which gives the difference in a hash
回答4:
Try a ruby gem for that https://rubygems.org/gems/time_difference - Time Difference gem for Ruby
Documentation at https://github.com/tmlee/time_difference
start_time = Time.new(2013,1)
end_time = Time.new(2014,1)
TimeDifference.between(start_time, end_time).in_years
=> 1.0
回答5:
Time difference a pretty printed string:
class Numeric
def duration
rest, secs = self.divmod( 60 ) # self is the time difference t2 - t1
rest, mins = rest.divmod( 60 )
days, hours = rest.divmod( 24 )
# the above can be factored out as:
# days, hours, mins, secs = self.duration_as_arr
#
# this is not so great, because it could include zero values:
# self.duration_as_arr.zip ['Days','Hours','Minutes','Seconds']).flatten.join ' '
result = []
result << "#{days} Days" if days > 0
result << "#{hours} Hours" if hours > 0
result << "#{mins} Minutes" if mins > 0
result << "#{secs} Seconds" if secs > 0
return result.join(' ')
end
end
Time difference as an Array:
class Numeric
def duration_as_arr
rest, secs = self.divmod( 60 )
rest, mins = rest.divmod( 60 )
days, hours = rest.divmod( 24 )
[days, hours, mins, secs]
end
end
Example:
x = 1209801.079257
x.duration
=> "14 Days 3 Minutes 21.079257000004873 Seconds"
x.duration_as_arr
=> [14, 0, 3, 21.079257000004873]
回答6:
Building on Michael Richard's answer, here's a replacement for the if
block that gets English pluralization right, and won't say things like "14 days and 0 hours":
if days > 0
hour_remainder = hours % 24
if hour_remainder > 0
hour_str = hour_remainder == 1 ? 'hour' : 'hours'
"#{days} days and #{hour_remainder} #{hour_str}"
elsif days == 1
"#{days} day"
else
"#{days} days"
end
elsif hours > 0
min_remainder = mins % 60
if min_remainder > 0
min_str = min_remainder == 1 ? 'minute' : 'minutes'
"#{hours} hours and #{min_remainder} #{min_str}"
elsif hours == 1
"#{hours} hour"
else
"#{hours} hours"
end
elsif mins > 0
sec_remainder = secs % 60
if sec_remainder > 0
sec_str = sec_remainder == 1 ? 'second' : 'seconds'
"#{mins} minutes and #{sec_remainder} #{sec_str}"
elsif minutes == 1
"#{mins} minute"
else
"#{mins} minutes"
end
elsif secs == 1
"#{secs} second"
elsif secs >= 0
"#{secs} seconds"
end
回答7:
I could not withstand to put a generic solution here - although: has a year 365 days?
Additional I put an abs
when converting self.to_int
class Numeric
def duration
steps=[60, 60, 24, 365,0]
names=[:seconds, :minutes, :hours, :days, :years]
results=[]
stepper = self.to_int.abs
steps.each { |div|
if stepper>0
if div>0
results<<stepper % div
stepper/=div
else
results << stepper
end
end
}
e= results.empty? ? 0 : results.count-1
mt= e>0 ? results[e-1] : 0
et=results[e] || 0
et.to_s+" "+names[e].to_s + (mt>0 ? " "+mt.to_s+" "+names[e-1].to_s : '')
end
end
and with translation
class Numeric
def i18n_duration
steps=[60, 60, 24, 365,0]
names=[:seconds, :minutes, :hours, :days, :years]
results=[]
stepper = self.to_int.abs
steps.each { |div|
if stepper>0
if div>0
results<<stepper % div
stepper/=div
else
results << stepper
end
end
}
e= results.empty? ? 0 : results.count-1
mt= e>0 ? results[e-1] : 0
et=results[e] || 0
I18n.t("datetime.distance_in_words.x_#{names[e]}", count: et) +
(mt>0 ? " "+I18n.t("datetime.distance_in_words.x_#{names[e-1]}", count: mt):'')
end
end
回答8:
As an alternative you can do this:
start = DateTime.now.strftime('%Q').to_i / 1000.0
#=> 1409163050.088
sleep 3.5
#=> 3
now_ms = DateTime.now.strftime('%Q').to_i / 1000.0
#=> 1409163053.606
'%.3f' % (now_ms - start)
#=> "3.518"
回答9:
I've given an alternative implementation for this when used in script logs here [copied from there]:
How to generate a human readable time range using ruby on rails
If you want to show significant durations in the seconds to days range, an alternative would be (as it doesn't have to perform the best):
def human_duration(secs, significant_only = true)
n = secs.round
parts = [60, 60, 24, 0].map{|d| next n if d.zero?; n, r = n.divmod d; r}.
reverse.zip(%w(d h m s)).drop_while{|n, u| n.zero? }
if significant_only
parts = parts[0..1] # no rounding, sorry
parts << '0' if parts.empty?
end
parts.flatten.join
end
start = Time.now
# perform job
puts "Elapsed time: #{human_duration(Time.now - start)}"
human_duration(0.3) == '0'
human_duration(0.5) == '1s'
human_duration(60) == '1m0s'
human_duration(4200) == '1h10m'
human_duration(3600*24) == '1d0h'
human_duration(3600*24 + 3*60*60) == '1d3h'
human_duration(3600*24 + 3*60*60 + 59*60) == '1d3h' # simple code, doesn't round
human_duration(3600*24 + 3*60*60 + 59*60, false) == '1d3h59m0s'
Alternatively you may be only interested in stripping the seconds part when it doesn't matter (also demonstrating another approach):
def human_duration(duration_in_seconds)
n = duration_in_seconds.round
parts = []
[60, 60, 24].each{|d| n, r = n.divmod d; parts << r; break if n.zero?}
parts << n unless n.zero?
pairs = parts.reverse.zip(%w(d h m s)[-parts.size..-1])
pairs.pop if pairs.size > 2 # do not report seconds when irrelevant
pairs.flatten.join
end
Hope that helps.
回答10:
I came with this solution
def formate_duration(started, ended)
differences = ["secs", "mins", "hours", "days"].reduce([[nil, [(ended - started).round, nil]]]) do |acc, unit|
mod = unit == "hours" ? 24 : 60
# will return [ over, unit_value ]
# over is than used for calculation in next step
# accumulator is in format [["unit" [ over, value]]] and we are interesting in latest over in each step
# => This is why main diff is in this place in accumulator
entry = acc[acc.size-1][1][0].divmod(mod)
acc << [ unit, entry ]
end
# this will do string conversion and reverse in one step
str = differences.drop(1).reduce("") do |acc, current|
if (current[1][1] > 0)
"#{current[1][1]} #{current[0]} #{acc}"
else acc end
end
str.empty? ? "now" : str
end
Be aware that this won't work for differences bigger than 60 days (In that case you will need to add condition)
回答11:
time_difference = current_time - old_time
def seconds_fraction_to_time(time_difference)
days = hours = mins = 0
mins = (seconds / 60).to_i
seconds = (seconds % 60 ).to_i
hours = (mins / 60).to_i
mins = (mins % 60).to_i
days = (hours / 24).to_i
hours = (hours % 24).to_i
return [days,hours,mins,seconds]
end
then you can print it out what ever way you wish,
i.e
if(days > 0)
return "#{days} Days #{hours} Hours"
else
return "#{hours} Hours #{mins} Minutes"
end