Confusing ActiveSupport::Duration calculation results - ruby-on-rails

I understand this from ActiveSupport::Duration (Rails 4.2.4 + ruby 2.2.1):
main > (1.month + 2.days).parts
[[:months, 1], [:days, 2]]
I don't understand this though:
main > (1.hour + 35.minutes).parts
[[:seconds, 3600], [:seconds, 2100]]
Does ActiveSupport::Duration not handle hours and seconds the way it handles months and days? The docs on ActiveSupport::Duration are quite sparse (I can't seem to find better docs).
My main goal is to store a duration (hours and minutes) in a Rails model. Should I forget about ActiveSupport::Duration and just store seconds and then do my own calculations?

ActiveSupport handles months and days (and years) separately from hours, minutes and seconds.
This is because a duration of one hour (or 23 minutes) is always an exact number of seconds. A month on the other hand is a varying number of days and a day can have either 23, 24, or 25 hours depending on daylight savings changes.
It sounds like you don't need this functionality, in which case storing a number of seconds is probably simpler.

Related

Rails timestamps, See how much time passed since last time

In my app I want to print out the duration of time I spent from I made the model until I updated it.
So if I have the value
:created_at set to 2016-04-13 14:00:49 UTC
and
:updated_at set to 2016-04-13 15:05:49 UTC
I want to print out that it took 1hour and 5minutes. (or just 01.05).
How do I do this?
I'm not sure why this was downvoted, though a little googling would probably have gotten you to the right answer fairly quickly.
What you're looking for is called time_ago_in_words
Here is the doc http://apidock.com/rails/ActionView/Helpers/DateHelper/time_ago_in_words
And usage is:
time_ago_in_words #object.created_at
time_ago_in_words #object.updated_at
If you want to use it the console to play with it, make sure you preface it with helper so it's loaded into console
e.g.
helper.time_ago_in_words #object.created_at
update
For checking between 2 dates, not just one date from right now, then you can use distance_of_time_in_words
distance_of_time_in_words(#object.created_at, #object.updated_at)
That gives words like
12 days ago
If you're ONLY looking for hours, and nothing else then you can use basic subtraction and division
#object.updated_at.to_i - #object.created_at.to_i) / 60 / 60
You can try this
diff =#object.updated_at.to_time.to_i - #object.created_at.to_time.to_i
hour = "#{diff / 3600}:#{(diff % 3600) / 60}"

Ruby convert decimal duration (day|week|month|year) to end date

I have a start_at, a decimal quantity and an interval which is one of day | week | month | year.
start_at = Time.parse('2016-01-01 00:00:00 UTC') # leap year
quantity = BigDecimal.new('1.998') # changed from 2.998, should end on 2/29/16 sometime
interval = 'month' # could be any of day|week|month|year
With whole numbers, I've used duration i.e. 1.month, and I looked at Date#advance, though it only recognizes integer values.
It would seem simple but I cannot find anything in the standard libraries or in ActiveSupport.
References:
SO answer potentially used for input to Date#advance?
SO explanation of duration
Question
How can I establish the end_at date from a decimal?
Why? What purpose?
Proration to the second for a given amount and given interval.
Expectations
I'm looking for an end_at to the second as accurate as possible with respect to advancing the next interval(s) by the decimal quantity. Given interval = 'month', for the fractional part, when you pass the start of the month, means you are in that month and using it's duration. For example, January 2016 is 31 days, while February (leap) is 29 days (only in the leap year).
I'd say your best option is to use Ruby's date methods to advance time based on the whole number of the decimal, then calculate how many seconds your fraction is of your current interval.
So for 1.998 months, advance time 1 month. Find the current month you are in and get the .998 of the seconds in that month (i.e. for July, 31x24x60x60x.998) and then advance time that many seconds.
What does advancing time a fractional month mean?
Lets say we have the following date 2015-01-01 00:00:00 UTC. It is easy to advance exactly 1 whole month, we simply increment the number that represents months: 2015-02-01 00:00:00 UTC. Alternatively, we could view this as adding 31 days, which we know is the number of days in January.
But what if we want to advance 0.5 months from 2015-01-01 00:00:00 UTC?
We can't just increment like we did when advancing a whole month. Since we know January has 31 days, perhaps we could just advance 15.5 days: 2015-01-16 12:00:00 UTC. That sort of works.
How about 1.5 months from 2015-01-01 00:00:00 UTC? If we combine our previous approaches, we'd first increment, getting us to 0.5 left to advance and 2015-02-01 00:00:00 UTC. Then we'd take half of 28 and get to 2015-02-15 00:00:00 UTC.
But wait, what if instead we took the total number of days between the two months and then took 3/4 of that? Like 2(month) * (3/4), which would simplify to (3(month)) / 2, or 1.5(month). Lets try it.
(28 days + 31 days) * 0.75 = 44.25 days
Now adding that to 2015-01-01 00:00:00 UTC we get 2015-02-14 06:00:00 UTC. That's three-quarters of a day off from our other answer.
The problem here is that the length of a month varies. So fractional months are not consistently definable.
Imagine you have two oranges. One contains a little bit more juice than the other (perhaps 31ml and 29ml of juice). Your recipe calls for the juice of 1.5 oranges. Depending on which one you decide to cut in half, you could have either 44.5 ml or 45.5 ml. But if your recipe calls for 40 ml of orange juice, you can pretty consistently measure that. Much like you can consistently (kind of) increment a date by 40 days.
Time is really tricky. We have leap seconds, leap years, inconsistent units (months), timezones, daylight saving time, etc... to take into account. Depending on your use case, you could attempt to approximate fractional months, but I'd highly recommend trying to avoid the need for dealing with fractional months.

Rails age of an article in days?

I need to get the age of the article in days. For example, the article was written on Tue, 01 Apr 2014 18:31:07 EDT -04:00 and now I need the days from that date to now printed as an integer. How can I do so?
Please try something like this:
gem install time_diff
install the gem.
require 'time_diff'
time_diff_components = Time.diff(start_date_time, end_date_time)
time_diff_components[:year], time_diff_components[:month], time_diff_components[:week]
This will give more option.
More detail click
This isn't the cleverest way, but it's probably the simplest: use a "magic number": 86400, which is the number of seconds in a day. (you probably already know there are 3600 seconds in an hour, mentally file this number alongside that)
Differences between Time/DateTime objects will be in seconds (as a float). If you divide this by 86400 you get the difference in days, as a float. You can then call to_i on this to get it as an integer if you want.
eg
((Time.now - #article.created_at)/86400).to_i
It's probably worth saving this as a constant, egs SECONDS_IN_A_DAY or something, to avoid mistyping.
With the Date class you can do
(Date.today - #article.created_at.to_date).to_i
to get the number of days between the two dates.

What is a good way to record time periods in Rails application, so that I can easily use them for calculations?

I have time periods like:
2 years
1 month
4 days
I would like to add them to existing records in my DB in a way that I could easily use them for calculations? The type of calculations I would need would be [time_period] - (Time.now - [datetime]) in order to see how much of the time period is left.
Since my periods are quite standard, I was thinking to save the time periods as strings "2-years", "1-month", "4-days" etc. and split them on use. For example "4-days" could become something like this:
4.send("days".to_sym)
What do you think about this method? Any better ideas?
Okay i have a better solution.
Here is the Class TimeDuration
five_minutes = TimeDuration.new("5 min")
five_minutes + Time.now
Time.now
# => 2013-09-13 02:50:06 +0200
five_minutes + Time.now
# => 2013-09-13 02:55:13 +0200
It would be best to store them all in a standardized format; like seconds, so that you can easily do calculations without having to first convert things. If you also stored some sort of units field (i.e. years, days, minutes, seconds), you could then easily convert the seconds back to their appropriate units for display - sort of like what you have above.
stored_value_in_minutes = stored_value.seconds / 1.minutes

Humanizing time

I have a number of products that are perishable. Therefore, each product has an attribute called hours_expiration that tells how many hours the product can be used before it goes bad.
For ex, apple expires in 168 hours; nut expires in 4320 hours.
Given, the product's hours-to-expiration and the current time (Time.now or Date.now), how can I humanize the time-to-expiration in some of the following sample ways?
Your item is set to expire in about:
6 months and 14 days
1 month and 13 days
1 month and 1 day
27 days
1 day
23 hours
1 hour
50 minutes
1 minute
Looking for something robust and simple!
The distance_of_time_in_words helper seems to be what you ask for.
Another easy helper is time_ago_in_words: https://apidock.com/rails/ActionView/Helpers/DateHelper/time_ago_in_words
The method name might sound like it can only deal with past dates but actually it handles future dates just fine. You can try it in your rails console:
expiration_date = Time.now + 5.days
puts "Expires in #{helper.time_ago_in_words(expiration_date)}"
"Expires in 5 days"
Look at Distance of time docs: http://apidock.com/rails/ActionView/Helpers/DateHelper/distance_of_time_in_words

Resources