-------------------------------------------------------------------------------
--
--  <STRONG>Copyright &copy; 2001, 2002 by Thomas Wolf.</STRONG>
--  <BLOCKQUOTE>
--    This piece of software is free software; you can redistribute it and/or
--    modify it under the terms of the  GNU General Public License as published
--    by the Free Software  Foundation; either version 2, or (at your option)
--    any later version. This software is distributed in the hope that it will
--    be useful, but <EM>without any warranty</EM>; without even the implied
--    warranty of <EM>merchantability or fitness for a particular purpose.</EM>
--    See the GNU General Public License for  more details. You should have
--    received a copy of the GNU General Public License with this distribution,
--    see file "<A HREF="GPL.txt">GPL.txt</A>". If not, write to the Free
--    Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307,
--    USA.
--  </BLOCKQUOTE>
--  <BLOCKQUOTE>
--    As a special exception from the GPL, if other files instantiate generics
--    from this unit, or you link this unit with other files to produce an
--    executable, this unit does not by itself cause the resulting executable
--    to be covered by the GPL. This exception does not however invalidate any
--    other reasons why the executable file might be covered by the GPL.
--  </BLOCKQUOTE>
--
--  <AUTHOR>
--    Thomas Wolf  (TW) <E_MAIL>
--  </AUTHOR>
--
--  <PURPOSE>
--    Root package of the calendar subsystem. Provides a day-of-week
--    calculation for Gregorian dates.
--  </PURPOSE>
--
--  <DL><DT><STRONG>Literature:</STRONG><DD>
--  <P>See Christian Zeller; <EM>Kalender-Formeln</EM>, Acta Mathematica, vol.
--  <STRONG>9</STRONG>, pp. 131-136; Nov. 1886. (Yes, that's <EM>not</EM>
--  a typo, it really appeared in eighteen-eightysix. In fact, that paper
--  is an expanded version of an earlier one from 1883!)
--  <P>See also
--  <A HREF="http://www.merlyn.demon.co.uk/zeller-c.htm">J.R. Stockton's page
--  on Zeller's congruence</A>.</DL>
--
--  <HISTORY>
--    29-JUL-2002   TW  Initial version.
--  </HISTORY>
-------------------------------------------------------------------------------

pragma License (Modified_GPL);

package body Util.Calendar is

   function Day_Of_Week
     (Year  : in Ada.Calendar.Year_Number;
      Month : in Ada.Calendar.Month_Number;
      Day   : in Ada.Calendar.Day_Number)
     return Weekday
   is
      Zeller : constant array (Ada.Calendar.Month_Number) of Natural :=
        (6, 2, 1, 4, 6, 2, 4, 0, 3, 5, 1, 3);
      --  Precomputed table Zeller (Month) = ((13 * M - 1) / 5 + 6) mod 7 for
      --  M = 1 + (Month + 9) mod 12, i.e. M=1 for March, 10 for December,
      --  and 11 for January and 12 for February.
      --
      --  Zeller's original formula used 26*(N+1)/10 instead of (13*M-1)/5,
      --  where N was 3 for March, 4 for April, 10 for December, and January
      --  was N=13; February N=14 of the preceeding year. His formula yielded
      --  1 for Sunday, 2 for Monday, 6 for Friday, and zero for Saturday.
      --  For computers, that's not so nice. Our term relates to his as
      --  (26*(N+1)/10) mod 7 = (13*(M+3)/5) mod 7 = ((13*M-1)/5) mod 7 + 1,
      --  i.e. we map the result such that 0 is Sunday, 1 is Monday, and 6 is
      --  Saturday.
      --
      --  The "+6" finally maps this such that 0 is Monday, 1 is Tuesday, 5
      --  is Saturday, and 6 is Sunday, for modern usage (and ISO) considers
      --  Monday the first day of the week, not Sunday.
      --
      --  Note: in his 1886 paper, Zeller proudly stated that his formula
      --  made it possible for the first time to compute the day of the week
      --  for any given date without auxiliary tables. For a computer implemen-
      --  tation, using such a precomputed table is faster, though.
      --
      --  Also note that Zeller also gave a formula for the Julian calendar:
      --
      --  Z := (Day + 26*(N+1)/10 + Y + Y/4 + 5 - Century) mod 7
      --
      --  or
      --
      --  Z := (Day + 26*(N+1)/10 + Y + Y/4 + 5 + 6*Century) mod 7
      --
      --  to make the left-hand side always positive. With our month numbering,
      --  this would become
      --
      --  Z := (Day + (13*M-1)/5 + Y + Y/4 + 5 + 6*Century + 6) mod 7,
      --
      --  or
      --
      --  Z := (Day + Zeller_J (Month) + Y + Y/4 + 6*Century) mod 7,
      --
      --  with Zeller_J = (Zeller + 5) mod 7, i.e. 4, 0, 6, 2, 4, 0, 2, 5, 1,
      --  3, 6, 1.

      Y, Century : Natural;
   begin
      --  This is Zeller's formula.
      Y := Natural (Year);
      if Month < 3 then Y := Y - 1; end if;
      Century := Y / 100; Y := Y mod 100;
      return
        Weekday'Val
          ((Natural (Day) + Zeller (Month) + Y + Y / 4 +
            Century / 4 + 5 * Century) mod 7);
      --  Zeller used "- 2 * Century" instead of "+ 5 * Century", but then
      --  the left operand of the "mod" may become negative. Now in Ada 95,
      --  that's not a problem, for "A mod B" is defined to return a result in
      --  the range 0 .. B-1 for all B > 0 regardless of the sign of A, but to
      --  facilitate porting this code to other programming languages where the
      --  "mod" operator may return negative values (as in C, where -30 % 7
      --  yields -2), I chose to add 7 * Century (which won't change the final
      --  value because it's a multiple of 7).
      --
      --  (A side note on the above: in C99 (ISO/IEC 9899:1999), -30 % 7 == -2
      --  is required. In C90 (ISO/IEC 9899:1990), -30 % 7 could yield either
      --  -2 or 5, depending on the implementation. The only requirement was
      --  that (a/b)*b + a%b == a. (And -30 / 7 was allowed to yield either
      --  -4 or -5!) Most C90 implementations used truncation towards zero,
      --  because that's what most hardware does, and gave -30 / 7 = -4 and
      --  -30 % 7 == -2. But the C90 standard would also have allowed to use
      --  truncation towards -infinity: -30 / 7 == -5 and -30 % 7 == 5.)
   end Day_Of_Week;

   function Day_Of_Week
     (Date : in Ada.Calendar.Time)
     return Weekday
   is
      Day   : Ada.Calendar.Day_Number;
      Month : Ada.Calendar.Month_Number;
      Year  : Ada.Calendar.Year_Number;
      Secs  : Ada.Calendar.Day_Duration;

   begin
      Ada.Calendar.Split (Date, Year, Month, Day, Secs);
      return Day_Of_Week (Year, Month, Day);
   end Day_Of_Week;

   procedure Split
     (Secs : in     Ada.Calendar.Day_Duration;
      Hrs  :    out Hours_Type;
      Min  :    out Minutes_Type;
      Sec  :    out Seconds_Type;
      Frac :    out Ada.Calendar.Day_Duration)
   is
      use Ada.Calendar;
      S : Natural := Natural (Secs);
   begin
      if Day_Duration (S) > Secs then S := S - 1; end if;
      Frac := Secs - Day_Duration (S);
      Hrs  := Hours_Type (S / 3600);
      S    := S mod 3600;
      Min  := Minutes_Type (S / 60);
      Sec  := Seconds_Type (S mod 60);
   end Split;

end Util.Calendar;
