/*
   ###########################################################################
   This function returns the Julian Day Number for any given calendar date in
   the range from BC 19999 to AD 19999 on the old Julian calendar or on the
   modern Gregorian calendar system.

   It returns negative JD numbers for dates prior to mathematical origins of
   the respective calendar systems.

   --------------------
   CALENDAR YEAR RANGE:

   BC 19999  to  AD 19999
   There is no calendar year 0 (zero).


   --------------------------------------------------------
   For the mathematical year number (y) used in calendrical
   computations for a BC calendar year Y = ('BC Year'):
   Y = 'BC 1949'  --->  y = '-1948'

   We take the numerical value  following the 'BC' prefix
   and subtract 1  and then change the result to negative
   to obtain the mathematical year value required for use
   in the (JDNum) computation.  This adjustment will only
   apply to 'BC' date strings.

   Example:  'BC 1949-May-20'
              y = -(Y - 1)
                = -(1949 - 1)
                = -1948
   So, (y,m,d)  = (-1948,5,20)


   -------------------------------------------------------------
   Positive calendar year numbers do NOT require any adjustment.
   Example:  'AD 1949-May-20'
   So, (y,m,d) = (1949,5,20)


   --------------------------------------------
   MATHEMATICAL ORIGINS OF THE CALENDAR SYSTEMS

   BC 4713-Jan-01-Mon   JDNum = 0   On the old Julian calendar
   BC 4714-Nov-24-Mon   JDNum = 0   on the modern Gregorian calendar

   NOTE:
   If a year is  given as a negative number,  it refers to a 'BC' year
   and will be converted to 'BC|AD' format internally for computations
   because 'BC' years first require a special numerical adjustment not
   needed for 'AD' years.

   Month = 1 to 12 or as 3-letter abbreviation string ('Jan' to 'Dec').
   Date strings are NOT case-sensitive.

   The returned signed JD Number is left-space-padded to 8 characters
   to facilitate easy columnar alignment if used for tabulation.

   ARGUMENTS:
   $BCADDateStr = Date string in BC|AD format.
                  VALID EXAMPLES:
                  'BC 9949-May-20'
                  'AD 1949-5-20'
                  '16959-1-29'
                  '-1023-Nov-15'

   JGAMode = Calendar mode to apply, where:
             'G' = Gregorian
             'J' = Julian

   ERRORS:
   FALSE is returned if an invalid argument is detected.

   NO DEPENDENCIES
   ###########################################################################
*/

   function JD_Num ($BCADDateStr, $JGMode='G')
{
// --------------------------------------------------
// Read and adjust input date string argument format.

   $BCADDateStr = PReg_Replace("/\s+/", " ", trim($BCADDateStr));


/* ---------------------------------------------------
   If first character is a minus sign (negative year),
   then convert it into a 'BC' calendar year string.

   '-1949' becomes ---> 'BC 1949'
*/
   if (substr($BCADDateStr,0,1) == '-')
      {$BCADDateStr = 'BC ' . substr($BCADDateStr, 1, StrLen($BCADDateStr));}


// ---------------------------------------------------------------
// If no 'BC|AD' prefix at all, then attach a default 'AD' prefix.

   $ww = StrToUpper(substr($BCADDateStr,0,2));
   if ($ww <> 'BC' and $ww <> 'AD') {$BCADDateStr = "AD $BCADDateStr";}


// ------------------------------
// Read and parse date arguments.

   list($BCADYear,$Month,$Day) = PReg_Split("[-]", $BCADDateStr);


// ------------------
// A few adjustments.
   $BCADYear = trim($BCADYear);
   $Month    = trim($Month);
   $Day      = trim($Day);


// -----------------------------------
// Get BC|AD prefix and calendar year.

   $BCAD = StrToUpper(substr($BCADYear, 0,2));
   $Y = trim(substr($BCADYear, 2, StrLen($BCADYear)));


// ---------------------------------
// Adjust for BC year, if necessary.

   if ($BCAD == 'BC') {$Y = -$Y;}


// ------------------------------------------------------------
// Read calendar year argument value and return FALSE on error.

   $w = abs($Y);  if ($w < -19999 or $w == 0 or $w > 19999)  {return FALSE;}


// ---------------------------------------------------
// Read month argument. Could be a string or a number.

   $m = UCFirst(substr(StrToLower(trim($Month)),0,3));


// ------------------------
// Read day argument value.

   $d = trim($Day);


/* ----------------------------
  Read calendar mode argument.
  'G' = Gregorian | 'J' = Julian
*/
   $JGMode = substr(StrToUpper(trim($JGMode)),0,1);
             if ($JGMode == '') {$JGMode = 'G';}


// -------------------------------------------------
// Define abbreviations for month and weekday names.

   $MONTHS   = 'JanFebMarAprMayJunJulAugSepOctNovDec';
   $WEEKDAYS = 'SunMonTueWedThuFriSat';
   $JGDiff   = 0;


/* -----------------------------------------------------
   If month is a 3-letter abbreviation ('Jan' to 'Dec'),
   then replace it with the month number 1 to 12, if
   possible.  Otherwise, return FALSE if it cannot
   resolve the abbreviation text.
*/
   if (!Is_Numeric($m))
      {
       $i = StrPos($MONTHS, $m);
       if ($i === FALSE) {return $i;}
       $m = 1 + $i/3;
      }


// ---------------------------------------
// Error if invalid month number.

   if ($m < 1 or $m > 12) {return FALSE;}


/* -------------------------------------------------
   Proceed to compute the Julian calendar JD Number.
   This is the base JD Number value. If the Gregorian
   calendar is selected, then the difference between
   the calendars is applied to obtain the Gregorian
   calendar JD Number.
*/
   $A = floor((14-$m) / 12);
   $B = (($Y < 0)? $Y+1 : $Y) - $A;
   $C = floor($B/100);

   $JDNum = floor(30.6001*(12*$A + $m + 1))
          + floor(365.25*($B + 4716)) - 1524 + $d;



/* ---------------------------------------------
   Handle Gregorian (= default) calendar mode by
   by ADDING the difference  in days between the
   Julian and Gregorian JD Numbers, if indicated
   by the JGMode setting.    This value could be
   negative or positive,  depending on the given
   date.    DEFAULT LOGIC: If not 'J', then 'G'.

   GregorianJDNum = JulianJDNum + (+-JGDiff)
*/

   if ($JGMode <> 'J')
      {
       $A = ($Y < 0)? $Y+1 : $Y;
       $B = trim($m);
       $C = $A - floor((14-$B) / 12);
       $D = floor($C/100);
       $JGDiff = (floor($D/4) - $D + 2);
      }
       $JDNum += $JGDiff;


/* -------------------------------------------------
   Error if (JDNum) is outside of the valid calendar
   range from:
   BC 19999-Jan-01-Thu J   -5584211
   BC 19999-Jan-01-Tue G   -5583059
   to
   AD 19999-Dec-31-Sat J    9026057
   AD 19999-Dec-31-Fri G    9025909
*/
   if ($JDNum < -5584211 or $JDNum > 9026908) {return FALSE;}

/* -----------------------------------------------
   Left-pad the signed JD number digits field with
   spaces to span exactly 8 characters width, just
   in case the values are used in a table.  This
   helps to arrange any (JDNum) column uniformly.

   The white space can be removed by performing
   a simple trim(JDNum) command, if not wanted.
*/
   $JDNum = SPrintF("% 8d", $JDNum);

   return $JDNum;

} // End of  JD_Num(...)