Approximate comparison of 2 Ruby DateTimes - ruby

I have 2 DateTimes and I want to check if they're roughly 24 hours apart, plus or minus a small amount, say 5 minutes. Is there a built in way to do this?

There is not, but it is easy enough:
(-5 * 60 .. 5 * 60).include?((t2 - t1).abs - 24 * 3600)
"is the absolute difference between the two dates, when you subtract a full day, within plus or minus five minutes?"

Version 1: Works for both DateTime and Time. Converts everything to seconds.
def time_apart_within_drift?(t1, t2, diff: 24*60*60, drift: 5*60)
(t1.to_i - t2.to_i).abs <= diff + drift
end
As per the Ruby Style Guide, you should almost always use Time instead of DateTime. A Time object can still include the date, and it will make your calculations much cleaner.
This is because subtracting two DateTime objects gives you the difference in days as a Rational object, whereas subtracting two Time objects gives you the difference in seconds as a Float.
This allows you to write your function like so:
def time_apart_within_drift?(t1, t2, diff: 24*60*60, drift: 5*60)
(t1 - t2).abs <= diff + drift
end
# true
time_apart_within_drift?(Time.new(2020,3,5), Time.new(2020,3,4))
# true
time_apart_within_drift?(Time.new(2020,3,5), Time.new(2020,3,3,23,57))
# false
time_apart_within_drift?(Time.new(2020,3,5), Time.new(2020,3,3,23,47))
If using Time as well as rails and/or activesupport, the args can be made more readable using Duration objects:
def time_apart_within_drift?(t1, t2, diff: 24.hours, drift: 5.minutes)
(t1 - t2).abs <= diff + drift
end

Related

Ruby. How to write a loop of 1% percentage profit for one year on a 1000$ investment

I'd like to create a ruby program to calculate the 1% on my investment every day for one year.
For example, if I invest 1000$ and get a profit of 1% at the end of the day will be 1010.0$ The second day I will invest 1010.0$ and I will get a 1% profit of 1020.1$ and so on.
I'd like to determine after 365 days what will be my initial investment.
I'm trying with a loop to print every single returning value but as you see I'm still a superrookie.
Thanks. Sam
I made it alone! Thanks for all of your answers!
money = 1000
days = 0
perc = 0.01
while days < 366
puts days
puts money
days += 1
money = money * perc + money
end
1000 * 1.01**365
#=> 37783.43433288728
You don't need to write a program for this; it's a one-line calculation.
But if you want to do it one day at a time and show the output of each day, how about:
money = 1000
(1..365).each do |day|
money *= 1.01
puts "After #{day} days: $#{money.round(2)}"
end
You should use BigDecimal instead of Float when dealing with monetary values:
require 'bigdecimal'
money = BigDecimal('1000')
percentage = BigDecimal('0.01')
For the loop I'd use upto which works very intuitively:
1.upto(365) do |day|
money += (money * percentage).round(2)
printf("%3d: %8.2f$\n", day, money)
end
money * percentage calculates the day's profit, rounded to 2 digits via round. You can adjust the rounding mode by passing a second argument.
printf then outputs day and money using the given formatting:
%3d prints an integer with width 3
%8.2f prints a float with 2 fractional digits and a total width of 8
Output:
1: 1010.00$
2: 1020.10$
3: 1030.30$
...
363: 37039.07$
364: 37409.46$
365: 37783.55$
The following is super simple but will get the job done. Basically you initialize a variable to 1000, we then loop 365 times. We have a block where all the math happens. It takes the value 1000 and multiplies it by 1.01 and overwrites the value of the intial investment. You can change the 365 to 2 or however many days you want. The puts print the value of the start. Just run this by putting it in a .rb file and running 'ruby file.rb'
start = 1000
365.times do
start = start*1.01
puts start
end
puts start

Get current system time in milliseconds

