Comparing times in Rails - ruby-on-rails

I am using this code to compare times:
(time1.to_i - time2.to_i).abs < 5
The intention is that if two times are measured within 5 seconds from each other, they will be equal. I'm using it to compare updating of records, so 5 seconds is acceptable as a buffer, and stops the code returning false when the records are only splitseconds apart.
Is there a better way to do this?

In Ruby, you can subtract two Time objects directly to get the difference in seconds. Rails provides some convenience helpers on integers to convert them into seconds as well:
(time1 - time2).abs < 5.seconds
If you know that time2 always comes after time1, you can get rid of the abs:
time2 - time1 < 5.seconds

Related

How may i convert minutes "00:15" into hours in Rails 4

I have send the value as "00:15" in database.But its in string.So I converted into time but i'm getting error while doing so.
I used
("00:15".to_time) / 1.hours
but its giving error as
"NoMethodError: undefined method `/' for 2017-02-16 00:15:00 +0530:Time"
So i need to convert minutes into hours and update it into database so i get value as 15mins = 0.25 hrs
According to Stefan's comment I would suggest the following method:
time = "02:15" #or whatever time you want
time.to_time.hour + time.to_time.min / 60.00 #results in 2.25
The hour methods returns the hour part of a given time object, the min methods returns only the minute part of a time object.
The right part of the addition in the second line converts the minute part into the decimal part of an hour.
You can't do division on a string, you need to convert it to an integer before doing the calculation. Dividing by a float automatically converts it to a float so you get the decimal places; if you divided by 60 you would get 0
("00:15".to_time.strftime('%M').to_i / 60.00)

Getting "argument out of range" when trying to turn a duration into milliseconds in Rails 4

I’m using Rails 4.2.4. I have the below method for converting a time (duration) to milliseconds …
Time.parse(convert_to_hrs(duration)).seconds_since_midnight * 1000
in which the method “convert_to_hrs” is defined as
def convert_to_hrs(string)
case string.count(':')
when 0
'00:00:' + string.rjust(2, '0')
when 1
'00:' + string
else
string
end
end
However, if the duration is something really big (e.g. “34:13:00” -- read: 34 hours, 13 minutes, and zero seconds), the above fails with the error
Error during processing: argument out of range
/Users/mikea/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/time.rb:302:in `local'
/Users/mikea/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/time.rb:302:in `make_time'
/Users/mikea/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/time.rb:366:in `parse'
/Users/mikea/Documents/workspace/myproject/app/services/my_service.rb:25:in `block in process_page_data'
/Users/mikea/Documents/workspace/myproject/app/services/my_service.rb:22:in `each'
/Users/mikea/Documents/workspace/myproject/app/services/my_service.rb:22:in `process_page_data'
How do I rewrite my first line to accurately convert duration into milliseconds?
If you know you're always going to be using a hours:minutes:seconds format, but the number in each field isn't guaranteed to be inside the 'normal' range (e.g. 0-23 for hours, 0-59 for minutes, etc), then you're probably best off doing it 'manually' using something like this:
def duration_in_milliseconds(input)
h, m, s = input.split(':').map(&:to_i)
(h.hours + m.minutes + s.seconds) * 1000
end
puts duration_in_milliseconds('34:13:00') #=> 123180000
Note that this only works with ActiveSupport, but you have that, since you've specified Rails. Also, this assumes you're always getting all three terms (e.g. 5 seconds is 00:00:05). The full setup that accepts shorter strings as well would want to also use your convert_to_hrs method.
Note also that this works even if formatting isn't strictly 'time-like', as long as you have consistent colons as seperators:
puts duration_in_milliseconds('1:1:5') #=> 3665000
The Numeric#hours, Numeric#minutes and Numeric#seconds methods are provided by ActiveSupport, as part of active_support/core-ext/time.rb. They aren't particularly documented, but they return ActiveSupport::Duration objects, which have fancy methods for interacting with Time and Date issues like 5.days.ago, but when treated as an integer are effectively a number of seconds.
Time.parse is throwing error, becuase values you passing in duration variable is out of range.
For Ex:
Time.parse(convert_to_hrs('59:59')) as per your written code, it's return 2016-07-27 00:59:59 +0530
Here the value 59:59 consider as minutes:seconds, so if you pass the value 60:60 then it will raise the error argument out of range
Here is the official documentation for parse method of Time
Hope this will help you.

Timecodes in Rails - time or numeric values?

