Timecodes in Rails - time or numeric values? - ruby-on-rails

I'm working on a project that stores data on audio tracks and requires the use of timecodes for the start and end points of the track on the audio. I also need to calculate and display the duration of the track. Eg. a track starts at 0:01:30 and finishes at 0:04:12. So its duration is a total of 2 mins and 42 secs.
The trick is that everything needs to be displayed and handled as timecodes, so in the above example the duration needs to be displayed as 0:02:42.
So my question is how you would store the values? The easiest option would be to store the start and end times as Time in the database. Its very easy to calculate the duration and you can utilise the Rails time helpers in the forms. The only painful part is turning the duration back into a time value for display (since if I supply just the number of seconds to strptime it keeps using the current time to fill in the other fields)
The other option that I considered is storing them as numeric values (as the number of seconds). But then I have to write a lot of code to convert them to and from some type of timecode format and I can't use the Rails time helpers.
Is there another idea that I haven't considered? Is there an easy way to calculate and display the duration as a timecode format?

I would store them as seconds or milliseconds. I've been working on a music library manager/audio player in Ruby, and I actually had to write the two methods you would need. It's not that much code:
# Helper method to format a number of milliseconds as a string like
# "1:03:56.555". The only option is :include_milliseconds, true by default. If
# false, milliseconds won't be included in the formatted string.
def format_time(milliseconds, options = {})
ms = milliseconds % 1000
seconds = (milliseconds / 1000) % 60
minutes = (milliseconds / 60000) % 60
hours = milliseconds / 3600000
if ms.zero? || options[:include_milliseconds] == false
ms_string = ""
else
ms_string = ".%03d" % [ms]
end
if hours > 0
"%d:%02d:%02d%s" % [hours, minutes, seconds, ms_string]
else
"%d:%02d%s" % [minutes, seconds, ms_string]
end
end
# Helper method to parse a string like "1:03:56.555" and return the number of
# milliseconds that time length represents.
def parse_time(string)
parts = string.split(":").map(&:to_f)
parts = [0] + parts if parts.length == 2
hours, minutes, seconds = parts
seconds = hours * 3600 + minutes * 60 + seconds
milliseconds = seconds * 1000
milliseconds.to_i
end
It's written for milliseconds, and would be a lot simpler if it was changed to work with seconds.

Related

QB Time Tracking WebService

so this is the xml node i am creating for Quickbooks. Sending over duration (its time kinda thingie)
XmlElement duration2 = inputXmlDoc.CreateElement("Duration");
timeTrackingAdd2.AppendChild(duration2);
duration2.InnerText = "PT0H14M0S"
as per the format, i am assuming it should convert this into
0.14 in Quickbooks. But the conversion is different from my assumption
You can see the changed value in the Image Below:
this is the image of the data transferred to QB. It converted the duration to 0.23. What could be the formula here?
Some multiple tries with different values:
PT0H1M0S 1 converts to 0.02
PT0H10M0S 10 converts to 0.17
PT0H14M0S 14 converts to 0.23
PT0H30M0S 30 converts to 0.50
which approach will help me transfer the duration from my Web Application to Quickbooks converting it into accurate duration?
According to your edited example and the standard, it converts those inputs to decimal.
1 hour converts to 1, thus:
1 minute = 1/60 = 0.0166667 which rounds up to 0.02.
10 minutes = 10/60 = 0.166667 which rounds up to 0.17.
14 minutes = 14/60 = 0.23333 which rounds up to 0.23.
30 minutes = 30/60 = 0.50.

How may i convert minutes "00:15" into hours in Rails 4

I have send the value as "00:15" in database.But its in string.So I converted into time but i'm getting error while doing so.
I used
("00:15".to_time) / 1.hours
but its giving error as
"NoMethodError: undefined method `/' for 2017-02-16 00:15:00 +0530:Time"
So i need to convert minutes into hours and update it into database so i get value as 15mins = 0.25 hrs
According to Stefan's comment I would suggest the following method:
time = "02:15" #or whatever time you want
time.to_time.hour + time.to_time.min / 60.00 #results in 2.25
The hour methods returns the hour part of a given time object, the min methods returns only the minute part of a time object.
The right part of the addition in the second line converts the minute part into the decimal part of an hour.
You can't do division on a string, you need to convert it to an integer before doing the calculation. Dividing by a float automatically converts it to a float so you get the decimal places; if you divided by 60 you would get 0
("00:15".to_time.strftime('%M').to_i / 60.00)

Rails: What is the correct way of saving the time of a video?

There is a time span for every video. For example, for every youtube video, the time it last may be 4minutes or 30s.
The problem is how shold I save it into database?
Just store it as a string like 4:30? Or change it to seconds and store it as an integer? Then how can I change it back to minute:second format?
An additonal note:
Now I'm making a video congrugator, so the time actually come in as seconds: 30, or just in plain format like 4:30, then how can I turn the latter format into seconds integer?
The way of keeping time depends of you business logic.
I would prefer to keep length in seconds, and then convert to minutes, hours and so on.
You can easily do it by simple devision:
minutes = (seconds / 60)
seconds_left = (seconds % 40)
human_time = "#{minutes}:#{seconds_left}"
You can use the Time class to parse your time string.
require 'date' # Only needed outside of rails!
require 'active_support/all' # Only needed outside of rails!
video_length = "04:30"
# This ensures the format of video_length is HH:MM:SS
def hours_minutes_seconds(str)
parts = str.split(':').reverse
len = parts.length
if ! parts.length || parts.length > 3
raise("bad input (#{str}) for hours_minutes_seconds") and return
end
(0..2).map do |i|
part = parts[len -i]
part || "00"
end.join(':')
end
#Remember how unix time starts at 1 Jan 1970?
time = DateTime.iso8601( "1970-01-01T#{hours_minutes_seconds(video_length)}Z" )
# Lets print it out!
puts time.utc.strftime("%H:%M:%S") # 00:04:30
You could then store the value as the number of seconds [integer]. Getting a time object back from the length in seconds is easy.
puts Time.at(seconds).utc.strftime("%H:%M:%S") # 00:04:30

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