Ruby time multiplication - ruby-on-rails

Can some break this down for me? In my mind 5 minutes squared is 25 minutes
irb(main):014:0> now = Time.now.utc
=> 2019-05-03 01:36:41 UTC
irb(main):015:0> now + (5.minutes ** 2)
=> 2019-05-04 02:36:41 UTC

There is no Numeric#minutes in ruby, that’s Rails monkeypatching everything.
Numeric#minutes is delegated to ActiveSupport::Duration#minutes which in turn constructs an ActiveSupport::Duratioon::Scalar instance, with an amount of seconds as a “number behind.” That number will be used in:
coercion that might be used in any arithmetics involving Numeric, amongst others.
That said, when foo.minutes meets the arithmetic operation with a Numeric as a RHO, it [using coercion] does the math using the number of seconds.
Even more, the comparison against Numeric would also work:
5.minutes == 300
#⇒ true
Hence my advise: never ever use these misleading monkeypatched crap. Use seconds explicitly to perform date/time operations.

Related

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.

Round date to first year of the decade in Ruby

In Rails, I have a date saved in an instance variable. I need to grab the beginning of the decade before it. If #date.year= 1968 then I need to return 1960. How would I do that?
You can do this several ways. As suggested, you can always use integer division which divides the number and truncates the remainder. So 1968/10 returns 196 and if you multiply it by 10, it will give you 1960. Or simply,
#date.year = #date.year/10 * 10
#date.year
=> 1960
I prefer the method of using modular arithmetic. If you do #date.year % 10 it will return the remainder if you divide by 10 which you can then subtract from the year like so:
#date.year = #date.year - (#date.year % 10)
#date.year
=> 1960
The reason I prefer the latter is because integer division truncating the remainder may not be some thing that is obvious to everyone looking at your code. However, modular arithmetic works generally the same in all programming languages.
Keep in mind if you're trying to change the date, you need to use the appropriate method.
#date.change(:year => 1960)
just divide and multiply integers: try #date.year/10*10
Do an integer division by 10, and then multiply by 10.
1.9.3-p286 :001 > 1855/10
=> 185
1.9.3-p286 :002 > 185 * 10
=> 1850
The reason why this works (in Ruby, and in C/C++, Python, and possibly many other languages), is that integer division will always truncate the remainder. This will not be the case if you are dividing by a floating point however.

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 cap and round number in ruby

I would like to "cap" a number in Ruby (on Rails).
For instance, I have, as a result of a function, a float but I need an int.
I have very specific instructions, here are some examples:
If I get 1.5 I want 2 but if I get 2.0 I want 2 (and not 3)
Doing number.round(0) + 1 won't work.
I could write a function to do this but I am sure one already exists.
If, nevertheless, it does not exist, where should I create my cap function?
Try ceil:
1.5.ceil => 2
2.0.ceil => 2
How about number.ceil?
This returns the smallest Integer greater than or equal to number.
Be careful if you are using this with negative numbers, make sure it does what you expect:
1.5.ceil #=> 2
2.0.ceil #=> 2
(-1.5).ceil #=> -1
(-2.0).ceil #=> -2
.ceil is good, but remember, even smallest value in float will cause this:
a = 17.00000000000002
17.0
a.ceil
18
Use Numeric#ceil:
irb(main):001:0> 1.5.ceil
=> 2
irb(main):002:0> 2.0.ceil
=> 2
irb(main):003:0> 1.ceil
=> 1
float.ceil is what you want for positive numbers. Be sure to consider the behavior for negative numbers. That is, do you want -1.5 to "cap" to -1 or -2?

ruby on rails int to minutes::seconds::milliseconds

I have this line, which shows the minutes and seconds. But I have to add milliseconds to it as well for greater accuracy. How do I add that in this line, or is there an easier way to get the desired result?
#duration = [cd.ExactDuration/60000000, cd.ExactDuration/1000000 % 60].map{|t| t.to_s.rjust(2, '0') }.join(':'))
The exact duration type is saved in microseconds. So the first converts to microseconds to minutes, the second part is microseconds to seconds. Now I need to add milliseconds.
cd.ExactDuration/1000 % 1000 should do the trick.
Of course you may also want to tweak the formatting, since that's a datum you don't want to right-justify in a 2-wide field;-). I'd suggest sprintf for string-formatting, though I realize its use is not really intuitive unless you come from a C background.

Resources