When I’ve started to design time_math2 gem, I’ve already had a strong set of opinions in my head, of why and how it should be done. Like:

• concise set of well-defined operators for math arithmetics, easy to comprehend and remember;
• no invasion into Ruby core classes;
• code using TimeMath should be readable, unambiguous and short.

As for me, I’ve managed to achieve this for simpe operations from the very beginning:

``````TimeMath.day.floor(Time.now) # => current midnight
TimeMath.day.advance(Time.now, 4) # => 4 days since now
#... and so on.
``````

But, it turned out, typical math arithmetics code frequently require several consequtive operations, like “2 hours and 5 minutes before current midnight”. Also, “amount of time to add to something” turned out to be useful abstraction.

And in those cases TimeMath were still much more verbose (in a bad way! verbosity is not always a drawback) than ActiveSupport’s `Time.now.midinight + 1.hour` and similar stuff.

In chase of ActiveSupport-alike expressiveness, I’ve made several false moves in previous version:

• added `TimeMath::Span` simple object, with usage like `TimeMath.day.span(4).before(Time.now)`;
• as a last resort for desperates, added optional `core_ext.rb`, now allowing `Time.now.advance_by(:day, 2)`.

Both steps were obviously false and superfluous, adding several ways of do the same with methods with different names:

``````TimeMath.day.advance(Time.now, 1)
TimeMath.day.span(1).after(Time.now)
``````

Okay, ActiveSupport tends to allow itself in all kind of “useful friendly methods”, with all of `midnight`, `beginning_of_week` and others, but we can do better! Or so I hope.

So, let me introduce you to time math operation. It is:

• chainable;
• uses exactly the same names and abstraction as atomic operations;
• behaves well in Ruby idiomatics;
• can suite as a value object;
• integrates enchantingly with other innumerous TimeMath abstractions.

Here it is:

Task: calculate 10:20 at first day of current month.

Implementations:

ActiveSupport (presumably, fix me if I’m wrong):

``````Time.now.beginning_of_month + 10.hours + 20.minutes
``````

Cons: readable, but too many core extensions and method names to remember.

Plain atomic ops of `TimeMath`:

``````TimeMath.min.advance(TimeMath.hour.advance(TimeMath.month.floor(Time.now), 10), 20)
``````

Cons: the sense can be grasped, but–it is really un-Ruby-ish, to be honest.

TimeMath chainable operations:

``````TimeMath(Time.now).floor(:month).advance(:hour, 10).advance(:min, 20).call
``````

Yes, this is still longer than ActiveSupport version, yet it is chainable, readable and introduces minimal new concepts (and no core extensions).

The more I’ve thought about this concept, the more I’ve liked it. And, at some point, I’ve understood this form is also pretty legitimate:

``````op = TimeMath().floor(:month).advance(:hour, 10).advance(:min, 20)

# What now?.. Whatever!
op.call(Time.now)
# or
op.call(Time.parse('2016-06-01'), Time.parse('2016-05-01'), Time.parse('2016-04-01'))
# or -- we have applicable operation here! Isn't it a block?..
[Time.parse('2016-06-01'), Time.parse('2016-05-01'), Time.parse('2016-04-01')].
map(&op)
``````

Do you like it? I, for one, do. (OK, I’ve designed it, but that’s not the cause, I swear!)

This is legitimate value object, which, without any additional effort, could be serialized/desirealized with YAML (and, therefore, be an argument for Sidekiq job, for example). So, you can now with an ease pass concepts like this (“calculate 10:20 at first-of-month for any argument”) wherever you want.

But, there is more.

When you design a good abstraction you know it by the fact it seduces your other abstractions in most of the library. Look, how it goes:

``````TimeMath.day
.sequence(Time.parse('2016-06-01')..Time.parse('2016-06-08'))
.to_a
# => [2016-06-01 00:00:00 +0300, 2016-06-02 00:00:00 +0300, 2016-06-03 00:00:00 +0300, 2016-06-04 00:00:00 +0300, 2016-06-05 00:00:00 +0300, 2016-06-06 00:00:00 +0300, 2016-06-07 00:00:00 +0300, 2016-06-08 00:00:00 +0300]

# ...and...
# why not?
TimeMath.day
.sequence(Time.parse('2016-06-01')..Time.parse('2016-06-08'))
# => [2016-06-01 10:00:00 +0300, 2016-06-02 10:00:00 +0300, 2016-06-03 10:00:00 +0300, 2016-06-04 10:00:00 +0300, 2016-06-05 10:00:00 +0300, 2016-06-06 10:00:00 +0300, 2016-06-07 10:00:00 +0300, 2016-06-08 10:00:00 +0300]
``````

Neat, huh?..

So, to the meat. TimeMath 0.0.5 adds the aforementioned operation object (including addition of it to sequences) and abandons any attempt to core_ext’ing Ruby classes (even opt-in ones).

• support for `Date`, previously shamefully absent;
• float/rational advances, yay for the `hour.advance(Time.now, 1/3r)`;
• arbitrary float/ceil, enjoy `hour.floor(Time.now, 3)` (floor to 3-hour mark), floats/rationals work too;
Stay tuned! I feel like I already know how to solve entire `crontab` case with only 2 more core methods in the gem ;)