Low commit activity in last 3 years
Parse human strings and ISO 8601 durations, perform arithmetic and comparison, and output to human-readable or ISO 8601 formats.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

philiprehberger-duration

Tests Gem Version Last updated

Immutable Duration value object with parsing, arithmetic, and formatting

Requirements

  • Ruby >= 3.1

Installation

Add to your Gemfile:

gem "philiprehberger-duration"

Or install directly:

gem install philiprehberger-duration

Usage

require "philiprehberger/duration"

d = Philiprehberger::Duration.parse("2h 30m")
d.to_seconds   # => 9000.0
d.to_human     # => "2 hours, 30 minutes"
d.to_iso8601   # => "PT2H30M"

Parsing

Duration = Philiprehberger::Duration

Duration.parse("2 weeks 3 days")  # human string
Duration.parse("1 day 3 hours")   # human string
Duration.parse("PT2H30M")         # ISO 8601
Duration.parse("P2W")             # ISO 8601 weeks
Duration.parse(3600)              # numeric seconds

Use Duration.parse? for a non-raising variant that returns nil on invalid input:

Duration.parse?("2h 30m")  # => Duration("2 hours, 30 minutes")
Duration.parse?("xyz")     # => nil
Duration.parse?("")        # => nil
Duration.parse?(nil)       # => nil

Arithmetic

d1 = Duration.parse("2h")
d2 = Duration.parse("30m")

d1 + d2   # => Duration("2 hours, 30 minutes")
d1 - d2   # => Duration("1 hour, 30 minutes")
d2 * 3    # => Duration("1 hour, 30 minutes")
d1 / 2    # => Duration("1 hour")

Comparison

Duration.parse("2h") > Duration.parse("1h")   # => true
Duration.parse("60m") == Duration.parse("1h")  # => true

Short Format

Duration.parse("2h 30m").to_short      # => "2h 30m"
Duration.from_hash(weeks: 1).to_short  # => "1w"
Duration.zero.to_short                  # => "0s"

Between Two Times

Duration.between(start_time, end_time).to_human  # => "3 hours, 15 minutes"

Time Arithmetic

d = Duration.parse("2h 30m")

d.from_now          # => Time.now + 9000 seconds
d.ago               # => Time.now - 9000 seconds
d.since(start_time) # => start_time + 9000 seconds
d.before(deadline)  # => deadline - 9000 seconds

Component Accessors

d = Philiprehberger::Duration.parse("2 weeks 1 day 2 hours 30 minutes 45 seconds")
d.weeks    # => 2
d.days     # => 1
d.hours    # => 2
d.minutes  # => 30
d.seconds  # => 45
d.to_hash  # => { weeks: 2, days: 1, hours: 2, minutes: 30, seconds: 45 }

Rounding

d = Philiprehberger::Duration.parse("1h 45m")
d.round(:hour).to_human  # => "2 hours"

Total Conversion

d = Philiprehberger::Duration.parse("2h 30m")
d.to_minutes  # => 150.0
d.to_hours    # => 2.5
d.to_days     # => 0.104166...
d.to_weeks    # => 0.014880...
d.to_i        # => 9000
d.to_f        # => 9000.0

Constructing from Components

d = Philiprehberger::Duration.from_hash(weeks: 1, days: 2, hours: 3)
d.to_human  # => "1 week, 2 days, 3 hours"

Custom Formatting

d = Philiprehberger::Duration.parse("1 day 2h 3m 4s")
d.format("%D days %T")  # => "1 days 02:03:04"
d.format("%H:%M:%S")    # => "02:03:04"
Philiprehberger::Duration.zero.zero?  # => true

API

Method Description
Duration.parse(input) Parse human string, ISO 8601, or numeric seconds
Duration.parse?(input) Non-raising variant of parse — returns nil on nil, empty, or invalid input
Duration.from_hash(**components) Construct from named components (weeks, days, hours, minutes, seconds)
Duration.between(time_a, time_b) Duration between two Time objects
Duration.zero Zero-length duration
#zero? Whether the duration is zero
#format(pattern) strftime-style formatter (%W %D %H %M %S %T %s %%)
#to_seconds Total seconds as float
#to_minutes Total minutes as float
#to_hours Total hours as float
#to_days Total days as float
#to_weeks Total weeks as float
#to_i Total seconds as integer
#to_f Total seconds as float
#to_human Human-readable string
#to_iso8601 ISO 8601 formatted string
#weeks Extracted week component
#days Extracted day component (0-6)
#hours Extracted hour component (0-23)
#minutes Extracted minute component (0-59)
#seconds Extracted second component (0-59)
#to_hash Components as { weeks:, days:, hours:, minutes:, seconds: }
#from_now Time.now + to_seconds
#ago Time.now - to_seconds
#since(time) time + to_seconds
#before(time) time - to_seconds
#round(unit) Round to nearest :week, :day, :hour, :minute, or :second
#to_short Compact format like "2h 30m" or "1w"
#+, #-, #*, #/ Arithmetic operations
<, >, ==, <=> Comparison (via Comparable)

Development

bundle install
bundle exec rspec
bundle exec rubocop

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT