<?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 "&#97;&#116;&#32;&#76;&#111;&#99;&#97;&#108;&#32;&#84;&#105;&#109;&#101;&#32;"$LTC "&#85;&#84;&#67;";
   
$_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_))."&minus;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&nbsp;Span:<br>BC 19999-Jan-01-Tue/Thu &nbsp;to&nbsp; 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 or $m 12) {$Month 0; break;}
       
$Month substr($MONTHS3*($m-1), 3);
      }
        
$jj StrPos($MONTHS$Month);
              if (
$jj === FALSE) {$Month 0; break;}
        
$Month substr($MONTHS$jj3);
        
$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 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 <= or $Year 19999)
      {
       
$ErrFlag TRUE;
       
$ErrMssg "Bad year number value.\n'$Year'\n\nThe year value must be a non-zero integer &le; 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 &minus; 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 Max(Array_Map('StrLen'PReg_Split("[\n]"trim($TextArea1Text))));
   if (
$Text1Cols 80) {$Text1Cols 80;}  // Default
   
$Text1Rows Substr_Count($TextArea1Text"\n");

// -----------
// Text Area 2

   
$Text2Cols Max(Array_Map('StrLen'PReg_Split("[\n]"trim($TextArea2Text))));
   if (
$Text2Cols 80) {$Text2Cols 80;} // Default
   
$Text2Rows 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&nbsp;Start&nbsp;Date&nbsp;&nbsp;<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&nbsp;to&nbsp;Tabulate&nbsp;From&nbsp;Start&nbsp;&nbsp;<input name="N" type="text" value="
$N" size="5" maxlength="4">&nbsp;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;'>
&nbsp;View/Copy Source Code&nbsp;</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($BCADDateStr1StrLen($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($BCADYear0,2));
   
$Y trim(substr($BCADYear2StrLen($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 == 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 $i/3;
      }


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

   
if ($m 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+$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+$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     = (+ ($JDNum 1) % 7) % 7;
   
$DoW   substr('SunMonTueWedThuFriSat'3*$i3);
   
$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+$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+$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 == 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 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 == and $LeapAdj == 1)? $mDays $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 = (+ (($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"
;

}



?>