(*:Name: `Miscellaneous`Calendar *)

(*:Title: Unifiying Calendar Computations by Considering a Calendar 
          as a Mixed Radix Representation Generalized to Lists.
*)

(*:Author: Ilan Vardi *)

(*:Mathematica Version: 2.0 *)

(*:Package Version: 1.01 *)

(*:History: V1.0, Ilan Vardi
		V1.01, minor revisions, John M. Novak, Feb. 1992
*)

(*:Keywords: Calendar, Julian, Gregorian, Islamic, Digits.
*)

(*:Requirements: none. *)

(*:Warnings: A date is written as {y, m, d} where y is the year,
             m is the month, and d is the day. 

             The computations can be done either in the Gregorian,
             Julian, or Islamic calendars. The Gregorian calendar is 
             the usual calendar in use today and this calendar is 
             taken to be the default if no calendar is specified.

             Great Britain and its colonies switched from the Julian
             calendar to the Gregorian calendar in September, 1752.
             In these countries {1752, 9, 2} was followed by 
             {1752, 9, 14}. The default calendar for dates on
             or before {1752, 9, 2} is taken to be the Julian calendar.
             This requires making some adjustements to DayOfWeek, 
             DaysBetween, and DaysPlus. For example,
             DaysBetween[{1752, 9, 2}, {1752, 9, 14}] will return 1.
             
             Catholic countries switched from the Julian to the Gregorian
             calendar in October 1582, so that {1582, 10, 4} was followed
             by {1582, 10, 15}. This change to the Package can be made
             quite easily.

             The algorithm for the Julian calendar will fail for the year
             4 and earlier since the leap years from 40 B.C. to the
             year 4 did not follow a regular pattern (see Bickerman's book),
             and also the year zero does not exist (it is taken to be 
             1 B.C.). The first valid Julian date is therefore {4, 3, 1}.

             This only computes the Jewish New Year for the years 1900 
             up to 2099.

*)

(*:Source:  Ilan Vardi, Computational Recreations in Mathematica,
            Addison Wesley 1991, Chapter 3

            E.R. Berlekamp, J.H. Conway, and R.K. Guy, Winning Ways,
            Volume 2, Academic Press 1982, pages 795-800

            W.A. Schocken, The Calculated Confusion of the Calendar,
            Vantage Press, New York 1976

            E.J. Bickerman, The Chronology of the Ancient World,
            Revised Edition, Thames and Hudson, London 1980
*)

(*:Limitations: This package is meant to show how Mathematica can be
                used to give a unified treatment of a problem usually
                done using specialized hacks. This means that these 
                functions can be speeded up somewhat. For example, 
                DayOfWeek can be computed efficiently for the Gregorian
                calendar by using an algorithm of Reverend Zeller. See
                Computational Recreations in Mathematica, Problem 3.1.

                I have not yet implemented the Jewish calendar. This
                calendar is the most nontrivial of all, since it is the
                only one that keeps track of both the lunar and solar 
                periods (see Schocken's book). A Lisp program implementing 
                the Jewish calendar is given by E.M. Reingold and N. Dershowitz,
                Calendrical Calculations, Technical Report 
                UIUCCDCS-R-89-1541 (1989), Dept. of Computer Science, 
                Univ.  of Ill. at Urbana-Champaign.
*)

(*:Discussion: This package was written in order to give a unified
               treatement of the basic calendar operations. This is
               done by considering the calendar as a mixed radix 
               positional number system where the radices are lists.
               This requires a further generalization as the radix must
               actually be a tree. This is necessary since, for example,
               the numbers of days in a month depend on what year it is.
               The calendars are quite regular, so they are most 
               compactly represented as trees of functions. See 
               Chapter 3 of Computational Recreations in Mathematica
               for details.

               The Julian calendar is the simplest calendar consisting
               of the usual western calendar with leap years every 
               four years. This calendar gives a year that is slightly 
               too long. It was replaced with the Gregorian calendar
               in Catholic countries in 1582 and by Britain and its 
               colonies in 1752. It is still used to compute Greek 
               Orthodox holidays, such as Easter.

               The Gregorian calendar is the calendar presently in 
               use in the western world. It differs from the Julian
               calendar by eliminating leap years for centuries not
               divisible by 400. In other words, 1900 was not a leap 
               year, but the year 2000 will be a leap year.

               The Islamic calendar is a purely lunar calendar, and a year
               has 354 or 355 days. The months do not correspond to the 
               solar year, and migrate over the solar year following a 
               30 year cycle. The names of the months are 

               Muharram, Safar, Rabia I, Rabia II, Jumada I, Jumada II, 
               Rajab, Sha'ban, Ramadan, Shawwal, Dhu al-Qada, Dhu al-Hijah

               The functions computing Easter and the Jewish New Year
               are taken directly from Winning Ways. 
*)

BeginPackage["Miscellaneous`Calendar`"]

DayOfWeek::usage = "DayOfWeek[{y, m, d}, cal] gives the day of the
week for year y, month m, and day d in calendar cal. The default 
calendar is the usual American calendar."

DaysBetween::usage = "DaysBetween[{y1,m1, d1}, {y2,m2,d2}, cal] gives
the number of days between the dates {y1, m1, d1} and {y2, m2, d2}
in calendar cal. The default calendar is the usual American calendar."

DaysPlus::usage = "DaysPlus[{y, m, d}, n, cal] gives the date n days
after {y, m, d} in calendar cal. The default calendar is the usual
American calendar."

CalendarChange::usage = "CalendarChange[date, calendar1, calendar2]
converts a date in calendar1 to a date in calendar2."

Calendar::usage = "Calendar indicates which calendar system is being
used. Either the Gregorian, Julian, or Islamic calendars."

Gregorian::usage = "An option to DayOfWeek, DaysBetween, and DaysPlus,
and argument to CalendarChange. It indicates that the Gregorian
calendar is used. It is assumed that the date must be {1752, 9, 14} or
later."

Julian::usage = "An option to DayOfWeek, DaysBetween, and DaysPlus,
and argument to CalendarChange. It indicates that the Julian calendar
is used. This calendar is only valid starting {4, 3, 1}."

Islamic::usage = "An option to DayOfWeek, DaysBetween, and DaysPlus,
and argument to CalendarChange. It indicates that the Islamic calendar
is used. This calendar began on {622, 7, 16} Julian, in other words,
this is {1, 1, 1} in the Islamic calendar (the Hejira)."

EasterSunday::usage = "EasterSunday[year] computes the date of Easter
Sunday in the Gregorian calendar according to the Gregorian
calculation."

EasterSundayGreekOrthodox::usage = "EasterSunday[year] computes the
date of Easter Sunday according to the Greek Orthodox church. The
result is given as a Gregorian date."

JewishNewYear::usage = "JewishNewYear[y] gives the date of the Jewish
New Year occurring in Christian year y, 1900 <= y < 2100. Add 3761 to
Christian year y to get the corresponding new Jewish Year. "

Sunday::usage = "Day of Week."

Monday::usage = "Day of Week."

Tuesday::usage = "Day of Week."

Wednesday::usage = "Day of Week."

Thursday::usage = "Day of Week."

Friday::usage = "Day of Week."

Saturday::usage = "Day of Week."


Begin["`Private`"]


Options[DayOfWeek]:= {Calendar -> Gregorian}

Options[DaysBetween]:= {Calendar -> Gregorian}

Options[DayPlus]:= {Calendar -> Gregorian}

DayOfWeekNumber[date_List, opts___]:= 
Block[{calendar = Calendar /. {opts}},
       If[calendar === Calendar,
          calendar = If[OrderedQ[{date, {1752, 9, 2}}],
                        Julian, Gregorian]];
           Mod[DateToNumber[date, calendar] + DayOfWeekInit[calendar], 7]
       ]

DayOfWeek[date_List, opts___]:=
 {
  Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
  }[[
      1 + DayOfWeekNumber[date, opts]
    ]]
          

DayOfWeekInit[Gregorian] = 0

DayOfWeekInit[calendar_]:= DayOfWeekInit[calendar] = 
 Mod[3 - 
     DateToNumber[
     CalendarChange[{1990, 10, 31}, Gregorian, calendar], 
     calendar], 
     7]


DaysBetween[date1_List, date2_List, opts___]:= 0 /; date1 == date2

DaysBetween[date1_List, date2_List, opts___]:=
 DaysBetween[date2, date1, opts] /;  OrderedQ[{date2, date1}] 

DaysBetween[date1_List, date2_List, opts___]:=
 Block[{calendar = Calendar /. {opts}},
        If[calendar === Calendar, 
           If[OrderedQ[{date1, {1752, 9, 2}}] && 
              OrderedQ[{1752, 9, 14}, date2], 
              1 + DaysBetween[date1, {1752, 9, 2}, Calendar -> Julian] +
              DaysBetween[{1752, 9, 14}, date2, Calendar -> Gregorian],
              DaysBetween[date1, date2, Calendar -> 
                          If[OrderedQ[{date1, {1752, 9, 2}}], 
                             Julian, Gregorian]]
              ], 
            DateToNumber[date2, calendar] - DateToNumber[date1, calendar]
           ]
         ]

DaysPlus[date_List, n_Integer, opts___]:= 
 Block[{calendar = Calendar /. {opts}, d},
        If[calendar === Calendar, 
           If[OrderedQ[{date, {1752, 9, 2}}],
              d = DaysPlus[date, n, Calendar -> Julian];
              If[OrderedQ[{{1752, 9, 3}, d}],
                 CalendarChange[d, Julian, Gregorian],
                 d],
              d = DaysPlus[date, n, Calendar -> Gregorian];
              If[OrderedQ[{d, {1752, 9, 13}}],
                 CalendarChange[d, Gregorian, Julian],
                 d]
              ],
            NumberToDate[DateToNumber[date, calendar] + n, calendar]
           ]
        ]


CalendarChange[date_, calendar1_, calendar2_]:= 
 NumberToDate[DateToNumber[date, calendar1] + 
       CalendarChangeInit[calendar1, calendar2], calendar2]


EasterSunday[y_Integer]:= 
      Block[{paschal, golden, c, h, t},
            golden= Mod[y,19] +1;
            h = Quotient[y,100];
            c = - h + Quotient[h,4] + Quotient[8(h +11),25];
            t = DaysPlus[{y, 4, 19}, - Mod[11 golden +c, 30],
                         Calendar -> Gregorian];
            paschal = If[(t[[3]] == 19) || (t[[3]] == 18 && golden >11), 
                          t - {0,0,1} , t];
            DaysPlus[paschal, 
             7 - DayOfWeekNumber[paschal, Calendar -> Gregorian]]
           ]

EasterSundayGreekOrthodox[y_Integer]:= 
      Block[{paschal, golden, c, h, t},
            golden= Mod[y,19] +1;
            h = Quotient[y,100];
            c = 3;
            t = DaysPlus[{y, 4, 19}, - Mod[11 golden +c, 30],
                         Calendar -> Gregorian];
            paschal = If[(t[[3]] == 19) || (t[[3]] == 18 && golden >11), 
                          t - {0,0,1} , t];
            CalendarChange[DaysPlus[paschal, 
                    7 - DayOfWeekNumber[paschal, Calendar -> Julian]],
                    Julian, Gregorian]
           ]

JewishNewYear[y_] := 
            Block[{t,g,n,f,d},
                  g= Mod[12 (Mod[y,19] +1) , 19];
                  t= Quotient[y,100] -Quotient[y,400] -2  +
                     765433/ 492480 g + Mod[y,4] / 4 -
                     (313 y + 89091)/98496 ;
                  n = Floor[t]; 
                  f = t - n;
                  d = Switch[Mod[ DateToNumber[y,9,n] [[1]],7], 
                         0, n+1,
                         1, If[ f >= 23269/25920 &&  g >11 , n+1,n],
                         2, If[ f>= 1367/2160 && g > 6, n+2,n],
                         3, n+1,
                         4, n,
                         5, n+1,
                         6,n];
                   If[d < 31,{y, 9, d},{y, 10, d - 30}]
                   ] 


(* Generalization of Digits to mixed list radices *)

MyDigits[n_, list_List, path_]:= 
         Block[{md = MyDigitsInit[n, list, path]},
                If[Last[md] != 0, 
                   md,
                   MapAt[# + 1&,
                         MyDigitsInit[n-1, list, path], 
                         {-1}]
               ]]

MyDigitsInit[n_, {}, _]:= {n}

MyDigitsInit[n_, list_List, path_]:= 
   Block[{r = MyQuotient[n, First[list][path]]}, 
   Prepend[MyDigits[MyMod[n, First[list] [path]], 
                        Rest[list], Append[path, r]],
           MyQuotient[n, First[list] [path]]]]



DigitsToNumber[date_, list_, path_]:= 
 1 + date[[1]] list[[1]][path+1][[1]] + Last[date] + 
 (Plus @@  
  (Fold[Plus, 0, Take[list[[#]][path+1], date[[#]]]]& /@
        Range[2, Length[list]]))

MyDigits[n_, b_List]:= {n} /; b == {} ||  0 < n < Last[b] 

MyDigits[n_, b_List]:= 
Append[MyDigits[Quotient[n, Last[b]], Drop[b, -1]],
       Mod[n, Last[b]]]

DigitsToNumber[{n_}, b_]:= n

DigitsToNumber[digits_List, b_]:=
DigitsToNumber[Drop[digits, -1], 
               Drop[b, -1]] Last[b] + Last[digits]

(* Quotient and Mod generalized to lists *)

MyQuotient[n_Integer, list_List]:= 
  Quotient[n, First[list]] + 1 /; Length[list] == 1

MyQuotient[n_Integer, list_List]:= 
   Block[{s = First[list], q = 1}, 
          While[n > s, q++; s += list[[q]]]; q] /; Length[list] > 1

MyMod[n_Integer, list_List]:= 
   Mod[n, First[list]] /; Length[list] == 1

MyMod[n_Integer, list_List]:= 
   n - Fold[Plus, 0, Take[list, MyQuotient[n, list]-1]] /; Length[list] > 1



(* Julian calendar *)

JulianCalendar = {JulianFourYears, JulianYears, JulianMonths}

JulianFourYears[_]:= {1461}

JulianYears[_]:= {365, 365, 365, 366}

JulianMonths[path_List]:= 
{31, 28 + Quotient[path[[2]], 4], 
 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}


NumberToDate[n_, Julian]:=  
Prepend[Drop[#, 2], DigitsToNumber[Take[# - 1, 2], {4}] + 1]& @
        MyDigits[n, JulianCalendar, {}]

DateToNumber[date_, Julian]:= 
Block[{d = Join[MyDigits[First[date]-1, {4}] , 
                Rest[date]-1]},
       d = Join[Table[0, {4 - Length[d]}], d];
       DigitsToNumber[d, JulianCalendar, d]]


(* Gregorian calendar *)

GregorianCalendar = 
{GregorianFourCenturies, GregorianCentury, 
 GregorianFourYears, GregorianYears, GregorianMonths}

GregorianFourCenturies[_]:= {146097}

GregorianCentury[_]:= {36524, 36524, 36524, 36525}

GregorianFourYears[path_]:= 
 Append[Table[1461, {24}], 1460 + Quotient[path[[2]], 4]]

GregorianYears[path_]:= 
 {365, 365, 365, 366 - 
 (1-Quotient[path[[2]], 4]) Quotient[path[[3]], 25]}

GregorianMonths[path_]:= 
{31, 28 + Quotient[path[[4]], 4] * (1 - 
   (1 - Quotient[path[[2]], 4]) Quotient[path[[3]], 25]),
 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}

NumberToDate[n_, Gregorian]:= 
Prepend[Drop[#, 4], DigitsToNumber[Take[#-1, 4], {4,25,4}] + 1]& @
 MyDigits[n, GregorianCalendar, {}]

DateToNumber[date_, Gregorian]:= 
Block[{d = Join[MyDigits[First[date]-1, {4, 25, 4}] , Rest[date]-1]},
       d = Join[Table[0, {6 - Length[d]}], d];
       DigitsToNumber[d, GregorianCalendar, d]]


CalendarChangeInit[Gregorian, Julian] = 2

CalendarChangeInit[Julian, Gregorian] = -2


(* Islamic calendar *)


IslamicCalendar = {IslamicThirtyYears, 
                   IslamicYears, 
                   IslamicMonths}

IslamicThirtyYears[_]:= {30 354 + 11}

IslamicYears[_]:= 354 +
   {0,1,0,0,1,0,1,0,0,1,0,0,1,0,0,
    1,0,1,0,0,1,0,0,1,0,1,0,0,1,0}

IslamicMonths[path_]:= 
  {30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29 + 
   {0,1,0,0,1,0,1,0,0,1,0,0,1,0,0,
    1,0,1,0,0,1,0,0,1,0,1,0,0,1,0}[[path[[2]]]]}


NumberToDate[n_, Islamic]:= 
Prepend[Drop[#, 2], DigitsToNumber[Take[#-1, 2], {30}] + 1]& @
 MyDigits[n, IslamicCalendar, {}]

DateToNumber[date_, Islamic]:= 
Block[{d = Join[MyDigits[First[date]-1, {30}], 
                Rest[date]-1]},
       d = Join[Table[0, {4 - Length[d]}], d];
       DigitsToNumber[d, IslamicCalendar, d]]

CalendarChangeInit[Islamic, Julian] = 
  DateToNumber[{622, 7, 15}, Julian]

CalendarChangeInit[Julian, Islamic] = 
-CalendarChangeInit[Islamic, Julian]

CalendarChangeInit[Gregorian, Islamic] = 
-DateToNumber[CalendarChange[{622, 7, 15}, Julian, Gregorian], 
              Gregorian]

CalendarChangeInit[Islamic, Gregorian] = 
-CalendarChangeInit[Gregorian, Islamic] 


End[]   (* Miscellaneous`Calendar`Private` *)

Protect[DayOfWeek, DaysBetween, DaysPlus, CalendarChange, Calendar, 
        Julian, Gregorian, Islamic, EasterSunday, JewishNewYear,
        EasterSundayGreekOrthodox]
        
       

EndPackage[]   (* Miscellaneous`Calendar` *)



