How to get a DateTime duration? - ruby-on-rails

I'm baffled how to do this.
I need to take a datetime object and get the duration in hours, days, whatever, to the current time.
Thank you.

Getting the duration in seconds is easy:
>> foo = Time.new
=> Mon Dec 29 18:23:51 +0100 2008
>> bar = Time.new
=> Mon Dec 29 18:23:56 +0100 2008
>> print bar - foo
5.104063=> nil
So, a little over five seconds.
But to present this in a somewhat more human-friendly form, you'll need a third-party addition, like time_period_to_s, or the Duration package.

distance_of_time_in_words will give you a string representation of the time between two time/date objects.

You want the Time class rather than DateTime.

If you are using rails, then distance_of_time_in_words_to_now may be what you are looking for.

i just want to share my code in order to get more readable duration time.
instead of using distance_of_time_in_words like #Ryan Bigg suggest, i like #Sören Kuklau suggestion more.
but to show human-friendly form, i make something like this.
def get_duration opts
from = Time.parse opts[:from]
to = Time.parse opts[:to]
duration = to - from
durationLabel = []
separator = " "
hours = (duration / 3600).floor
restDuration = duration%3600
durationLabel << "#{hours}h" if hours > 0
return durationLabel.join separator if restDuration.floor == 0
minutes = (restDuration / 60).floor
restDuration = duration%60
durationLabel << "#{minutes}m" if minutes > 0
return durationLabel.join separator if restDuration.floor == 0
seconds = restDuration.floor
durationLabel << "#{seconds}s" if seconds > 0
durationLabel.join separator
end
get_duration from: "2018-03-15 11:50:43", to: "2018-03-15 11:51:50"
# return 1m 7s
it will calculate the hours, minutes, and seconds.
instead of getting a vague response like about 1 minutes or hard response like 67.0, we will get something more human-friendly but accurate until second like 1m 7s

Related

Ruby strptime with greater than 24 hours

I have a string of the following format:
"136:16:11.862504"
(hours:minutes:seconds:milliseconds)
Whenever I try to use Ruby's strptime to parse this string, it throws an ArgumentError: invalid strptime format - '%H:%M:%S'
I've actually searched quite extensively and cannot figure out an elegant way to parse this (besides the rather clunky solution of splitting the string by its colons and periods, and doing it all manually). Is there a way of doing this that I'm overlooking?
EDIT: I'm not looking to get a timestamp out of this, I'm looking to get a time duration.
What is your expected output? '136' is not a valid hour, and since you don't have a date portion, we can't simply turn those 'extra' hours into days. If you don't care about the date portion, this solution may work for you:
time = "136:16:11.862504"
hours, minutes, seconds = time.split(":").map(&:to_f)
hours %= 24
minutes %= 60
seconds %= 60
Time.new(0, 1, 1, hours, minutes, seconds, 0)
=> 0000-01-01 16:16:11 +0000
In case if nothing blocks you from using Regexp, you could use something based on this answer, for example:
/^(\d+):([0-5][0-9]):([0-5][0-9])\.\d+$/ =~ "136:16:11.862504"
puts "#{$1} : #{$2} : #{$3}"
136 : 16 : 11

How to just show minutes or hours or weeks in rails time ago?

I would like to get the following result based on created_at:
1-59M
1-24H
1-999+W
E.g, if a post is 5 minutes old it will say 5M. If it is 15 hours old it will say: 15H and lastly it will say 52W if it is 52 weeks old.
Bonus: how would I make it work with: https://github.com/basecamp/local_time
You just want it in weeks, hours or minutes? How about this (it would go in a helpers file)
def short_age_string(time)
diff = Time.now - time #value is seconds (float)
if diff >= 0
result = "1-"
else
result = "1+"
end
diff = diff.abs.to_i
if diff >= 604800 #seconds in a week
weeks = diff/604800
return "#{result}#{weeks}#{"+" if weeks >= 999}W"
elsif diff > 3600 #seconds in an hour
return "#{result}#{diff/3600}H"
else
return "#{diff/60}#{minutes}M"
end
end
I took the liberty of making it return "1+..." for times in the future.
I believe you'd use strftime to manage it with i18n
According to strftimer, you'd need to use %-dH, %-dM, %-dD, %-dW to get the format you desire:
#view
<%=l record.created_at, format: :small %>
#config/locales/en.yml
time:
small: %-dH
I've tried testing this & it will only bring back the initial number. More testing is needed, but should set you on the right track

How to convert a long to date?

