/*
###########################################################################
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(...)