Subject: bug#26101: Counterproductive calculation order in date

From: Assaf Gordon <[email-masked]>
Date: Wed, 15 Mar 2017 10:40:33 -0400

> On Mar 15, 2017, at 09:43, Ulf Zibis <[email-masked]> wrote:
> 
> Am 15.03.2017 um 13:44 schrieb Eric Blake:
>> Maybe you are confused on how date implements "subtract a month".  It
>> does NOT do "subtract 28, 29, 30, or 31 days as appropriate", but rather
>> does "subtract 30 days, for lack of anything better to do".
> 
> Are you really sure ???
> Here on my 8.25 version I get:
> $ date -d "-12 month" +%F
> 2016-03-15
> $ date -d "-360 day" +%F
> 2016-03-20

To give more details about the inter working of date adjustments:

Assuming the current date is 2017-03-15:

"2017-03-15 - 12 months":
   Year  = 2017
   Month = -9 (yes, it is stored temporarily as -9)
   Day   = 15
 Then it is normalized to:
   Year  = 2016
   Month = 3
   Day   = 15

While "2017-03-15 - 360 days" =
   Year  = 2017
   Month = 3
   Day   = -345 (yes, stored as -345)
 then normalized to:
   Year  = 2016
   Month = 3
   Day   = 20

More about the normalization:

When adjusting months (e..g "-12 months),
The value of 'day' isn't touched.
It will be (unexpectedly) modified only in cases where the
month does not have that day, e.g.:
 "2017-03-30 - 1 month" becomes:
   Year = 2017
   Month = 2
   Day = 30
 then normalized to:
   Year = 2017
   Month = 3
   Day = 2
(because "2017-02-30" means "2 days after 2017-02-28" which is "2017-03-02").

When adjusting days, it is equivalent to subtracting
the number of seconds from the current unix epoch (i.e. seconds since 1970-01-01).
Example:

Current unix time:
   $ date -d '20170315' +%s
   1489536000

Number of days you want to subtract, in seconds:
   $ bc -l<<<'360*86400'
   31104000

Resulting unix time:
   $ bc -l<<<'1489536000-360*86400'
   1458432000

Which is equivalent to:
   $ date -d @1458432000 +%F
   2016-03-20

Technical note:
date adjustment (years/months/days) is done directly on the member variables
of a 'struct tm' , which is why it ignores the number of days in months.
time adjustment (hours/minutes/seconds) is done on the unix time, AFTER
the normalized date has been converted to unix time.

> So I think my list for enhancements is still fully applicable.

I'm late-comer to the thread, so I'll verify:
You'd like to be able to do date calculations in some predictable way,
in your case to be able to change a file's timestamp.

However,
consider that adjusting by any scale (years/months/days/hours/minutes) will
have side-effects.

Adjusting by months can shift days (e.g. "2016-03-30 - 1 month" is still march).
Adjusting by days can shift hours (e.g. on Day light saving time),
Adjusting by hours can stay in the same day (e.g. "+24 hours" on a when day light saving results in 25 "hours").
etc.

It is the edge-cases that make day adjustment tricky (e.g. "+1 month" is not "+30 days", "+1 day" is not always "+24 hours", etc.).

If you can come up with reliable and predictable rules for date adjustment that will not have these unexpected results - there's definitely room for improvements.
However, there's also the existing behavior and backwards compatibility that will need to be taken into account.

regards,
 -assaf