Subject: Re: bug#12318: gnu date has incorrect date when using date math during a leap year

From: Bob Proulx <[email-masked]>
Date: Fri, 31 Aug 2012 11:59:52 -0600

John Mizell wrote:
> gnu date has incorrect date when using date math during a leap year

Thank you for the report.  But this appears to be incorrect usage.

> Here are the steps to reproduce

Thank you very much for providing your reproducing steps.  It makes
diagnosis easy.  So many people do not include enough information.

> [jmizell@backdoor ~]$ date "+%m%Y" --date='1 month ago'
> 072012

Two problems with the reproducer.  One is that you don't tell us what
timezone you are in.  Dates are very timezone dependent.  Secondly
+%m%Y does not give enough information.  I recommend -R, --rfc-2822
which will produce a standard and unambiguous result.

  $ TZ=US/Mountain date -R --date='1 month ago'
  Tue, 31 Jul 2012 11:30:38 -0600

> [jmizell@backdoor ~]$ date "+%m%Y" --date='5 month ago'
> 032012

  $ TZ=US/Mountain date -R --date='5 month ago'
  Sat, 31 Mar 2012 11:31:11 -0600

> [jmizell@backdoor ~]$ date "+%m%Y" --date='6 month ago'
> 032012

  $ TZ=US/Mountain date -R --date='6 month ago'
  Fri, 02 Mar 2012 10:31:34 -0700

I think at that you you can see where things have gone wrong.  The
date documentation warns of this case with this information:

     The fuzz in units can cause problems with relative items.  For
  example, `2003-07-31 -1 month' might evaluate to 2003-07-01, because
  2003-06-31 is an invalid date.  To determine the previous month more
  reliably, you can ask for the month before the 15th of the current
  month.  For example:

       $ date -R
       Thu, 31 Jul 2003 13:02:39 -0700
       $ date --date='-1 month' +'Last month was %B?'
       Last month was July?
       $ date --date="$(date +%Y-%m-15) -1 month" +'Last month was %B!'
       Last month was June!

Also the FAQ entry goes into some detail on this issue:

  http://www.gnu.org/software/coreutils/faq/coreutils-faq.html#The-date-command-is-not-working-right_002e

Months are problematic because subtracting 30 days (or even 31 days)
does not equate to a correct answer for everyone.  There is no
solution that everyone will agree is correct.  On March 1st
subtracting 30 days will yield a result of January.  As warned in the
documentation month calculations are better done at the time of the
middle of the month to avoid those issues.

At the least you should include a time of day so that the time that
math is being done isn't being done near a daylight savings time
change.

Taking that into consider and improving the usage yields:

  $ TZ=US/Mountain date -R --date='today 12:00z 6 month ago'
  Fri, 02 Mar 2012 05:00:00 -0700

  $ for i in 1 2 3 4 5 6 7 8; do date -R --date="2012-08-15 12:00 -0600 $i months ago";done
  Sun, 15 Jul 2012 12:00:00 -0600
  Fri, 15 Jun 2012 12:00:00 -0600
  Tue, 15 May 2012 12:00:00 -0600
  Sun, 15 Apr 2012 12:00:00 -0600
  Thu, 15 Mar 2012 12:00:00 -0600
  Wed, 15 Feb 2012 11:00:00 -0700
  Sun, 15 Jan 2012 11:00:00 -0700
  Thu, 15 Dec 2011 11:00:00 -0700

This way the month is correct.  We can see the effect of DST on the
result.  Doing all calculations in UTC avoids DST problems.

  $ for i in 1 2 3 4 5 6 7 8; do date -u -R --date="2012-08-15 12:00 +0000 $i months ago";done
  Sun, 15 Jul 2012 12:00:00 +0000
  Fri, 15 Jun 2012 12:00:00 +0000
  Tue, 15 May 2012 12:00:00 +0000
  Sun, 15 Apr 2012 12:00:00 +0000
  Thu, 15 Mar 2012 12:00:00 +0000
  Wed, 15 Feb 2012 12:00:00 +0000
  Sun, 15 Jan 2012 12:00:00 +0000
  Thu, 15 Dec 2011 12:00:00 +0000

Now if you are really looking to see what calendar date it would be
one month ago on the calendar then you need to supply an improved
definition of what one month means.  Is it four weeks with a week
being exactly seven days?  That can skip over months.  Is it 30 days
previously?  That can skip over months.  Is it the relative position
from the first of the month such as the first Monday?  Would
2012-07-31 minus one month be 2012-06-31?

There isn't any definition that makes sense in every case because
months are not consistent value items.  They vary in size.  For a
calculation such as this you would need to program in what you would
want with additional code that met your needs.

Hope this helps,
Bob