(*:Name: Miscellaneous`Calendar` *)

(*:Context: Miscellaneous`Calendar` *)

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

(*:Summary:
This package provides functions for basic calendar operations.
The Gregorian, Julian, and Islamic calendars are supported.
*)

(*:Author: Ilan Vardi *)

(*:Mathematica Version: 2.0 *)

(*:Package Version: 1.0.2 *)

(*:History: V1.0, Ilan Vardi
		V1.0.1, minor revisions, John M. Novak, February 1992.
		V1.0.2 more minor revisions, John M. Novak, April 1994.
			-- added ability of externally visible functions to use
			   six argument form of Date[], based on suggestion by
			   Larry Calmer
*)

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

(*:Requirements: None. *)

(* :Copyright: Copyright 1992-1999, Wolfram Research, Inc.*)

(*: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 calendar commonly 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 adjustments 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 computes the Jewish New Year only for the years 1900 
             up to 2099.

			 Input to functions that accept a date can also take the
			 date in {year, month, day, hour, minute, second} form
			 (such as that returned by Mathematica's Date[] command).
			 Output will also be returned in this form, but no calculations
			 are performed based on hour, minute, and second.

*)

(*: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
               treatment 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 in 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 the following:

               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. The date can also be
given in {y, m, d, h, m, s} form."

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.
Dates can also be given in {y, m, d, h, m, s} form."

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. The date can also be given in {y, m, d, h, m, s}
form."

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: the Gregorian, Julian, or Islamic. If set to Automatic, the
Julian calendar is used before 2 September 1752, and the Gregorian
calendar afterwards."

Gregorian::usage =
"Gregorian is 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 = "Julian is 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 = "Islamic is 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 = "Sunday is a day of the week."

Monday::usage = "Monday is a day of the week."

Tuesday::usage = "Tuesday is a day of the week."

Wednesday::usage = "Wednesday is a day of the week."

Thursday::usage = "Thursday is a day of the week."

Friday::usage = "Friday is a day of the week."

Saturday::usage = "Saturday is a day of the week."

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

Begin["`Private`"]

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

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

Options[DaysPlus]:= {Calendar -> Automatic}

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

DayOfWeek[date_List?(Length[#] == 6 &), opts___] :=
	DayOfWeek[Take[date, 3], opts]

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?(Length[#] == 6 &), rest___] :=
   DaysBetween[Take[date1, 3], rest]

DaysBetween[date1_List, date2_List?(Length[#] == 6 &), rest___] :=
   DaysBetween[date1, Take[date2, 3], rest]

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

DaysBetween[date1_List, date2_List, opts___]:=
 Block[{calendar = Calendar /. Flatten[{opts, Options[DaysBetween]}]},
        If[calendar === Automatic, 
           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?(Length[#] == 6 &), rest___] :=
	Join[DaysPlus[Take[date, 3], rest], Take[date, -3]]

DaysPlus[date_List, n_Integer, opts___]:= 
 Block[{calendar = Calendar /. Flatten[{opts, Options[DaysPlus]}], d},
        If[calendar === Automatic, 
           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_List?(Length[#] == 6 &), rest___] :=
	Join[CalendarChange[Take[date, 3], rest], Take[date, -3]]

CalendarChange[date_List, 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` *)



