Iterate over Ruby Time object with delta - ruby

Is there a way to iterate over a Time range in Ruby, and set the delta?
Here is an idea of what I would like to do:
for hour in (start_time..end_time, hour)
hour #=> Time object set to hour
end
You can iterate over the Time objects, but it returns every second between the two. What I really need is a way to set the offset or delta (such as minute, hour, etc.)
Is this built in to Ruby, or is there a decent plugin available?

Prior to 1.9, you could use Range#step:
(start_time..end_time).step(3600) do |hour|
# ...
end
However, this strategy is quite slow since it would call Time#succ 3600 times. Instead,
as pointed out by dolzenko in his answer, a more efficient solution is to use a simple loop:
hour = start_time
while hour < end_time
# ...
hour += 3600
end
If you're using Rails you can replace 3600 with 1.hour, which is significantly more readable.

If your start_time and end_time are actually instances of Time class then the solution with using the Range#step would be extremely inefficient since it would iterate over every second in this range with Time#succ. If you convert your times to integers the simple addition will be used but this way you will end up with something like:
(start_time.to_i..end_time.to_i).step(3600) do |hour|
hour = Time.at(hour)
# ...
end
But this also can be done with simpler and more efficient (i.e. without all the type conversions) loop:
hour = start_time
begin
# ...
end while (hour += 3600) < end_time

Range#step method is very slow in this case. Use begin..end while, as dolzenko posted here.
You can define a new method:
def time_iterate(start_time, end_time, step, &block)
begin
yield(start_time)
end while (start_time += step) <= end_time
end
then,
start_time = Time.parse("2010/1/1")
end_time = Time.parse("2010/1/31")
time_iterate(start_time, end_time, 1.hour) do |t|
puts t
end
if in rails.

Related

Simple Ruby Rate Limiting

I am trying to build a very simple rate limit algorithm using an array.
Let's for example use the following rate limit as an example "5 requests every 5 minutes"
I have an array that stores a list of timestamps (where each element is a Time.now) and is added to the array when an API gets called (assuming it's under the rate limit)
I also used a Mutex here so different threads can both share the timestamp resource as well as ensuring there's no race condition happening.
However, I'd like this array to be self-cleaning of sorts. If there are 5 (or more) elements in the array AND one or more of it is outside of the 5 minute interval, it would automatically remove this entry.
And this is sort of where I am stuck on.
I have the following code:
def initialize(max, interval)
#max, #interval = max, interval
#m = Mutex.new
#timestamp = []
end
def validate_rate
#m.synchronize do
if #timestamp.count > #max && self.is_first_ts_expired
#timestamp.shift
if self.rate_count < #max
#timestamp << Time.now
return false
else
return true
end
end
end
end
def is_first_ts_expired
return false if ##timestamp[#name].first.nil? # no logged entries = no expired timestamps
return ##timestamp[#name].first <= Time.now - #interval
end
# Gets the number of requests that are under the allowed interval
def rate_count
count = 0
#timestamp.each { |x|
if x >= Time.now - #interval
count += 1
end
}
count
end
The following is how you will call this simple class. rl.validate_rate will return true if it's under the rate limit, but false if it's above. And ideally it will self-clean the timestamp array when it's greater than the max variable.
rl = RateLimit.new(5, 5.minutes)
raise RateLimitException unless rl.validate_rate do
# stuff
end
I am curious if where I put the "clean up" is_first_ts_expired code is called at the right place?
I think this is a totally valid approach.
Two quick notes:
1) It seems like you're only allowing insertion into the array when there are less than the max number of elements:
if rate_count < #max
#timestamp << Time.now
return true
else
return false
end
However, you're also only clearing out expired elements when there are greater than the number of allowed elements in the array:
if #timestamp.count > max && is_first_ts_expired
#timestamp.shift
I think in order to get this working, you want to remove that first condition when you are checking if you should clear elements from the array. It will look something like this:
if is_first_ts_expired
#timestamp.shift
2) You will only ever clean one item out of your array here:
if is_first_ts_expired
#timestamp.shift
To make this solution more robust, you may want to replace the if with a while so you can clean out multiple expired items. For example:
while is_first_ts_expired do
#timestamp.shift
end
Updated based on comment below:
Since you'll potentially be going through all of the timestamps if the timestamps are all expired, you'll want to slightly modify the is_first_ts_expired to handle an empty timestamp array. Something like this:
def is_first_ts_expired
current_ts = #timestamp.first
current_ts && current_ts <= Time.now - #interval
end

Benchmarking Ruby Code

