DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

Open Source Adventures: Episode 14: Timecalc

So it turns out someone already created Evil Wordle, using the exact same algorithm I wanted to use. Well, it's a fairly obvious idea.

Before I pick up a new project, I want to do a few episodes about random things I created before, starting with a very small thing - Timecalc.

The problem

Some things like haircuts or cleaning up kitty boxes need to be done over and over, every N weeks or so. I don't want to put such tasks as a recurring events in the calendar, because if I do it late, I want it to start the new count down from when it was actually done, not from when it was supposed to be done.

So when I do something like that, I need a quick way to know what date is today + N days, weeks, or months.

This can be done in Ruby without that much difficulty, but it's still not perfect:

$ ruby -rdate -e 'puts + 7 * 4'
Enter fullscreen mode Exit fullscreen mode

But I thought that maybe it would be more useful to create a custom command for it.

Timecalc command

The command works like this:

$ timecalc '10.days'
$ timecalc '4.weeks'
$ ./bin/timecalc '4.weeks + 2.days'
$ ./bin/timecalc 'week - day'
Enter fullscreen mode Exit fullscreen mode

The string passed to timecalc is just arbitrary Ruby code with some DSL preloaded, and automatically prints out the value. If the value is a Duration, it's added to before printing.


The binary doesn't do anything special, it doesn't even support --help or such. It just calls the library.

#!/usr/bin/env ruby

require_relative "../lib/timecalc"

ARGV.each do |expr|
Enter fullscreen mode Exit fullscreen mode

By the way, I don't love any of the getopt-like libraries, as they tend to be overcomplicated and unflexible. optimist is OK.


The library just loads Date and ActiveSupport, and then has some logic how to format the result. As you can see I thought about making it working with hours, minutes, and seconds as well, but I never actually used that functionality:

require "date"
gem "activesupport", ">=5"
require "active_support/core_ext/numeric/time"
require "active_support/core_ext/time/calculations"
require "active_support/core_ext/date/calculations"

class Timecalc
  def initialize(
    @today =

  attr_reader :today

    second seconds
    minute minutes
    hour hours
    day days
    week weeks
  ].each do |unit|
    define_method(unit) { 1.send(unit) }

  def call(expr)
    format_output eval(expr)

  def format_output(result)
    case result
    when ActiveSupport::Duration
      (@today + result).to_s
Enter fullscreen mode Exit fullscreen mode


Here are specs, for library only:

describe Timecalc do
  examples = {
    "today" => "2019-07-20",
    "today + 3.days" => "2019-07-23",
    "today + week" => "2019-07-27",
    "today + 1.week" => "2019-07-27",
    "7.days" => "2019-07-27",
    "" => "2019-07-27",

  examples.each do |expr, expected_output|
    describe expr do
      let(:today) { Date.parse("2019-07-20") }
      let(:timecalc) { }
      let(:output) { }
      it do
        expect(output).to eq expected_output
Enter fullscreen mode Exit fullscreen mode

Was it successful?

Timecalc was a case of me thinking this use case might be big enough, and worth creating a fancy tool for it (something like Unix units command), then prototyping a super simple version, and discovering that the prototype does everything I want, and I never needed anything fancier.

And in retrospect, even that is only a modest improvement over what Ruby one-liners like ruby -rdate -e 'puts + 7 * 4', so I didn't even mention timecalc until now.

I still use timecalc occasionally, so in the end it sort of worked out. And it was definitely worth doing a quick prototype before starting with parsers etc.

If you're interested, you can get the code here.

Coming next

In the next few episodes I want to showcase some of my other tiny projects, and how they went.

Top comments (0)