Module: Heatwave::Duration
- Defined in:
- app/lib/heatwave/duration.rb
Overview
Human-readable duration formatting and parsing.
Vendored, modernized port of the chronic_duration gem (MIT licensed).
The gem's published RubyGems build (0.10.6) was 12 years stale, so it
was dropped in favour of this in-repo module. The natural-language
number support (the numerizer dependency — "two hours" → "2 hours")
is intentionally not ported: Duration.parse still handles digit, colon
(3:41:59) and unit-suffixed (1h 30m) forms, which is everything
Heatwave feeds it.
Constant Summary collapse
- HOURS_PER_DAY =
Hours in one day, for rolling hours up into days.
24- DAYS_PER_WEEK =
Days in one week, for rolling days up into weeks.
7- SECONDS_PER =
Seconds in one of each named unit. Months are a flat 30 days and
years 365.25 days, matching the original gem. { seconds: 1, minutes: 60, hours: 3600, days: 3600 * HOURS_PER_DAY, weeks: 3600 * HOURS_PER_DAY * DAYS_PER_WEEK, months: 3600 * HOURS_PER_DAY * 30, years: 31_557_600 }.freeze
- UNIT_ORDER =
Units from largest to smallest — the order they appear in output.
%i[years months weeks days hours minutes seconds].freeze
- FORMATS =
Per-format unit suffixes.
pluralizeappends an "s" to any unit
whose count is not exactly 1;joineroverrides the default space. { micro: { suffixes: { years: 'y', months: 'mo', weeks: 'w', days: 'd', hours: 'h', minutes: 'm', seconds: 's' }, joiner: '' }, short: { suffixes: { years: 'y', months: 'mo', weeks: 'w', days: 'd', hours: 'h', minutes: 'm', seconds: 's' } }, default: { suffixes: { years: ' yr', months: ' mo', weeks: ' wk', days: ' day', hours: ' hr', minutes: ' min', seconds: ' sec' }, pluralize: true }, long: { suffixes: { years: ' year', months: ' month', weeks: ' week', days: ' day', hours: ' hour', minutes: ' minute', seconds: ' second' }, pluralize: true } }.freeze
- MAPPINGS =
Maps every recognized word/abbreviation to its canonical unit name.
{ 'seconds' => 'seconds', 'second' => 'seconds', 'secs' => 'seconds', 'sec' => 'seconds', 's' => 'seconds', 'minutes' => 'minutes', 'minute' => 'minutes', 'mins' => 'minutes', 'min' => 'minutes', 'm' => 'minutes', 'hours' => 'hours', 'hour' => 'hours', 'hrs' => 'hours', 'hr' => 'hours', 'h' => 'hours', 'days' => 'days', 'day' => 'days', 'dy' => 'days', 'd' => 'days', 'weeks' => 'weeks', 'week' => 'weeks', 'wks' => 'weeks', 'wk' => 'weeks', 'w' => 'weeks', 'months' => 'months', 'month' => 'months', 'mos' => 'months', 'mo' => 'months', 'years' => 'years', 'year' => 'years', 'yrs' => 'years', 'yr' => 'years', 'y' => 'years' }.freeze
- FLOAT_MATCHER =
Matches an integer or decimal number token within a parsed string.
/[0-9]*\.?[0-9]+/
Class Method Summary collapse
-
.humanize(seconds, format: :default, units: nil, limit_to_hours: false, joiner: nil, weeks: false, keep_zero: false) ⇒ String?
Formats a number of seconds into a readable elapsed-time string.
-
.parse(string, keep_zero: false, default_unit: 'seconds') ⇒ Integer, ...
Parses a string representation of elapsed time into seconds.
Class Method Details
.humanize(seconds, format: :default, units: nil, limit_to_hours: false, joiner: nil, weeks: false, keep_zero: false) ⇒ String?
Formats a number of seconds into a readable elapsed-time string.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'app/lib/heatwave/duration.rb', line 77 def humanize(seconds, format: :default, units: nil, limit_to_hours: false, joiner: nil, weeks: false, keep_zero: false) return nil if seconds.nil? int = seconds.to_i seconds = int if (seconds - int).zero? # collapse a trailing ".0" decimal_places = seconds.is_a?(Float) ? seconds.to_s.split('.').last.length : nil counts = split_units(seconds, limit_to_hours: limit_to_hours, weeks: weeks) spec = FORMATS.fetch(format, FORMATS[:default]) joiner ||= spec[:joiner] || ' ' parts = UNIT_ORDER.filter_map do |unit| next if unit == :weeks && !weeks count = counts[unit] count = "%.#{decimal_places}f" % count if decimal_places && unit == :seconds && count.is_a?(Float) component(count, spec[:suffixes][unit], spec[:pluralize], keep_zero && unit == :seconds) end parts = parts.first(units) if units result = parts.join(joiner) result.empty? ? nil : result end |
.parse(string, keep_zero: false, default_unit: 'seconds') ⇒ Integer, ...
Parses a string representation of elapsed time into seconds.
Accepts digit + unit forms ("1h 30m", "45 minutes"), colon forms
("3:41:59" → hⓂ️s) and bare numbers (interpreted as default_unit).
111 112 113 114 |
# File 'app/lib/heatwave/duration.rb', line 111 def parse(string, keep_zero: false, default_unit: 'seconds') result = sum_words(normalize(string.to_s), default_unit: default_unit) result.zero? && !keep_zero ? nil : result end |