Get time in seconds from string and add to current time? - ruby

timestr = '15h 37m 5s'
I want to get the hours minutes and seconds from the above string and add it to current time.
def next_run
timestr = '15h 37m 5s'
timearr = timestr.split(' ').map { |t| t.to_i }
case timearr.count
when 3
next_ = (timearr[0] * 3600) + (timearr[1] * 60) + timearr[2]
when 2
next_ = (timearr[1] * 60) + timearr[2]
when 1
next_ = timearr[2]
else
raise 'Unknown length for timestr'
end
time_to_run_in_secs = next_
end
Now I get the total seconds. I want to make it into hours minutes and seconds, then add it to current time to get next run time. Is there any easy way to do this?

The following method can be used to compute the the numbers of seconds from the string.
def seconds(str)
3600 * str[/\d+h/].to_i + 60 * str[/\d+m/].to_i + str[/\d+s/].to_i
end
Note nil.to_i #=>0. A slight variant would be to write 3600 * (str[/\d+h/] || 0) +....
Then
Time.now + seconds(str)
Examples of possible values of str are as follows: ”3h 26m 41s”, ”3h 26m”, ”3h 41s”, ”41s 3h”, ”3h”,”41s” and ””.
One could instead write the operative line of the method as follows.
%w| s m h |.each_with_index.sum { |s,i| 60**i * str[/\d+#{s}/].to_i }
Though DRYer, I find that less readable.

DateTime#+ accepts Rational instance as days to be added. All you need as you have seconds would be to convert it to a number of days and plain add to the current timestamp:
DateTime.now.tap do |dt|
break [dt, dt + Rational(100, 3600 * 24) ]
end
#⇒ [
# [0] #<DateTime: 2018-05-27T11:13:00+02:00 ((2458266j,33180s,662475814n),+7200s,2299161j)>,
# [1] #<DateTime: 2018-05-27T11:14:40+02:00 ((2458266j,33280s,662475814n),+7200s,2299161j)>
# ]

you can convert your string into seconds from this method
def seconds(str)
(3600 * str[/\d+(h|H)/].to_i) + (60 * str[/\d+(m|M)/].to_i) + (str[/\d+(s|S)/].to_i)
end
and then convert current time to seconds using method
next_run_time = Time.now.to_i + seconds(<Your Time String>)
now get next run time using
Time.at(next_run_time)
get desired format of time by using strftime method, in your case
Time.at(next_run_time).strftime("%Hh %Mm %Ss")

If you don't need to parse the duration of time, and just want to define it in your code, use ActiveSupport::Duration for readability . (add the gem to your Gemfile, and read the guide on how to use it)
Then you can use it like this:
require 'active_support'
require 'active_support/core_ext/integer'
DURATION = 15.hours + 37.minutes + 5.seconds
# use DURATION.seconds or DURATION.to_i to get the seconds
def next_run
Time.now + DURATION
end
See the API documentation of ActiveSupport::Duration
If you need to define the next run by a user input, it's a good practice to use ISO 8601 to define a duration of time: https://en.wikipedia.org/wiki/ISO_8601#Durations
ISO 8601 durations are parseable:
ActiveSupport::Duration.parse('PT15H37M5S') # => 15 hours, 37 minutes, and 5 seconds (duration)

Firstly instead of spliting the string, you can use Time#parse method. Make sure you have required the library as well.
require 'time'
=> true
Time.parse('15h 37m 5s')
=> 2018-05-27 15:37:05 +0300
This returns a new object of class Time and it has some really useful methods for you - #sec, #min, #hour.
time = Time.parse('15h 37m 5s')
time.sec #=> 5
time.min #=> 37
time.hour #=> 15
Adding adding one Time object to another is pretty straightforward since you can do it only by seconds. A simple solution for the current problem would be:
def next_run
time = Time.parse('15h 37m 5s')
seconds_to_add = time.hour * 3600 + time.min * 60 + time.sec
Time.now + seconds_to_add
end
Hopefully this will answer your question! :)

Related

tell if DateTime is within one hour of current time

My goal is to stop the loop if dte is within 1 hour of the current time. Is there a "ruby way" to do this?
#THIS IS AN INFINITE LOOP, DONT RUN THIS
dte=DateTime.strptime("2000-01-01 21:00:00", '%Y-%m-%d %H:%M:%S')
while(dte<(DateTime.now.to_time-1).to_datetime)
#increments dte by one hour, not shown here
end
Pure Ruby way (without including rails's active_support) :
You just need to take off fractions of a day.
one_hour_ago_time = (DateTime.now - (1.0/24))
1.0 = one day
1.0/24 = 1 hour
1.0/(24*60) = 1 minute
1.0/(24*60*60) = 1 second
If you really need to use the DateTime, then you are doing it the right way.
Subtract n hours from a DateTime in Ruby
You can also do:
require 'time'
time = Time.parse('2000-01-01 21:00:00')
while time < Time.now - 3600 do
...
end
You can be more efficient using active_support core extensions.
require 'active_support/core_ext/numeric/time'
while time < Time.now - 1.hour do
...
end
Even better
while time < 1.hour.ago do
...
end
You can also try this:
current_time = DateTime.now.strftime("%Y-%m-%d %H:%M:%S")
last_time = (DateTime.now - (1.0/24)).strftime("%Y-%m-%d %H:%M:%S")
last_time.upto(current_time) {|date| puts date }

Converting military time in Ruby

What is the easiest way to convert military (24 hour time) to HH:MM format in Ruby?
I have two 24 hour time values stored in a record and I want to display them in the view as something like "2:00 - 4:00" when starting with two integer values, say 1400 and 1600.
My hack-y solution was to create a helper function:
def format_time(time)
if (time-1200 < 1000)
if (time-1200 < 100)
return time.to_s.insert(2, ":")
else
return (time-1200).to_s.insert(1, ":")
end
else
return (time-1200).to_s.insert(2, ":")
end
end
I'm not even sure that works all the time. I'm assuming there is a better way to do this.
UPDATE: I also need this to work on values that do not end in '00'. IE 1430 or 830.
You could parse the string with Time.strptime to get a time object. This can be used to be printed with Time.strftime:
require 'time'
%w{1400 1600}.each{|t|
p Time.strptime(t, '%H%M').strftime('%l:%M')
}
Advantage: Changes of input and output format are quite easy.
Alternative:
require 'date' #load DateTime.strptime
%w{1400 1600}.each{|t|
p DateTime.strptime(t, '%H%M').strftime('%l:%M')
}
I hope the solution with DateTime works also with ruby 1.8.
Update for 830:
This solution will not work with 830 - strptime does not accpt %l. But 0830 would work fine. For this case you need an adapted solution:
require 'date' #load DateTime
%w{1400 1600 830}.each{|t|
t = '%04i' % t
p DateTime.strptime(t, '%H%M').strftime('%l:%M')
}
This will work, and it's easier o read
def fomat_time(time)
time.to_s.sub(/^(\d{1,2})(\d{2})$/,'\1:\2')
end
You could do something like this:
def format_time(time)
t = (time <= 1200)?time : time - 1200
return (t.to_s.size == 3)?t.to_s.insert(1,":") : t.to_s.insert(2,":")
end
That should work...
If you always have the times in the form of integers like eg like 1600, 1630, 930 or 130, then this might be a solution:
require 'time'
def format_time(time)
# normalize time
time = time.to_s.rjust(4, '0') if time[0] !~ /[12]/
time = time.to_s.ljust(4, '0') if time[0] =~ /[12]/
Time.strptime(time, '%H%M').strftime('%l:%M').strip
end
time = 900
p format_time(time) # "9:00"
time = 1630
p format_time(time) # "4:30"
time = 930
p format_time(time) # "9:30"
time = 130
p format_time(time) # "1:30"

Ruby: Convert time to seconds?

How can I convert a time like 10:30 to seconds? Is there some sort of built in Ruby function to handle that?
Basically trying to figure out the number of seconds from midnight (00:00) to a specific time in the day (such as 10:30, or 18:45).
You can use DateTime#parse to turn a string into a DateTime object, and then multiply the hour by 3600 and the minute by 60 to get the number of seconds:
require 'date'
# DateTime.parse throws ArgumentError if it can't parse the string
if dt = DateTime.parse("10:30") rescue false
seconds = dt.hour * 3600 + dt.min * 60 #=> 37800
end
As jleedev pointed out in the comments, you could also use Time#seconds_since_midnight if you have ActiveSupport:
require 'active_support'
Time.parse("10:30").seconds_since_midnight #=> 37800.0
Yet another implementation:
Time.now.to_i - Date.today.to_time.to_i # seconds since midnight
The built in time library extends the Time class to parse strings, so you could use that. They're ultimately represented as seconds since the UNIX epoch, so converting to integers and subtracting should get you what you want.
require 'time'
m = Time.parse('00:00')
t = Time.parse('10:30')
(t.to_i - m.to_i)
=> 37800
There's also some sugar in ActiveSupport to handle these types of things.
Perhaps there is a more succinct way, but:
t = Time.parse("18:35")
s = t.hour * 60 * 60 + t.min * 60 + t.sec
would do the trick.
You can simply use
Time.parse("10:30").seconds_since_midnight
I like these answers very much, especially Teddy's for its tidyness.
There's one thing to note. Teddy's answer gives second of day in current region and I haven't been able to convert Date.today.to_time to UTC. I ended up with this workaround:
Time.now.to_i % 86400
It's based on the fact that Time.now.to_i gives seconds since Unix Epoch which is always 1970-01-01T00:00:00Z, regardless of your current time zone. And the fact that there's 86400 seconds in a day as well. So this solution will always give you seconds since last UTC midnight.
require 'time'
def seconds_since_midnight(time)
Time.parse(time).hour * 3600 + Time.parse(time).min * 60 + Time.parse(time).sec
end
puts seconds_since_midnight("18:46")
All great answers, this is what I ended up using.
In plain ruby the fastest is the sum of time parts:
require 'benchmark'
require 'time'
Benchmark.bm do |x|
x.report('date') { 100_000.times { Time.now.to_i - Date.today.to_time.to_i } }
x.report('parse') { 100_000.times { Time.now.to_i - Time.parse('00:00').to_i } }
x.report('sum') { 100_000.times { Time.now.hour * 3600 + Time.now.min * 60 + Time.now.sec } }
end
user system total real
date 0.820000 0.000000 0.820000 ( 0.822578)
parse 1.510000 0.000000 1.510000 ( 1.516117)
sum 0.270000 0.000000 0.270000 ( 0.268540)
So, here is a method that takes timezone into account, if needed
def seconds_since_midnight(time: Time.now, utc: true)
time = time.utc if utc
time.hour * 3600 + time.min * 60 + time.sec
end

Ruby, get hours, seconds and time from Date.day_fraction_to_time

I've found this method here.
start = DateTime.now
sleep 15
stop = DateTime.now
#minutes
puts ((stop-start) * 24 * 60).to_i
hours,minutes,seconds,frac = Date.day_fraction_to_time(stop-start)
I have the following error:
`<main>': private method `day_fraction_to_time' called for Date:Class (NoMethodError)
I've checked /usr/lib/ruby/1.9.1/date.rb and I've found it:
def day_fraction_to_time(fr) # :nodoc:
ss, fr = fr.divmod(SECONDS_IN_DAY) # 4p
h, ss = ss.divmod(3600)
min, s = ss.divmod(60)
return h, min, s, fr * 86400
end
But I have no problem if I run it with ruby1.8. /usr/lib/ruby/1.8/date.rb gives me:
def self.day_fraction_to_time(fr)
ss, fr = fr.divmod(SECONDS_IN_DAY) # 4p
h, ss = ss.divmod(3600)
min, s = ss.divmod(60)
return h, min, s, fr
end
So i went to see the documentation(1.9) and there's no trace of this method. I know it's a dumb question, but why did they remove it? There is even this example on how to use the method in /usr/lib/ruby/1.9.1/date.rb:
def secs_to_new_year(now = DateTime::now())
new_year = DateTime.new(now.year + 1, 1, 1)
dif = new_year - now
hours, mins, secs, ignore_fractions = Date::day_fraction_to_time(dif)
return hours * 60 * 60 + mins * 60 + secs
end
but I'm still getting the error:
test.rb:24:in `secs_to_new_year': private method `day_fraction_to_time' called for Date:Class (NoMethodError)
from test.rb:28:in `<main>'
I don't know why it was made private, but you can still access it:
hours,minutes,seconds,frac = Date.send(:day_fraction_to_time, stop-start)
This way you override the OOP encapsulation mechanizm... This is not a very nice thing to do, but it works.
I've found another method which seems more elegant to me:
start = DateTime.now
sleep 3
stop = DateTime.now
puts "Date.day_fraction_to_time using wrapper"
class Date
class << self
def wrap_day_fraction_to_time( day_frac )
day_fraction_to_time( day_frac )
end
end
end
hours, minutes, seconds, frac =
Date.wrap_day_fraction_to_time( stop - start )
p hours, minutes, seconds, frac
Thanks to Colin Bartlett from ruby-forum.com
To understand how it works I suggest to read David Seiler's answer

How to parse days/hours/minutes/seconds in ruby?

Is there a gem or something to parse strings like "4h 30m" "1d 4h" -- sort of like the estimates in JIRA or task planners, maybe, with internationalization?
Posting a 2nd answer, as chronic (which my original answer suggested) doesn't give you timespans but timestamps.
Here's my go on a parser.
class TimeParser
TOKENS = {
"m" => (60),
"h" => (60 * 60),
"d" => (60 * 60 * 24)
}
attr_reader :time
def initialize(input)
#input = input
#time = 0
parse
end
def parse
#input.scan(/(\d+)(\w)/).each do |amount, measure|
#time += amount.to_i * TOKENS[measure]
end
end
end
The strategy is fairly simple. Split "5h" into ["5", "h"], define how many seconds "h" represents (TOKENS), and add that amount to #time.
TimeParser.new("1m").time
# => 60
TimeParser.new("1m wtf lol").time
# => 60
TimeParser.new("4h 30m").time
# => 16200
TimeParser.new("1d 4h").time
# => 100800
It shouldn't be too hard making it handle "1.5h" either, seeing the codebase is as simple as it is.
chronic_duration does this.
You can use chronic. It can parse pretty much everything you trhow at it, including "yesterday", "last week" etc.
Update: As the OP points out in the comment, Chronic is for dates, not timespans. See my other answer.
I wrote this method that does it pretty well
def parse_duration(dur)
duration = 0
number_tokens = dur.gsub(/[a-z]/i,"").split
times = dur.gsub(/[\.0-9]/,"").split
if number_tokens.size != times.size
raise "unrecognised duration!"
else
dur_tokens = number_tokens.zip(times)
for d in dur_tokens
number_part = d[0].to_f
time_part = d[1]
case time_part.downcase
when "h","hour","hours"
duration += number_part.hours
when "m","minute","minutes","min","mins"
duration += number_part.minutes
when "d","day","days"
duration += number_part.days
when "w","week","weeks"
duration += number_part.weeks
when "month", "months"
duration += number_part.months
when "y", "year", "years"
duration += number_part.years
else
raise "unrecognised duration!"
end
end
end
duration
end
Parse into what though?
This will parse into a Hash:
"4h 30m".split(/\s/).each{|i| h[i.gsub(/\d+/,"")] = i.gsub(/\w/,"")}
Sorry. not familiar with JIRA....

Resources