Operations on DateTime in rails - ruby-on-rails

Is it possible to subtract one DateTime from another and get the result in Time.
Example if we subtract 2011-08-27 01:00:00 UTC from 2011-08-29 08:13:00 UTC, the result should be 55:13:00 (hope I didn't make a mistake while calulating :p)
Thanks.

Time is generally expressed in seconds when doing math like this, even fractional seconds if you want. A Time represents a specific point in time, which while internally represented as seconds since the January 1, 1970 epoch, is not intended to be a scalar unit like that.
If you have two DateTime objects, you can determine the difference between them like this:
diff = DateTime.parse('2011-08-29 08:13:00 UTC').to_time - DateTime.parse('2011-08-27 01:00:00 UTC').to_time
# => 198780.0
Once you have the number of seconds, the rest is simply a formatting problem:
'%d:%02d:%02d' % [ diff / 3600, (diff / 60) % 60, diff % 60 ]
# => "55:13:00"

Related

How to calculate the difference in minutes between two bedtimes (NOT time elapsed)?

I'm trying to get Google Sheets functions to calculate the difference in minutes between two bedtimes and have been spinning my wheels for at least five hours on this. Here are four examples of what I'm trying to accomplish:
BEDTIME 1 BEDTIME 2 DIFF IN MINS
9:00 PM 9:15 PM 15
9:00 PM 10:00 PM 60
11:30 PM 1:00 AM 90
1:00 AM 11:00 PM 120
As you can see, the date doesn't figure at all. I apologize for not offering up code, but I've tried at least half a dozen approaches from other answers and they aren't working -- mainly, I suspect, because most people are looking to find the time elapsed between the two times whereas I'm looking to determine "how much earlier" or "how much later" one bedtime is relative to another (always expressed as a positive value).
Any help would be appreciated. Thanks.
Times are stored as numbers between 0 and 1. If you subtract two times and multiply the result by 24 x 60 = 1440 and format as a number you’ll get number of minutes. I think you’ll need something like:
=MIN(ABS(1440*(B1-A1)), ABS(1440*(B1-A1-1)), ABS(1440*(B1-A1+1)))
The difference between two times is a duration. The question requests that durations be converted to "digital minutes", but that is often not as readable as one would think. 175 minutes is more difficult to understand than 2:55 hours.
There is therefore usually no point in multiplying by 24 * 60 — instead, just use the duration value as is:
=min( abs(B2 - A2), abs(B2 - A2 - 1), abs(B2 - A2 + 1) )
Format the result cell as Format > Number > Duration.
See this answer for an explanation of how date and time values work in spreadsheets.
use arrayformula:
=INDEX(IFERROR(1/(1/TRANSPOSE(QUERY(TRANSPOSE(
IF(A2:A&B2:B="", 0, ABS(1440*(B2:B-A2:A+{-1, 0, 1})))),
"select "&TEXTJOIN(",", 1,
"min(Col"&ROW(A2:A)-ROW(A2)+1&")"))))),, 2)
or:
=INDEX(IFERROR(1/(1/QUERY(SPLIT(FLATTEN(
ROW(A2:A)&"×"&ABS(1440*(B2:B-A2:A+{-1, 0, 1}))), "×"),
"select min(Col2) group by Col1 label min(Col2)''"))))
Try to implement a modulus function in your code. It would basically do something like this:
If x = -5, then y = f(x) = – (-5) = 5, since x is less than zero
If x = 10, then y = f(x) = 10, since x is greater than zero
If x = 0, then y = f(x) = 0, since x is equal to zero
Therefore calculating how much time passed without negative values.

group_by_day and sum works incorrect

I have a code:
total_rate = company.crews
.joins(:assignment)
.where(assignments: { date: week[:from]..week[:to] })
.group_by_day('assignments.date', format: '%Y-%m-%d')
.joins(:workers)
.sum('workers.rate')
in output, we have, as a crew with 2 workers, with rates = 4,5 and it should be 9. It's correct.
If in the last line it’s easy to do .sum ('workers.rate'), then everything is grouped normally, and summed up.
and the output is
{13:00 => 9} 9 is worker.first.rate = 4, worker.last = 5.
But, here, when I try to multiply by a condition more time, it turns out that every worker.reit is multiplied by the time difference,
But you need the amount of worker rate 9 * 13:00,
.sum('workers.rate * (time_arriving - time_leaving)')
not 4 * 13.00 + 5 * 13.00
That’s the trouble ...
Assuming Postgres:
'time' - 'time' will return an interval, multiplying it with an integer will not change the type.
You will need to extract the hours first, then you can multiply it by the rate.
This query shows you how to do get the hours:
select extract(epoch from time_leaving - time_starting) / 3600 from assignments
Note that select extract(hour from time_leaving - time_starting) from assignments will only extract full hours, so 12:00 - 10:15 will return '1'

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

Convert duration to hours:minutes:seconds (or similar) in Rails 3 or Ruby

I have a feeling there is a simple/built-in way to do this but I can't find it.
I have a duration (in seconds) in an integer and I want to display it in a friendly format.
e.g. 3600 would be displayed as "01:00:00" or "1 hour" or something.
I can do it with time_ago_in_words(Time.zone.now+3600) but that feels like a bit of a hack, there is no reason to add/subtract from the current time just to format this value. Is there a duration_in_words() or something?
Thanks
Summing up:
assuming that total_seconds = 3600
Option 1:
distance_of_time_in_words(total_seconds) #=> "about 1 hour"
Option 2:
Time.at(total_seconds).utc.strftime("%H:%M:%S") #=> "01:00:00"
Note: it overflows, eg. for total_seconds = 25.hours.to_i it'll return "01:00:00" also
Option 3:
seconds = total_seconds % 60
minutes = (total_seconds / 60) % 60
hours = total_seconds / (60 * 60)
format("%02d:%02d:%02d", hours, minutes, seconds) #=> "01:00:00"
Option 4:
ActiveSupport::Duration.build(total_seconds).inspect #=> "1 hour"
# OR
parts = ActiveSupport::Duration.build(total_seconds).parts
"%02d:%02d:%02d" % [parts.fetch(:hours, 0),
parts.fetch(:minutes, 0),
parts.fetch(:seconds, 0)] #=> "01:00:00"
See: http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html
distance_of_time_in_words(3600)
=> "about 1 hour"
Ruby's string % operator is too unappreciated and oft forgotten.
"%02d:%02d:%02d:%02d" % [t/86400, t/3600%24, t/60%60, t%60]
Given t is a duration in seconds, this emits a zero-padded colon-separated string including days. Example:
t = 123456
"%02d:%02d:%02d:%02d" % [t/86400, t/3600%24, t/60%60, t%60]
=> "01:10:17:36"
Lovely.
I guess you could do also something like:
(Time.mktime(0)+3600).strftime("%H:%M:%S")
To format it as you wish.
BTW, originally I thought of using Time.at() but seems that EPOCH time on my Ubuntu is Thu Jan 01 01:00:00 +0100 1970 and not 00:00:00 hours as I expected, and therefore if I do:
Time.at(3600).strftime("%H:%M:%S")
Gives me 1 hour more than wanted.
I use this to show time durations in my Rails Project:
Add a custom method to the Integer class. You can create a new file called pretty_duration.rb in the initializers folder:
class Integer
def pretty_duration
parse_string =
if self < 3600
'%M:%S'
else
'%H:%M:%S'
end
Time.at(self).utc.strftime(parse_string)
end
end
Call seconds.pretty_duration anywhere in your project:
275.pretty_duration # => "04:35"
9823.pretty_duration # => "02:43:43"
This answer builds up on Lev Lukomsky's Code
This one uses the obscure divmod method to divide and modulo at the same time, so it handles Float seconds properly:
def duration(seconds)
minutes, seconds = seconds.divmod(60)
hours, minutes = minutes.divmod(60)
days, hours = hours.divmod(24)
"#{days.to_s.rjust(3)}d #{hours.to_s.rjust(2)}h #{minutes.to_s.rjust(2)}m #{seconds}s"
end
ActiveSupport::Duration.build + inspect gives you valid results
>> ActiveSupport::Duration.build(125557).inspect
=> "1 day, 10 hours, 52 minutes, and 37 seconds"
Using Time.utc.strftime works only for values when total number of hours is less then 24:
2.2.2 :004 > Time.at(60 * 60).utc.strftime('%H h %M m')
=> "01 h 00 m"
For greater values it returns incorrect results:
2.2.2 :006 > Time.at(60 * 60 * 24).utc.strftime('%H h %M m')
=> "00 h 00 m"
I suggest using the simplest method I found for this problem:
def formatted_duration total_seconds
hours = total_seconds / (60 * 60)
minutes = (total_seconds / 60) % 60
seconds = total_seconds % 60
"#{ hours } h #{ minutes } m #{ seconds } s"
end
You can always adjust returned value to your needs.
Be careful with the duration longer than one day.
(timing/3600).to_i.to_s.rjust(2,'0') + ":"+Time.at(timing).utc.strftime("%M:%S")
An answer inspired from Lev Lukomsky's one taking advantage of ActiveSupport::Duration, and handling milliseconds (useful to benchmark code)
# duration in ms modulus number of ms in one second
milliseconds = duration.in_milliseconds % 1.second.in_milliseconds
# duration in seconds modulus number of seconds in one minute
seconds = (duration / 1.second) % (1.minute / 1.second)
# duration in minutes modulus number of minutes in one hour
minutes = (duration / 1.minute) % (1.hour / 1.minute)
# duration in hours modulus number of hours in one day
hours = (duration / 1.hour) % (1.day / 1.hour)
format("%02d:%02d:%02d:%03d", hours, minutes, seconds, milliseconds) #=> "12:05:00:001"
Of course you can extend this easily with days, months, years, etc using related ActiveSupport methods and repeating the same structure.
Keep in mind that for too long durations, this may be inaccurate since the duration of 1 month is not fixed in number of days, and I'm not sure how AS:Duration deals with that.
Shout out to #joshuapinter who gave the best answer (in the form of a comment).
Use the drop-in replacement dotiw gem to gain more control over the accuracy of the output to suit different needs:
https://github.com/radar/distance_of_time_in_words
Sample view code:
%label
Logoff after:
- expire_in = distance_of_time_in_words(Time.now, Time.now + user.custom_timeout.minutes, :only => [:minutes, :hours, :days])
= expire_in
Resulting in something like this:
Logoff after: 1 day, 13 hours, and 20 minutes
Just to throw in my 2 cents:
Time.at(i).utc.strftime((i < 3600) ? '%-M minutes and %-S seconds' : '%-H hours, %-M minutes, and %-S seconds')
Built off of Xiao Bin's answer.
Here a simple solution using divmod and map:
hours = 3.5456
value = (hours*60).divmod(60).map{ |a| "%02d"%[a.floor] }.join(":")
=> "03:32"

How do I get the number of seconds between two DateTimes in Ruby on Rails

I've got code that does time tracking for employees. It creates a counter to show the employee how long they have been clocked in for.
This is the current code:
start_time = Time.parse(self.settings.first_clock_in)
total_seconds = Time.now - start_time
hours = (total_seconds/ 3600).to_i
minutes = ((total_seconds % 3600) / 60).to_i
seconds = ((total_seconds % 3600) % 60).to_i
This works fine. But because Time is limited to the range of 1970 - 2038 we are trying to replace all Time uses with DateTimes. I can't figure out how to get the number of seconds between two DateTimes. Subtracting them yields a Rational which I don't know how to interpret, whereas subtracting Times yields the difference in seconds.
NOTE: Since Ruby 1.9.2, the hard limit of Time is removed. However, Time is optimized for values between 1823-11-12 and 2116-02-20.
Subtracting two DateTimes returns the elapsed time in days, so you could just do:
elapsed_seconds = ((end_time - start_time) * 24 * 60 * 60).to_i
Or, more readably:
diff = datetime_1 - datetime_2
diff * 1.days # => difference in seconds; requires Ruby on Rails
Note, what you or some other searchers might really be looking for is this:
diff = datetime_1 - datetime_2
Date.day_fraction_to_time(diff) # => [h, m, s, frac_s]
You can convert them to floats with to_f, though this will incur the usual loss of precision associated with floats. If you're just casting to an integer for whole seconds it shouldn't be big enough to be a worry.
The results are in seconds:
>> end_time.to_f - start_time.to_f
=> 7.39954495429993
>> (end_time.to_f - start_time.to_f).to_i
=> 7
Otherwise, you could look at using to_formatted_s on the DateTime object and seeing if you can coax the output into something the Decimal class will accept, or just formatting it as plain Unix time as a string and calling to_i on that.
Others incorrectly rely on fractions or helper functions. It's much simpler than that. DateTime itself is integer underneath. Here's the Ruby way:
stop.to_i - start.to_i
Example:
start = Time.now
=> 2016-06-21 14:55:36 -0700
stop = start + 5.seconds
=> 2016-06-21 14:55:41 -0700
stop.to_i - start.to_i
=> 5
I am using ruby-2.1.4 and for me the following worked
Time.now - Time.new(2014,11,05,17,30,0)
gave me the time difference in seconds
reference: ruby doc
there's a method made for that:
Time.now.minus_with_coercion(10.seconds.ago)
equals 10.
Source: http://apidock.com/rails/Time/minus_with_coercion
Hope I helped.
Define a Ruby function like this,
def time_diff(start_time, end_time)
seconds_diff = (start_time - end_time).to_i.abs
days = seconds_diff / 86400
seconds_diff -= days * 86400
hours = seconds_diff / 3600
seconds_diff -= hours * 3600
minutes = seconds_diff / 60
seconds_diff -= minutes * 60
seconds = seconds_diff
"#{days} Days #{hours} Hrs #{minutes} Min #{seconds} Sec"
end
And Call this function,
time_diff(Time.now, Time.now-4.days-2.hours-1.minutes-53.seconds)

Resources