Strftime's %C and %y formats versus wide-ranging tm_year values

Ken Pizzini tz. at
Sat Oct 9 21:12:22 UTC 2004

On Wed, Oct 06, 2004 at 05:36:07PM -0700, Ken Pizzini wrote:
> For the most part, I like it.  Two things bother me a little though:
>   1) I think it's a shame that isleap_sum() needs the compiler to
>      do a % more than isleap() does, but I certainly don't see any
>      clean way of getting around this.  (There are ways, but the
>      cures I'm coming up with are all worse than the "disease".)
>      Not a big deal, but I felt a need to mention it in case someone
>      else does have a bright idea.

Well, one can easily quibble over whether the patch that I'm attaching
to this message uses a "clean" solution to this minor problem, but I
find that the more I look at it, the fonder I am, so I'll offer it
for general consideration.  Note that I also threw in some officialese
for good measure, which can be considered independently of the
the proposed isleap_sum() redefinintion.

Thanks to Paul Eggert, who pointed out a technical flaw with an
earlier incarnation of this expression.

		--Ken Pizzini
-------------- next part --------------
--- caldefs.h-orig	2004-10-09 12:32:32.851897696 -0700
+++ caldefs.h	2004-10-09 13:33:18.500674840 -0700
@@ -51,22 +51,77 @@
 #define TM_YEAR_BASE	1900
 #endif /* !defined TM_YEAR_BASE */
-#ifndef isleap
-#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
-#endif /* !defined isleap */
 #ifndef isleap_sum
-** Since everything in isleap is modulo 400 (or a factor of 400), we know that
-**	isleap(y) == isleap(y % 400)
-** and so
-**	isleap(a + b) == isleap((a + b) % 400)
-** or
-**	isleap(a + b) == isleap(a % 400 + b % 400)
-** This is true even if % means modulo rather than Fortran remainder
-** (which is allowed by C89 but not C99).
-** We use this to avoid addition overflow problems.
+** Predicate determining if the year (a+b) is a leap-year on the
+** proleptic Gregorian calendar.  We pass in seperate arguments a
+** and b instead of a single y=a+b value in order to avoid potential
+** overflow problems with the addition.
+** The leap year rule used by the calendar that the C time API
+** implements was defined in Pope Gregory XIII's Papal Bull of February
+** 24, 1582.  The clause most relevant to our task is:
+**   9. Deinde, ne in posterum a XII kalendas aprilis aequinoctium
+**   recedat, statuimus bissextum quarto quoque anno (uti mos est)
+**   continuari debere, praeterquam in centesimis annis; qui, quamvis
+**   bissextiles antea semper fuerint, qualem etiam esse volumus
+**   annum MDC, post eum tamen qui deinceps consequentur centesimi
+**   non omnes bissextiles sint, sed in quadringentis quibusque annis
+**   primi quique tres centesimi sine bissexto transigantur, quartus
+**   vero quisque centesimus bissextilis sit, ita ut annus MDCC,
+**   MDCCC, MDCCCC bissextiles non sint. Anno vero MM, more consueto
+**   dies bissextus intercaletur, februario dies XXIX continente,
+**   idemque ordo intermittendi intercalandique bissextum diem in
+**   quadringentis quibusque annis perpetuo conservetur.
+** One reasonable translation into modern-day English is:
+**   9. Then, lest the equinox recede from XII calends April
+**   [March 21st] in the future, we establish that a bissextile
+**   [intercalation day] shall be inserted every four years (as with
+**   the present custom), except in centennial years. Those, although
+**   they were always bissextile until now, will not be from now on,
+**   although we decree that year 1600 is still bissextile; after
+**   that, however, those centennial years that follow are not all
+**   bissextile, but the first year is, and then three centennial
+**   years have no bissextile [intercallation] performed, and, then,
+**   the fourth centennial year is indeed bissextile. Thus, the years
+**   1700, 1800 and 1900 will not be bissextile, and then, as with
+**   the habit with which we are now accustomed, the year 2000 will
+**   have a bissextile intercalation day, as the day February 29,
+**   and that the same order of intermittent bissextile intercalations
+**   in each four hundred year period will be preserved in perpetuity.
+** Or, using a more mathematical formulation, and interpolating for
+** what qualified as "uti mos est (as with the present custom)":
+**   A leap day is inserted in years which are divisible by 4 (with
+**   zero remainder), *except* that years whose value is exactly
+**   divisible by 100 are only leap years when the year's value is
+**   also exactly divisible by 400.
+** In order to avoid heavy use of the comparatively expensive % operator,
+** the code below makes use of the following observations:
+**   1. A year is divisible by 4 if its low-order two bits are both
+**      zero;
+**   2. If the low-order four bits of a year are all zero, then
+**      it doesn't matter if the year is divisible by 100,
+**      because lcm(16,100) is 400;
+**   3. To make the above two statments formally true in C, we need
+**      to ensure that the arguments are cast to unsigned values;
+**   4. Since we only care the values of (low-order) bits,
+**      the possibilty that the addition of the two unsigned
+**      values might "overflow" is not a problem;
+**   5. (a+b)%100 == 0 if and only if ((a%100)+(b%100))%100 == 0.
-#define isleap_sum(a, b)	isleap((a) % 400 + (b) % 400)
+#define isleap_sum(a, b) ( 0 == (3 & (unsigned int)(a)+(unsigned int)(b)) && \
+	(    0 == (0xf & (unsigned int)(a)+(unsigned int)(b)) \
+	  || 0 != ((a)%100+(b)%100)%100 ) )
 #endif /* !defined isleap_sum */
+#ifndef isleap
+#define isleap(y) isleap_sum(y, 0)
+#endif /* !defined isleap */

More information about the tz mailing list