how to convert 270921sec into days + hours + minutes + sec ? (ruby) - ruby

I have a number of seconds. Let's say 270921. How can I display that number saying it is xx days, yy hours, zz minutes, ww seconds?

It can be done pretty concisely using divmod:
t = 270921
mm, ss = t.divmod(60) #=> [4515, 21]
hh, mm = mm.divmod(60) #=> [75, 15]
dd, hh = hh.divmod(24) #=> [3, 3]
puts "%d days, %d hours, %d minutes and %d seconds" % [dd, hh, mm, ss]
#=> 3 days, 3 hours, 15 minutes and 21 seconds
You could probably DRY it further by getting creative with collect, or maybe inject, but when the core logic is three lines it may be overkill.

I was hoping there would be an easier way than using divmod, but this is the most DRY and reusable way I found to do it:
def seconds_to_units(seconds)
'%d days, %d hours, %d minutes, %d seconds' %
# the .reverse lets us put the larger units first for readability
[24,60,60].reverse.inject([seconds]) {|result, unitsize|
result[0,0] = result.shift.divmod(unitsize)
result
}
end
The method is easily adjusted by changing the format string and the first inline array (ie the [24,60,60]).
Enhanced version
class TieredUnitFormatter
# if you set this, '%d' must appear as many times as there are units
attr_accessor :format_string
def initialize(unit_names=%w(days hours minutes seconds), conversion_factors=[24, 60, 60])
#unit_names = unit_names
#factors = conversion_factors
#format_string = unit_names.map {|name| "%d #{name}" }.join(', ')
# the .reverse helps us iterate more effectively
#reversed_factors = #factors.reverse
end
# e.g. seconds
def format(smallest_unit_amount)
parts = split(smallest_unit_amount)
#format_string % parts
end
def split(smallest_unit_amount)
# go from smallest to largest unit
#reversed_factors.inject([smallest_unit_amount]) {|result, unitsize|
# Remove the most significant item (left side), convert it, then
# add the 2-element array to the left side of the result.
result[0,0] = result.shift.divmod(unitsize)
result
}
end
end
Examples:
fmt = TieredUnitFormatter.new
fmt.format(270921) # => "3 days, 3 hours, 15 minutes, 21 seconds"
fmt = TieredUnitFormatter.new(%w(minutes seconds), [60])
fmt.format(5454) # => "90 minutes, 54 seconds"
fmt.format_string = '%d:%d'
fmt.format(5454) # => "90:54"
Note that format_string won't let you change the order of the parts (it's always the most significant value to least). For finer grained control, you can use split and manipulate the values yourself.

Needed a break. Golfed this up:
s = 270921
dhms = [60,60,24].reduce([s]) { |m,o| m.unshift(m.shift.divmod(o)).flatten }
# => [3, 3, 15, 21]

Rails has an helper which converts distance of time in words.
You can look its implementation: distance_of_time_in_words

If you're using Rails, there is an easy way if you don't need the precision:
time_ago_in_words 270921.seconds.from_now
# => 3 days

You can use 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.
2.2.2 :062 > formatted_duration 3661
=> "1 h 1 m 1 s"

I modified the answer given by #Mike to add dynamic formatting based on the size of the result
def formatted_duration(total_seconds)
dhms = [60, 60, 24].reduce([total_seconds]) { |m,o| m.unshift(m.shift.divmod(o)).flatten }
return "%d days %d hours %d minutes %d seconds" % dhms unless dhms[0].zero?
return "%d hours %d minutes %d seconds" % dhms[1..3] unless dhms[1].zero?
return "%d minutes %d seconds" % dhms[2..3] unless dhms[2].zero?
"%d seconds" % dhms[3]
end

I just start writing ruby. i guess this is only for 1.9.3
def dateBeautify(t)
cute_date=Array.new
tables=[ ["day", 24*60*60], ["hour", 60*60], ["minute", 60], ["sec", 1] ]
tables.each do |unit, value|
o = t.divmod(value)
p_unit = o[0] > 1 ? unit.pluralize : unit
cute_date.push("#{o[0]} #{unit}") unless o[0] == 0
t = o[1]
end
return cute_date.join(', ')
end

Number of days = 270921/86400 (Number of seconds in day) = 3 days this is the absolute number
seconds remaining (t) = 270921 - 3*86400 = 11721
3.to_s + Time.at(t).utc.strftime(":%H:%M:%S")
Which will produce something like 3:03:15:21

