Display duration in human-readable format, such as "X hours, Y minutes"
I am using Rails 4, Ruby 2.1 with PostgreSQL.
I have a database field called duration, which is the datatype of an interval.
When retrieving data in that column, it is returned in hh: mm: ss format, for example. 1:30:00.
I am trying to find a way to show it like: 1 hour, 30 minutes
Other examples: 02:00:00 to 2:00 02:15:00 to 2:00, 15 minutes 02:01:00 to 2:00, 1 minute
Any advice is appreciated.
I would start with something like this:
def duration_of_interval_in_words(interval)
hours, minutes, seconds = interval.split(':').map(&:to_i)
[].tap do |parts|
parts << "#{hours} hour".pluralize(hours) unless hours.zero?
parts << "#{minutes} minute".pluralize(minutes) unless minutes.zero?
parts << "#{seconds} hour".pluralize(seconds) unless seconds.zero?
end.join(', ')
end
duration_of_interval_in_words('02:00:00')
# => '2 hours'
duration_of_interval_in_words('02:01:00')
# => '2 hours, 1 minute'
duration_of_interval_in_words('02:15:00')
# => '2 hours, 15 minutes'
See also ActionView :: Helpers :: DateHelper distance_of_time_in_words (and related)
eg.
0 <-> 29 secs # => less than a minute
30 secs <-> 1 min, 29 secs # => 1 minute
1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes
... etc
https://apidock.com/rails/ActionView/Helpers/DateHelper/distance_of_time_in_words
Perhaps there is no need to include model validation in the error? (this is my use case)
You can try the following method for display, for example:
- minutes_to_human (45) # 45 minutes
- minutes_to_human (120) # 2 hours
- minutes_to_human (75) # 2.5 hours
- minutes_to_human (75) # 1.15 hours
def minutes_to_human(minutes)
result = {}
hours = minutes / 60
result[:hours] = hours if hours.positive?
result[:minutes] = ((minutes * 60) - (hours * 60 * 60)) / 60 if minutes % 60 != 0
result[:minutes] /= 60.0 if result.key?(:hours) && result.key?(:minutes)
return I18n.t('helper.minutes_to_human.hours_minutes', time: (result[:hours] + result[:minutes]).round(2)) if result.key?(:hours) && result.key?(:minutes)
return I18n.t('helper.minutes_to_human.hours', count: result[:hours]) if result.key?(:hours)
return I18n.t('helper.minutes_to_human.minutes', count: result[:minutes].round) if result.key?(:minutes)
''
end
Translations:
en:
helper:
minutes_to_human:
minutes:
zero: '%{count} minute'
one: '%{count} minute'
other: '%{count} minutes'
hours:
one: '%{count} hour'
other: '%{count} hours'
hours_minutes: '%{time} hours'
Just use duration
+inspect
seconds = 86400 + 3600 + 15
ActiveSupport::Duration.build(seconds).inspect
=> "1 day, 1 hour, and 15.0 seconds"
Or it can be customized a little
ActiveSupport::Duration.build(seconds).parts.map do |key, value|
[value.to_i, key].join
end.join(' ')
=> "1days 1hours 15seconds"
PS
You can get seconds with
1.day.to_i
=> 86400
Time can only be analyzed in ISO8601 format
ActiveSupport::Duration.parse("PT2H15M").inspect
=> "2 hours and 15 minutes"
I find duration.inspect serves the purpose well.
> 10.years.inspect
=> "10 years"