In Ruby, what is the right way to get the current system time since epoch(1970) in milliseconds?
I tried Time.now.to_i , it seems not the result I want. I need the result shows milliseconds and with long type, not float or double.
(Time.now.to_f * 1000).to_i
Time.now.to_f shows you the time including decimal numbers. To get number of miliseconds just multiply the time by 1000.
You can combine to_i and usec. The former returns the number of seconds since the Epoch, the latter returns the number of microseconds:
require 'time'
t = Time.at(1473152006, 2000)
t.to_i * 1000 + t.usec / 1000
#=> 1473152006002
This is equivalent to:
t.strftime('%s%L')
#=> "1473152006002"
In some cases like the above, to_f would introduce a slight floating point error:
t.to_f * 1000
#=> 1473152006001.9998
(t.to_f * 1000).to_i
#=> 1473152006001

Two equal Time objects are not equal according to ==

I've found some behaviour of Time in Ruby that I don't understand while writing some tests. Am I missing something or is this a genuine problem?
I can reproduce the situation in irb as follows - first create a Time and add 30 seconds to it:
t = Time.new(2007,01,15,11,15,30.1)
# => 2007-01-15 11:15:30 +0000
t1 = t + 30
# => 2007-01-15 11:16:00 +0000
Then create another time which should be equal to t1:
t2 = Time.new(2007,01,15,11,16,0.1)
# => 2007-01-15 11:16:00 +0000
Now I'd expect t1 and t2 to be equal, but they aren't according to ==. From the rough experiments I've done it seems like == works, except when the addition of seconds moves t1 onto a new minute:
t1 == t2
# => false
However if you call to_f on them then == does return true:
t1.to_f == t2.to_f
# => true
Just to confirm that there aren't any nano seconds hanging around:
t1.nsec
# => 100000000
t2.nsec
# => 100000000
=== Added after answers from joanbm and Vitalii Elenhaupt (sorry to open this up again)
joanbm and Vitalii Elenhaupt point out that t1.to_r and t2.to_r produce different results.
But... according to the Ruby-Doc Time within the normal range is stored as a 63 bit integer of the number of nano seconds since the epoch - which suggests that float-point issues shouldn't come into it.
So... why if Time is stored as an integer, and #to_f and #nsec can produce the same result to 9 decimal places, can't == use this info to recognize the two times as equal? (Maybe Ruby uses #to_r in the equality test?).
And... is it safe to assume that t1.to_f == t2.to_f will always give an accurate test of equality to 9 decimal places and is that the best way to compare Time objects?
This is the notorious problem with inaccurate representation of floating point numbers in computers, quite unrelated to Ruby or Time class implementation.
You give to both objects floating numbers as seconds argument, and its (inaccurate) fractional part constructor stores as a rational number for its internal representation:
t = Time.new(2007,01,15,11,15,30.1)
t.subsec # (14073748835533/140737488355328) <- may vary on your system
t.subsec.to_f # 0.10000000000000142 <- this is not equal to 0.1 !!
dtto for the 2nd time:
t2 = Time.new(2007,01,15,11,16,0.1)
t2.subsec # (3602879701896397/36028797018963968)
t2.subsec.to_f # 0.1 <- there accidentally precision didn't get lost
Just use exact numerical types like Rational and you are done:
t = Time.new(2007,01,15,11,15,Rational('30.1'))
t1 = t + 30
t2=Time.new(2007,01,15,11,16,0.1r) # alternate notation, see docs
t1 == t2 # true
Or do a rough comparison with Time#round method applied on both sides.
That happens because both times are still different. Here is a good illustration with Time#to_r:
>> t1.to_r #=> (164501373566169071275213/140737488355328)
>> t2.to_r #=> (42112351632939282246454477/36028797018963968)
If you don’t care about milliseconds, you may compare timestamps using to_i method:
>> t1.to_i == t2.to_i # => true
or make a new Time object from timestamps:
>> Time.at(t1.to_i) == Time.at(t2.to_i) # => true
You are using different parameters for your Time objects
#irb
t = Time.new(2007,01,15,11,15,30,0.1)
=> 2007-01-15 11:15:30 +0000
t1 = t2 + 30
=> 2007-01-15 11:16:00 +0000
if you do not pass the minutes, on my system the timezone seems to change:
t2 = Time.new(2007,01,15,11,16,0.1)
=> 2007-01-15 11:16:00 +0100 # what happened here? '+0100'
t1 == t2
=> false
Even if that is not the case for the op, it is interesting that this happens in my irb.
Building the objects like so Time.utc(2007,01,15,11,15,30,0.1), using an explicit time zone, returned exactly the behaviour you described.
However, if you pass the same amount of parameters (0 minutes for t2) the comparison will return the expected result as you will probably not even run into the floating point problem described by joanbm.
t = Time.new(2007,01,15,11,15,30,0.1)
=> 2007-01-15 11:15:30 +0000
t1 = t + 30
=> 2007-01-15 11:16:00 +0000
t2 = Time.new(2007,01,15,11,16,0,0.1)
=> 2007-01-15 11:16:00 +0000
t1 == t2
=> true
If you print the times rational form you get:
t1.to_r
=> (42112611033072059383231283/36028797018963968)
t2.to_r
=> (42112611033072059383231283/36028797018963968)
As to_r returns the time in seconds since the epoch (or Unix Time), there is no reason for t1 and t2 not to be equal, as long as date, time and time zone match.
Unfortunately I can not give a good explenation why this is the case.
This seems interesting though and I will come back when I find a reason for this behaviour.
Edit
This does not explain the timezone change, hence does not explain the problem, but your are missing a comma for t so you're actually passing 30.1 instead of 30 seconds.
t = Time.new(2007,01,15,11,15,30.1)
It probably should be
t = Time.new(2007,01,15,11,15,30,0.1)
However this does not cause your comparison to fail.