I'm doing the following Ruby Tutorial https://rubymonk.com/learning/books/4-ruby-primer-ascent/chapters/50-debugging/lessons/124-benchmarking_ruby_code. One of the exercises asks me to:
use Ruby's super-awesome blocks to create a method which takes in a
block, executes it, and returns the time it took.
The exercise looks like this:
def benchmark
# your code here!
end
time_taken = benchmark do
sleep 0.1
end
puts "Time taken #{time_taken}"
there is a hint (Need a hint?) below the exercise:
Ruby Blocks - Introduction to Blocks in Ruby (Ruby Primer)
and i did so:
def benchmark(time)
begin_time = Time.now
end_time = Time.now
time.benchmark {|time| yield time}
end
time_taken = benchmark do
sleep 0.1
end
puts "Time taken #{time_taken}
but received an error.
i am interested in: why is local variable - 'time_taken', suggested without representing an element after 'do'? or is it not necessary? Can anyone tell me how to write code to get the positive result.
You should do it much easier:
def benchmark
begin_time = Time.now
yield
end_time = Time.now
end_time - begin_time
end
time_taken = benchmark do
sleep 0.1
end
puts "Time taken #{time_taken}"
First you collect the time and store in in variable begin_time, then yield - so run the block, then collect the end time. Return the difference. That's it.
That's pretty far off, and not really at all salvagible.
Your benchmark method should look like this pseudo code:
def benchmark
let begin_time -> current time
execute the block
let end_time -> current_time
return endtime - begintime
end
As far as executing the block being passed in, there is no time.benchmark method, I'm not sure where that came from, and you do not need to pass anything into the block. You want a single, simple yield.

Ruby, Recursion Method

I have a method that is basically a loop and it calls itself at the end each time. What is the best way for the method to not call itself when the date reaches a certain point? Each iteration through adds 1 day and basically processes stats for that day. It looks like the below:
def loop(start_day)
date = start_day
#do a bunch of stuff
date = date +1.day
if date > Time.now
puts "loop should be over"
end
loop(date)
end
Each iteration through adds 1 day
That's not true for the code you've posted. In your code you add 1 day to the start date once and then you keep processing the same date over and over again because you recurse on the old date (start_date), not the incremented date (date).
What is the best way for the method to not call itself when the date reaches a certain point?
Just put the recursive call inside an if or, in this case, inside of the else of the if that you already have.
Since you set date to start_date immediately, it seems there's no point in having both. Here's the more canonical form of doing recursion:
def loop(date)
return if date > Time.now
#do a bunch of stuff
loop(date + 1.day)
end
Update: If it's not obvious to you that recursion isn't necessary here, in real life, it would make more sense to do something like this:
def process_from(date)
while date <= Time.now
# Process date
date += 1.day
end
end
What about this?
def loop(start_day)
return "loop should be over" if start_day >= Time.now
#...
loop(modified_date)
end
or...
def loop(start_day)
date = start_day.dup
time = Time.now
date += 1.day while date <= time
'loop should be over'
end
It seems like you want to iterate over all days from starting date to today. Then maybe this is even more simple:
def map_date(date)
(date.to_date..Date.today).map do |d|
d.strftime("Today is %A")
end
end
You must have base case (stop case) for recursive function;
example:
def loop(date)
if date == certain_point
return [something_for_stop]
end
loop(date - 1)
end

Time-of-day range in Ruby?

