© J R Stockton, ≥ 2012-04-18

Material Related to Zeller's Congruence.

Links within this site :-

Note : Elsewhere, I might still be using my own Day-of-Week code, and Easter formulae taken from The Calendar FAQ.

Versions of Zeller's Congruence

This was part of my main Zeller page. It includes some of my original Zeller Day-of-Week Algorithm material, substantially dating from before I saw the Papers, and based mainly on popular report.

The only mention of month-lengths that I can see in Zeller's papers is in 1886.I.3, and in corresponding parts of those of 1885 & 1882.

Overview

Complications in the Day-of-Week calculation are minimised by starting the day and month counts with March, thus not needing the length of February itself.

The Day-of-Week formulae appear useful for programmable calculators; but in a program for a true computer one can also use table lookup.

The resulting Day-of-Week number might need to be transformed, e.g. for compatibility with ISO 8601, where the week starts with Monday = 1.

Principle

In essence, the Congruence embodies the fact that the total number of days in excess of four weeks per month from the beginning of March up to but not including Month M is given by :-
  for March as M=0,  S := ((13*M+2) div 5) ,
  for March as M=1,  S := ((13*M+4) div 5) - 3 ,
  for March as M=3,  S := ((13*M+3) div 5) - 8 ,   which Zeller himself used.
These give, for the beginnings of the months from March to February,
  0 3 5, 8 10 13, 16 18 21, 23 26 29 days.

Zeller's method proper does not, however, calculate the total number of days explicitly.

Zeller did not, I think, state that the sums of the last decimal digits of the months are given by similar formulae, but with 3*M.

JavaScript Test

This is a basic test of Zeller-type expressions for the accumulated days in excess of four weeks per month.

Zeller's Congruence in Pascal and Delphi

The following is a direct implementation in (Borland) Pascal of Zeller's Acta Mathematica (1886) method, by SK & myself; it is configured to fit in with my dateprox.pas, where it now resides :-

function EchtZeller(const Cal : Calendar ; J, m, q : word) : byte { ISO } ;
var K, h : word ;
begin
  if m < 3 then begin Inc(m, 12) ; Dec(J) end ;
  K := J mod 100 ; J := J div 100 ;
  h := q + (m+1)*26 div 10 + K + K div 4 ;
  case Cal of
    Julian    : h := h +    5    + 6*J ; { Z had   -J; +6*J avoids negative }
    Gregorian : h := h + J div 4 + 5*J ; { Z had -2*J; +5*J avoids negative }
    else RunError(245) end ;
  { Now adjust to ISO 8601} EchtZeller := Succ((h+5) mod 7) ;
  end {EchtZeller} ;

It has been tested from AD 0001-01-01 to AD 32100-12-31, on the Julian and Gregorian calendars, in Borland Pascal 7 and Delphi 3.

Zeller's algorithms can be optimised; see the JavaScript above, which for Gregorian Day-Count gives this Pascal/Delphi (tested by programs/zel_cmjd.pas and to be developed in programs/mjd_date.pas) :-

function YMDzuCMJD(Y, M, D : longint) : longint { Gregorian } ;
  begin { Inc(Y, 7e5) ; Dec(Result, 7e5*365.2425) ;; 153 = 13 + 5*28 }
  if M<3 then begin Inc(M, 12) ; Dec(Y) end ;
  YMDzuCMJD := -678973 + D + (M*153-2) div 5 +
    Y*365 + Y div 4 - Y div 100 + Y div 400 end {YMDzuCMJD} ;

( Slightly faster with Y*1461 div 4 ? )
( Speed depends on compiler and variable types )

Other Implementations for Day-of-Week

The full quasi-Zeller formula for the day of the week (N : Sunday=0..Saturday=6) of the first of the month seems, in Pascal terms, to be equivalent to :-
  M := 1 + (Month + 9) mod 12 ; if M>10 then Dec(Year) ;
  C := Year div 100 ; D := Year mod 100 ;
  N := ((13*M-1) div 5 + D + D div 4 + C div 4 + 5*C + 1) mod 7 ; ?
  N := ((13*M-1) div 5 + D + D div 4 + C div 4 + 5*C) mod 7 ; ?

(verified; in dateprox.pas). Some implementations might be over-simplified and of limited range? Versions with -2*C might give a negative intermediate.

Ordinal Date

See in JavaScript Date Object Information.

Mike Keith's Day-of-Week Congruence

In Calculating the day of the week, Mike Keith has given, as the shortest C Day-of-Week expression (45 characters) :-

