seconds: CLI for quick time interval calculation

Here’s a quick script I swear I’ve written before but couldn’t find. If given a string as an argument, it converts it to seconds, and if given just a series of numbers, it converts the number to a human-readable string.

I needed this today when setting update intervals for Sparkle, and it’s something I’ve run into in the past. I usually pull up a calculator, which is annoying. There’s probably a simple Unix way to do it that I’ll hear about in the comments, which is ok, I still had fun writing it.

Since this is mostly likely to be of use to programmers, I won’t bother detailing how to turn it into an executable script. You got this.

Convert strings to seconds

Arguments are numbers followed by a timespan. It works on shorthand (w = week, day = d, hours = h, minutes = m, seconds = s), but will convert most strings to this format automatically, e.g. “2 days, 3 hours, and 30 min” is the same as “2d3h”. As long as the format is a number followed by strings that start with w, d, h, m, or s, it’ll figure it out.


$ seconds 3d2h
=> 266400
$ seconds 2 days, and 3 hours
=> 183600

Convert seconds to a human-readable string


$ seconds 266400
=> 3 days, 2 hours

Here’s the script. Do with it what you will.

seconds.rbraw
#!/usr/bin/env ruby
# seconds, a CLI for interval conversion by Brett Terpstra 2017
# Free for use by anyone, anywhere, at any time
#
## convert days, hours, and minutes to seconds
# Arguments are numbers followed by a timespan
# (w = week day = d, hours = h, minutes = m, seconds = s)
# e.g. 2 days and 3 hours = "2d3h"
# unrecognized characters don't matter, so also "2 days, and 3 hours"
# $ seconds 2 days, and 3 hours
# => 183600
## convert seconds to days, hours, and minutes
# $ seconds 183600
# => 2 days, 3 hours

require 'bigdecimal'

def help(do_exit=false)
  app = File.basename(__FILE__)
  usage = ["#{app}: convert days, hours, and minutes to seconds"]
  usage.push("Usage: #{app} [Xw[Xd[Xh[Xm]]]] | [seconds]")
  usage.push("Examples:")
  usage.push("#{app} 5d2h3m => 439380")
  usage.push("#{app} 4d => 345600")
  usage.push("Or convert seconds to time units: #{app} 1125093 => 13 days, 31 minutes, 33 seconds")
  $stdout.puts(usage.join("\n"))
  if do_exit
    code = do_exit.to_i rescue 0
    Process.exit code
  end
end

class String
  def plural
    num, unit = self.split(/ /)
    if num.to_i > 1
      return self + 's'
    else
      return self
    end
  end
end


def from_seconds(seconds)
  t = BigDecimal.new(seconds)
  mm, ss = t.divmod(60)
  hh, mm = mm.divmod(60)
  dd, hh = hh.divmod(24)
  output = []

  output.push("#{dd.to_i} day".plural) if dd > 0
  output.push("#{hh.to_i} hour".plural) if hh > 0
  output.push("#{mm.to_i} minute".plural) if mm > 0
  output.push("#{ss.to_i} second".plural) if ss > 0
  output.join(", ")
  # "%d days, %d hours, %d minutes, %d seconds" % [dd, hh, mm, ss]
end

def to_seconds(input)
  secs = {
    'w' => 604800,
    'd' => 86400,
    'h' => 3600,
    'm' => 60,
    's' => 1
  }
  total = 0
  parts = input.gsub(/([wdhms])[a-z]+/,'\1').gsub(/[^wdhms\d]/,'').scan(/\d+\w/)
  parts.each do |p|
    num, qty = p.split(/(?=[wdhms])/)
    if secs.key? qty
      total += num.to_i * secs[qty]
    end
  end
  total
end

if STDIN.stat.size > 0
  if RUBY_VERSION.to_f > 1.9
    Encoding.default_external = Encoding::UTF_8
    Encoding.default_internal = Encoding::UTF_8
    input = STDIN.read.force_encoding('utf-8')
  else
    input = STDIN.read
  end
else
  if ARGV.length > 0
    if ARGV[0].strip.match(/^(-h|help|--help)$/)
      help(0)
    end
    input = ARGV.join('')
  else
    help(1)
  end
end

if input =~ /^\d+$/
  res = from_seconds(input)
else
  res = to_seconds(input)
end

$stdout.print(res)
Brett Terpstra

Brett is a writer and developer living in Minnesota, USA. You can follow him as ttscoff on Twitter, GitHub, and Mastodon. Keep up with this blog by subscribing in your favorite news reader.

This content is supported by readers like you.