I have a long value in Rails, 134740800, which is the number of milliseconds since the epoch.
How do I convert that to a date in mm-dd-yyyy format?
I figure the formatting would be done with something like strftime but I can't seem to find the right method to convert the long into a valid date.
secs = 134740800/1000 # millisecs / 1000
t = Time.at(secs)
t.strftime("%m-%d-%Y")
Output
"01-02-1970"
Try this:
require 'date'
DateTime.strptime("1318996912",'%s')
I assume you mean seconds since the epoch.
Time.at seconds_since_epoch
You can also pass a float. If you have milliseconds, divide by 1000.0 first.
You can then call strftime on the returned Time object.
Use Time.at:
irb(main):003:0> Time.at(134740800)
=> Tue Apr 09 08:00:00 -0400 1974
This is an advisory... It's often a good idea to look at how fast some answers run. Here's a simple benchmark:
require 'benchmark'
require 'date'
SECS = 134740800
LOOPS = 1_000_000
puts Time.at(SECS).strftime('%m-%d-%Y')
puts Date.strptime(SECS.to_s, '%s').strftime('%m-%d-%Y')
Benchmark.bm(14) do |x|
x.report('Time.at:') { LOOPS.times { Time.at(SECS) }}
x.report('Date.strptime:') { LOOPS.times { Date.strptime(SECS.to_s, '%s') }}
end
And the output is:
04-09-1974
04-09-1974
user system total real
Time.at: 0.370000 0.020000 0.390000 ( 0.392761)
Date.strptime: 6.320000 0.050000 6.370000 ( 6.373248)

Convert duration to hours:minutes:seconds (or similar) in Rails 3 or Ruby

I have a feeling there is a simple/built-in way to do this but I can't find it.
I have a duration (in seconds) in an integer and I want to display it in a friendly format.
e.g. 3600 would be displayed as "01:00:00" or "1 hour" or something.
I can do it with time_ago_in_words(Time.zone.now+3600) but that feels like a bit of a hack, there is no reason to add/subtract from the current time just to format this value. Is there a duration_in_words() or something?
Thanks
Summing up:
assuming that total_seconds = 3600
Option 1:
distance_of_time_in_words(total_seconds) #=> "about 1 hour"
Option 2:
Time.at(total_seconds).utc.strftime("%H:%M:%S") #=> "01:00:00"
Note: it overflows, eg. for total_seconds = 25.hours.to_i it'll return "01:00:00" also
Option 3:
seconds = total_seconds % 60
minutes = (total_seconds / 60) % 60
hours = total_seconds / (60 * 60)
format("%02d:%02d:%02d", hours, minutes, seconds) #=> "01:00:00"
Option 4:
ActiveSupport::Duration.build(total_seconds).inspect #=> "1 hour"
# OR
parts = ActiveSupport::Duration.build(total_seconds).parts
"%02d:%02d:%02d" % [parts.fetch(:hours, 0),
parts.fetch(:minutes, 0),
parts.fetch(:seconds, 0)] #=> "01:00:00"
See: http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html
distance_of_time_in_words(3600)
=> "about 1 hour"
Ruby's string % operator is too unappreciated and oft forgotten.
"%02d:%02d:%02d:%02d" % [t/86400, t/3600%24, t/60%60, t%60]
Given t is a duration in seconds, this emits a zero-padded colon-separated string including days. Example:
t = 123456
"%02d:%02d:%02d:%02d" % [t/86400, t/3600%24, t/60%60, t%60]
=> "01:10:17:36"
Lovely.
I guess you could do also something like:
(Time.mktime(0)+3600).strftime("%H:%M:%S")
To format it as you wish.
BTW, originally I thought of using Time.at() but seems that EPOCH time on my Ubuntu is Thu Jan 01 01:00:00 +0100 1970 and not 00:00:00 hours as I expected, and therefore if I do:
Time.at(3600).strftime("%H:%M:%S")
Gives me 1 hour more than wanted.
I use this to show time durations in my Rails Project:
Add a custom method to the Integer class. You can create a new file called pretty_duration.rb in the initializers folder:
class Integer
def pretty_duration
parse_string =
if self < 3600
'%M:%S'
else
'%H:%M:%S'
end
Time.at(self).utc.strftime(parse_string)
end
end
Call seconds.pretty_duration anywhere in your project:
275.pretty_duration # => "04:35"
9823.pretty_duration # => "02:43:43"
This answer builds up on Lev Lukomsky's Code
This one uses the obscure divmod method to divide and modulo at the same time, so it handles Float seconds properly:
def duration(seconds)
minutes, seconds = seconds.divmod(60)
hours, minutes = minutes.divmod(60)
days, hours = hours.divmod(24)
"#{days.to_s.rjust(3)}d #{hours.to_s.rjust(2)}h #{minutes.to_s.rjust(2)}m #{seconds}s"
end
ActiveSupport::Duration.build + inspect gives you valid results
>> ActiveSupport::Duration.build(125557).inspect
=> "1 day, 10 hours, 52 minutes, and 37 seconds"
Using Time.utc.strftime works only for values when total number of hours is less then 24:
2.2.2 :004 > Time.at(60 * 60).utc.strftime('%H h %M m')
=> "01 h 00 m"
For greater values it returns incorrect results:
2.2.2 :006 > Time.at(60 * 60 * 24).utc.strftime('%H h %M m')
=> "00 h 00 m"
I suggest using the simplest method I found for this problem:
def formatted_duration total_seconds
hours = total_seconds / (60 * 60)
minutes = (total_seconds / 60) % 60
seconds = total_seconds % 60
"#{ hours } h #{ minutes } m #{ seconds } s"
end
You can always adjust returned value to your needs.
Be careful with the duration longer than one day.
(timing/3600).to_i.to_s.rjust(2,'0') + ":"+Time.at(timing).utc.strftime("%M:%S")
An answer inspired from Lev Lukomsky's one taking advantage of ActiveSupport::Duration, and handling milliseconds (useful to benchmark code)
# duration in ms modulus number of ms in one second
milliseconds = duration.in_milliseconds % 1.second.in_milliseconds
# duration in seconds modulus number of seconds in one minute
seconds = (duration / 1.second) % (1.minute / 1.second)
# duration in minutes modulus number of minutes in one hour
minutes = (duration / 1.minute) % (1.hour / 1.minute)
# duration in hours modulus number of hours in one day
hours = (duration / 1.hour) % (1.day / 1.hour)
format("%02d:%02d:%02d:%03d", hours, minutes, seconds, milliseconds) #=> "12:05:00:001"
Of course you can extend this easily with days, months, years, etc using related ActiveSupport methods and repeating the same structure.
Keep in mind that for too long durations, this may be inaccurate since the duration of 1 month is not fixed in number of days, and I'm not sure how AS:Duration deals with that.
Shout out to #joshuapinter who gave the best answer (in the form of a comment).
Use the drop-in replacement dotiw gem to gain more control over the accuracy of the output to suit different needs:
https://github.com/radar/distance_of_time_in_words
Sample view code:
%label
Logoff after:
- expire_in = distance_of_time_in_words(Time.now, Time.now + user.custom_timeout.minutes, :only => [:minutes, :hours, :days])
= expire_in
Resulting in something like this:
Logoff after: 1 day, 13 hours, and 20 minutes
Just to throw in my 2 cents:
Time.at(i).utc.strftime((i < 3600) ? '%-M minutes and %-S seconds' : '%-H hours, %-M minutes, and %-S seconds')
Built off of Xiao Bin's answer.
Here a simple solution using divmod and map:
hours = 3.5456
value = (hours*60).divmod(60).map{ |a| "%02d"%[a.floor] }.join(":")
=> "03:32"