Get the count of elements in a ruby range made of Time objects

How would I be able to get the size or count of a range made up of Time objects?
Something that would achieve the same result as my pseudo Ruby code, which doesn't work:
((Time.now.end_of_day - 31.days)..(Time.now.end_of_day - 1.day)).size == 30
currently doing the above gives an error:
NoMethodError: undefined method `size' for 2012-05-18 23:59:59 -0400..2012-06-17 23:59:59 -0400:Range
and trying to turn it into array (range).to_a :
can't iterate from Time
update
Interesting, Just tried to do
((Date.today.end_of_day - 31.days)..(Date.today.end_of_day - 1.day)).count
Users/.../gems/ruby/1.9.1/gems/activesupport-3.0.15/lib/active_support/time_with_zone.rb:322: warning: Time#succ is obsolete; use time + 1
However
((Date.today - 31.days)..(Date.today - 1.day)).count == 31
I would be willing to settle for that?
Also ((Date.today - 31.days)..(Date.yesterday)).count == 31
update 2
On the other hand, taking Mu's hint we can do:
(((Time.now.end_of_day - 31.days)..(Time.now.end_of_day - 1.day)).first.to_date..((Time.now.end_of_day - 31.days)..(Time.now.end_of_day - 1.day)).last.to_date).count == 31
There's no such method as Range#size, try Range#count (as suggested by Andrew Marshall), though it still won't work for a range of Time objects.
If you want to perform number-of-days computations, you're better off using Date objects, either by instantiating them directly (Date.today - 31, for example), or by calling #to_date on your Time objects.
Date objects can be used for iteration too:
((Date.today - 2)..(Date.today)).to_a
=> [#<Date: 2012-06-17 ((2456096j,0s,0n),+0s,2299161j)>,
#<Date: 2012-06-18 ((2456097j,0s,0n),+0s,2299161j)>,
#<Date: 2012-06-19 ((2456098j,0s,0n),+0s,2299161j)>]
((Date.today - 2)..(Date.today)).map(&:to_s)
=> ["2012-06-17", "2012-06-18", "2012-06-19"]
It's because a size for a date range doesn't make sense—it doesn't know if you want to view it as days, minutes, seconds, months, or something else. The reason the error mentions iterating is that in order to determine the size of the range, it must know how to iterate over them so that it may count the number of elements.
Since what you want is the difference in days, just do that:
date_one = Time.now.end_of_day - 31.days
date_two = Time.now.end_of_day - 1.day
((date_one - date_two) / 1.day).abs
#=> 30.0
You must divide by 1.day since a difference of Times returns seconds.
To have any chance of your code working you should wrap everything before .size in parentheses.
Instead of using a range, maybe you can just subtract one time object from another?
I know you make ranges out of Date objects so you could convert to that.

How to time an operation in milliseconds in Ruby?

I'm wishing to figure out how many milliseconds a particular function uses. So I looked high and low, but could not find a way to get the time in Ruby with millisecond precision.
How do you do this? In most programming languages its just something like
start = now.milliseconds
myfunction()
end = now.milliseconds
time = end - start
You can use ruby's Time class. For example:
t1 = Time.now
# processing...
t2 = Time.now
delta = t2 - t1 # in seconds
Now, delta is a float object and you can get as fine grain a result as the class will provide.
You can also use the built-in Benchmark.measure function:
require "benchmark"
puts(Benchmark.measure { sleep 0.5 })
Prints:
0.000000 0.000000 0.000000 ( 0.501134)
Using Time.now (which returns the wall-clock time) as base-lines has a couple of issues which can result in unexpected behavior. This is caused by the fact that the wallclock time is subject to changes like inserted leap-seconds or time slewing to adjust the local time to a reference time.
If there is e.g. a leap second inserted during measurement, it will be off by a second. Similarly, depending on local system conditions, you might have to deal with daylight-saving-times, quicker or slower running clocks, or the clock even jumping back in time, resulting in a negative duration, and many other issues.
A solution to this issue is to use a different time of clock: a monotonic clock. This type of clock has different properties than the wall clock.
It increments monitonically, i.e. never goes back and increases at a constant rate. With that, it does not represent the wall-clock (i.e. the time you read from a clock on your wall) but a timestamp you can compare with a later timestamp to get a difference.
In Ruby, you can use such a timestamp with Process.clock_gettime(Process::CLOCK_MONOTONIC) like follows:
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
# => 63988.576809828
sleep 1.5 # do some work
t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
# => 63990.08359163
delta = t2 - t1
# => 1.5067818019961123
delta_in_milliseconds = delta * 1000
# => 1506.7818019961123
The Process.clock_gettime method returns a timestamp as a float with fractional seconds. The actual number returned has no defined meaning (that you should rely on). However, you can be sure that the next call will return a larger number and by comparing the values, you can get the real time difference.
These attributes make the method a prime candidate for measuring time differences without seeing your program fail in the least opportune times (e.g. at midnight at New Year's Eve when there is another leap-second inserted).
The Process::CLOCK_MONOTONIC constant used here is available on all modern Linux, BSD, and macOS systems as well as the Linux Subsystem for Windows. It is however not yet available for "raw" Windows systems. There, you can use the GetTickCount64 system call instead of Process.clock_gettime which also returns a timer value in millisecond granularity on Windows (>= Windows Vista, Windows Server 2008).
With Ruby, you can call this function like this:
require 'fiddle'
# Get a reference to the function once
GetTickCount64 = Fiddle::Function.new(
Fiddle.dlopen('kernel32.dll')['GetTickCount64'],
[],
-Fiddle::TYPE_LONG_LONG # unsigned long long
)
timestamp = GetTickCount64.call / 1000.0
# => 63988.576809828
You should take a look at the benchmark module to perform benchmarks. However, as a quick and dirty timing method you can use something like this:
def time
now = Time.now.to_f
yield
endd = Time.now.to_f
endd - now
end
Note the use of Time.now.to_f, which unlike to_i, won't truncate to seconds.
Also we can create simple function to log any block of code:
def log_time
start_at = Time.now
yield if block_given?
execution_time = (Time.now - start_at).round(2)
puts "Execution time: #{execution_time}s"
end
log_time { sleep(2.545) } # Execution time: 2.55s
Use Time.now.to_f
The absolute_time gem is a drop-in replacement for Benchmark, but uses native instructions to be far more accurate.
If you use
date = Time.now.to_i
You're obtaining time in seconds, that is far from accurate, specially if you are timing little chunks of code.
The use of Time.now.to_i return the second passed from 1970/01/01. Knowing this you can do
date1 = Time.now.to_f
date2 = Time.now.to_f
diff = date2 - date1
With this you will have difference in second magnitude. If you want it in milliseconds, just add to the code
diff = diff * 1000
I've a gem which can profile your ruby method (instance or class) - https://github.com/igorkasyanchuk/benchmark_methods.
No more code like this:
t = Time.now
user.calculate_report
puts Time.now - t
Now you can do:
benchmark :calculate_report # in class
And just call your method
user.calculate_report

Resources