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
Related
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! :)
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 }
In C# there is a TimeSpan class. It represents a period of time and is returned from many date manipulation options. You can create one and add or subtract from a date etc.
In Ruby and specifically rails there seems to be lots of date and time classes but nothing that represents a span of time?
Ideally I'd like an object that I could use for outputting formatted dates easily enough using the standard date formatting options.
eg.
ts.to_format("%H%M")
Is there such a class?
Even better would be if I could do something like
ts = end_date - start_date
I am aware that subtracting of two dates results in the number of seconds separating said dates and that I could work it all out from that.
You can do something similar like this:
irb(main):001:0> require 'time' => true
irb(main):002:0> initial = Time.now => Tue Jun 19 08:19:56 -0400 2012
irb(main):003:0> later = Time.now => Tue Jun 19 08:20:05 -0400 2012
irb(main):004:0> span = later - initial => 8.393871
irb(main):005:0>
This just returns a time in seconds which isn't all that pretty to look at, you can use the strftime() function to make it look pretty:
irb(main):010:0> Time.at(span).gmtime.strftime("%H:%M:%S") => "00:00:08"
Something like this? https://github.com/abhidsm/time_diff
require 'time_diff'
time_diff_components = Time.diff(start_date_time, end_date_time)
No, it doesn't. You can just add seconds or use advance method.
end_date - start_date will have Float type
In the end I forked the suggestion in #tokland's answer. Not quite sure how to make it a proper gem but it's currently working for me:
Timespan fork of time_diff
Not yet #toxaq... but I've started something!
https://gist.github.com/thatandyrose/6180560
class TimeSpan
attr_accessor :milliseconds
def self.from_milliseconds(milliseconds)
me = TimeSpan.new
me.milliseconds = milliseconds
return me
end
def self.from_seconds(seconds)
TimeSpan.from_milliseconds(seconds.to_d * 1000)
end
def self.from_minutes(minutes)
TimeSpan.from_milliseconds(minutes.to_d * 60000)
end
def self.from_hours(hours)
TimeSpan.from_milliseconds(hours.to_d * 3600000)
end
def self.from_days(days)
TimeSpan.from_milliseconds(days.to_d * 86400000)
end
def self.from_years(years)
TimeSpan.from_days(years.to_d * 365.242)
end
def self.diff(start_date_time, end_date_time)
TimeSpan.from_seconds(end_date_time - start_date_time)
end
def seconds
self.milliseconds.to_d * 0.001
end
def minutes
self.seconds.to_d * 0.0166667
end
def hours
self.minutes.to_d * 0.0166667
end
def days
self.hours.to_d * 0.0416667
end
def years
self.days.to_d * 0.00273791
end
end
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"
I have a Time object and would like to find the next/previous month. Adding subtracting days does not work as the days per month vary.
time = Time.parse('21-12-2008 10:51 UTC')
next_month = time + 31 * 24 * 60 * 60
Incrementing the month also falls down as one would have to take care of the rolling
time = Time.parse('21-12-2008 10:51 UTC')
next_month = Time.utc(time.year, time.month+1)
time = Time.parse('01-12-2008 10:51 UTC')
previous_month = Time.utc(time.year, time.month-1)
The only thing I found working was
time = Time.parse('21-12-2008 10:51 UTC')
d = Date.new(time.year, time.month, time.day)
d >>= 1
next_month = Time.utc(d.year, d.month, d.day, time.hour, time.min, time.sec, time.usec)
Is there a more elegant way of doing this that I am not seeing?
How would you do it?
Ruby on Rails
Note: This only works in Rails (Thanks Steve!) but I'm keeping it here in case others are using Rails and wish to use these more intuitive methods.
Super simple - thank you Ruby on Rails!
Time.now + 1.month
Time.now - 1.month
Or, another option if it's in relation to the current time (Rails 3+ only).
1.month.from_now
1.month.ago
Personally I prefer using:
Time.now.beginning_of_month - 1.day # previous month
Time.now.end_of_month + 1.day # next month
It always works and is independent from the number of days in a month.
Find more info in this API doc
you can use standard class DateTime
require 'date'
dt = Time.new().to_datetime
=> #<DateTime: 2010-04-23T22:31:39+03:00 (424277622199937/172800000,1/8,2299161)>
dt2 = dt >> 1
=> #<DateTime: 2010-05-23T22:31:39+03:00 (424282806199937/172800000,1/8,2299161)>
t = dt2.to_time
=> 2010-05-23 22:31:39 +0200
There are no built-in methods on Time to do what you want in Ruby. I suggest you write methods to do this work in a module and extend the Time class to make their use simple in the rest of your code.
You can use DateTime, but the methods (<< and >>) are not named in a way that makes their purpose obvious to someone that hasn't used them before.
If you do not want to load and rely on additional libraries you can use something like:
module MonthRotator
def current_month
self.month
end
def month_away
new_month, new_year = current_month == 12 ? [1, year+1] : [(current_month + 1), year]
Time.local(new_year, new_month, day, hour, sec)
end
def month_ago
new_month, new_year = current_month == 1 ? [12, year-1] : [(current_month - 1), year]
Time.local(new_year, new_month, day, hour, sec)
end
end
class Time
include MonthRotator
end
require 'minitest/autorun'
class MonthRotatorTest < MiniTest::Unit::TestCase
describe "A month rotator Time extension" do
it 'should return a next month' do
next_month_date = Time.local(2010, 12).month_away
assert_equal next_month_date.month, 1
assert_equal next_month_date.year, 2011
end
it 'should return previous month' do
previous_month_date = Time.local(2011, 1).month_ago
assert_equal previous_month_date.month, 12
assert_equal previous_month_date.year, 2010
end
end
end
below it works
previous month:
Time.now.months_since(-1)
next month:
Time.now.months_since(1)
I just want to add my plain ruby solution for completeness
replace the format in strftime to desired output
DateTime.now.prev_month.strftime("%Y-%m-%d")
DateTime.now.next_month.strftime("%Y-%m-%d")
You can get the previous month info by this code
require 'time'
time = Time.parse('2021-09-29 12:31 UTC')
time.prev_month.strftime("%b %Y")
You can try convert to datetime.
Time gives you current date, and DateTime allows you to operate with.
Look at this:
irb(main):041:0> Time.new.strftime("%d/%m/%Y")
=> "21/05/2015"
irb(main):040:0> Time.new.to_datetime.prev_month.strftime("%d/%m/%Y")
=> "21/04/2015"
Here is a solution on plain ruby without RoR, works on old ruby versions.
t=Time.local(2000,"jan",1,20,15,1,0);
curmon=t.mon;
prevmon=(Time.local(t.year,t.mon,1,0,0,0,0)-1).mon ;
puts "#{curmon} #{prevmon}"
Some of the solutions assume rails. But, in pure ruby you can do the following
require 'date'
d = Date.now
last_month = d<<1
last_month.strftime("%Y-%m-%d")
Im using the ActiveSupport::TimeZone for this example, but just in case you are using Rails or ActiveSupport it might come in handy.
If you want the previous month you can substract 1 month
time = Time.zone.parse('21-12-2008 10:51 UTC')
time.ago(1.month)
$ irb
irb(main):001:0> time = Time.now
=> 2016-11-21 10:16:31 -0800
irb(main):002:0> year = time.year
=> 2016
irb(main):003:0> month = time.month
=> 11
irb(main):004:0> last_month = month - 1
=> 10
irb(main):005:0> puts time
2016-11-21 10:16:31 -0800
=> nil
irb(main):006:0> puts year
2016
=> nil
irb(main):007:0> puts month
11
=> nil
irb(main):008:0> puts last_month
10
=> nil