(d+=m<3?y--:y-2,23*m/9+d+4+y/4-y/100+y/400)%7 // Sunday = 0

It is not Zeller's Congruence, but apparently Mike Keith's, given in Journal of Recreational Mathematics, Vol. 22, No.4, 1990, p.280. It gives Sunday=0..Saturday=6.

Note that the values of y, d are not preserved; and that one can derive code for daycount.

In news:comp.lang.javascript, 'rh' has put that into JavaScript :-

Test one day per month in 2000-2400     Scroll the code to see the test code.

Reductions

August 2006 : Linus Flannagan has reduced it by one character :-

(d+=m<3?y--:y-2,23*m/9+d+4+y/4+y/100*25/4)%7

Mike Keith then reduced it by one more character, and produced yet another form :-

((m<3?y--:y-2)+23*m/9+4+d+y/4+y/100*25/4)%7
((m<3?4+y--:y+2)+23*m/9+d+y/4+y/100*25/4)%7

   

As Math.floor(x/y) can be replaced by ((x/y)|0) these cannot be the shortest or fastest JavaScript code.

ISO 8601 Weeks

Those give Day-of-Week as Sunday=0 to Saturday=6 (Zeller used Saturday=0); but international usage is Monday=1 to Sunday=7; reduce the argument of %7 (mod 7) by 1 and then add 1.

Day-of-Week & Day-Count for Computers

Zeller only dealt with Day-of-Week, but extension to Day-Count is easy. Day-Count is more useful, and can easily be converted to Day-of-Week :-

DoW := 1 + (DC+K) mod 7 { K depends on origin ; beware DC+K<0 } ;

Zeller, with manual evaluation in mind, made arithmetic easier by splitting the year number, but a computer has no need of this.

The Gregorian rules are exhibited more clearly below; the scheme is not new, but can be found for example in "Mapping Time" by E.G.Richards. As in Zeller's own formulae, division is integer division.

The code is implemented in JavaScript here. It uses the integer bitwise OR operator; here (a/b)|0 is equivalent to a div b in Pascal.

For dates before 0000-03-01, one can add a multiple of 400 (Gregorian), 28 (Julian), or 2800 (both) years to y and for Day-Count adjust the constant term correspondingly. Adding 14000 will safely accommodate all known terrestrial dates.

For dates before 0000-03-01, or in the far future, replace the 32-bit integer operations.

Gregorian Day-of-Week

Years have 52 weeks plus 1 or 2 days; so successive years start one day of the week later, and another day later after a leap year.

if (m<3) { m = m+12 ; y = y-1 } // just as Zeller
DoW = (2 + d + (13*m-2)\5 + y + y\4 - y\100 + y\400) % 7 } // Sun = 0
// the operator \ is integer division, div.

For positive y it cannot generate a negative (or unreasonably large) intermediate.

The replacement of part of the code line marked <20060127 saves a little time; and |0 is faster than floor .

 
   

Gregorian Day Count

Years have 365 or 366 days. The month term is 28 days bigger.

The constant is chosen to give CMJD, with base date CMJD=0 being A.D. 1858-11-17 local time.

  if (m<3) { m = m+12 ; y = y-1 } // just as Zeller
  DNo = -678973 + d + (153*m-2)/5 + 365*y + y/4 - y/100 + y/400



P4 3GHz XP sp2 IE6 : 6 seconds.

YMDzuCMJD(y, m, d) was much faster than new Date(y, m', d) in MSIE4

and is of similar speed to Date.UTC(y, m', d)


It seems that in Opera 9.21 (UK), but not FireFox and MSIE, function LongTest wrongly indicated failure because the assignments of new Date(2001, 0, K) did not always give successive dates near BST changes from 2038. Adding 6 hours fixed it.

Julian Equivalents

For the Julian Calendar, omit the terms containing 100 and 400. For Day-of-Week, omit also the initial 2 term; for Day-Count, subtract 2 from the constant term. Check those.

Day-count to Date

The reverse calculation can be done in various ways; I seek a style-matching version. See in Date and Day Count.

Lewis Carroll's Method
See also
The Calendrical Works of Rektor Chr. Zeller
Calendar Weeks, Week Number Calculation,
Date and Day Count, Date Miscellany I, Date Miscellany II; and Time Miscellany.
Home Page
Mail: no HTML
© Dr J R Stockton, near London, UK.
All Rights Reserved.
These pages are tested mainly with Firefox and W3's Tidy.
This site, , is maintained by me.
Head.