Not a direct answer to the OP but it might help someone who lands here.
I had this string
"Sorry, you cannot change team leader in the last #{freeze_period_time} of a #{competition.kind}"
freeze_period_time resolved to 5 days inside irb, but inside the string, it resolved to time in seconds eg 47200, so the string became something ugly
"Sorry, you cannot change team leader in the last 47200 of a hackathon"
To fix it, I had to use .inspect on the freeze_period_time object.
So the following made it work
"Sorry, you cannot change team leader in the last #{freeze_period_time.inspect} of a #{competition.kind}"
Which returned the correct sentence
"Sorry, you cannot change team leader in the last 5 days of a hackathon"
TLDR
You might need time.inspect - https://www.geeksforgeeks.org/ruby-time-inspect-function/

Related

how to calculate relative time in ruby?

How to calculate relative_time for seconds,minutes, days, months and year in ruby?
Given a DateTime object, implement relative time
d = DateTime.now
d.relative_time # few seconds ago
Possible outputs
# few seconds ago
# few minutes ago - till 3 minutes
# x minutes ago (here x starts from 3 till 59)
# x hours ago (x starts from 1 - 24) and so on for the below outputs
# x days ago
# x weeks ago
# x months ago
# x years ago
how to implement this in ruby? please help
Use GNU Date
In Ruby's core and standard library, there are no convenience methods for the type of output you want, so you'd have to implement your own logic. Without ActiveSupport extensions, you'd be better off calling out to the GNU (not BSD) date utility. For example:
now = Time.now
last_week = %x(date -d "#{now} - 1 week")
If you pass a format flag to GNU date such as +"%s", the date command will provide the date as seconds since epoch. This allows you to create case statements for whatever units of time you want to compare, such whether it's more than 360 seconds ago or less than 86,400.
Please see the date input formats defined by GNU coreutils for a more comprehensive look at how to use the various date string options.
Using Rails Extensions
If you're willing to install and require a couple of Rails extensions in your code, you can use ActionView::Helpers::DateHelper#distance_of_time_in_words. For example:
require "active_support/core_ext/integer/time"
require "active_support/core_ext/numeric/time"
require "action_view"
include ActionView::Helpers::DateHelper
2.weeks.ago
#=> 2020-07-12 09:34:20.178526 -0400
distance_of_time_in_words Date.current, 1.month.ago
#=> "30 days"
sprintf "%s ago", distance_of_time_in_words(Time.now, 1.hour.ago)
#=> "about 1 hour ago"
Some combination of the various #ago methods coupled with date/time calculations, and calls to the distance helper will do what you want, but you'll have to implement some of your own logic if you want to cast the results differently (e.g. specifying that you want the output of large distances as weeks or months instead of in years).
Other Gems
There are certainly other Ruby gems that could help. The Ruby Toolbox shows a number of gems for determining time differences, but none seem to be well-maintained. Caveat emptor, and your mileage may vary.
See Also
ActiveSupport Core Extensions
DateAndTime::Calculations
First of all, DateTime is intended for historical dates, whereas Time is used for current dates, so you probably want the latter. (see When should you use DateTime and when should you use Time?)
Given a time instance 45 minutes ago: (45 × 60 = 2,700)
t = Time.now - 2700
You can get the difference in seconds to the current time via Time#-:
Time.now - t
#=> 2701.360482
# wait 10 seconds
Time.now - t
#=> 2711.148882
This difference can be used in a case expression along with ranges to generate the corresponding duration string:
diff = Time.now - t
case diff
when 0...60 then "few seconds ago"
when 60...180 then "few minutes ago"
when 180...3600 then "#{diff.div(60)} minutes ago"
when 3600...7200 then "1 hour ago"
when 7200...86400 then "#{diff.div(3600)} hours ago"
# ...
end
You might have to adjust the numbers, but that should get you going.
I will construct a method relative_time that has two arguments:
date_time an instance of DateTime that specifies a time in the past; and
time_unit, one of :SECONDS, :MINUTES, :HOURS, :DAYS, :WEEKS, :MONTHS or :YEARS, specifying the unit of time for which the difference in time between the current time and date_time is to be expressed.
Code
require 'date'
TIME_UNIT_TO_SECS = { SECONDS:1, MINUTES:60, HOURS:3600, DAYS:24*3600,
WEEKS: 7*24*3600 }
TIME_UNIT_LBLS = { SECONDS:"seconds", MINUTES:"minutes", HOURS:"hours",
DAYS:"days", WEEKS: "weeks", MONTHS:"months",
YEARS: "years" }
def relative_time(date_time, time_unit)
now = DateTime.now
raise ArgumentError, "'date_time' cannot be in the future" if
date_time > now
v = case time_unit
when :SECONDS, :MINUTES, :HOURS, :DAYS, :WEEKS
(now.to_time.to_i-date_time.to_time.to_i)/
TIME_UNIT_TO_SECS[time_unit]
when :MONTHS
0.step.find { |n| (date_time >> n) > now } -1
when :YEARS
0.step.find { |n| (date_time >> 12*n) > now } -1
else
raise ArgumentError, "Invalid value for 'time_unit'"
end
puts "#{v} #{TIME_UNIT_LBLS[time_unit]} ago"
end
Examples
date_time = DateTime.parse("2020-5-20")
relative_time(date_time, :SECONDS)
5870901 seconds ago
relative_time(date_time, :MINUTES)
97848 minutes ago
relative_time(date_time, :HOURS)
1630 hours ago
relative_time(date_time, :DAYS)
67 days ago
relative_time(date_time, :WEEKS)
9 weeks ago
relative_time(date_time, :MONTHS)
2 months ago
relative_time(date_time, :YEARS)
0 years ago
Explanation
If time_unit equals :SECONDS, :MINUTES, :HOURS, :DAYS or :WEEKS I simply compute the number of seconds elapsed between date_time and the current time, and divide that by the number of seconds per the given unit of time. For example, if time_unit equals :DAYS the elapsed time in seconds is divided by 24*3600, as there are that many seconds per day.
If time_unit equals :MONTHS, I use the method Date#>> (which is inherited by DateTime) to determine the number of months that elapse from date_time until a time is reached that is after the current time, then subtract 1.
The calculation is similar if time_unit equals :YEARS: determine the number of years that elapse from date_time until a time is reached that is after the current time, then subtract 1.
One could require the user to enter a Time instance (rather than a DateTime instance) as the first argument. That would not simplify the method, however, as the Time instance would have to be converted to a DateTime instance when time_unit equals :MONTH or :YEAR, to use the method Date#>>.
The following function uses the power of Date#<< to represent past dates relative to today.
def natural_past_date(date)
date_today = Date.today
date_then = Date.parse(date)
if (date_today << 12) >= date_then
dy = (1..100).detect { (date_today << (_1.next * 12)) < date_then }
"#{dy} year#{dy > 1 ? 's' : ''} ago"
elsif (date_today << 1) >= date_then
dm = (1..12).detect { (date_today << _1.next) < date_then }
"#{dm} month#{dm > 1 ? 's' : ''} ago"
elsif (dw = (dd = (date_today - date_then).to_i)/7) > 0
"#{dw} week#{dw > 1 ? 's' : ''} ago"
else
if (2...7) === dd
"#{dd} days ago"
elsif (1...2) === dd
'yesterday'
else
'today'
end
end
end
Some sample usages of the function:
Date.today
=> #<Date: 2022-03-16 ...
natural_past_date '2020/01/09'
=> "2 years ago"
natural_past_date '2021/09/09'
=> "6 months ago"
natural_past_date '2022/03/08'
=> "1 week ago"
natural_past_date '2022/03/14'
=> "2 days ago"
natural_past_date '2022/03/15'
=> "yesterday"
natural_past_date '2022/03/16'
=> "today"