I'm working on a project that stores data on audio tracks and requires the use of timecodes for the start and end points of the track on the audio. I also need to calculate and display the duration of the track. Eg. a track starts at 0:01:30 and finishes at 0:04:12. So its duration is a total of 2 mins and 42 secs.
The trick is that everything needs to be displayed and handled as timecodes, so in the above example the duration needs to be displayed as 0:02:42.
So my question is how you would store the values? The easiest option would be to store the start and end times as Time in the database. Its very easy to calculate the duration and you can utilise the Rails time helpers in the forms. The only painful part is turning the duration back into a time value for display (since if I supply just the number of seconds to strptime it keeps using the current time to fill in the other fields)
The other option that I considered is storing them as numeric values (as the number of seconds). But then I have to write a lot of code to convert them to and from some type of timecode format and I can't use the Rails time helpers.
Is there another idea that I haven't considered? Is there an easy way to calculate and display the duration as a timecode format?
I would store them as seconds or milliseconds. I've been working on a music library manager/audio player in Ruby, and I actually had to write the two methods you would need. It's not that much code:
# Helper method to format a number of milliseconds as a string like
# "1:03:56.555". The only option is :include_milliseconds, true by default. If
# false, milliseconds won't be included in the formatted string.
def format_time(milliseconds, options = {})
ms = milliseconds % 1000
seconds = (milliseconds / 1000) % 60
minutes = (milliseconds / 60000) % 60
hours = milliseconds / 3600000
if ms.zero? || options[:include_milliseconds] == false
ms_string = ""
else
ms_string = ".%03d" % [ms]
end
if hours > 0
"%d:%02d:%02d%s" % [hours, minutes, seconds, ms_string]
else
"%d:%02d%s" % [minutes, seconds, ms_string]
end
end
# Helper method to parse a string like "1:03:56.555" and return the number of
# milliseconds that time length represents.
def parse_time(string)
parts = string.split(":").map(&:to_f)
parts = [0] + parts if parts.length == 2
hours, minutes, seconds = parts
seconds = hours * 3600 + minutes * 60 + seconds
milliseconds = seconds * 1000
milliseconds.to_i
end
It's written for milliseconds, and would be a lot simpler if it was changed to work with seconds.

Using and comparing relative time in Rails

I need to know how to do relative time in rails but not as a sentence, more like something i could do this with (when i input format like this 2008-08-05 23:48:04 -0400)
if time_ago < 1 hour, 3 weeks, 1 day, etc.
execute me
end
Basic relative time:
# If "time_ago" is more than 1 hour past the current time
if time_ago < 1.hour.ago
execute_me
end
Comparison:
Use a < to see if time_ago is older than 1.hour.ago, and > to see if time_ago is more recent than 1.hour.ago
Combining times, and using fractional times:
You can combine times, as davidb mentioned, and do:
(1.day + 3.hours + 2500.seconds).ago
You can also do fractional seconds, like:
0.5.seconds.ago
There is no .milliseconds.ago, so if you need millisecond precision, just break it out into a fractional second. That is, 1 millisecond ago is:
0.001.seconds.ago
.ago() in general:
Putting .ago at the end of just about any number will treat the number as a #of seconds.
You can even use fractions in paranthesis:
(1/2.0).hour.ago # half hour ago
(1/4.0).year.ago # quarter year ago
NOTE: to use fractions, either the numerator or denominator needs to be a floating point number, otherwise Ruby will automatically cast the answer to an integer, and throw off your math.
You mean sth. like this?
if time_ago < Time.now-(1.days+1.hour+1.minute)
execute me
end

How to find if range is contained in an array of ranges?

