HealthSamurai/unit-map

Year-month interval logic

Closed this issue · 5 comments

KGOH commented

#22 Should intervals which are greater than 4 years use leap years or every year should be 365 days

When you add one or more years to date you except to receive same date with increased year. Same for months. No matter have year leap day or not.
Adding and subtracting days, hours, minutes, seconds and seconds fractures have different logic.
According this in sql92 implemented with two different types: year-month and day-second.
If we want have intervals that includes all values from year to seconds, I think there is only one way - use astronomical year length with ~365.25, but real problems will start when we we want know, how much days in month. 365.25/12?

Originally posted by @Flawless in https://github.com/HealthSamurai/chrono/issues/22#issuecomment-639090550

I think in some cases this can be done easy without caring about days at all:
For example:

(ch/+ {:type date, :year 2020}
      {:type interval, :year 10})
;; => {:type date, :year 2030}

The problem may arise when the result type should be an interval which we have to normalize

Originally posted by @KGOH in https://github.com/HealthSamurai/chrono/issues/22#issuecomment-639333787

postgres=# SELECT date_part('epoch', INTERVAL '1 year') / (60*60*24) days;
  days
--------
 365.25
(1 row)

Originally posted by @Flawless in https://github.com/HealthSamurai/chrono/issues/22#issuecomment-639355634

And what to do when in the result of date+interval we have a value with fractional :year value? Coerce to int with ceiling?

Originally posted by @KGOH in https://github.com/HealthSamurai/chrono/issues/22#issuecomment-639358041

I can't find now about psql math, but it seems under the hood it combines year-month and day-seconds intervals:

postgres=# SELECT INTERVAL '0.3 year';
 interval
----------
 3 mons
(1 row)

postgres=# SELECT INTERVAL '1.33123123125195721 year';
   interval
---------------
 1 year 3 mons
(1 row)

postgres=# SELECT INTERVAL '1.33123123125195721 year 1 hour';
        interval
------------------------
 1 year 3 mons 01:00:00
(1 row)

There is no clear way to translate years to seconds or vice versa, so for most situations we may do like in psql and for exceptions - user may manually take epoch from interval and add or subtract it from date. I think most time people except round dates like when calculating age or guarantee period.
Also normalizing dates and intervals can't have same logic, cause we know when calendar started, but can't suggest it about intervals.

Originally posted by @Flawless in https://github.com/HealthSamurai/chrono/issues/22#issuecomment-639377863

KGOH commented

Maybe we can add restrictions like in postgres. Maybe we shouldn't

The interval type has an additional option, which is to restrict the set of stored fields by writing one of these phrases:

YEAR
MONTH
DAY
HOUR
MINUTE
SECOND
YEAR TO MONTH
DAY TO HOUR
DAY TO MINUTE
DAY TO SECOND
HOUR TO MINUTE
HOUR TO SECOND
MINUTE TO SECOND

https://www.postgresql.org/docs/current/datatype-datetime.html

KGOH commented

And maybe we can make some extensible logic for intervals, so user can define own normalization rules

KGOH commented

Or if #22 is implemented we can make complex types like {:type [:interval :day-sec]}

7even commented

From what we already discussed it's pretty obvious that we can't [de]normalize between seconds/minutes/hours/days and months/years (since days cannot be transformed into months inside the interval).

So, intervals break down into 2 different categories:

  1. sec/min/hour/day interval
  2. month/year interval

A {:day 60} interval stays in the first type; a {:month 0.5} interval - if it was supported by the library - would stay in the second type.

Now what about the adding/subtracting a datetime and an interval? For the first interval type current implementation seems to be correct: add seconds, minutes, hours and days, then normalize, possibly increasing month and year.

For the month/year interval we can just increase the date's month and year without denormalizing this period into days.

Now the main point: actually we can combine both types into one but apply different logic to different data fields:

(ch/plus {:year 2020 :month 6 :day 10}
         {:month 8 :day 25})
;; => {:year 2021 :month 3 :day 5}

First the day/hour/min/sec fields are applied, resulting in {:year 2020 :month 7 :day 5}; then year/month fields are added to this value.

This can be somewhat confusing to the library's users (in case they actually mix months and days inside one interval) but it allows having one type for all possible intervals while having a determined calculation algorithm.

KGOH commented

Can be implemented by user using new 91f6064...3c6b964 logic/type/arithmetic system