Adding together string-based minutes and seconds values

I have a a Track model that has a duration attribute. The attribute is string based, and reads in minutes:seconds format. I was wondering what the best way would be to take these string-based values and add them together. For example, if there are duration values like this:
Duration 1: "1:00"
Duration 2: "1:30"
how could I get it to output "2:30"?
Most of the questions I found related to this issue start with an integer based value. What's the best way to get this done?
My suggestion is to store/manipulate them as seconds.
It's definitely easier to store them as the integer number of seconds, and apply a function to parse/format the value into the proper string representation.
Storing them as integer will make it very easy to sum and subtract them.
Here is one way this can be done:
class Track
attr_accessor :duration
def initialize(duration)
#duration = duration
end
end
arr = [Track.new("1:00"), Track.new("1:30")]
total_seconds = arr.reduce(0) do |a, i|
min, sec = i.duration.split(":").map(&:to_i)
a + min * 60 + sec
end
p total_duration = '%d:%02d' % total_seconds.divmod(60)
#=> "2:30"
Edit: I missed #Wand's earlier answer, which is the same as mine. I'll leave mine just for the way I've organized the calculations.
arr = %w| 1:30 3:07 12:53 |
#=> ["1:30", "3:07", "12:53"]
"%d:%2d" % arr.reduce(0) do |tot,str|
m,s = str.split(':')
tot + 60*m.to_i + s.to_i
end.divmod(60)
#=> "17:30"
I just had to implement something like this in a recent project. Here is a simple start. If you are sure you will always have this format 'H:S', you will not need to convert your duration to time objects:
entries = ["01:00", "1:30", "1:45"]
hours = 0
minutes = 0
entries.each do |e|
entry_hours, entry_minutes = e.split(':', 2)
hours += entry_hours.to_i
minutes += entry_minutes.to_i
end
hours += (minutes/60)
minutes = (minutes%60)
p "#{hours}:#{minutes}"
#=> "4:15"
I agree with #SimoneCarletti: store them as an integer number of seconds. However, you could wrap them in a duration value class that can output itself as a nicely formatted string.
class Duration
attr_accessor :seconds
def initialize(string)
minutes, seconds = string.split(':', 2)
#seconds = minutes.to_i * 60 + seconds.to_i
end
def to_s
sprintf("%d:%02d", #seconds / 60, #seconds % 60)
end
def self.sum(*durations)
Duration.new(durations.reduce(0) { |t,d| t + d.seconds })
end
end
EDIT: Added a sum method similar to that suggested by #CarySwoveland below. You can use this as follows:
durations = ["1:30", "2:15"].map { |str| Duration.new(str) }
total = Duration.sum *durations

How can I make the minutes variable two digits?

I'm trying to get the minutes to show two digits even when a single digit answer is assigned to the min_side variable. It's probably a simple answer but I cannot seem to get it to work.
def time_conversion(minutes)
hour = minutes/60
min_side = minutes%60
min_side = %02d
time = "#{hour}:#{min_side}"
return time
end
puts time_conversion(360)
You can use sprintf:
def time_conversion(minutes)
hour = minutes / 60
min_side = minutes % 60
sprintf("%d:%02d", hour, min_side)
end
you can use rjust to add zeros
minutes.to_s.rjust(2, '0')
You could do it like so:
def time_conversion(minutes)
"%d:%02d" % minutes.divmod(60)
end
puts time_conversion(360)
#=> "6:00"
def time_conversion(minutes)
hour = minutes/60
min_side = minutes%60
"#{hour}:#{min_side < 10 ? "0#{min_side}" : min_side}"
end
By the way, the latter part of the last line is not a comment. It's a nested string interpolation that a code formatting here cannot recognize
Low readability, in-place perl'ish way :)
time = "#{hour}:#{"0#{min_side}"[-2,2]}"
Adds zero and gets last two chars of the result.

Ruby programming double a penny a day for period of time

I am new, three wekks into Ruby as my first Language. here is the code I already have:
=begin
this program is to calculate doubling a penny a day for thirty one days
=end
puts "how many days would you like to calculate"
days = gets.chomp.to_i
i = 1
loop do
puts "#{i}"
break i >=100
end
I have tried to use ** as this is they syntax for exponential use. I have considered an until loop also, but the thing I am having most difficulty with is how to double per day each integer for given time.
I have also tried "#{i**2}" , "#{i**i}" , I have tried to google this problem for the past 2 days, to no avail.
It can be done using a simple bit shifting operation. Binary value "1" shifted left n times is used to calculate 2^n.
puts "how many days would you like to calculate"
days = gets.chomp.to_i
puts 1 << (days - 1)
You don't need any loop here. What about a power? If you want to double 1 penny in 31 days, you need to calculate 2^30:
puts "how many days would you like to calculate"
days = gets.chomp.to_i
puts 2 ** (days - 1)
Try:
# Display the question to the user in the terminal
puts 'How many days would you like to calculate?'
# Get the number of days from stdin
days = gets.chomp.to_i
# starting at 1 and up to the number of days start doubling. Reduce returns the result back to itself, thus doubling the return of each number until you have reached the up to limit.
result = 1.upto(days).reduce { |start| start * 2 }
# Put the result
puts result
31.times.reduce 1 do |a| a * 2 end
#=> 2147483648

How to convert amount of time into seconds?

Say it took someone 3 minutes and 45 seconds to complete a task.
I'd represent that as 3:45.
But what I need to do is, assuming I'm given 3:45, convert that to the number of seconds it took.
So when given 3:45, I want to convert that to 225.
This would need to work with Ruby 1.8.7.
You could use something like Dave suggested, or if you need more stuff, there's a duration library that does this stuff.
It would look like:
001:0> ChronicDuration.parse("3:45")
225
I'd be careful about reinventing the wheel here. While you may assume you'll have only minutes and seconds, and that you'll always have the same format, it's safer to have something more robust.
Check out chronic_duration, a gem for parsing elapsed time.
def time_to_seconds(str)
time_in = []
time_in = str.split(":").reverse
asec = 0
secs = [1, 60, 60*60, 60*60*24]
time_in.size.times do {|i|
asec += secs[i].to_i * time_in[i].to_i
end
sec
end
class String
def to_secs
split(':').reverse.zip([1, 60, 60*60]).inject(0) { |m, e| m += e[0].to_i * e[1] }
end
end
puts '3:45'.to_secs # 225
puts '1:03:45'.to_secs # 3825
pry(main)> a = "3:45".split(":")
=> ["3", "45"]
pry(main)> a[0].to_i*60 + a[1].to_i
=> 225
(Wrapped up in a method, of course.)

Resources