I want to know if a time belongs to an schedule or another.
In my case is for calculate if the time is in night schedule or normal schedule.
I have arrived to this solution:
NIGHT = ["21:00", "06:00"]
def night?( date )
date_str = date.strftime( "%H:%M" )
date_str > NIGHT[0] || date_str < NIGHT[1]
end
But I think is not very elegant and also only works for this concrete case and not every time range.
(I've found several similar question is SO but all of them make reference to Date ranges no Time ranges)
Updated
Solution has to work for random time ranges not only for this concrete one. Let's say:
"05:00"-"10:00"
"23:00"-"01:00"
"01:00"-"01:10"
This is actually more or less how I would do it, except maybe a bit more concise:
def night?( date )
!("06:00"..."21:00").include?(date.strftime("%H:%M"))
end
or, if your schedule boundaries can remain on the hour:
def night?(date)
!((6...21).include? date.hour)
end
Note the ... - that means, basically, "day time is hour 6 to hour 21 but not including hour 21".
edit: here is a generic (and sadly much less pithy) solution:
class TimeRange
private
def coerce(time)
time.is_a? String and return time
return time.strftime("%H:%M")
end
public
def initialize(start,finish)
#start = coerce(start)
#finish = coerce(finish)
end
def include?(time)
time = coerce(time)
#start < #finish and return (#start..#finish).include?(time)
return !(#finish..#start).include?(time)
end
end
You can use it almost like a normal Range:
irb(main):013:0> TimeRange.new("02:00","01:00").include?(Time.mktime(2010,04,01,02,30))
=> true
irb(main):014:0> TimeRange.new("02:00","01:00").include?(Time.mktime(2010,04,01,01,30))
=> false
irb(main):015:0> TimeRange.new("01:00","02:00").include?(Time.mktime(2010,04,01,01,30))
=> true
irb(main):016:0> TimeRange.new("01:00","02:00").include?(Time.mktime(2010,04,01,02,30))
=> false
Note, the above class is ignorant about time zones.
In Rails 3.2 it has added Time.all_day and similars as a way of generating date ranges. I think you must see how it works. It may be useful.

How do I get elapsed time in milliseconds in Ruby?

If I have a Time object got from :
Time.now
and later I instantiate another object with that same line, how can I see how many milliseconds have passed? The second object may be created that same minute, over the next minutes or even hours.
As stated already, you can operate on Time objects as if they were numeric (or floating point) values. These operations result in second resolution which can easily be converted.
For example:
def time_diff_milli(start, finish)
(finish - start) * 1000.0
end
t1 = Time.now
# arbitrary elapsed time
t2 = Time.now
msecs = time_diff_milli t1, t2
You will need to decide whether to truncate that or not.
You can add a little syntax sugar to the above solution with the following:
class Time
def to_ms
(self.to_f * 1000.0).to_i
end
end
start_time = Time.now
sleep(3)
end_time = Time.now
elapsed_time = end_time.to_ms - start_time.to_ms # => 3004
I think the answer is incorrectly chosen, that method gives seconds, not milliseconds.
t = Time.now.t­o_f
=> 1382471965.146
Here I suppose the floating value are the milliseconds
DateTime.now.strftime("%Q")
Example usage:
>> DateTime.now.strftime("%Q")
=> "1541433332357"
>> DateTime.now.strftime("%Q").to_i
=> 1541433332357
To get time in milliseconds, it's better to add .round(3), so it will be more accurate in some cases:
puts Time.now.to_f # => 1453402722.577573
(Time.now.to_f.round(3)*1000).to_i # => 1453402722578
ezpz's answer is almost perfect, but I hope I can add a little more.
Geo asked about time in milliseconds; this sounds like an integer quantity, and I wouldn't take the detour through floating-point land. Thus my approach would be:
t8 = Time.now
# => Sun Nov 01 15:18:04 +0100 2009
t9 = Time.now
# => Sun Nov 01 15:18:18 +0100 2009
dif = t9 - t8
# => 13.940166
(1000 * dif).to_i
# => 13940
Multiplying by an integer 1000 preserves the fractional number perfectly and may be a little faster too.
If you're dealing with dates and times, you may need to use the DateTime class. This works similarly but the conversion factor is 24 * 3600 * 1000 = 86400000 .
I've found DateTime's strptime and strftime functions invaluable in parsing and formatting date/time strings (e.g. to/from logs). What comes in handy to know is:
The formatting characters for these functions (%H, %M, %S, ...) are almost the same as for the C functions found on any Unix/Linux system; and
There are a few more: In particular, %L does milliseconds!
The answer is something like:
t_start = Time.now
# time-consuming operation
t_end = Time.now
milliseconds = (t_start - t_end) * 1000.0
However, the Time.now approach risks to be inaccurate. I found this post by Luca Guidi:
https://blog.dnsimple.com/2018/03/elapsed-time-with-ruby-the-right-way/
system clock is constantly floating and it doesn't move only forwards. If your calculation of elapsed time is based on it, you're very likely to run into calculation errors or even outages.
So, it is recommended to use Process.clock_gettime instead. Something like:
def measure_time
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
yield
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
elapsed_time = end_time - start_time
elapsed_time.round(3)
end
Example:
elapsed = measure_time do
# your time-consuming task here:
sleep 2.2321
end
=> 2.232
%L gives milliseconds in ruby
require 'time'
puts Time.now.strftime("%Y-%m-%dT%H:%M:%S.%L")
or
puts Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")
will give you current timestamp in milliseconds.
Time.now.to_f can help you but it returns seconds.
In general, when working with benchmarks I:
put in variable the current time;
insert the block to test;
put in a variable the current time, subtracting the preceding current-time value;
It's a very simple process, so I'm not sure you were really asking this...
Try subtracting the first Time.now from the second. Like so:
a = Time.now
sleep(3)
puts Time.now - a # about 3.0
This gives you a floating-point number of the seconds between the two times (and with that, the milliseconds).
If you want something precise, unaffected by other part of your app (Timecop) or other programs (like NTP), use Process#clock_gettime with Process::CLOCK_MONOTONIC to directly get the processor time.
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
# other code
t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
Also, if you are trying to benchmark some code tho, there is the Benchmark module for that!
require "benchmark"
time = Benchmark.realtime do
# code to measure
end

Resources