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 Date.today + 7 * 4'
2022-04-08
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'
2022-03-21
$ timecalc '4.weeks'
2022-04-08
$ ./bin/timecalc '4.weeks + 2.days'
2022-04-10
$ ./bin/timecalc 'week - day'
2022-03-17
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 Date.today
before printing.
bin/timecalc
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|
puts Timecalc.new.call(expr)
end
By the way, I don't love any of the getopt-like libraries, as they tend to be overcomplicated and unflexible. optimist
is OK.
lib/timecalc.rb
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=Date.today)
@today = Date.today
end
attr_reader :today
%i[
second seconds
minute minutes
hour hours
day days
week weeks
].each do |unit|
define_method(unit) { 1.send(unit) }
end
def call(expr)
format_output eval(expr)
end
def format_output(result)
case result
when ActiveSupport::Duration
(@today + result).to_s
else
result.to_s
end
end
end
Specs
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",
"7.day" => "2019-07-27",
}
examples.each do |expr, expected_output|
describe expr do
let(:today) { Date.parse("2019-07-20") }
let(:timecalc) { Timecalc.new(today) }
let(:output) { timecalc.call(expr) }
it do
expect(output).to eq expected_output
end
end
end
end
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 Date.today + 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)