p5-time-moment icon indicating copy to clipboard operation
p5-time-moment copied to clipboard

Cleaner API for time arithmetic

Open lammel opened this issue 11 years ago • 5 comments

While watching the progress of Time::Moment I stumbled across the updated API for time arithmetic, which seem to clutter up the very clean API.

I would suggest to introduce a Time::Moment::Period (or similar like Relative or Duration) which will be a relative time, consisting of the same components as a real datetime but with some restrictions of course. A relative time will never need

$period1 = new Time::Moment::Period(year => 4, hour => 1)
# Or with some helpers to improve usability / readability
$period2 = Time::Moment::Relative('1Y2D12H')
$period3 = Time::Moment::Relative('09:22:28')

If you plan on also supported time ranges (which would allow to identify if a specific moment is within a range) also the introduction of Time::Moment::Period (or Range) would be interesting.

Time::Moment::Period('2013-12-24P7D'); # Christmas holidays til new year
Time::Moment::Period($tm, $relative); 
Time::Moment::Period($tm, $tm);

For arithmetics this would allow a cleaner API then (no need for plus_year, minus_year).

$tm = new Time::Moment();
$relative = Time::Moment::Relative('02:03:15');

# Time arithmetics
$tm2 = $tm->add($relative);
$tm2 = $tm + $relative;
$tm2 = $tm->substract($relative);
$tm2 = $tm - $relative;

lammel avatar Dec 15 '13 14:12 lammel

You raise some very good points. I intend to provide a duration object which models time only (seconds and nanosecond), I haven't decided on the API yet but it will probably look something like this:

$duration = $tm1->minus($tm2);
$tm2      = $tm1->plus($duration);
$tm2      = $tm1->minus($duration);

$duration = $tm1 - $tm2;
$tm2      = $tm1 + $duration;
$tm2      = $tm1 - $duration;

$duration = $duration + $duration;
$duration = $duration - $duration;

$duration = Time::Moment::Duration->from_hours(10);
$duration = Time::Moment::Duration->from_minutes(10);
$duration = Time::Moment::Duration->from_seconds(10);
$duration = $duration->plus($duration);
$duration = $duration->minus($duration);
$hours    = $duration->hours;
$minutes  = $duration->minutes;
$seconds  = $duration->seconds;

# perhaps with some convenient shortcuts:
use Time::Moment::Duration qw[hours minutes seconds nanoseconds];

$duration = hours(10) + seconds(1) + nanoseconds(123456789);

say $duration; # "PT10H1S.123456789"  # ISO 8601 Duration

I don't intend to implement support for calendar periods/durations because they are tricky and the reversibility depends on the producing and consuming implementation, for example how many months and days are there beetween 2012-08-31 and 2013-09-30? one month or 30 days? This can get even more fun when the day of the month is last day of february in a leap year. [1]

There has to be an API for adding/subtracting months/years, so I added plus/minus years/months and then the plus/minus time units to be consistent.

Nothing is set in stone and still open for ideas on how to improve the API.

[1] This is an API decission, not an implementation of code since I have written code for exactly that purpose.

chansen avatar Dec 15 '13 22:12 chansen

That looks very reasonable and pretty much gets to my point. Looking forward to this.

Supported months is probably a non-trivial tasks, but the question is if all operations need to be reversible (even just adding a month is tricky, as you have to handle the 30day vs. 31day month like 2013-01-31 + 1 month = ?). But that should be another issue. This one is about the API.

I would strongly favour removing the plus_year, minus_year and similar operations (methods) from Time::Moment to keep the API clean.

I really like your approch to tackling datetimes in Perl and that you show that it can be modern, slick and fast.

lammel avatar Dec 16 '13 00:12 lammel

I agree with your reasoning about months, let's get back to the API discussion =)

One API possibility could be:

$tm2      = $tm1->plus($duration);
$tm2      = $tm1->plus($amount, $unit);
$tm2      = $tm1->plus(10 => 'years');
$tm2      = $tm1->plus(10 => 'seconds');

$duration = $tm1->minus($tm2);
$tm2      = $tm1->minus($duration);
$tm2      = $tm1->minus(10 => 'years');
$tm2      = $tm1->minus(1 => 'year');

Where the unit can be given in singular or plural form.

But I'm not convinced that the above is better that the plus_$unit/minus_$unit methods.

chansen avatar Dec 17 '13 01:12 chansen

Actually it would make sense from an API perspective, if the parameters accepted by Plus would be input parameters to the Time::Moment::Duration constructor. That would make sense and makes the use quite obvious. I would reverse the order, as I usually prefer the "units" to be the static attribute keys (they wont change) and values to be the dynamic parts. Making single and plural available makes sense and improves readability, good idea.

$tm2 = $tm1->plus($duration);
$tm2 = $tm1->plus($unit => $amount);

### The following are the same (and actually could be handled

internally in that way $tm2 = $tm1->plus(year => 1, days => 3); $tm2 = $tm1->plus(Time::Moment::Duration(year => 1, days => 3));

Even if we actually add some flexibility here, we could still keep the main Time::Moment API pretty thin, using just 2 variants of $tm->plus. If you are thinking about cleaning the API it would really make sense to do that together with the introduciton of Time::Moment::Duration (or whatever you like to call it).

Roland Lammel QuikIT - IT Lösungen - flexibel und schnell Web: http://www.quikit.at Email: [email protected]

"Enjoy your job, make lots of money, work within the law. Choose any two."

On Tue, Dec 17, 2013 at 2:09 AM, Christian Hansen [email protected]:

I agree with your reasoning about months, let's get back to the API discussion =)

One API possibility could be:

$tm2 = $tm1->plus($duration); $tm2 = $tm1->plus($amount, $unit); $tm2 = $tm1->plus(10 => 'years'); $tm2 = $tm1->plus(10 => 'seconds');

$duration = $tm1->minus($tm2); $tm2 = $tm1->minus($duration); $tm2 = $tm1->minus(10 => 'years'); $tm2 = $tm1->minus(1 => 'year');

Where the unit can be given in singular or plural form.

But I'm not convinced that the above is better that the plus_$unit/minus_$unit methods.

— Reply to this email directly or view it on GitHubhttps://github.com/chansen/p5-time-moment/issues/2#issuecomment-30717879 .

lammel avatar Dec 17 '13 08:12 lammel

Personally, I'd hate to have to create a whole separate object just to do a calculation.

A 'clean' API to me is one that is consistent and logical. Having parallel with_*, plus_*, minus_* methods makes sense because they are so parallel.

I see little benefit of plus( hours => 5 ). It's longer to type and if hours is misspelled, it's a runtime error instead of a compile time error. (Plus the method needs to validate an extra input on every call.)

I suspect it looks cluttered because they are all documented individually, whereas they could be easily grouped:

  • {plus,minus}_{years,months} — modifies with truncation to last day of month
  • {plus,minus}_{weeks,days,hours,minutes,seconds,nanoseconds} -- modifies directly

And explanation along those lines instead of listing every method individually would reduce pages of documentation down to a dozen lines or so.

I would suggest changing all the with_* method to exactly match the set of accessor names, which I think only means adding some like with_day_of_quarter, with_millisecond, etc. While not strictly necessary, having accessors and modifiers exactly parallel is a win for API clarity.

dagolden avatar Dec 17 '13 19:12 dagolden