<?php
/*
###########################################################################
PARALLEL JULIAN AND GREGORIAN CALENDAR JD NUMBERS - WITH 30-DAY COOKIE
AUTHOR : Jay Tanner - 2025
LANGUAGE : PHP v8.2.12
LICENSE : Public Domain
This interface template is for functions that require only a
single date argument.
The date consists of: BCAD Year-Month-Day Number of days to tabulate.
###########################################################################
*/
ob_start(); // Oy!
$MONTHS = 'JanFebMarAprMayJunJulAugSepOctNovDec';
// ---------------------------------------------------------------
// Define the program cookie name and set it to expire in 30 days.
$CookieName = 'Parallel-Gregorian-Julian-JD_Number_Table';
$SetToExpireIn30Days = time() + 30*86400;
// ---------------------------------
// Define PHP program and HTML info.
$_AUTHOR_ = 'PHP Program By Jay Tanner of Geneva, NY, USA';
$_PROGRAM_VERSION_ = 'v1.00 - '; $at = "at Local Time "; $LTC = "UTC";
$_SCRIPT_FILE_PATH_ = Filter_Input(INPUT_SERVER, 'SCRIPT_FILENAME');
$_REVISION_DATE_ = $_PROGRAM_VERSION_ .'Revised: '. date("Y-F-d-l $at h:i:s A ($LTC", FileMTime($_SCRIPT_FILE_PATH_))."−05:00)";
$_BROWSER_TAB_TEXT_ = "Parallel Gregorian/Julian JD Number Table";
$_INTERFACE_TITLE_ = "<span style='font-size:16pt;'>Parallel Gregorian/Julian JD Number Table Calculator</span><br><br><span style='font-size:12pt;'>Calendar Span:<br>BC 19999-Jan-01-Tue/Thu to AD 19999-Dec-31-Fri/Sat</span><br><br><span style='font-size:10pt;'>$_AUTHOR_</span>";
// -------------------------------------
// Define main TextArea text and background
// colors and HTML table row span. If an
// error is reported, then these colors
// will change internally to red/white.
$TxColor = 'black';
$BgColor = 'white';
// ---------------------------------------------
// Do this only if [SUBMIT] button was clicked.
$w = Filter_Input(INPUT_POST, 'SubmitButton');
if (!IsSet($w))
{
/* ----------------------------------------------------------------------
If this program is being called externally, rather than being executed
by clicking the [SUBMIT] button, and an active cookie also exists,
then restore the previously saved interface settings from it. If
the user leaves and comes back later, all the interface settings
will be remembered and restored if the cookie was not deleted.
*/
$w = Filter_Input(INPUT_COOKIE, $CookieName);
if (IsSet($w))
{
$CookieDataString = Filter_Input(INPUT_COOKIE, $CookieName);
list($BCAD,$Year,$Month,$Day,$N) = Preg_Split("[\|]", $CookieDataString);
}
else
// -----------------------------------------------------------
// If there is no previous cookie with the interface settings,
// then set the initial default interface startup values and
// store them in a new cookie.
{
$BCAD = 'AD';
$Year = date('Y');
$Month = date('M');
$Day = date('d');
$StepSize = '1 hour';
$N = '15';
// -------------------------------------------
// Store current interface settings in cookie.
$CookieDataString = "$BCAD|$Year|$Month|$Day|$N";
SetCookie ($CookieName, $CookieDataString, $SetToExpireIn30Days);
} // End of else {...}
} // End of if (!isset(_POST['SubmitButton']))
// ------------------------------------------
// Read values of all interface arguments and
// set any empty arguments to default values.
$w = Filter_Input(INPUT_POST, 'SubmitButton');
if (isset($w))
{
$BCAD = StrToUpper(trim(Filter_Input(INPUT_POST, 'BCAD')));
if (substr($BCAD,0,1) == 'B') {$BCAD = 'BC';}
if (substr($BCAD,0,1) == 'A') {$BCAD = 'AD';}
$Year = trim(Filter_Input(INPUT_POST, 'Year'));
if ($Year == '') {$Year = date('Y');}
$year = ($BCAD == 'BC')? -($Year - 1) : $Year;
/* -------------------------------------------
Get month as a number (1 to 12) or as a
3-letter abbreviation ('Jan' to 'Dec').
NOT case-sensitive.
*/
$Month = UCFirst(substr(StrToLower(trim(Filter_Input(INPUT_POST, 'Month'))),0,3));
// exit("124:<br>Month = $Month");
if ($Month == '') {$Month = date('M');}
$xMonth = $Month;
if (Is_Numeric($Month) and $Month == 0) {$Month = 'Jan';}
if (Is_Numeric($Month) and $Month > 12) {$Month = 'Dec';}
for ($ii=0; $ii < 1; $ii++)
{
if (Is_Numeric($Month))
{
$m = IntVal($Month);
if ($m < 0 or $m > 12) {$Month = 0; break;}
$Month = substr($MONTHS, 3*($m-1), 3);
}
$jj = StrPos($MONTHS, $Month);
if ($jj === FALSE) {$Month = 0; break;}
$Month = substr($MONTHS, $jj, 3);
$m = $jj;
}
$Day = trim(Filter_Input(INPUT_POST, 'Day'));
if ($Day == '') {$Day = date('d');}
if (Is_Numeric($Day)) {$Day = SPrintF("%02d", $Day);}
$N = abs(trim(Filter_Input(INPUT_POST, 'N')));
if ($N == 0) {$N = 1;}
// ----------------------------------
// Set default values if empty input.
if ($BCAD == '') {$BCAD = 'AD';}
if ($Year == '') {$Year = date('Y');}
if ($Month == '') {$Month = date('M');}
if ($Day == '') {$Day = date('d');}
if ($N == '') {$N = 15;}
// --------------------------------------
// Store interface arguments in a cookie.
$CookieDataString = "$BCAD|$Year|$Month|$Day|$N";
SetCookie ($CookieName, $CookieDataString, $SetToExpireIn30Days);
}
// ---------------------------------------------
// Check input values for validity.
// If error, set error flag and message values.
$ErrFlag = FALSE;
$ErrMssg = '';
if (abs($N) > 366)
{
$ErrFlag = TRUE;
$ErrMssg = "Table size too large.\n'$N'\n\nThe table size must be in the range from 1 to 366 days from the start date.";
}
if (!Is_Numeric($Day) or $Day < 1 or $Day > 31)
{
$ErrFlag = TRUE;
$ErrMssg = "Bad day number.\n'$Day'\n\nThe day must be an integer in the range from 1 to 31, depending on the month.";
}
if ($Month == '0')
{
$ErrFlag = TRUE;
$ErrMssg = "Bad month number or abbreviation.\n'$xMonth'\n\nThe month must be an integer from 1 to 12 or \na 3-letter abbreviation from 'Jan' to 'Dec'. \n\nNOT case-sensitive.";
$Month = $xMonth;
}
if (!Is_Numeric($Year) or $Year <= 0 or $Year > 19999)
{
$ErrFlag = TRUE;
$ErrMssg = "Bad year number value.\n'$Year'\n\nThe year value must be a non-zero integer ≤ 19999.";
}
if ($BCAD <> 'BC' and $BCAD <> 'AD')
{
$ErrFlag = TRUE;
$ErrMssg = "'$BCAD'\nBad BC/AD era symbol.\n\nThe era symbol must be 'BC' or 'AD' only. \nAn empty input defaults to 'AD' era.\n\nNOT case-sensitive.";
}
// $StartDate = "$BCAD $Year-$Month-$Day";
/*
if (!Is_Valid_Date($year,$m,$Day, 'G') or !Is_Valid_Date($year,$m,$Day, 'J'))
{
$ErrFlag = TRUE;
$ErrMssg = "Invalid date error.\n$ The given date does not exist on the real calendar.\n";
}
*/
// -----------------------------
// Set initial uniform width for
// tables alignment in pixels.
$TableWidth = '800';
// ******************************************
// ******************************************
// If error was reported (TRUE), then display
// the error message on a red background.
if ($ErrFlag === TRUE)
{
$TxColor = 'white';
$BgColor = '#CC0000';
$TextArea2Text = '';
$TextArea1Text =
"=== ERROR ===
$ErrMssg";
}
else
{
// *********************************************************
// BEGIN MAIN COMPUTATIONS HERE IF NO ERRORS DETECTED ABOVE.
// *********************************************************
// ----------------------------------------------
// Force year value into positive 5-digit format.
$Year = SPrintF("%05d", abs($Year));
// *******************************************
// DROP THROUGH HERE AFTER COMPUTATIONS ABOVE
// TO PRINT OUT THE RESULTS OF THE OPERATIONS.
// *******************************************
// Set Starting Gregorian date and number of
// days to tabulate.
$StartDate = "$BCAD $Year-$Month-$Day";
// ----------------------------------------------------------
// Create loop to create a table for +N days from start date.
$TableText = JD_Num_Table ($StartDate, $N);
$TextArea1Text =
" PARALLEL GREGORIAN/JULIAN JD NUMBER TABLE
$TableText
";
}
// ****************************
// Define TextArea2 text block.
$TextArea2Text =
"This program displays a parallel table of signed Gregorian and Julian JD
Numbers for both the modern Gregorian calendar and the old Julian Calendar.
##############################################################################
The 'Diff' column is the number of days (Gregorian − Julian) to apply to
the Julian calendar JD number to obtain the corresponding JD Number on the
Gregorian calendar.
The difference between the calendars is due to the differences in their rules
used to handle leap years and the fact that 10 days were dropped from the cal-
endar during the Julian to Gregorian calendar transition in October of 1582 to
realign the calendar date of spring (March 21) with the sun again. According
to the calendar, spring had drifted to occuring 10 days too early due to the
old Julian calendar leap year rule being slightly off by 1 day every 400 years
and not being corrected for over 1600 years, until mid-October of 1582.
------------------------------
JULIAN CALENDAR LEAP YEAR RULE
Every year that is divisible by 4 is a leap year without exception, including
all century years (years ending in -00).
---------------------------------
GREGORIAN CALENDAR LEAP YEAR RULE
Every year BETWEEN century years that is divisible by 4 is a leap year, but
every century year (ending in -00) is a leap year ONLY if it is divisible by
400, otherwise it is a common year. On our modern Gregorian calendar, only
one century year out of every 400 years is a leap year. The century year
AD 1600 was a leap year. The century year AD 2000 was a leap year. That will
not happen again until the year AD 2400.
##############################################################################
THE DAYS OF THE WEEK
To find the weekday (Sun to Sat), from the signed JD Number value, we compute
its cyclic weekday index number starting from Sunday (DoWi = 0).
Let:
JDNum = Signed Julian Day Number (Span: -5583059 to 9025909)
DoWi = DayOfWeek index (0 to 6)
Where: 0=Sun, 1=Mon, 2=Tue, 3=Wed, 4=Thu, 5=Fri and 6=Sat
DoWi = (7 + ((JDNum + 1) mod 7)) mod 7
Given a source string of 3-letter weekday abbreviations, like:
WEEKDAYS = 'SunMonTueWedThuFriSat'
The DoWi index value gives us a pointer to where the 3-letter abbreviation
is located within the WEEKDAYS string. For example, if the DoWi value is 5,
then it refers to 'Fri' at string index location 3*DoWi = 3*5 = 15. This says
that the 3-letter abbreviation for Friday ('Fri') starts at string character
index 15 within the WEEKDAYS source string. Text strings are indexed from
zero in most computer languages, by default. The 3 refers to the number of
characters in each weekday abbreviation substring.
";
/* **************************************************************************
Determine number of text columns and rows to use in the output text areas.
These values vary randomly according to the text block width and length.
The idea is to eliminate the need for scroll-bars within the text areas
or worry as much about the variable dimensions of a text display area.
*/
// -----------
// Text Area 1
$Text1Cols = 1 + Max(Array_Map('StrLen', PReg_Split("[\n]", trim($TextArea1Text))));
if ($Text1Cols < 80) {$Text1Cols = 80;} // Default
$Text1Rows = 2 + Substr_Count($TextArea1Text, "\n");
// -----------
// Text Area 2
$Text2Cols = 1 + Max(Array_Map('StrLen', PReg_Split("[\n]", trim($TextArea2Text))));
if ($Text2Cols < 80) {$Text2Cols = 80;} // Default
$Text2Rows = 2 + Substr_Count($TextArea2Text, "\n");
// ******************************************
// GENERATE CLIENT WEB PAGE TO DISPLAY OUTPUT
print <<< _HTML
<!DOCTYPE HTML>
<HTML>
<head>
<title>$_BROWSER_TAB_TEXT_</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="expires" content="-1">
<meta name="description" content="xxxxxxxxxxxxxxx">
<meta name="keywords" content="NeoProgrammics.com">
<meta name="author" content="Jay Tanner - https://www.NeoProgrammics.com">
<meta name="robots" content="index,follow">
<meta name="googlebot" content="index,follow">
<style>
BODY {color:white; background:black; font-family:Verdana; font-size:12pt; line-height:125%;}
TABLE
{font-size:13pt; border: 1px solid black;}
TD
{
color:black; background:white; line-height:150%; font-size:10pt;
padding:6px; text-align:center;
}
UL
{font-family:Verdana; font-size:12pt; line-height:150%; text-align:justify;}
PRE
{
background:white; color:black; font-family:monospace; font-size:12.5pt;
font-weight:bold; text-align:left; line-height:125%; padding:6px;
border:2px solid black; border-radius:8px;
page-break-before:page;
}
DIV
{
background:white; color:black; font-family:Verdana; font-size:11pt;
font-weight:normal; line-height:125%; padding:6px;
}
TEXTAREA
{
background:white; color:black; font-family:monospace; font-size:13pt;
font-weight:bold; padding:4pt; white-space:pre; border-radius:8px;
line-height:125%;
}
INPUT[type='text']::-ms-clear {width:0; height:0;}
INPUT[type='text']
{
font-family:monospace; color:black; background:white; font-size:13pt;
font-weight:bold; text-align:center; box-shadow:2px 2px 3px #666666;
border:2px solid black; border-radius:4px;
}
INPUT[type='text']:focus
{
font-family:monospace; background:white; box-shadow:2px 2px 3px #666666;
font-size:13pt; border:2px solid blue; text-align:center; font-weight:bold;
border-radius:4px;
}
INPUT[type='submit']
{
background:black; color:gray; font-family:Verdana; font-size:10pt;
font-weight:bold; border-radius:4px; border:4px solid #777777;
padding:3pt;
}
INPUT[type='submit']:hover
{
background:black; color:white; font-family:Verdana; font-size:10pt;
font-weight:bold; border-radius:4px; border:4px solid GreenYellow;
padding:3pt;
}
// Link states MUST be set in the following order:
// :link, :visited, :hover, :active
A:link
{
font-size:10pt; background:transparent; color:#8080FF; border-radius:4px;
font-family:Verdana; font-weight:bold; text-decoration:none;
line-height:175%; padding:3px; border:1px solid transparent;
}
A:visited
{
font-size:10pt; background:transparent; color:DarkCyan; border-radius:4px;
}
A:hover
{
font-size:10pt; background:yellow; color:black; border:1px solid black;
box-shadow:1px 1px 3px #222222; border-radius:4px;
}
A:active
{
font-size:10pt; background:yellow; color:black; border-radius:4px;
}
HR {background:red; height:4px; border:0px;}
[title-text]:hover:after
{
opacity:1.0;
transition:all 1.0s ease 1.0s;
text-align:left;
visibility:visible;
}
[title-text]:after
{
opacity:1.0;
content:attr(title-text);
text-align:left;
left:50%;
background-color:yellow;
color:black;
font-size:10pt;
position:absolute;
padding:1px 5px 2px 5px;
white-space:pre;
border:1px solid red;
z-index:1;
visibility:hidden;
}
[title-text] {position: relative;}
::selection{background-color:yellow !important; color:black !important;}
::-moz-selection{background-color:yellow !important; color:black !important;}
</style>
</head>
<body>
<!-- Define container form --->
<form name="form1" method="post" action="">
<!-- Define main page title/header. --->
<table width="$TableWidth" align="center" border="0" cellspacing="1" cellpadding="3">
<tr><td colspan="99" style="color:white; background-color:#000066; border:2px solid white; border-radius:8px 8px 0px 0px;">$_INTERFACE_TITLE_</td></tr>
</table>
<!-- Define input text boxes. --->
<table width="$TableWidth" align="center" border="0" cellspacing="1" cellpadding="3">
<tr><td width="50%" style="background:LightYellow; line-height:175%;">
Gregorian Start Date <input name="BCAD" type="text" value="$BCAD" size="3" maxlength="2">
<input name="Year" type="text" value="$Year" size="6" maxlength="5">
<input name="Month" type="text" value="$Month" size="4" maxlength="3">
<input name="Day" type="text" value="$Day" size="3" maxlength="2">
</td>
<td style='background:#CCFFCC;' title=' 1 to 366 days '>
Days to Tabulate From Start <input name="N" type="text" value="$N" size="5" maxlength="4"> d</td>
</table>
<!-- Define [SUBMIT] button --->
<table width="$TableWidth" align="center" border="0" cellspacing="1" cellpadding="3">
<tr><td colspan="99" style="background-color:black;"><input type="submit" name="SubmitButton" value=" S U B M I T "></td></tr>
<tr>
<td colspan="1" style='font-size:10pt; color:black; background:black;
text-align:center;' title=' Tries to Open in a New Tab. '>
<b><a href="View-Source-Code.php" target="_blank"
style='font-family:Verdana; color:black; background:yellow;
text-decoration:none; border:1px solid black; padding:4px;
border-radius:4px; font-weight:normal;'>
View/Copy Source Code </a></b>
</td>
</tr>
</table>
<!-- Define TextArea1 --->
<table width="$TableWidth" align="center" border="0" cellspacing="1" cellpadding="3">
<tr>
<td colspan="99" style="text-align:center; color:yellow; background-color:black;">Double-Click Within Text Area to Select ALL Text<br>
<textarea ID="TextArea1" name="TextArea1" style="color:$TxColor; background:$BgColor; padding:6px; border:2px solid white;" cols="$Text1Cols" rows="$Text1Rows" ReadOnly OnDblClick="this.select();" OnMouseUp="return true;">
$TextArea1Text
</textarea>
</td>
</tr>
</table>
<!-- Define TextArea2 --->
<table width="$TableWidth" align="center" border="0" cellspacing="1" cellpadding="3">
<tr>
<td colspan="99" style="text-align:center; color:yellow; background:black;">Double-Click Within Text Area to Select ALL Text<br>
<textarea ID="TextArea2" name="TextArea2" style="color:black; background:white; padding:6px;" cols="$Text2Cols" rows="$Text2Rows" ReadOnly OnDblClick="this.select();" OnMouseUp="return true;">
$TextArea2Text
</textarea>
</tr>
</table>
<!-- Define page footer --->
<table width="$TableWidth" align="center" border="0" cellspacing="1" cellpadding="3">
<tr>
<td colspan="99" style="color:GreenYellow; background:black;">PHP Program by $_AUTHOR_<br><span style="color:silver; background:black;">$_REVISION_DATE_</span></td>
</tr>
</table>
</form>
<!-- End of container form --->
<!-- Extra bottom scroll space --->
<br><br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br><br>
</body>
</HTML>
_HTML;
/*
###########################################################################
This function returns the signed 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 the mathematical origin
of the selected calendar system.
---------------
CALENDAR RANGE:
BC 19999-Jan-01 to AD 19999-Dec-31
There is no calendar year 0 (zero),
by definition.
--------------------------------------------------------
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);
// DONE:
return $JDNum;
} // End of JD_Num(...)
/*
###########################################################################
This function is the inverse of the JD number function. Given any signed
JD Number, it will return the corresponding calendar date string in
the same format as: 'BC|AD Yyyyy-Mmm-dd-DoW'
The year will be formatted to 5 digits padded with zeros as needed.
EXAMPLE: 'BC 17191-May-12', 'AD 01949-June-02', 'BC 00107-Oct-13'
CALENDAR YEAR RANGE:
BC 19999 Jan-01 to AD 19999-Dec-31
There is no calendar year 0 (zero).
Mathematical origins of the calendar systems:
BC 4713-Jan-01-Mon JDNum = 0 On old Julian calendar
BC 4714-Nov-24-Mon JDNum = 0 On modern Gregorian calendar
ARGUMENT:
JDNumber = Julian Day number for the calendar date to be computed.
JGAMode = Calendar mode
'G' = Gregorian (Default)
'J' = Julian
RETURNS:
Calendar date string in 'BC|AD Yyyy-Mmm-dd-DoW' format
according to the selected calendar mode.
ERRORS:
FALSE is returned if JD number argument is non-numeric.
NO DEPENDENCIES
###########################################################################
*/
function Inv_JD_Num ($JDNumber, $JGMode='G', $DoWFlag=TRUE)
{
$JDNum = trim($JDNumber);
/* ----------------------------
Read calendar mode argument.
'G' = Gregorian = Default
'J' = Julian
*/
$JGMode = substr(StrToUpper(trim($JGMode)),0,1);
if ($JGMode == '') {$JGMode = 'G';}
// ----------------------------------------------
// Set calendar mode according to JGMode setting.
$CalMode = ($JGMode == 'J')? 0:1;
// -------------------
// Read DoWFlag state.
// LOGIC: If not TRUE, then FALSE.
$DoWFlag = ($DoWFlag === TRUE)? TRUE : FALSE;
/* --------------------------------------------
Compute mathematical date elements (y, m, d)
according to the calendar mode selection.
There IS a mathematical year zero but there
is NOT a calendrical year zero. The year
BC 00001 is followed by AD 00001
*/
$A = floor($JDNum + 0.5);
$B = $CalMode*floor(($A - 1867216.25) / 36524.25);
$C = $A + $CalMode*($B - floor($B/4) + 1);
$D = $C + 1524;
$E = floor(($D - 122.1) / 365.25);
$F = floor(365.25 * $E);
$G = floor(($D - $F) / 30.6001);
// ------------------------------------------------
// Compute final numeric calendrical date elements.
$d = $D - $F - floor(30.6001 * $G); // Day num (1 to 31)
$m = $G - 12*floor($G/14) - 1; // Month num (1 to 12)
$y = $E - 4716 + floor((14 - $m) / 12); // Mathematical year (y)
$Y = ($y > 0)? $y : $y-1; // Calendrical year (Y) Negative = BC
/* --------------------------------------------------------------
At this point we have the calendrical date elements (Y, m, d).
The next step is to construct the full calendar date text
string for output. EXAMPLE OUTPUT: 'BC 9998-May-20-Tue'
*/
$i = (7 + ($JDNum + 1) % 7) % 7;
$DoW = substr('SunMonTueWedThuFriSat', 3*$i, 3);
$Y = SPrintF("%+06d", $Y);
$Y = Str_Replace('-', 'BC ', $Y);
$Y = Str_Replace('+', 'AD ', $Y);
$Mmm = substr('JanFebMarAprMayJunJulAugSepOctNovDec', 3*($m-1), 3);
$dd = SPrintf("%02d", $d);
$JDNum = SPrintF("% 8d", $JDNum);
/* -------------------------------------
Set append day of week string option
according to the (DoWFlag) setting.
TRUE = Append Day of Week abbreviation
FALSE = Append No Day of Week abbreviation
LOGIC : If not TRUE, then FALSE
*/
$DoWStr = ($DoWFlag === TRUE)? "-$DoW" : '';
// DONE.
return "$Y-$Mmm-$dd$DoWStr";
} // End of Inv_JD_Num(...)
function Num_JD_Num ($YearNum, $MonthNum, $DayNum, $JGMode='G')
{
$Y = trim($YearNum);
$m = trim($MonthNum);
$d = trim($DayNum);
$JGMode = (StrToUpper(substr(trim($JGMode),0,1)) == 'J')? 'J':'G';
/* -------------------------------------------------
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;
// --------------------------------------------------------
// If NOT Julian calendar, then apply Gregorian difference.
$JGDiff = 0;
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' to 'AD 19999-Dec-31'.
*/
if ($JDNum < -5583211 or $JDNum > 9025909) {return FALSE;}
return $JDNum;
}
function Inv_Num_JD_Num ($JDNum, $JGMode='G')
{
$JDNum = trim($JDNum);
$JGMode = substr(StrToUpper(trim($JGMode)),0,1);
$JGMode = ($JGMode == 'J')? 'J':'G';
$CMode = ($JGMode == 'J')? 0:1;
// -----------------------------------------
// Compute numerical date elements (y, m, d)
// according to the calendar mode selection.
$A = floor($JDNum + 0.5);
$B = $CMode*floor(($A - 1867216.25) / 36524.25);
$C = $A + $CMode*($B - floor($B/4) + 1);
$D = $C + 1524;
$E = floor(($D - 122.1) / 365.25);
$F = floor(365.25 * $E);
$G = floor(($D - $F) / 30.6001);
$d = $D - $F - floor(30.6001 * $G); // Day num (1 to 31)
$m = $G - 12*floor($G/14) - 1; // Month num (1 to 12)
$y = $E - 4716 + floor((14 - $m) / 12); // Mathematical year
$Y = ($y > 0)? $y : $y-1; // Calendar year (Negative = BC)
return "$Y-$m-$d";
}
/*
###########################################################################
This function checks numeric date elements
(y,m,d) to see if they correspond to a real
calendar date.
BC years = Negative values. There
is no year 0 (zero), by definition.
DEPENDENCIES: Num_JD_Num()
Inv_Num_JD_Num()
###########################################################################
*/
function Is_Valid_Date ($y,$m,$d, $JGMode='G')
{
$y = IntVal(trim($y));
$m = IntVal(trim($m));
$d = IntVal(trim($d));
$DateStr = "$y-$m-$d";
$JDNum = Num_JD_Num($y,$m,$d,$JGMode);
$CheckDateStr = Inv_Num_JD_Num($JDNum,$JGMode);
return ($DateStr == $CheckDateStr)? TRUE:FALSE;
}
/*
###########################################################################
Given a numerical calendar year and calendar mode, this boolean function
will return TRUE if a leap year or FALSE if a common year.
Negative years = BC years. There is no
calendar year 0 (zero), by definition.
###########################################################################
*/
function Is_Leap_Year ($YearNum, $JGMode='G')
{
$y = trim($YearNum);
// Get Julian or Gregorian (default) calendar mode argument.
$JGMode = substr(StrToUpper(trim($JGMode)),0,1);
$JGMode = ($JGMode == 'J')? 'J':'G';
// Handle Julian calendar leap year.
$LeapFlag = ($y % 4 == 0)? TRUE:FALSE;
if ($JGMode == 'J') {return $LeapFlag;}
// Done if NOT a Gregorian century year ending in 00.
if ($y % 100 <> 0) {return $LeapFlag;}
// Leap year ONLY if Gregorian century year is divisible by 400.
return ($y % 400 == 0)? TRUE:FALSE;
}
/*
###########################################################################
This function returns the number of days in any given month automatically
taking into account any leap years.
DEPENDENCY:
Is_Leap_Year()
ERRORS:
The value 0 (zero) is returned on error.
###########################################################################
*/
function Month_Days ($YearNum, $MonthNum, $JGMode='G')
{
$y = trim($YearNum);
$m = trim($MonthNum);
if (!Is_Numeric($y) or !Is_Numeric($m) or $m < 1 or $m > 12)
{return 0;}
// No Error. If not 'J' mode, then default to 'G' mode.
$JG = substr(StrToUpper(trim($JGMode)),0,1);
$JG = ($JG == 'J')? 'J':'G';
// Determine raw month days prior to any leap year adjustment.
$mDays = substr('312831303130313130313031', 2*($m-1), 2);
// Get leap year adjustment.
$LeapAdj = (Is_Leap_Year($y, $JG))? 1:0;
return ($m == 2 and $LeapAdj == 1)? $mDays + 1 : $mDays;
}
/*
###########################################################################
This function returns the 3-letter day of the week abbreviation correspond-
ing to any signed (+/-) Julian Day Number value.
It works equally well for both the Julian and Gregorian calendars.
It does not matter to which calendar the JD Number belongs, the days of
the week work out to the same values either way. JD Number 2433070 is
Thursday on the Julian calendar. The same JD Number refers to Thursday
on the Gregorian calendar as well, even though the calendar dates are
very different. Thus, the same algorithm is used for either calendar.
ERRORS:
Returns FALSE if JD Number argument is non-numeric.
NO DEPENDENCIES
###########################################################################
*/
function JD_Num_to_DoW_Str ($JDNumber)
{
$JDNum = trim($JDNumber);
// Return FALSE if JD Number argument is non-numeric.
if (!Is_Numeric($JDNum)) {return FALSE;}
// Compute Day-Of-Week index (0 to 6)
// from signed JD Number.
$DoWi = (7 + (($JDNum + 1) % 7)) % 7;
// Return the 3-letter month abbreviation string.
return substr('SunMonTueWedThuFriSat', 3*((7+(($JDNum + 1) % 7)) % 7), 3);
}
// AD 02025-Nov-16
function JD_Num_Table ($DateInit, $NumDays)
{
$N = abs(trim($NumDays)); if ($N == 0) {$N = 1;}
$JDNumInit = JD_Num ($DateInit, 'G');
$JDNumStop = $JDNumInit + $N;
if ($JDNumStop >= 9025909) {$JDNumStop = 9025909+1;}
$TableText = '';
$PrevDateStrG = '';
for ($JDNum = $JDNumInit; $JDNum < $JDNumStop; $JDNum++)
{
$DateStr = Inv_JD_Num($JDNum, 'G');
$JDNumJ = JD_Num($DateStr, 'J');
$JDNumG = JD_Num($DateStr, 'G');
$DiffDays = SPrintF("% +4d", $JDNumG - $JDNumJ);
$DateStrG = Inv_JD_Num($JDNumG, 'G');
$DateStrJ = Inv_JD_Num($JDNumJ, 'J');
$TableText .= "$DateStrG $JDNumG | $DateStrJ $JDNumJ | $DiffDays d\n";
}
$TableText = trim(Str_Replace('+0 d', '0 d', $TableText));
return
"GREGORIAN_CALENDAR JD_Num | JULIAN_CALENDAR JD_Num | Diff
----------------------------- | ----------------------------- | ------
$TableText
----------------------------- | ----------------------------- | ------
GREGORIAN_CALENDAR JD_Num | JULIAN_CALENDAR JD_Num | Diff";
}
?>