Strftime's %C and %y formats versus wide-ranging tm_year values
Ken Pizzini
tz. at explicate.org
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