Skip to content
Snippets Groups Projects
user avatar
Timur Safin authored
Part of #6923

NO_TEST=docrequest

@TarantoolBot document
Title: Document interval arithmetic

Interval arithmetic
-------------------

If we need to shift date values by the given period of time we call either
modifier methods (i.e. `:add` or `:sub`) or apply interval arithmetic
using overloaded `+` (__add) or `-` (__sub) methods.

`:add`/`:sub` modify current object, but `__add`/`__sub` create copy of
object for operation result.

In the interval operation we sequentially calculate each interval
subcomponents
heading from largest (year) to smallest (nanosecond):
* `year` - year(s);
* `month` - month(s);
* `week` - week(s);
* `day` - day(s);
* `hour` - hour(s);
* `min` - minute(s);
* `sec` - second(s);
* `nsec` - nanosecond(s).

If results of operation exceed allowed range for any of component then
exception to be raised.

Modifier methods `:add`/`:sub` return `self` object thus it''s possible to
chain their calls together, i.e.

```
-- add 9000 years, 82 months, 5 weeks, 201 days, 183 hours, 292 minutes
-- and 191.001239234 seconds to given date
dt:add{
	year  = 9000,
	month = 82,
	week  = 5,
	day   = 201,

	sec   = 191,
	min   = 292,
	hour  = 183,

	nsec  = 1239234,
}

dt:sub{
	year  = 9000,
	month = 82,
	week  = 5,
	day   = 201,

	sec   = 191,
	min   = 292,
	hour  = 183,

	nsec  = 1239234,
}

-- chaining
dt:add{year = 2}:add{month = 2}:sub{day = 2}

-- create a copy of current date, while moving it to the next day
local dt2 = datetime.new(dt:totable()):add{day = 1}
```

Arithmetic operations
=====================

Date and interval objects may participate in arithmetic operations:
* Sum of 2 intervals is interval object, which fields would be sum of each
  particular components of operands;
* Subtraction is similar to summation above - result is interval object
  where each subcomponent would be result of subtraction of particular
  fields in original operands;
* If you add date and interval then result is a date. Addition to be
  performed in a determined order from largest component (i.e. year) to
  smallest (nanosecond);
* Subtraction of dates to produce an interval object. Difference of 2 time
  moments is performed not as difference of epoch seconds, but as
  difference of all subcomponents (i.e. years, months, days, hours,
  minutes and seconds);
* Untyped table object may be used in each context where typed date or
  interval object used if left operand is typed object with overloaded
  operation of '+' or '-'.

Matrix of *addition* operands eligibility and their result type:

|                     | datetime | interval | table    |
|---------------------|----------|----------|----------|
| **datetime**        |          | datetime | datetime |
| **interval**        | datetime | interval | interval |
| **table**           |          |          |          |

Matrix of *subtraction* operands eligibility and their result type:

|                     | datetime | interval | table    |
|---------------------|----------|----------|----------|
| **datetime**        | interval | datetime | datetime |
| **interval**        |          | interval | interval |
| **table**           |          |          |          |

Date adjustions and leap years
==============================

It''s always tricky to operate with days if we move date between months of
different lengths. The default mode is - the day number in month of
original date should become the same day but not exceed the length of
resultant month.

* 28 February of non-leap year if added +1 year should become 28 february
  of a leap year;
* Addition of year to the 29 February of a leap year would result with 28
  February of a non-leap year;

The general rule is as following: addition of months to the date should
produce (if possible) the same day number, if this number is not exceeding
number of days in resultant month. Otherwise it produces the last day in
month.

* 31 january + 1 month = 28 or 29 february;
* 30 january + 1 month = 28 or 29 february;
* 29 february + 1 month = 29 march;
* 31 march + 1 month = 30 april.

Optionally you may prefer to use "accountant" logics of operations when
month operations (addition or subtraction) should leave last day in month
as last day, i.e.:

* 31 january + 1 month = 28 or 29 february (depending on leap year);
* 29 february + 1 month = 31 march;
* 31 march + 1 month = 30 april;
* 30 april + 1 month = 31 may.

- 28 february 2001 + 1 month = 28 march 2001;
- february 2004 + 1 month = 28 марта 2004;

This adjustment option named `adjust` may be passed as additional argument
to `:add`/`:sub` methods or additional argument to interval constructor
call.

There are 3 supported `adjust` values:

* `none` last day mode is not maintained, if there is overflow then value
  is truncated to the maximum possible value in month (corresponding to
  the c-dt `DT_LIMIT`). This is default mode if not provided;

```
    -- 29.02.* -> 29.03.*
    dt:add( {month = 1, adjust = "none" } )
```

* `last` last day mode ("accountant" mode) maintained. i.e. last day in
  original month should stay last day in resultant month. This corresponds
   to `c-dt` `DT_SNAP` mode;

```
-- 28.02.2001 -> 31.03.2001 -- last day in Feb 2001
-- 28.02.2004 -> 28.03.2004 -- not a last day in Feb => no abjustments
-- 29.02.2004 -> 31.03.2001 -- last day in Feb 2004
dt:add( {month = 1,  adjust = "last" } )
```

* `excess` overflow to the next month performed. According to `c-dt`
  `DT_EXCESS` rules.

Stringization
=============

Intervals show each their subcomponents as negative or positive value.
Seconds and nanoseconds normalized to the human-interpretable form.

* '+1 seconds';
* '+12 hours, 10 minutes, 30 seconds';
* '-20 months, -10 weeks, -8 hours, -10 minutes, -30 seconds';
* '-5000000 years, -20 months, -10 weeks, -10 minutes, -30 seconds';
08d6f7fa
History
Name Last commit Last update