Example
business_hours['monday'] = [800..1200, 1300..1700]
business_hours['tuesday'] = [900..1100, 1300..1700]
...
I then have a bunch of events which occupy some of these intervals, for example
event = { start_at: somedatetime, end_at: somedatetime }
Iterating over events from a certain date to a certain date, I create another array
busy_hours['monday'] = [800..830, 1400..1415]
...
Now my challenges are
Creating an available_hours array that contains business_hours minus busy_hours
available_hours = business_hours - busy_hours
Given a certain duration say 30 minutes, find which time slots are available in available_hours. In the examples above, such a method would return
available_slots['monday'] = [830..900, 845..915, 900..930, and so on]
Not that it checks available_hours in increments of 15 minutes for slots of specified duration.
Thanks for the help!
I think this is a job for bit fields. Unfortunately this solution will rely on magic numbers, conversions helpers and a fair bit of binary logic, so it won't be pretty. But it will work and be very efficient.
This is how I'd approach the problem:
Atomize your days into reasonable time intervals. I'll follow your example and treat each 15 minute block of time as considered one time chunk (mostly because it keeps the example simple). Then represent your availability per hour as a hex digit.
Example:
0xF = 0x1111 => available for the whole hour.
0xC = 0x1100 => available for the first half of the hour.
String 24 of these together together to represent a day. Or fewer if you can be sure that no events will occur outside of the range. The example continues assuming 24 hours.
From this point on I've split long Hex numbers into words for legibility
Assuming the day goes from 00:00 to 23:59 business_hours['monday'] = 0x0000 0000 FFFF 0FFF F000 0000
To get busy_hours you store events in a similar format, and just & them all together.
Exmample:
event_a = 0x0000 0000 00F0 0000 0000 0000 # 10:00 - 11:00
event_b = 0x0000 0000 0000 07F8 0000 0000 # 13:15 - 15:15
busy_hours = event_a & event_b
From busy_hours and business_hours you can get available hours:
available_hours = business_hours & (busy_hours ^ 0xFFFF FFFF FFFF FFFF FFFF FFFF)
The xor(^) essentialy translates busy_hours into not_busy_hours. Anding (&) not_busy_hours with business_hours gives us the available times for the day.
This scheme also makes it simple to compare available hours for many people.
all_available_hours = person_a_available_hours & person_b_available_hours & person_c_available_hours
Then to find a time slot that fits into available hours. You need to do something like this:
Convert your length of time into a similar hex digit to the an hour where the ones represent all time chunks of that hour the time slot will cover. Next right shift the digit so there's no trailing 0's.
Examples are better than explanations:
0x1 => 15 minutes, 0x3 => half hour, 0x7 => 45 minutes, 0xF => full hour, ... 0xFF => 2 hours, etc.
Once you've done that you do this:
acceptable_times =[]
(0 .. 24 * 4 - (#of time chunks time slot)).each do |i|
acceptable_times.unshift(time_slot_in_hex) if available_hours & (time_slot_in_hex << i) == time_slot_in_hex << i
end
The high end of the range is a bit of a mess. So lets look a bit more at it. We don't want to shift too many times or else we'll could start getting false positives at the early end of the spectrum.
24 * 4 24 hours in the day, with each represented by 4 bits.
- (#of time chunks in time slot) Subtract 1 check for each 15 minutes in the time slot we're looking for. This value can be found by (Math.log(time_slot_in_hex)/Math.log(2)).floor + 1
Which starts at the end of the day, checking each time slot, moving earlier by a time chunk (15 minutes in this example) on each iteration. If the time slot is available it's added to the start of acceptable times. So when the process finishes acceptable_times is sorted in order of occurrence.
The cool thing is this implementation allows for time slots that incorporate so that your attendee can have a busy period in their day that bisects the time slot you're looking for with a break, where they might be otherwise busy.
It's up to you to write helper functions that translate between an array of ranges (ie: [800..1200, 1300..1700]) and the hex representation. The best way to do that is to encapsulate the behaviour in an object and use custom accessor methods. And then use the same objects to represent days, events, busy hours, etc. The only thing that's not built into this scheme is how to schedule events so that they can span the boundary of days.
To answer your question's title, find if a range of arrays contains a range:
ary = [800..1200, 1300..1700]
test = 800..830
p ary.any? {|rng| rng.include?(test.first) and rng.include?(test.last)}
# => true
test = 1245..1330
p ary.any? {|rng| rng.include?(test.first) and rng.include?(test.last)}
# => false
which could be written as
class Range
def include_range?(r)
self.include?(r.first) and self.include?(r.last)
end
end
Okay, I don't have time to write up a full solution, but the problem does not seem too difficult to me. I hacked together the following primitive methods you can use to help in constructing your solution (You may want to subclass Range rather than monkey patching, but this will give you the idea):
class Range
def contains(range)
first <= range.first || last >= range.last
end
def -(range)
out = []
unless range.first <= first && range.last >= last
out << Range.new(first, range.first) if range.first > first
out << Range.new(range.last, last) if range.last < last
end
out
end
end
You can iterate over business hours and find the one that contains the event like so:
event_range = event.start_time..event.end_time
matching_range = business_hours.find{|r| r.contains(event_range)}
You can construct the new array like this (pseudocode, not tested):
available_hours = business_hours.dup
available_hours.delete(matching_range)
available_hours += matching_range - event_range
That should be a pretty reusable approach. Of course you'll need something totally different for the next part of your question, but this is all I have time for :)

Resources