How do I get the number of seconds between two DateTimes in Ruby on Rails

I've got code that does time tracking for employees. It creates a counter to show the employee how long they have been clocked in for.
This is the current code:
start_time = Time.parse(self.settings.first_clock_in)
total_seconds = Time.now - start_time
hours = (total_seconds/ 3600).to_i
minutes = ((total_seconds % 3600) / 60).to_i
seconds = ((total_seconds % 3600) % 60).to_i
This works fine. But because Time is limited to the range of 1970 - 2038 we are trying to replace all Time uses with DateTimes. I can't figure out how to get the number of seconds between two DateTimes. Subtracting them yields a Rational which I don't know how to interpret, whereas subtracting Times yields the difference in seconds.
NOTE: Since Ruby 1.9.2, the hard limit of Time is removed. However, Time is optimized for values between 1823-11-12 and 2116-02-20.
Subtracting two DateTimes returns the elapsed time in days, so you could just do:
elapsed_seconds = ((end_time - start_time) * 24 * 60 * 60).to_i
Or, more readably:
diff = datetime_1 - datetime_2
diff * 1.days # => difference in seconds; requires Ruby on Rails
Note, what you or some other searchers might really be looking for is this:
diff = datetime_1 - datetime_2
Date.day_fraction_to_time(diff) # => [h, m, s, frac_s]
You can convert them to floats with to_f, though this will incur the usual loss of precision associated with floats. If you're just casting to an integer for whole seconds it shouldn't be big enough to be a worry.
The results are in seconds:
>> end_time.to_f - start_time.to_f
=> 7.39954495429993
>> (end_time.to_f - start_time.to_f).to_i
=> 7
Otherwise, you could look at using to_formatted_s on the DateTime object and seeing if you can coax the output into something the Decimal class will accept, or just formatting it as plain Unix time as a string and calling to_i on that.
Others incorrectly rely on fractions or helper functions. It's much simpler than that. DateTime itself is integer underneath. Here's the Ruby way:
stop.to_i - start.to_i
Example:
start = Time.now
=> 2016-06-21 14:55:36 -0700
stop = start + 5.seconds
=> 2016-06-21 14:55:41 -0700
stop.to_i - start.to_i
=> 5
I am using ruby-2.1.4 and for me the following worked
Time.now - Time.new(2014,11,05,17,30,0)
gave me the time difference in seconds
reference: ruby doc
there's a method made for that:
Time.now.minus_with_coercion(10.seconds.ago)
equals 10.
Source: http://apidock.com/rails/Time/minus_with_coercion
Hope I helped.
Define a Ruby function like this,
def time_diff(start_time, end_time)
seconds_diff = (start_time - end_time).to_i.abs
days = seconds_diff / 86400
seconds_diff -= days * 86400
hours = seconds_diff / 3600
seconds_diff -= hours * 3600
minutes = seconds_diff / 60
seconds_diff -= minutes * 60
seconds = seconds_diff
"#{days} Days #{hours} Hrs #{minutes} Min #{seconds} Sec"
end
And Call this function,
time_diff(Time.now, Time.now-4.days-2.hours-1.minutes-53.seconds)

Resources