<?php

/*
Lunar Distance, Apogee and Perigee Calculator

Language: PHP v7.1.8
Author: Jay Tanner - 2018
www.NeoProgrammics.com

This program computes the basic geocentric lunar ephemeris statistics for
any date, time and local time zone at any given moment within the 600-year
span from 1600 to 2200.

It computes the basic geocentric lunar ephemeris statistics which are
given below:

Coordinates, distance and phase image of the moon at any given moment:
Right Ascension
Declination
Distance in kilometers and miles
Phase Angle
Phase Description
Phase Image


Dates, times, coordinates and distances of the lunar quarter phases:
New Moon
First Quarter
Full Moon
Last Quarter moon

Dates, times and coordinates of distance extremes:
lunar apogee  - Moon at maximum orbital distance
lunar perigee - Moon at minimum orbital distance

--------------
TECHNICAL DATA

Ephemeris Model : NASA/JPL Development Ephemeris (DE405).
Applied Delta T : NASA polynomial expressions, resolved to nearest second.
Nutation Series : IAU 1980 Nutation Series.
Time Scale Used : Astronomical TT scale used for internal computations.

The DE405 ephemeris spans the 600-year range from 1600 Jan 01 to 2200 Dec 31
on the modern Gregorian calendar.

The computations are referred to the true ecliptic and equinox of the date.
-----------------------------------------------------------------------------

   This program 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 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

*/

// -------------------------
// Prepare for cookie usage.
   ob_start();

// Define the program cookie name and set it to expire in 30 days.
   $CookieName = 'Geocentric_Lunar_Events_Calculator_Settings';
   $SetToExpireIn30Days = time() + 30*86400;

// Set minimum and maximum year span to allow.   Any year outside this
// range will be considered an invalid year.   For the DE405 ephemeris,
// the valid year range is from 1600 to 2200 on the Gregorian calendar.
   $YMin = '1600';
   $YMax = '2200';

// Define TextArea text and background colors.
   $TxColor = 'black';
   $BgColor = 'white';

   $LTUTStr = 'LT';

// Define script path and filename.
   $_SCRIPT_PATH_     = BaseName($_SERVER['SCRIPT_FILENAME']);
   $at = "&#97;&#116;"; $UTC = "&#85;&#84;&#67";
   $_RUN_THIS_SCRIPT_ = $_SERVER['SCRIPT_NAME'];

// Define internal document page title, HTML page heading text and revision date
   $_INTERNAL_TITLE_  = "Basic Geocentric Lunar Events Calculator";
   $_INTERFACE_TITLE_ = "<span style='font-size:18px;'>Basic 600-Year Geocentric Lunar Events Calculator</span><br><span style='font-size:12px;'>Year Range &nbsp;=&nbsp; $YMin &nbsp;to&nbsp; $YMax &nbsp;=&nbsp; 600 years&nbsp; on the Gregorian Calendar</span><br><span style='font-size:10px;'>Astronomy Program by Jay Tanner</span>";
   $_REVISION_DATE_   = 'Revised: '. gmdate("l, F d, Y $at h:i A   $UTC", FileMTime($_SCRIPT_PATH_));


// Define status message to be displayed while working.
   $w = "            COMPUTING ... THIS COULD TAKE SEVERAL SECONDS.";
   $StatusText = "OutputText.innerHTML='$w';";

// Do this only if the [COMPUTE] button was NOT clicked.
   if (!isset($_POST['ComputeButton']))
      {
// IF the [COMPUTE] button was NOT clicked AND an active cookie is
// found, then read and restore the interface settings from it.
   if (isset($_COOKIE[$CookieName]))
  {
   $CookieDataString = $_COOKIE[$CookieName];
    list
   (
    $Y,
    $mn,
    $dn,
    $JG,
    $hhmmssLT,
    $hhmmTZ,
    $DTOnOff
   ) = preg_split("[\|]", $CookieDataString);

// Do some formatting
   $m = intval($mn);
   $d = intval($dn);
   $JG = (strtoupper(substr(trim($JG), 0,1)) == 'J')? 'J':'G';
   $DTOnOff = (substr(strtoupper(trim($DTOnOff)),0,2) == 'OF')? 'OFF':'ON';
   $dTBGColor = 'white';
   $DTOnOffBGColor = ($DTOnOff == 'OFF')? 'silver':'cyan';
   if ($DTOnOffBGColor == 'silver') {$dTBGColor = 'silver';}

  } // End of  if isset(_COOKIE[...])

   else

// SET THE DEFAULT INTERFACE STARTUP VALUES
// TO CURRENT SYSTEM DATE/TIME/ZONE.
 {
  $Y  = date('Y');
  $m  = date('n'); $mn  = sprintf("%02d", $m);
  $d  = date('j'); $dn  = sprintf("%02d", $d);
  $JG = 'G';

  $hhmmssLT = date('H:i:s');
  $hhmmTZ   = substr(Hours_to_HMS(date('Z')/3600, 0, '+', ':'),0,6);
  $DTOnOff  = 'ON';
  $DTOnOffBGColor = 'cyan';
  $dTBGColor = 'white';

  $hhmmssdT = '';

// Store interface settings in cookie.
   $CookieDataString = "$Y|$mn|$dn|$JG|$hhmmssLT|$hhmmTZ|$DTOnOff";
   setcookie ($CookieName, $CookieDataString, $SetToExpireIn30Days);
  }
      } // End of  if (!isset(_POST['ComputeButton']))


// Read values of all interface settings.
   if (isset($_POST['ComputeButton']))
{
   $Y  = @trim($_POST['Y']);  $Y = intval($Y);
   $mn = @trim($_POST['mn']); $m = intval($mn);
   $dn = @trim($_POST['dn']); $d = intval($dn);

   $JG = @trim($_POST['JG']);
   $JG = (strtoupper(substr(trim($JG), 0,1)) == 'J')? 'J':'G';

   $hhmmssLT = @trim($_POST['hhmmssLT']);
   $hhmmssLT = Num_to_HMS ($hhmmssLT);

   $hhmmTZ = @trim($_POST['hhmmTZ']); if ($hhmmTZ == '') {$hhmmTZ = '+00:00';}
   if (HMS_to_Hours($hhmmTZ) > 0 and (substr($hhmmTZ,0,1)) <> '+') {$hhmmTZ = "+$hhmmTZ";}
   $hhmmTZ = substr(Num_to_HMS ($hhmmTZ),0,6);
   $hhmmTZ = RTrim($hhmmTZ, ':');
   $hhmmTZ = (HMS_to_Hours($hhmmTZ) == 0)? '+00:00' : $hhmmTZ;

   $hhmmssdT = @trim($_POST['hhmmssdT']);

// Do some interface formatting.
   $mn = sprintf("%02d", $m);
   $dn = sprintf("%02d", $d);
   $hhmmssLT = Hours_to_HMS(HMS_to_Hours($hhmmssLT), 0, '', ':');

   $hhmmTZ = substr(Hours_to_HMS(HMS_to_Hours($hhmmTZ), 0, '+', ':'),0,6);

   $DTOnOff = @trim($_POST['DTOnOff']);
   $DTOnOff = (substr(strtoupper(trim($DTOnOff)),0,2) == 'OF')? 'OFF':'ON';

   $DTOnOffBGColor = ($DTOnOff == 'OFF')? 'silver':'cyan';
   $dTBGColor = ($DTOnOff == 'OFF')? 'silver':'white';

   if ($DTOnOffBGColor == 'silver') {$dTBGColor = 'silver';}

// Store interface settings in cookie.
   $CookieDataString = "$Y|$mn|$dn|$JG|$hhmmssLT|$hhmmTZ|$DTOnOff";
   setcookie ($CookieName, $CookieDataString, $SetToExpireIn30Days);
}

   $wYMin = ($JG == 'J')? -4713 : 1600;

   if ($JG == 'G')
  {
   $MinDateStr = Inv_JD_Num(JD_Num($wYMin, 11, 24, $JG), $JG, 3);
  }
 else
  {
   $MinDateStr = Inv_JD_Num(JD_Num($wYMin, 1, 1, $JG), $JG, 3);
  }

   $MaxDateStr = Inv_JD_Num(JD_Num($YMax, 12, 31, $JG), $JG, 3);

   if (intval($MinDateStr) > 1000) {$MinDateStr = str_replace('+', ' ', $MinDateStr);}





// Filter for date error. TRUE = Date is OK
// or FALSE = Bad date error.
   $DateFlag = Check_Date($Y, $m, $d, $JG);
   $YearFlag = ($Y < $YMin or $Y > $YMax)? FALSE:TRUE;

if ($DateFlag === FALSE or $YearFlag === FALSE)
{
   $TextArea2Text = $TextArea3Text = $TextArea4Text = 'ERROR';
   $MmmStr  = $DoWStr = $URL = '';

   $TiltAngDeg = '0deg';

   $PhaseIMG  = 'small_lunar_phases/blank_transp.png';
   $PhaseMssg = '';
   $DegPhase  = '';

   $DateTimeStringUT = '';

   $TxColor = 'white';   $BgColor = '#AA0000';

   $hhmmssDT = '-';  $hhmmssdT = '';

   $dTBGColor = 'silver';

   if ($YearFlag === FALSE)
  {

   $JGStr  = ($JG == 'J')? 'Julian':'Gregorian';

   $TextArea1Text =
"DATE ERROR:

The given year ($Y) is not within the valid range from 1600 to 2200
on the $JGStr calendar.";
  }
   else
{
   $TextArea2Text = $TextArea3Text = 'ERROR';
   $ZeroNoteStr = '';

   if ($YMin < 0) {$ZeroNoteStr = "For calendar years:  Neg|Pos = BC|AD\n\n";}

   $JGStr  = ($JG == 'J')? 'Julian':'Gregorian';

   $TextArea1Text =
"DATE ERROR:

The given date elements (Year Month Day) = ($Y $mn $dn) do not represent
any valid date within the allowed $JGStr calendar range.

--------------------
VALID CALENDAR RANGE

Valid dates must be in the 600-year range
From 1600 Jan 01 Sat  to  2200 Dec 31 Wed

-------------------
VALID DATE ELEMENTS\n\n"
.$ZeroNoteStr.
"Year  $wYMin to $YMax on the $JGStr calendar
Month    1 to 12 - For January to December
Day      1 to 31 - Depending on the month and if or not it is a leap year.";
}

}

   else

// Do this if no date error detected.

{
// Compute JD Number for the given local date.
   $JDNumLT = JD_Num($Y, $m, $d, $JG);

// Compute local Julian Date for 00:00:00 of Date.
   $JD00LT = $JDNumLT - 0.5;

// Compute NASA Delta T estimate for local date
// if option is ON, then Delta T is applied.
// If option is OFF, then set Delta T to zero.
   if ($DTOnOff == 'ON')
      {
       if (@trim($hhmmssdT) == '')
          {$hhmmssDT = NASA_Delta_T_Poly($JD00LT, $JG, 3);}
       else
          {
           $hhmmssDT = Num_to_HMS($hhmmssdT);
           $hhmmssdT = Hours_to_HMS(HMS_to_Hours($hhmmssDT), 0, '+', ':');
          }
      }
   else
      {
       $hhmmssDT = '-';  $hhmmssdT = @$hhmmssdT;
      }



// Compute fractions of a day corresponding to LT, TZ and DT.
   $FracDayLT = HMS_to_Hours($hhmmssLT)/24;
   $FracDayTZ = HMS_to_Hours($hhmmTZ)/24;
   $FracDayDT = HMS_to_Hours($hhmmssDT)/24;

// Compute Julian Dates for LT, UT and TT scales.
   $JDLT = $JD00LT + $FracDayLT;
   $JDUT = $JDLT - $FracDayTZ;
   $JDTT = $JDUT + $FracDayDT;

// Do some formatting.
   $JDLT = sprintf("%7.8f", $JDLT);
   $JDUT = sprintf("%7.8f", $JDUT);
   $JDTT = sprintf("%7.8f", $JDTT);

// Compute equivalent times table for LT, UT and TT scales.
   $TimesTableText = Times_Table($Y, $m, $d, $hhmmssLT, $hhmmTZ, $JG);

// Get number of days in the given month.
   $mDays = Days_in_Month($Y, $m, $JG);

// Get 3-letter abbreviations for month and day of week names.
   $MmmStr = substr("JanFebMarAprMayJunJulAugSepOctNovDec", 3*($m-1), 3);
   $DoWStr = substr("SunMonTueWedThuFriSat", 3*(($JDNumLT+1) % 7), 3);

// Construct Date/Time string for UT.
   $DateTimeStringUT = str_replace('+', '', Inv_JD ($JDUT));

// Construct times table corresponding to local time and zone.
   $TimesTableText = Times_Table($Y, $m, $d, $hhmmssLT, $hhmmTZ, 'G');
   $TimesTableText = str_replace('Year Convention: Neg|Pos = BC|AD', '', $TimesTableText);

// Determine time scale symbol to use.
// Default TScale = 'UT'
   $TScale = 'UT';
   if ($hhmmTZ == '+00:00' and $DTOnOff == 'OFF') {$TScale = 'TT';}

   if ($FracDayTZ == 0) ($LTUTStr = 'UT');

// ###############################################################
// ###############################################################
// ###############################################################
// START OF THE MAIN COMPUTATIONS BLOCK USING THE ABOVE VARIABLES.

// Construct master geocentric TT work table for the given Year/Month
// with all basic lunar events within the month marked.
   $GeoMonthTableTextTT = Lunar_Month_Table_TT($Y, $m);
   $GeoMonthTableTextTT = Mark_All_Lunar_Events($GeoMonthTableTextTT);

// Compute geocentric table for the given local time zone
// with all basic lunar events within the month marked.
   $GeoMonthTableTextLT = TT_Table_to_LT_TZ($GeoMonthTableTextTT, $hhmmTZ);
   $GeoMonthTableTextLT = Mark_All_Lunar_Events($GeoMonthTableTextLT);

// Construct working tables used for the lunar quarter phase computations.
   $LQPWorkTablesTextLT = trim(Make_Q_Phase_Tables($GeoMonthTableTextLT));

// Compute work tables used for the lunar apogee and perigee computations.
   $LAPWorkTablesTextLT = trim(Make_A_P_Tables($GeoMonthTableTextLT));

// Compute local JD values for all quarter phase events.
   $LQPJDTableTextLT = Compute_Q_Phases($LQPWorkTablesTextLT);

// Compute local JD values for all apogee and perigee events.
   $LAPJDTableTextLT = Compute_A_P($LAPWorkTablesTextLT);

// Combine both the quarter phases, apogee and perigee
// JD tables and sort the events by Julian Date order.
   $AllLunarEventsTableTextLT = Sort_Table_Text($LQPJDTableTextLT."\n".$LAPJDTableTextLT);

// Compute date and time strings for all combined lunar events.
   $AllLunarEventsDatesTimesTableTextLT = JD_Events_to_Dates_Times ($AllLunarEventsTableTextLT);

// Compute lunar ephemeris statistics for given local date/time/zone.
   $LunarEphemStatsStrLT = Interp_Lunar_Ephem_Stats ($GeoMonthTableTextLT, $JDLT);

// Parse the lunar ephemeris statistics.
   list
  (
   $wJDLT,
   $wRAHrs,
   $wDeclDeg,
   $wDistKm,
   $wPhaseDeg
  ) = preg_split("[ ]", strip_wspace($LunarEphemStatsStrLT));

// Compute details of all lunar events of the month.
   $LunarEventsDetailsText = Lunar_Events_Details ($AllLunarEventsDatesTimesTableTextLT, $GeoMonthTableTextLT, $hhmmTZ);






// ------------------------------
// DO SOME PRE-OUTPUT FORMATTING.

   if ($FracDayTZ == 0) {$LTUTStr = 'UT';}
   $JDLT = sprintf("%7.8f", $JDLT);
   $JDUT = sprintf("%7.8f", $JDUT);
   $JDTT = sprintf("%7.8f", $JDTT);
   $FracDayDT = sprintf("%+7.8f", $FracDayDT);

   $wRAHMS   = Hours_to_HMS ($wRAHrs, 3, '', 'h');
   if ($FracDayTZ == 0) {$LTUTStr = 'UT';}

   $wDeclDMS = str_replace  ('+0', '+', Deg_to_DMS ($wDeclDeg, 2, '+', 'd'));

   $wDistMi = sprintf("%6.3f", $wDistKm / 1.609344);

   $PhaseViewText = Lunar_Phase_to_Text ($wPhaseDeg);

   $DegPhase = round($wPhaseDeg);
   $PhaseIMG = "small_lunar_phases/phase_".sprintf("%03d", $DegPhase).".png";
   $PhaseMssg = 'Phase ';
   $wPhaseDeg = sprintf("%1.3f", $wPhaseDeg);

   if (abs(trim($wDeclDeg)) >= 10)
      {
       $wDeclDMS = str_replace('-0', '-', $wDeclDMS);
       $wDeclDMS = str_replace('+0', '+', $wDeclDMS);
      }

   if (abs(trim($wDeclDeg)) < 10)
      {
       $wDeclDMS = str_replace('-00', '-0', $wDeclDMS);
       $wDeclDMS = str_replace('+00', '+0', $wDeclDMS);
      }

   $wDeclDeg = sprintf("%+010.6f", $wDeclDeg);

   $wRAHrs = sprintf("%010.7f", $wRAHrs);

// Determine amount to tilt the lunar phase image.
// Simulation of the parallactic angle tilt effect.
   $w = ($wPhaseDeg > 0 and $wPhaseDeg < 180)? 23 : -23;
   $TiltAngDeg = round(($w * cos(deg2rad(trim($wPhaseDeg))))).'deg';


// END OF MAIN COMPUTATIONS BLOCK
// #############################################################
// #############################################################
// #############################################################











// Construct output text block 1.
   $TextArea1Text =
"             BASIC 600-YEAR LOCAL GEOCENTRIC LUNAR EVENTS CALCULATOR

            Based on the NASA/JPL Development Ephemeris (DE405) Model
                 and the NASA Polynomial Expressions for Delta T

$TimesTableText

=============================================================================
Lunar Ephemeris Statistics For the Given Date, Time and Zone Above

Julian Date     = $JDTT TT

Right Ascension =  $wRAHMS =  $wRAHrs h
Declination     = $wDeclDMS  = $wDeclDeg&deg;
Distance        =  $wDistMi mi   =  $wDistKm km
Phase Angle     =  $wPhaseDeg&deg;
Phase Image     =  $PhaseViewText";





// Construct output text block 2.
   $TextArea2Text =
"DETAILS OF THE BASIC GEOCENTRIC LUNAR EVENTS FOR THE MONTH  $LTUTStr

For month of $MmmStr $Y          Local Time Zone Offset UT$hhmmTZ
$LunarEventsDetailsText

";

// Construct output text block 3.
   $TextArea3Text =
"GEOCENTRIC  LUNAR EPHEMERIS TABLE   ($LTUTStr)
True Ecliptic and Equinox of Date

For the Month of : $Y $MmmStr 01 to $MmmStr $mDays
Time Zone Offset : UT$hhmmTZ
Tabular Interval : 0.5 day (at 00:00 and 12:00 $LTUTStr on each date)

==============================================================================
                                       GEOCENTRIC
                            --------------------------------
 Calendar_Date     JD_$LTUTStr     RA_Hours   Decl_Deg   Dist_Km    Phase_Deg
---------------  ---------  ---------- ---------- ----------  ---------
$GeoMonthTableTextLT
---------------  ---------  ---------- ---------- ----------  ---------
 Calendar_Date     JD_$LTUTStr     RA_Hours   Decl_Deg   Dist_Km    Phase_Deg
                            --------------------------------
                                       GEOCENTRIC
==============================================================================";



// Construct output text block 4.
   $TextArea4Text =
"GEOCENTRIC  LUNAR EPHEMERIS TABLE   (TT)
True Ecliptic and Equinox of Date

For the Month of : $Y $MmmStr 01 to $MmmStr $mDays
Tabular Interval : 0.5 day (at 00:00 and 12:00 TT on each date)

==============================================================================
                                       GEOCENTRIC
                            --------------------------------
 Calendar_Date     JD_TT     RA_Hours   Decl_Deg   Dist_Km    Phase_Deg
---------------  ---------  ---------- ---------- ----------  ---------
$GeoMonthTableTextTT
---------------  ---------  ---------- ---------- ----------  ---------
 Calendar_Date     JD_TT     RA_Hours   Decl_Deg   Dist_Km    Phase_Deg
                            --------------------------------
                                       GEOCENTRIC
==============================================================================";


}





// Determine the number of text rows to use in the output TextAreas.
   $TextArea1Rows = 2 + substr_count($TextArea1Text, "\n");
   $TextArea2Rows = 2 + substr_count($TextArea2Text, "\n");
   $TextArea3Rows = 2 + substr_count($TextArea3Text, "\n");
   $TextArea4Rows = 2 + substr_count($TextArea4Text, "\n");


   if ($TextArea1Rows == 2) {$TextArea1Rows = 0;}

// Generate HTML web page to display computed results.
   print <<< _HTML

<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="content-type" content="text/html; charset=UTF-8">

<meta name="robots"    content="noindex,nofollow">
<meta name="googlebot" content="noindex,nofollow">

<title>$_INTERNAL_TITLE_</title>

<script language="javascript">

function sleep_until (seconds) {
   var max_sec = new Date().getTime();
   while (new Date() < max_sec + seconds * 1000) {}
    return true;
}

</script>



<style type='text/css'>
BODY
{color:silver; background-color:black;  font-family:Verdana; font-size:14px}

TABLE
{font-size:12px; border: 1px solid black;}

TD
{color:black; background-color:white;  line-height:200%; font-size:12px; padding:6px; text-align:center;}

DIV
{color:black; background-color:white;  font-family:Verdana; text-align:justify; padding:4px; font-size:12px;}

PRE
{color:black; background-color:white;  font-family:monospace; font-size:14px; font-weight:bold; text-align:left; padding:4px;}

CODE
{font-family:monospace; font-size:15px; font-weight:normal;}

TEXTAREA
{color:black; background-color:white; font-family:monospace; font-size:15px; font-weight:bold; box-shadow:2px 3px 4px #888888; border-radius: 8px; border:1px solid black; padding:4px; white-space:pre;}

INPUT[type="text"]::-ms-clear {width:0; height:0;}

INPUT[type="text"]
{color:black; background-color:white; font-size:18px; font-family:monospace; font-weight:bold; text-align:center; box-shadow:2px 2px 3px #666666; border:2px solid black;}

INPUT[type="text"]:focus
{background-color:white; box-shadow:2px 2px 3px #666666; font-family:monospace; font-size:18px; border:2px solid red; text-align:center; font-weight:bold;}

INPUT[type="submit"]
{font-weight:bold;}

INPUT[type="submit"]:hover
{box-shadow:2px 2px 5px #555555; font-weight:bold;}

INPUT[type="checkbox"]:checked
{box-shadow:2px 2px 3px #555555;}

HR {background-color:black; height:2px; border:0px;}

A:link,  A:visited {background-color:transparent; color:blue; font-family:Verdana; font-size:12px; font-weight:bold; text-decoration:none; line-height:150%; padding:2px;}
A:hover, A:active  {background-color:transparent; color:red;  font-family:Verdana; font-size:12px; font-weight:bold; text-decoration:none; line-height:150%; padding:2px;}

::selection      {background-color:yellow; color:black;}  /* Only affects IE9+ and Safari     */
::-moz-selection {background-color:yellow; color:black;}  /* Only affects FireFox and Mozilla */
</style>


</head>

<body>

<form name="form1" method="post" action="">

<table width="800" border="0" cellspacing="1" cellpadding="3">

<tr><td colspan="9" style='color:white; background-color:#0000DD; border:2px solid white; border-radius:8px 8px 0px 0px;'><b>$_INTERFACE_TITLE_</b></td></tr>


<td width='20%'>
<b style='font-size:14px;'>Calendar Date</b><br>Year&nbsp;&nbsp;Month&nbsp;&nbsp;Day<br>
<input name='Y'  type='text' value="$Y"  size='6' maxlength='5'>
<input name='mn' type='text' value="$mn" size='3' maxlength='2'>
<input name='dn' type='text' value="$dn" size='3' maxlength='2'>
<input name='JG' type='hidden' value="$JG" size='2' maxlength='1' style='color:white; background-color:black;' readonly>
</td>


<td width='20%'>
<b style='font-size:14px;'>Local&nbsp;Time</b><br>
&nbsp;&nbsp;&nbsp;00h &nbsp;to&nbsp; 24h<br>
<input name="hhmmssLT" type="text" value="$hhmmssLT" size="9" maxlength="8">
</td>

<td width='20%'>
<b>Time&nbsp;Zone&nbsp;Offset</b><br>Neg&nbsp;|&nbsp;Pos&nbsp;&nbsp;=&nbsp;&nbsp;W&nbsp;|&nbsp;E<br>
<b style='font-size:15px;'>UT </b><input name="hhmmTZ" type="text" value="$hhmmTZ" size="7" maxlength="6">
</td>

<td width='20%'>
<b style='font-size:14px;'>&Delta;T (Delta T)</b><br>
NASA Polynomial&nbsp;Value<br>
<input name="hhmmssDT" style='color:white; background-color:black;' type="text" value="$hhmmssDT" size="10" maxlength="9" readonly><input name="hhmmssdT" type="hidden" value="$hhmmssdT" size="10" maxlength="9" style="color:black; background-color:$dTBGColor;">&nbsp;&nbsp;<input name='DTOnOff' type='hidden' size='4' maxlength='3' value="$DTOnOff" style="color:black; background-color:$DTOnOffBGColor;">
</td>

<td style='color:silver; background-color:#000044; border:1px solid #444444;'>
<img src="$PhaseIMG" style="-o-transform:rotate($TiltAngDeg);-ms-transform:rotate($TiltAngDeg);transform:rotate($TiltAngDeg);"
><br>$PhaseMssg&nbsp;&nbsp;$DegPhase&deg;
</td>


</tr>


<tr><td colspan='9' style='color:white; background-color:black;' ><input name="ComputeButton" type="submit" action="post" value=" C O M P U T E " OnClick="StatusText;"><br></td></tr>



<tr>
<td colspan="7" style='background-color:black;'>
<textarea name="OutputText1" style="font-size:18px; color:$TxColor; background-color:$BgColor; padding:6px; box-shadow:none; border:2px solid white;" cols="81" rows="$TextArea1Rows" OnDblClick="this.select();" OnMouseUp="return true;" readonly>
$TextArea1Text
</textarea>
</td>
</tr>

<tr>
<td colspan="7" style='color:silver; background-color:black;'>
Double-Click to Select ALL Text<br>
<textarea name="OutputText2" style="font-size:18px; color:black; background-color:white; padding:6px; box-shadow:none; border:2px solid white;" cols="81" rows="$TextArea2Rows" OnDblClick="this.select();" OnMouseUp="return true;" readonly>
$TextArea2Text
</textarea>
</td>
</tr>


<tr>
<td colspan="7" style='color:silver; background-color:black;'>
Double-Click to Select ALL Text<br>
<textarea name="OutputText3" style="font-size:18px; color:black; background-color:white; padding:6px; box-shadow:none; border:2px solid white;" cols="81" rows="$TextArea3Rows" OnDblClick="this.select();" OnMouseUp="return true;" readonly>
$TextArea3Text
</textarea>
</td>
</tr>


<tr>
<td colspan="7" style='color:silver; background-color:black;'>
Double-Click to Select ALL Text<br>
<textarea name="OutputText4" style="font-size:18px; color:black; background-color:white; padding:6px; box-shadow:none; border:2px solid white;" cols="81" rows="$TextArea4Rows" OnDblClick="this.select();" OnMouseUp="return true;" readonly>
$TextArea4Text
</textarea>
</td>
</tr>


<tr><td colspan="7" style='color:gray; background-color:black;'>Program by Jay Tanner - $_REVISION_DATE_</td></tr>

</table>
</form>
</body>

</html>

_HTML;





/*
   TRANSPORTED UTILITY AND LUNAR EPHEMERIS FUNCTIONS
*/


   function Strip_WSpace ($TextString)
{
   return preg_replace("/\s+/", " ", trim($TextString));
}




/*
   This function translates an assumed time string into standard form.
   It applies ONLY to purely numerical time strings in integer form.
   It does NOT handle fractional time values.

   Any numerical sign (±) is preserved in the returned result.

   Input Number         Output string
   1                    01:00:00
   12                   12:00:00
   123                  01:23:00
   1234                 12:34:00
   12345                01:23:45
   123456               12:34:56

Numeric strings are read in 2-digit groups from R to L

Odd length numbers are converted to even length and then
the even length numbers are converted to 6 digit values.

THIS FUMCTION DOES NOT CHECK FOR INVALID TIMES.  IT ONLY
CASTS THE GIVEN NUMERIC TIME STRING INTO NORMAL HH:MM:SS
FORMAT AS DEFINED ABOVE.

*/

/*
   This function translates an assumed time string into standard form.
   Applies ONLY to purely numerical time strings.


   Time String          Translates to
   1                    01:00:00
   12                   12:00:00
   123                  01:23:00
   1234                 12:34:00
   12345                01:23:45
   123456               12:34:56

Numeric strings are read in 2-digit groups from R to L

Odd length numbers are converted to even length and then
the even length numbers are converted to 6 digit values.

THIS FUMCTION DOES NOT CHECK FOR INVALID TIMES.  IT ONLY
CASTS THE GIVEN NUMERIC TIME STRING IN NORMAL HH:MM:SS
FORMAT AS DEFINED ABOVE.

*/

   function Num_to_HMS ($NumHMSVal)
{
   $w = trim($NumHMSVal);

   if ($w == '') {return '00:00:00';}

// Return input unchanged if non-numeric argument
// rather than return an error message.
   if (!is_numeric($w)) {return $NumHMSVal;}

   $FirstChar = substr($w,0,1);

   $NumSign = (intval($w) < 0)? '-':'+';

// Remove any numerical (±) sign
// and work with absolute value.
   $w = str_replace($NumSign, '', $w);

// Construct (w) into a 6-digit integer.
   if (strlen($w) == 1) {$w = "0$w";}
   if (strlen($w) == 2) {$w = $w."0000";}
   if (strlen($w) == 3) {$w = "0$w";}
   if (strlen($w) == 4) {$w = $w."00";}
   if (strlen($w) == 5) {$w = "0$w";}

// Only keep (+) sign if it was in the original argument.
// Otherwise, a positive value will NOT be returned with
// a (+) sign. This does not effect the sign of negative
// values.
   if ($FirstChar <> '+' and $NumSign == '+') {$NumSign = '';}

// Done.  Return time equivalent with colons.
   return $NumSign.substr($w,0,2).':'.substr($w,2,2).':'.substr($w,4,2);

} // END OF  Num_to_HMS (...)




/*
   Level 2 function
   Dependency:
   JD_Num()
   Inv_JD_Num()

   This function checks to see if the given date arguments
   (Year, Month, Day) are valid for the given JGMode.

   INPUT:
   ±Y         = Signed year (Neg|Pos = BC|AD)
   Month      = Month Number (1 to 12)
   Day Number = Day Number   (1 to 31)

   OUTPUT:
   FALSE = BAD DATE
   TRUE  = DATE OK

*/
   function Check_Date ($Year, $Month, $Day, $JGMode='G')
{
   $Y = intval(trim($Year));
   $m = intval(trim($Month));
   $d = intval(trim($Day));
   $w = JD_Num($Y, $m, $d, $JGMode);
   return ($w === FALSE)? $w : TRUE;
}




   function JD_Num ($Year, $Month, $Day, $JGMode='G')
{
   $Y = intval(trim($Year));
   $m = intval(trim($Month));
   $d = intval(trim($Day));

   $GivenYmdElements = "$Y $m $d";
   $M = (strtoupper(substr(trim($JGMode),0,1)) == 'J')? 0:1;
   $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)) + $d - 1524
          + $M*(floor($c/4) - $c + 2);

// Check for an invalid date error by computing the proper
// (Y m d) values corresponding to the computed JD number.
// These values should numerically match the given date
// (Y m d) values if the given date was valid.

   $J = floor($JDNum + 0.5);   if($J < 0){return FALSE;}
   $e = $M*floor(($J - 1867216.25)/36524.25);
   $f = $J + $M*($e - floor($e/4) + 1);
   $g = $f + 1524;
   $h = floor(($g - 122.1)/365.25);
   $i = floor(365.25 * $h);
   $j = floor(($g - $i)/30.6001);
   $d = $g - $i - floor(30.6001 * $j);
   $m = $j - 12*floor($j/14) - 1;
   $w = $h + floor((14-$m)/12) - 4716;
   $Y = ($w > 0)? $w : $w-1;

// Check if the input date elements EXACTLY match the check date
// elements.   If not, then the given date is invalid, so return
// FALSE to indicate the date error.  Otherwise, return the JD
// number value.

   return ($GivenYmdElements <> "$Y $m $d")? FALSE : $JDNum;

} // End of  JD_Num (...)


/*
   This function return the calendar date elements (Y m d) corre-
   sponding to the given calendar mode and Julian Day number, or
   JD number, not to be confused with the Julian Date (JD).

   The JD Number is the serial number of the date on the calendar
   and is always an integer value.

   Mathematically, the Julian Day number is numerically equivalent
   to the Julian Date value  at 12:00:00 noon on the corresponding
   calendar date.

   JDNum  = Julian Day Number
   JGMode = 'J' or 'G' = Julian or Gregorian calendar mode

   ReturnMode = 1 to 5
            1 = '127 1 3'
            2 = '+0127 01 03'
            3 = '+0127 Jan 03 Fri'
            4 = ' 1901 Feb 09 Sat'
*/


   function Inv_JD_Num ($JDNum, $JGMode='G', $ReturnMode=1)
{
   $NumRetModes = 4;

   $j = trim($JDNum);  if (!is_numeric($j)) {return FALSE;}

   $J = floor($j + 0.5);  if ($J < 0) {return FALSE;}

   $w = abs(intval(trim($ReturnMode)));
   if ($w > $NumRetModes) {$RMode = $NumRetModes;} else {$RMode = $w;}

   $M = (strtoupper(substr(trim($JGMode),0,1)) == 'J')? 0:1;
   $e = $M*floor(($J - 1867216.25)/36524.25);
   $f = $J + $M*($e - floor($e/4) + 1);
   $g = $f + 1524;
   $h = floor(($g - 122.1)/365.25);
   $i = floor(365.25 * $h);
   $j = floor(($g - $i)/30.6001);
   $d = $g - $i - floor(30.6001 * $j);
   $m = $j - 12*floor($j/14) - 1;
   $w = $h + floor((14-$m)/12) - 4716;
   $Y = ($w > 0)? $w : $w-1;

   $w   = '';
   $Mmm = substr('JanFebMarAprMayJunJulAugSepOctNovDec', 3*($m-1), 3);
   $DoW = substr('SunMonTueWedThuFriSat', 3*(($J+1) % 7), 3);
   $Y   = substr('     '.sprintf("%+05d", $Y), -5);

// Format returned date string according to return mode value.
   switch ($RMode)
  {
   case  1: $w = intval($Y)." $m $d"; break;
   case  2: $w = sprintf("%+05d", $Y).' '.sprintf("%02d", $m).' '.sprintf("%02d", $d); break;
   case  3: $w = "$Y $Mmm ".sprintf("%02d", $d)." $DoW"; break;

   case  4: $w = "$Y $Mmm ".sprintf("%02d", $d)." $DoW";
            $w = str_replace('+', '', $w);
            break;

   default: $w = intval($Y)." $m $d"; break;
  }
   return $w;

} // End of Inv_JD_Num (JDNum, ...)








/*
   This is the inverse Julian Date function.  Given any
   general Julian Date and calendar mode, it returns the
   corresponding full date and time string. The fractional
   part of the Julian Date value is used to determine the
   time of day.

   This function is NOT the same as the Inv_JD_Num() function,
   which only returns the calendar date string and nothing more.

   The JD Number is ALWAYS an integer value.

   The Julian Date can have a fractional part.  The fractional
   part of the Julian Date value is used to determine the time
   of day.

   EXAMPLE
   print Inv_JD (2433056.43211234, 'G'); // = xxxxxxxx


*/

   function Inv_JD ($JD, $JGMode='G')
{
   $J  = floatval(trim($JD));  $JDNum = floor($J + 0.5);
   $JG = (strtoupper(substr(trim($JGMode),0,1)) == 'J')? 'J':'G';

   $hhmmssLT = Hours_to_HMS(24*(($J - 0.5) - floor($J - 0.5)), 0, '', ':');

   list($Y, $m, $d) = preg_split("[ ]", Inv_JD_Num($JDNum, $JG));

// Do some formatting.
   $Y   = sprintf("%+05d", $Y);  $m = intval($m);  $d = intval($d);
   $Mmm = substr("JanFebMarAprMayJunJulAugSepOctNovDec", 3*($m - 1), 3);
    $dn = sprintf("%02d", $d);
   $DoW = substr("SunMonTueWedThuFriSat", 3*(($JDNum + 1) % 7), 3);

   return "$Y $Mmm $dn $DoW $hhmmssLT";
}





   function Days_in_Month ($Year, $Month, $JGMode='G')
{
   $Y = intval(trim($Year));  if ($Y > 9999) {return FALSE;}
   $m = intval(trim($Month)); if ($m < 1 or $m > 12) {return FALSE;}
   $JG = (strtoupper(substr(trim($JGMode), 0,1)) == 'J')? 'J':'G';
   $mDays = substr("312831303130313130313031", 2*($m-1), 2);

// Check for invalid J/G year range according to J/G mode.
   if (($Y < -4713 and $JG == 'J')
   or  ($Y < -4714 and $JG == 'G')) {return FALSE;}

   if ($JG == 'J' and $Y % 4 == 0) {$LeapYear = TRUE;}
   else
  {
   $LeapYear = ($Y % 4 == 0 and !($Y % 100 == 0 and $Y % 400 <> 0));
  }
   if ($LeapYear and $m == 2) {return $mDays + 1;} return $mDays;
}
















   function HMS_to_Hours ($hhmmss)
{

// ----------------------------------------------------------
// Read hour angle string argument and force to lower case in
// the event any (h m s :) symbols are used.

   $hms = strtolower(trim($hhmmss));

// ------------------------------------------
// Convert any (h m s :) symbols into spaces.

   $hms = str_replace('h', ' ', $hms);
   $hms = str_replace('m', ' ', $hms);
   $hms = str_replace('s', ' ', $hms);
   $hms = str_replace(':', ' ', $hms);


// ----------------------------------------------------
// Normalize the spacing and then split and extract the
// individual time string elements (hh, mm,ss).

   $hms = preg_replace("/\s+/", " ", trim($hms));
   $whms = preg_split("[ ]", $hms);
   $whmsCount = count($whms);
   $hh = ($whmsCount >= 1)? bcadd($whms[0],"0", 20) : "0";
   $mm = ($whmsCount >= 2)? bcadd($whms[1],"0", 20) : "0";
   $ss = ($whmsCount >= 3)? bcadd($whms[2],"0", 20) : "0";

// ------------------------------------------------
// Remember and then remove any numerical (±) sign.

   $NumSign = (substr($hhmmss,0,1) == '-')? '-' : '';
   $hh = str_replace('-', '', $hh);
   $hh = str_replace('+', '', $hh);

// -------------------------------------------------------------
// If original angle argument began with a + sign, then preserve
// it so that all returned positive results will have a + sign.
// Otherwise, positive results will NOT have a + sign.

   if (substr($hhmmss,0,1) == '+') {$NumSign = '+';}

// --------------------------------------
// Compute decimal hours value equivalent
// to the combined hh,mm,ss values.

   $w2 = bcadd(bcadd(bcmul($hh,'3600',20),bcmul($mm,'60',20),20),$ss,20);

// ------------------------------------------------------------
// If result equates to zero, then suppress any numerical sign.

   if (bccomp($w2, '0', 20) == 0) {$NumSign = '';}

// ----------------------------------------------------------
// Round off result to 16 decimals, recalling numerical sign.

   $w =  bcadd(bcdiv($w2,3600, 20), '0.00000000000000005', 16);

// Trim off any redundant zeros/decimal point.
   return $NumSign . rtrim(rtrim($w, '0'), '.');

} // End of  HMS_to_Hours(...)




   function Hours_to_HMS ($hours, $ssDecimals=0, $PosSignSymbol='', $SymbolsMode='h')
{

// ---------------------------
// Initialize symbol carriers.
   $_h_ = $_m_ = $_s_ = '';

   if (trim($PosSignSymbol) == '')  {$PosSignSymbol = FALSE;}

// --------------------------------------------------------------
// Remember original numerical sign and work with absolute value.
   $sign = ($hours < 0)? '-' : '';    $hours = abs($hours);

// -------------------------------------------------------
// Remember numerical sign to be restored on exit, if any.
//   if (($PosSignFlag === TRUE or $PosSignFlag == '+') and $sign == '')
//      {$sign = '+';}

   if (($PosSignSymbol === TRUE or $PosSignSymbol == '+') and $sign == '') {$sign = '+';}

// ---------------------------------------------------
// Compute time elements from absolute hours argument.
   $hh = floor($hours);
   $minutes = 60*($hours - $hh);    $mm = floor($minutes);
   $seconds = 60*($minutes - $mm);  $ss = sprintf("%1.3f",  $seconds);

// -------------------------
// Format the time elements.
   $hh = sprintf("%02d",  $hh);
   $mm = sprintf("%02d",  $mm);
   $ss = sprintf("%1.$ssDecimals" . "f", $ss);

// ----------------------------------
// Patch for that blasted 60s glitch.
   if ($ss == 60) {$ss = 0; $mm++;}
   if ($mm == 60) {$mm = 0; $hh++;}

   $hh = sprintf("%02d",  $hh);
   $mm = sprintf("%02d",  $mm);

   $ss = sprintf("%1.$ssDecimals" . "f", $ss);
   if ($ss < 10) {$ss = "0$ss";}

// -----------------------------------------------
// Attach optional (h m s :) symbols as indicated.
// Default = 'h'
   if ($SymbolsMode == 0 or $SymbolsMode == '')
      {$_h_  =  $_m_  =  $_s_  = ' ';}

   if ($SymbolsMode == 1 or $SymbolsMode == ':')
      {$_h_  =  ':';  $_m_  =  ':';  $_s_  = '';}

   if ($SymbolsMode == 2 or strtolower($SymbolsMode) == 'h')
      {$_h_  =  'h ';  $_m_  =  'm ';  $_s_  =  's';}

// Done.
   return "$sign$hh$_h_$mm$_m_$ss$_s_";

} // End of  Hours_to_HMS(...)











   function Times_Table ($Year, $Month, $Day, $HHmmssLT='0', $HHmmTZ='0', $JGMode='G')
{
// These are GLOBALs for use as interface settings.
   GLOBAL $DTOnOff, $hhmmssdT, $hhmmssDT;

   $Y  = intval(trim($Year));   $Y  = sprintf("%d",   $Y);
   $m  = intval(trim($Month));  $mn = sprintf("%02d", $m);
   $d  = intval(trim($Day));    $dn = sprintf("%02d", $d);
   $JG = (strtoupper(substr(trim($JGMode),0,1)) == 'J')? 'J':'G';

   $JDNumLT   = JD_Num($Y, $m, $d, $JG);
   $hhmmssLT  = Hours_to_HMS(HMS_to_Hours($HHmmssLT), 0,  '', ':');
   $hhmmTZ    = Hours_to_HMS(HMS_to_Hours($HHmmTZ),   0, '+', ':');
   $hhmmTZ    = substr($hhmmTZ, 0,6);
   $FracDayTZ = HMS_to_Hours($hhmmTZ)/24;

   if ($FracDayTZ == 0) {$LTUTStr = 'UT';}else{$LTUTStr = 'LT';}

// $dTLineStr = "\nNASA Delta T   [OFF]                                     ";
   $dTLineStr = "";


   if ($DTOnOff == 'ON')
      {
       if ($hhmmssdT == '')
          {$hhmmssDT = NASA_Delta_T_Poly($JDNumLT, $JG, 3);}
       else
          {
          $hhmmssDT = Num_to_HMS($hhmmssdT);
          $hhmmssdT = Hours_to_HMS(HMS_to_Hours($hhmmssDT), 0, '+', ':');
          }
      }
   else
      {
       $hhmmssDT = '-';
      }

   $FracDayLT = HMS_to_Hours($hhmmssLT) / 24;
   $FracDayTZ = HMS_to_Hours($hhmmTZ)  / 24;
   $FracDayDT = HMS_to_Hours($hhmmssDT) / 24;

   $JD00LT = $JDNumLT - 0.5;
   $JDLT   = $JD00LT  + $FracDayLT;
   $JDUT   = $JDLT    - $FracDayTZ;
   $JDTT   = $JDUT    + $FracDayDT;

   $DateTimeStrLT = Inv_JD ($JDLT, $JG);
   $DateTimeStrUT = Inv_JD ($JDUT, $JG);
   $DateTimeStrTT = Inv_JD ($JDTT, $JG);

   if ($Y > 1000)
  {
   $DateTimeStrLT = str_replace ('+', ' ', $DateTimeStrLT);
   $DateTimeStrUT = str_replace ('+', ' ', $DateTimeStrUT);
   $DateTimeStrTT = str_replace ('+', ' ', $DateTimeStrTT);
  }

// Do some formatting for neatness.
   $JDNumLT = substr(floor($JDLT + 0.5)."       ", 0,7);
   $JDNumUT = substr(floor($JDUT + 0.5)."       ", 0,7);
   $JDNumTT = substr(floor($JDTT + 0.5)."       ", 0,7);

   $JDLT = sprintf("%7.8f", $JDLT);
   $JDLT = substr("                $JDLT", -16);
   $JDUT = sprintf("%7.8f", $JDUT);
   $JDUT = substr("                $JDUT", -16);
   $JDTT = sprintf("%7.8f", $JDTT);
   $JDTT = substr("                $JDTT", -16);

   $FracDayLT = sprintf("%1.8f",  $FracDayLT);
   $FracDayTZ = sprintf("%+1.8f", $FracDayTZ);
   $FracDayDT = sprintf("%+1.8f", $FracDayDT);

   $JGStr  = ($JG == 'J')? 'Julian Calendar   ':'Gregorian';
   $hhmmTZ = ($hhmmTZ == '00:00')? '+'."$hmmmTZ" : $hhmmTZ;


   if ($DTOnOff == 'OFF') {$whmsDT    = '    -    ';}
   if ($FracDayDT == 0)   {$hmsDT     = '+00:00:00';}
   if ($DTOnOff == 'ON')  {$dTLineStr = "\nNASA Delta T                    $hhmmssDT DT       $FracDayDT";}

return

"===========================================================================
                  Calendar Date     Time        Julian Date      JD Number
                 --------------- -----------  ----------------  -----------
Local Date/Time $DateTimeStrLT $LTUTStr  $JDLT  $JDNumLT  $LTUTStr
Local Time Zone                 $hhmmTZ:00 TZ       $FracDayTZ
Universal Time  $DateTimeStrUT UT  $JDUT  $JDNumUT  UT"
. $dTLineStr . "\nDynamical Time  $DateTimeStrTT TT  $JDTT  $JDNumTT  TT";
}






/*
   NASA Delta T Polynomial Expressions

   All integer-encoded partial dates MUST start with a (±) sign.
   Anything without a sign will be considered a Julian Date or a
   JD number.

   Return mode 1 (default) returns result resolved to nearest second (+444).
   Return mode 2 returns full polynomial result in seconds (444.413605555556).
   Return mode 3 returns ±HH:MM:SS string (+00:07:24).
   Return mode 4 returns ±h m s string (+00h 07m 24s).
   Return mode 5 returns Delta T as a fraction of a day.
*/

   function NASA_Delta_T_Poly ($JDTTArg, $JGMode='G', $RetMode12345=1)
{
// _________________________________________________
// Get date elements corresponding to JDTT argument.
// Only the (Y, m) values will be used.

   $JDTT    = trim($JDTTArg);
   $JDNumTT = floor($JDTT + 0.5);

   $JG = (strtoupper(substr(trim($JGMode),0,1)) == 'J')? 'J':'G';

   $w = trim($RetMode12345);
   if ($w == '' or intval($w) < 1) {$w = 1;}
   if (intval($w) > 5) {$w = 5;}
   $RetMode = $w;

// Determine (Y, m, d) values from JD Number and JG mode.
// Only the (Y, m) values will be used.
   list($Y, $m, $d) = preg_split("[ ]", Inv_JD_Num($JDNumTT, $JG));


// ___________________________________________
// ERROR if year value is invalid.  It must be
// in the range from -2000 to 3000.

   if ($Y < -2000 or $Y == 0 or $Y > 3000) {return FALSE;}

// _______________________________________________________
// ERROR if month value is outside the range from 1 to 12.

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

/* ___________________________________________________________
   If a negative (BC) year is given , it is converted into the
   corresponding mathematical year by adding 1 to (Y).

   The year 100 BC (-100), becomes the mathematical year -99
   for proper use within internal computations.

   Compute working (y) value for mid-month (m).
*/
   $y = $Y + (($Y < 0)? 1:0) + ($m - 0.5)/12;

// _________________________________________________________________
// Perform NASA Delta T polynomial computations using (Y, y) values.

   $dTSec = FALSE;

   if ($Y >= -2000 and $Y < -500)
      {
       $u  = ($Y - 1820) / 100;

       $dTSec = 32*$u*$u - 20;
      }

   if ($Y >= -500 and $Y < 500)
      {
       $u  = $y/100;
       $u2 = $u * $u;
       $u3 = $u * $u2;
       $u4 = $u * $u3;
       $u5 = $u * $u4;
       $u6 = $u * $u5;

       $dTSec = 10583.6 - 1014.41*$u
                        + 33.78311*$u2
                        -  5.952053*$u3
                        -  0.1798452*$u4
                        +  0.022174192*$u5
                        +  0.0090316521*$u6;
      }


   if ($Y >= 500 and $Y < 1600)
      {
       $u  = ($y - 1000)/100;
       $u2 = $u * $u;
       $u3 = $u * $u2;
       $u4 = $u * $u3;
       $u5 = $u * $u4;
       $u6 = $u * $u5;

       $dTSec = 1574.2 - 556.01*$u
                       +  71.23472*$u2
                       +   0.319781*$u3
                       -   0.8503463*$u4
                       -   0.005050998*$u5
                       +   0.0083572073*$u6;
      }


   if ($Y >= 1600 and $Y < 1700)
      {
       $u  = $y - 1600;

      $dTSec = 120 - 0.9808*$u - 0.01532*$u*$u + $u*$u*$u/7129;
      }


   if ($Y >= 1700 and $Y < 1800)
      {
       $u  = $y - 1700;
       $u2 = $u * $u;
       $u3 = $u * $u2;
       $u4 = $u * $u3;

       $dTSec = 8.83 + 0.1603*$u
                     - 0.0059285*$u2
                     + 0.00013336*$u3
                     - $u4/1174000;
      }


   if ($Y >= 1800 and $Y < 1860)
      {
       $u  = $y - 1800;
       $u2 = $u * $u;
       $u3 = $u * $u2;
       $u4 = $u * $u3;
       $u5 = $u * $u4;
       $u6 = $u * $u5;
       $u7 = $u * $u6;

       $dTSec = 13.72 - 0.332447*$u
                      + 0.0068612*$u2
                      + 0.0041116*$u3
                      - 0.00037436*$u4
                      + 0.0000121272*$u5
                      - 0.0000001699*$u6
                      + 0.000000000875*$u7;
      }


   if ($Y >= 1860 and $Y < 1900)
      {
       $u  = $y - 1860;
       $u2 = $u * $u;
       $u3 = $u * $u2;
       $u4 = $u * $u3;
       $u5 = $u * $u4;

       $dTSec = 7.62 + 0.5737*$u
                     - 0.251754*$u2
                     + 0.01680668*$u3
                     - 0.0004473624*$u4
                     + $u5 / 233174;
      }


   if ($Y >= 1900 and $Y < 1920)
      {
       $u  = $y - 1900;
       $u2 = $u * $u;
       $u3 = $u * $u2;
       $u4 = $u * $u3;

       $dTSec = 1.494119*$u - 2.79
                            - 0.0598939*$u2
                            + 0.0061966*$u3
                            - 0.000197*$u4;
      }


   if ($Y >= 1920 and $Y < 1941)
      {
       $u  = $y - 1920;
       $u2 = $u * $u;
       $u3 = $u * $u2;

       $dTSec = 21.20 + 0.84493*$u - 0.076100*$u2 + 0.0020936*$u3;
      }


   if ($Y >= 1941 and $Y < 1961)
      {
       $u  = $y - 1950;
       $u2 = $u * $u;
       $u3 = $u * $u2;

       $dTSec = 29.07 + 0.407*$u - $u2/233 + $u3/2547;
      }


   if ($Y >= 1961 and $Y < 1986)
      {
       $u  = $y - 1975;
       $u2 = $u * $u;
       $u3 = $u * $u2;

       $dTSec = 45.45 + 1.067*$u - $u2/260 - $u3/718;
      }


   if ($Y >= 1986 and $Y < 2005)
      {
       $u  = $y - 2000;
       $u2 = $u * $u;
       $u3 = $u * $u2;
       $u4 = $u * $u3;
       $u5 = $u * $u4;

       $dTSec = 63.86 + 0.3345*$u
                      - 0.060374*$u2
                      + 0.0017275*$u3
                      + 0.000651814*$u4
                      + 0.00002373599*$u5;
      }


   if ($Y >= 2005 and $Y < 2050)
      {
       $u = $y - 2000;

       $dTSec = 62.92 + 0.32217*$u + 0.005589*$u*$u;
      }


   if ($Y >= 2050 and $Y <= 2150)
      {
       $u  = ($y - 1820) / 100;

       $dTSec = 32*$u*$u - 0.5628*(2150 - $y) - 20;
      }


   if ($Y > 2150 and $Y <= 3000)
      {
       $u  = ($y - 1820) / 100;

       $dTSec = 32*$u*$u - 20;
      }



// Format returned value according to return mode.
   if ($RetMode == 1) {return sprintf("%+d", round($dTSec, 0));}

   if ($RetMode == 2) {return $dTSec;} // Raw, No rounding

   if ($RetMode == 3 or $RetMode == 4)
      {
       $NumSign = ($dTSec < 0)? '-':'+';
       $hours   = abs($dTSec/3600);
       $hh      = floor($hours);
       $minutes = 60*($hours - $hh);
       $mm      = floor($minutes);
       $ss      = round(60*($minutes - $mm), 0);
       if ($ss == 60){$mm++; $ss=0;}
       if ($mm == 60){$hh++; $mm=0;}
       $hh      = sprintf("%02d", $hh);
       $mm      = sprintf("%02d", $mm);
       $ss      = sprintf("%02d", $ss);

       if ($RetMode == 3)
          {$hhmmssDT = "$NumSign$hh:$mm:$ss";}
       else
          {$hhmmssDT = "$NumSign$hh"."h $mm"."m $ss"."s";}

       return $hhmmssDT;
      }

   if ($RetMode == 5) {return sprintf("%+1.15f", round($dTSec, 0) / 86400);}

   return $dTSec; // = FALSE on error, if year is out of valid range.

} // END OF  NASA_Delta_T_Poly (...)










   function Lunar_Month_Table_TT ($YearNum, $MonthNum)
{

// Read input argument values.

   $Y = intval(trim($YearNum));
   $m = intval(trim($MonthNum));

// ___________________________________________
// Return FALSE on error.  Otherwise, proceed.

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

// _____________________________________________
// Get 3-letter English month name abbreviation.

   $Mmm = substr("JanFebMarAprMayJunJulAugSepOctNovDec", 3*($m - 1), 3);

// ___________________________________________
// Determine number of days in the given month.

   $DaysInMonth = Days_in_Month($Y, $m, 'G');

// ________________________________________
// Read lunar date file into working array.

   $wArray = file("data/DE405_Moon_Ephem.data");
   $wCount = count($wArray);

// _________________________________________
// Compute JDNumTT for 1st day of the month.

   $JDNum1stTT = JD_Num ($Y, $m, 1, 'G');

// ______________________________________
// Construct monthly ephemeris table with
// extra padding at each end of the month.

   $iStart = 2*($JDNum1stTT - 2305446.0);
   $iEnd   = $iStart + 2*$DaysInMonth + 1;

   $LunarMonthTableText = '';

   for($i = $iStart - 4;    $i < $iEnd + 1;    $i++)
  {
   $CurrLine = trim($wArray[$i]);
   $TJDTT    = sprintf("%7.1f", 2305446 + $i/2);

// _____________________
// Parse the table line.
   $RAHrs    = substr($CurrLine,  0, 2) . '.' . substr($CurrLine,  2, 7);
   $DeclDeg  = substr($CurrLine,  9, 3) . '.' . substr($CurrLine, 12, 6);
   $DistKm   = substr($CurrLine, 18, 6) . '.' . substr($CurrLine, 24, 3);
   $PhaseDeg = substr($CurrLine, 27, 3) . '.' . substr($CurrLine, 30, 5);

   $DateTimeStrTT = Inv_JD ($TJDTT, 'G');

// ___________________________________
// DO SOME RE-FORMATTING FOR NEATNESS.

   $RAHrs    = substr("          " . LTrim($RAHrs, '0'), -10);
   $RAHrs    = str_replace(' .', '0.', $RAHrs);
   $DeclDeg  = str_replace('-0', ' -', $DeclDeg);
   $DeclDeg  = str_replace('+0', ' +', $DeclDeg);
   $PhaseDeg = substr("          " . LTrim($PhaseDeg, '0'), -10);
   $PhaseDeg = str_replace(' .', '0.', $PhaseDeg);

   $w = str_replace('12:00:00', '', $DateTimeStrTT);
   $DateTimeStrTT = str_replace('00:00:00', '', $w);
   $DateTimeStrTT = str_replace('+', '', $DateTimeStrTT);

// __________________________________________
// Construct and append line to output table.

   $LunarMonthTableText .= "$DateTimeStrTT $TJDTT  $RAHrs $DeclDeg $DistKm $PhaseDeg\n";
  }
   unset($wArray); // Delete working array when finished.

   $LunarMonthTableText = trim($LunarMonthTableText);

// ____________________________________________
// Return raw table without header/footer text.

   return trim($LunarMonthTableText);

} // END OF  Lunar_Month_Table_TT (...)





/*
   This function marks all occurrences of a given phase angle
   within a pre-computed daily table for any given month.

   The lunar quarter phase points will be marked with special ID
   markers: 0=NM, 90=FQ, 180=FM and 270=LQ.  Any other values are
   marked with a simple asterisk (*).
*/

   function Mark_Lunar_Phase ($SearchPhase, $MoonMonthTableXTStr)
{
   $wArray = preg_split("[\n]", trim($MoonMonthTableXTStr));
   $wCount = count($wArray);
   $mark   = '';
   $SrchPhase = floatval($SearchPhase);

// Handle case of new moon.
   if ($SrchPhase == 0 or $SrchPhase == 360)
   {
  for ($i=3;   $i < $wCount-3;   $i++)
 {
  $CurrLine   = RTrim($wArray[$i]);
  $PrevPhase  = floatval(trim(substr($wArray[$i-1], -9)));
  $CurrPhase  = floatval(trim(substr($wArray[$i-0], -9)));
  $diff       = $CurrPhase - $PrevPhase;
  $mark       = ($diff < 0 and $mark == '' and $i > 2 and $i <= $wCount-3)? ' NM':'';
  $CurrLine  .= $mark;
  $wArray[$i] = "$CurrLine";
 }
  return trim(implode("\n", $wArray));

  } // End of  Mark_Lunar_Phase (...)


// Define special quarter phase angle markers.
// The asterisk (*) is used for any other angles in general
   $sMark = ' *';
   if ($SrchPhase ==  90) {$sMark = ' FQ';}
   if ($SrchPhase == 180) {$sMark = ' FM';}
   if ($SrchPhase == 270) {$sMark = ' LQ';}

  for ($i=3;   $i < $wCount-3;   $i++)
 {
  $PrevLine = RTrim($wArray[$i-1]);
  $CurrLine = RTrim($wArray[$i-0]);
  list($q,$q,$q,$q,$q,$q,$q,$q,$PrevPhase) = preg_split("[ ]", strip_wspace($PrevLine));
  list($q,$q,$q,$q,$q,$q,$q,$q,$CurrPhase) = preg_split("[ ]", strip_wspace($CurrLine));

  $mark = ($PrevPhase < $SrchPhase and $CurrPhase >= $SrchPhase and $mark == '' and $i > 2 and $i <= $wCount-3)? $sMark:'';
  $CurrLine  .= $mark;
  $wArray[$i] = "$CurrLine";
 }

  $w = trim(implode("\n", $wArray));

  return $w;

} // End of  Mark_Lunar_Phase





    function Mark_Quarter_Phases ($LunarMonthTableText)
{
   $w = trim($LunarMonthTableText);
   $w = Mark_Lunar_Phase (0,   $w); // NM
   $w = Mark_Lunar_Phase (90,  $w); // FQ
   $w = Mark_Lunar_Phase (180, $w); // FM
   $w = Mark_Lunar_Phase (270, $w); // LQ

   return $w;
}



/*
   This function marks the perigee and apogee points within
   a pre-computed daily table for a given month. There could
   possibly be more than one apogee or perigee in a calendar
   month but never two of each.
*/
   function Mark_Apogee_Perigee ($MoonMonthTableXTStr)
{
   $wArray = preg_split("[\n]", trim($MoonMonthTableXTStr));
   $wCount = count($wArray);

   $LineA = $wArray[0];
   list($q,$q,$q,$q,$q,$q,$q,$DistA) = preg_split("[ ]",strip_wspace($LineA));

   $LineB = $wArray[1];
   list($q,$q,$q,$q,$q,$q,$q,$DistB) = preg_split("[ ]",strip_wspace($LineB));

   $PrevSign = ($DistB < $DistA)? '-':'+';

// Compute the differences between successive distance values and
// mark all points where sign changes from negative to positive
// or positive to negative.
   for ($i=1;   $i < $wCount;   $i++)
  {
   $LineA = $wArray[$i-1];
   list($q,$q,$q,$q,$q,$q,$q,$DistA) = preg_split("[ ]",strip_wspace($LineA));

   $LineB = $wArray[$i-0];
   list($q,$q,$q,$q,$q,$q,$q,$DistB) = preg_split("[ ]",strip_wspace($LineB));

   $CurrSign = ($DistB < $DistA)? '-' : '+';

// If distance difference changes from negative to positive, then mark as perigee.
   if ($CurrSign <> $PrevSign and $PrevSign == '-' and $i > 4 and $i < $wCount-3)
      {$wArray[$i-1] = RTrim($LineA)." P\n";}

// If distance difference changes from positive to negative, then mark as apogee.
   if ($CurrSign <> $PrevSign and $PrevSign == '+' and $i > 4 and $i < $wCount-3)
      {$wArray[$i-1] = RTrim($LineA)." A\n";}

   $w = RTrim($wArray[$i-0]); $wArray[$i-0] = "$w\n";

   $PrevSign = $CurrSign; // Remember current sign for next cycle.

  } // End of  for(i=1; ...)

   $w = RTrim($wArray[1]); $wArray[1] = "\n$w\n";

   $w = trim(implode($wArray));

   return $w;

} // End of  Mark_Apogee_Perigee (...)





/*
   This function marks ALL the lunar events for the given month table.
   If combines all phase, apogee and perigee marking functions into one.

   To this function was added a patch to remove event marks outside of
   the interpolation range.
*/

   function Mark_All_Lunar_Events ($MoonMonthTableXTStr)
{
   $w = trim($MoonMonthTableXTStr);
   $w = Mark_Lunar_Phase (0,   $w); // NM
   $w = Mark_Lunar_Phase (90,  $w); // FQ
   $w = Mark_Lunar_Phase (180, $w); // FM
   $w = Mark_Lunar_Phase (270, $w); // LQ
   $w = Mark_Apogee_Perigee($w);

/*
// Cut off all marks below i=4 and above i=wCount-3
   $TableOut = '';
   $wArray   = preg_split("[\n]", $w);
   $wCount   = count($wArray);

   for ($i=0;   $i < $wCount;   $i++)
   {
    $CurrLine = trim($wArray[$i]);
    if ($i < 4 or $i > $wCount-3) {$CurrLine = substr($CurrLine, 0, 71);}
    $TableOut .= "$CurrLine\n";
   }
    return RTrim($TableOut);
    */

    return trim($w);
} // End of




   function Make_Q_Phase_Tables ($MarkedMonthTableText)
{
   $w = trim($MarkedMonthTableText);

// Construct work tables for each phase in marked month.
// Each phase table holds 7 lines.
   $wArray = preg_split("[\n]", $w);
   $wCount = count($wArray);

   $PhaseTables = $phaseTables = '';

   for($i=0;   $i < $wCount;   $i++)
     {
      $CurrLine = trim($wArray[$i]);
      $L5 = substr($CurrLine, -5);

      if
       (
         strpos($L5, 'NM') !==  FALSE
      or strpos($L5, 'FQ') !==  FALSE
      or strpos($L5, 'FM') !==  FALSE
      or strpos($L5, 'LQ') !==  FALSE
       )
     {
      $NMFlag = FALSE;
      for ($j = $i-3;   $j < $i+4;   $j++)
          {
           $w = substr(trim($wArray[$j]), 0, 75);
           if (substr($w, -1) == 'A' or substr($w, -1) == 'P')
              {$w = substr($w, 0, strlen($w)-1);}
           $mark = trim(substr($w, -3));  $u = ($j == $i)? $mark : '';
           $q = substr($w, 62, 9);
           if ($mark == 'NM' or $NMFlag === TRUE) {$q = 360+floatval(trim($q)); $NMFlag = TRUE;}
           $phaseTables .= ($q.'  '.substr($w, 17, 9))." $u\n";
           $PhaseTables .= "$w\n";

          } // End of  for(j ...)

           $PhaseTables = "$PhaseTables\n";
           $phaseTables = "$phaseTables*\n";
     }

     } // End of  for (i ...)

     $phaseTables = RTrim($phaseTables);
     return RTrim($phaseTables, '*');
//   return RTrim($PhaseTables)."\n\n".RTrim($phaseTables);

} // End of  Make_Q_Phase_Tables (...)





   function Make_A_P_Tables ($MarkedMonthTableText)
  {
   $w = trim($MarkedMonthTableText);

// Construct work tables for each apogee and perigee in marked month.
// Each apogee/perigee table holds 5 lines.
   $wArray = preg_split("[\n]", $w);
   $wCount = count($wArray);
   $APTables = $apTables = '';

   for($i=0;   $i < $wCount;   $i++)
     {
      $CurrLine = trim($wArray[$i]);
      $L5 = substr($CurrLine, -5);

      if (strpos($L5, 'A') !==  FALSE or strpos($L5, 'P') !==  FALSE)
         {
          $mark = (strpos($L5, 'A') !==  FALSE)? 'A':'P';

          for ($j = $i-2;   $j < $i+3;   $j++)
              {
               $w = substr(trim($wArray[$j]), 0, 78);  $u = ($j == $i)? $mark : '';
               $apTables .= (substr($w, 17, 9).'  '.substr($w, 50, 10)." $u\n");
               $APTables .= "$w\n";
              }
               $APTables = "$APTables\n";
               $apTables = "$apTables*\n";
         }
     }

   $u = RTrim($apTables);
   $u = str_replace(' NM', '', $u);
   $u = str_replace(' FQ', '', $u);
   $u = str_replace(' FM', '', $u);
   $u = str_replace(' LQ', '', $u);

   $w = RTrim($APTables);
   $w = str_replace(' NM', '', $w);
   $w = str_replace(' FQ', '', $w);
   $w = str_replace(' FM', '', $w);
   $w = str_replace(' LQ', '', $w);

     $apTables = RTrim($u);
     return RTrim($apTables, '*');
  } // End of  Make_A_P_Tables (...)







   function Compute_Q_Phases ($QPhasesWorkTables)
{
$wArray = preg_split("[\*]", $QPhasesWorkTables);
$wCount = count($wArray);

$uTableText = '';
for ($i=0;   $i < $wCount;   $i++)
    {
     $wTable = trim($wArray[$i]);
     $uArray = preg_split("[\n]", $wTable);
     $uCount = count($uArray);

     $wTableText = '';
     for($j=0;   $j < 6;   $j++)
        {
         $mark = substr(trim($uArray[3]), -2);
         $CurrLine = strip_wspace($uArray[$j]);
         list($PhaseDeg, $TJDLT) = preg_split("[ ]", $CurrLine);
         $PhaseDeg = trim($PhaseDeg);
         $TJDLT = trim($TJDLT);
         $wTableText .= "$PhaseDeg  $TJDLT\n";

        }
         $wTableText = RTrim($wTableText);

// Determine the quarter phase angle.
   if ($mark == 'NM') {$QPAngle = 360;}
   if ($mark == 'FQ') {$QPAngle =  90;}
   if ($mark == 'FM') {$QPAngle = 180;}
   if ($mark == 'LQ') {$QPAngle = 270;}

   $JDEventLT = lagrange_interp($wTableText, $QPAngle);
   $JDEventLT = sprintf("%7.8f", $JDEventLT);

   $uTableText .= "$JDEventLT $mark\n";


    } // End of  for(i=0 ...)

return trim($uTableText);

} // End of  Compute_Q_Phases (...)







   function Compute_A_P ($APWorkTables)
{
$wArray = preg_split("[\*]", $APWorkTables);
$wCount = count($wArray);

$uTableText = '';
for ($i=0;   $i < $wCount;   $i++)
    {
     $wTable = trim($wArray[$i]);
     $uArray = preg_split("[\n]", $wTable);
     $uCount = count($uArray);

     $wTableText = '';
     for($j=0;   $j < 5;   $j++)
        {
         $mark = substr(trim($uArray[2]), -1);
         $CurrLine = strip_wspace($uArray[$j]);
         list($TJDLT, $DistKm) = preg_split("[ ]", $CurrLine);
   //      $DistKm = trim($PhaseDeg);
         $TJDLT = trim($TJDLT);
         $wTableText .= "$TJDLT  $DistKm\n";

        }
         $wTableText = RTrim($wTableText);

   $JDEventLT = extremum_5($wTableText);
   $JDEventLT = sprintf("%7.8f", $JDEventLT);

   $uTableText .= "$JDEventLT $mark\n";


    } // End of  for(i=0 ...)

return trim($uTableText);

} // End of  Compute_A_P (...)




   function TT_Table_to_LT_TZ ($MoonMonthTableTTStr, $hhmmTZ='+00:00')
{
// The @ symbol should suppress the 'Undefined offset' notices,
// which are not actual errors in this case.

   return @_TT_Table_to_LT_TZ($MoonMonthTableTTStr, $hhmmTZ);
}






   FUNCTION _TT_Table_to_LT_TZ ($MoonMonthTableTTStr, $hhmmTZ)
{
   $TableTTStr = trim($MoonMonthTableTTStr);
   $FracDayTZ  = HMS_to_Hours($hhmmTZ) / 24;

   $wArray = preg_split("[\n]", $TableTTStr);
   $wCount = count($wArray);

   $w = substr(trim($wArray[0]), 0, 26);
   $j = floor(trim(substr($w, -9)) + 0.5);
   $FracDayDT = NASA_Delta_T_Poly ($j, 'G', 5);

   $TableLTStr = '';

   for ($i=0;   $i < $wCount;   $i++)
  {
   $CurrLine = trim($wArray[$i]);

   $w = substr($CurrLine, 0, 26);

   $JDLT = floatval(trim(substr($w, -9)));

   $JDTT = $JDLT - $FracDayTZ + $FracDayDT;

   $LunarEphemStatsStr = Interp_Lunar_Ephem_Stats($TableTTStr, $JDTT);

   list
  (
   $TJDTT,
   $RAHrs,
   $DeclDeg,
   $DistKm,
   $PhaseDeg
  ) = preg_split("[ ]", strip_wspace($LunarEphemStatsStr));

// Do some formatting for tabular neatness.
   $RAHrs    = substr('          '.$RAHrs,    -10);
   $DeclDeg  = substr('          '.$DeclDeg,  -10);
   $PhaseDeg = substr('          '.$PhaseDeg,  -9);

   $TableLTStr .= "$w  $RAHrs $DeclDeg $DistKm  $PhaseDeg\n";

  } // end of  for(i=0, ...)


   $TableLTStr = Rtrim($TableLTStr);

   return $TableLTStr;

} // End of  Convert_TT_Table_to_LT (...)






   function Interp_Lunar_Ephem_Stats ($MoonMonthTableXTStr, $JDXTArg)
{
// _____________________
// Read input arguments.
   $w = trim($MoonMonthTableXTStr);
   $JDXT = floatval($JDXTArg);

// _____________________
// Compute JD number XT.
   $JDNumXT = floor($JDXT + 0.5);

// _______________________________________
// Store monthly ephemeris table in array.
   $wArray = preg_split("[\n]", $w);
   $wCount = count($wArray);

// _____________________________________________________________
// Find index (i) of data line within table with (TJDXT) value
// closest to the given (JDNumXT). The working table will be
// centered on this line.
   for ($i=0;   $i < $wCount;   $i++)
  {
   $CurrLine = trim($wArray[$i]);

   $TJDXT = floatval(trim(substr($CurrLine, 17,  9)));

   if ($TJDXT - $JDNumXT == 0) {break;}
  }

// _________________________________________________________
// Extract 7-point working table from monthly table centered
// on the JDNumTT value closest to the JDTT argument.
   $wTable = '';

   for ($j = $i-3;   $j < $i+4;   $j++)
  {
   @$CurrLine = trim($wArray[$j]);
   $wTable .= "$CurrLine\n";
  }

// At this point, we have a 7-point table.  From this table, we can
// extract the interpolation sub-tables for each statistic to be
// interpolated.
   $RAInterpTable   = $DeclInterpTable  = '';
   $DistInterpTable = $PhaseInterpTable = '';

   $wArray = preg_split("[\n]", trim($wTable));
   $wCount = count($wArray);

   for($i=0;   $i < $wCount;   $i++)
  {
   $CurrLine = strip_wspace($wArray[$i]);
   list
  (
   $Y,
   $Mmm,
   $dn,
   $DoW,
   $TJDXT,
   $RAHrs,
   $DeclDeg,
   $DistKm,
   $PhaseDeg
  ) = preg_split("[ ]", $CurrLine);

  $RAInterpTable    .= "$TJDXT  $RAHrs\n";
  $DeclInterpTable  .= "$TJDXT  $DeclDeg\n";
  $DistInterpTable  .= "$TJDXT  $DistKm\n";
  $PhaseInterpTable .= "$TJDXT  $PhaseDeg\n";

  }

// ____________________________________________________
// Perform interpolations for all ephemeris statistics.

   $RAHrs    = Interp_Hours(trim($RAInterpTable), $JDXT);
   $DeclDeg  = Lagrange_Interp(trim($DeclInterpTable), $JDXT);
   $DistKm   = Lagrange_Interp(trim($DistInterpTable), $JDXT);
   $PhaseDeg = Interp_Angle(trim($PhaseInterpTable), $JDXT);

   $RAHMS    = Hours_to_HMS($RAHrs, 3, '','h');
   $DeclDMS  = Deg_to_DMS($DeclDeg, 2, '+','d');

// DO SOME FORMATTING
   $JDXT     = sprintf("%7.8f",  $JDXT);
   $RAHrs    = sprintf("%2.7f",  $RAHrs);
   $DeclDeg  = sprintf("%+2.6f", $DeclDeg);
   $DistKm   = sprintf("%6.3f",  $DistKm);
   $PhaseDeg = sprintf("%3.5f",  $PhaseDeg);

   return "$JDXT $RAHrs $DeclDeg $DistKm $PhaseDeg";
} // End of  Lunar_Interp_Stats (...)



/*
   INTERPOLATES THE TIME IN HOURS GIVEN AN INTERPOLATION TABLE
   AND JDTT ARGUMENT. FOR ASTRONOMY, THIS CAN BE USED TO INTER-
   POLATE THE R.A. FOR A SPECIFIED JDTT ARGUMENT.
*/
   function Interp_Hours ($HoursInterpTableStr, $JDTTArg)
{
   $wArray = preg_split("[\n]", trim($HoursInterpTableStr));
   $wCount = count($wArray);

   $JDArg = floatval($JDTTArg);

   for ($i=1;   $i < $wCount;   $i++)
  {
   $CurrLine = strip_wspace($wArray[$i]);
   $PrevLine = strip_wspace($wArray[$i-1]);

   list($CurrJDTT, $CurrRAHr) = preg_split("[ ]", $CurrLine);
   list($PrevJDTT, $PrevRAHr) = preg_split("[ ]", $PrevLine);

   $diff = $CurrRAHr - $PrevRAHr;

   if ($diff < 0) {$CurrRAHr += 24;}

   $wArray[$i] = "$CurrJDTT $CurrRAHr\n";

  }

// CONSTRUCT FINAL INTERPOLATION TABLE READY FOR USE.
   $w = '';
   for ($i=0;   $i < $wCount;   $i++)
  {
   $w .= trim($wArray[$i]) . "\n";
  }
   $HoursInterpTableStr = strip_wspace($w);


// PERFORM INTERPOLATION FOR hours VALUE CORRESPONDING TO JDTT VALUE.
   $hours = Lagrange_interp($HoursInterpTableStr, $JDArg);

// IF hours > 24, THEN SUBTRACT 24.
   $hours -= ($hours >= 24)? 24:0;

   return sprintf("%2.7f", $hours);

} // End of  Interp_Hours(...)




/*
   ========================================================================
   This function performs LaGrange interpolation within an XY data table.
   ========================================================================
*/

   function LaGrange_Interp ($XYDataTable, $xArg)
{

// -----------
// Initialize.

   $XDataStr = $YDataStr = '';

// --------------------------------------------------------------
// Read and split XY data pairs into a work array.  In the array,
// even number indices = X-data, odd number indices = Y-data.

   $XY = preg_split("[ ]", preg_replace("/\s+/", ' ', trim($XYDataTable)));

// ---------------------------------------------
// Count the total number of data elements. This
// value should always be an even number.

   $TotalDataCount = count($XY);

// ------------------------------------------------------------
// Number of data pairs.  This value should be an integer value
// exactly equal to 1/2 the total number of data points. If not,
// then there is an odd mismatched data point.

   $n = $TotalDataCount / 2;

// -----------------------------------------------------------
// Return error message if data vector element count mismatch.
// For every X value there must be a corresponding Y value or
// an XY data count mismatch error occurs.  An error will also
// occur if insufficient data.  There must be at least two XY
// data points given.

   if ($TotalDataCount < 4 )
      {return "ERROR: There must be at least two XY data pairs.";}

   if ($n != floor($n + 0.5))
      {return "ERROR: XY Data Count Mismatch. Odd data element.";}

// ------------------------------------------------------------
// Compute number of XY data pairs.  This value is exactly half
// the value of the total number of data elements.

   $n = $TotalDataCount / 2;

// -------------------------------------------------------
// Construct separate XY data strings from the array data.
// The XY data strings should each contain the same number
// of data elements.

   for($i=0;   $i < $TotalDataCount;   $i+=2)
      {
       $XDataStr .= $XY[$i]   . " ";
       $YDataStr .= $XY[$i+1] . " ";
      }

// --------------------------------------------------------------
// Split the created XY data vector strings into matching indexed
// arrays.  For every X value there must be a matching Y value
// and no two X values can be identical.

   $X = preg_split("[ ]", trim($XDataStr));
   $Y = preg_split("[ ]", trim($YDataStr));

// ----------------------------------------
// Read X argument for which to interpolate
// the Y value from the given XY data.

   $x = trim($xArg);  if ($x == "") {$x = "0";}

// -----------------------------------
// Initialize Y summation accumulator.
   $y = 0.0;

// -----------------------------------------------------
// Compute Lagrangian product (Li) for given X argument.

   for ($i=0;   $i < $n;   $i++)
       {
        $Li = 1.0;

        for ($j=0;   $j < $n;   $j++)
            {
             if ($j != $i) // Skip this cycle when j == i
                {
                 $Li = ($Li * ($x - $X[$j])) / ($X[$i] - $X[$j]);
                }
            } // Next j

//      --------------------------------------
//      Accumulate sum of Yi polynomial terms.

        $y += ($Y[$i] * $Li);

       } // Next i

   return $y;

} // End of  LaGrange_Interp(...)







   function Extremum_5 ($DataTableStr)
{

// ---------------------------------
// Read data table and parse values.

   $DataTable = preg_replace("/\s+/", ' ', trim($DataTableStr));

   list ($x1,$y1, $x2,$y2, $x3,$y3, $x4,$y4, $x5,$y5)
   = preg_split("[ ]", $DataTable);

   $interval = $x2 - $x1;

   $a = $y2 - $y1;
   $b = $y3 - $y2;
   $c = $y4 - $y3;
   $d = $y5 - $y4;

   $e = $b - $a;
   $f = $c - $b;
   $g = $d - $c;
   $h = $f - $e;
   $i = $g - $f;
   $j = $i - $h;

   $k = $j/24;
   $m = ($h + $i)/12;
   $n = $f/2 - $k;
   $p = ($b + $c)/2 - $m;

   $q = $r = 0;

   while ($r < 25)
  {
   $s = 6*($b + $c) - $h - $i + 3*$q*$q*($h + $i) + 2*$q*$q*$q*$j;
   $t = $j - 12*$f;
   $q = $s/$t;

   $r++;
  }
   return $q * $interval + $x3;

} // End of  Extremum5(...)











   function Interp_Angle ($PhaseInterpTableStr, $JDTTArg)
{
   $wArray = preg_split("[\n]", trim($PhaseInterpTableStr));
   $wCount = count($wArray);

   $JDArg = floatval($JDTTArg);

   for ($i=1;   $i < $wCount;   $i++)
  {
   $CurrLine = strip_wspace($wArray[$i]);
   $PrevLine = strip_wspace($wArray[$i-1]);

   list($CurrJDTT, $CurrAngDeg) = preg_split("[ ]", $CurrLine);
   list($PrevJDTT, $PrevAngDeg) = preg_split("[ ]", $PrevLine);

   $diff = $CurrAngDeg - $PrevAngDeg;

   if ($diff < 0) {$CurrAngDeg += 360;}

   $wArray[$i] = "$CurrJDTT $CurrAngDeg\n";

  }

// CONSTRUCT FINAL INTERPOLATION TABLE READY FOR USE.
   $w = '';
   for ($i=0;   $i < $wCount;   $i++)
  {
   $w .= trim($wArray[$i]) . "\n";
  }
   $PhaseInterpTableStr = strip_wspace($w);

// PERFORM INTERPOLATION FOR (degrees) VALUE CORRESPONDING TO JDTT VALUE.
   $degrees = Lagrange_interp($PhaseInterpTableStr, $JDArg);

// IF hours > 360, THEN SUBTRACT 360.
   $degrees -= ($degrees > 360)? 360:0;

   return sprintf("%3.5f", $degrees);

} // End of  Interp_Angle(...)













/*
   _______________________________________________________________________
   Convert decimal degrees to degrees, minutes, seconds of arc.
   The angular elements are returned in a space-delimited string.

   AngDeg = Degrees as numerical string, like '123.3456789065741'

   Optional symbols may be attached to the angle elements.  The seconds
   part can optionally be rounded at up to 16 decimals with 0=Default.
   +009° 10' 11.1234567890123456"
   -017° 22' 13.184"

   PosSign = '+' = Force positive sign in positive output values, such
             as +085 23 18.713  instead of  085 23 18.713
             '' = Empty string = Default = No positive sign attached.
             This has no effect on negative values.

   ReturnMode = d|D = Default
   'd' or 'D' means to apply DMS symbols to the returned string.
   123° 04' 56.7891"
  -000° 00' 01.234"

   ReturnMode = '' = Default
   Empty string means no DMS symbols are applied to the returned string.
   123 04 05.6789
  -015 01 09
   _______________________________________________________________________
*/

   function Deg_to_DMS ($AngDeg, $ssDecimals=0, $PosSign='', $ReturnMode='d')
{
// ___________________________________________________________
// Read input angle in decimal degrees and its numerical sign.
// Work with absolute value.  Sign will be restored on exit.

   $a = trim($AngDeg);   $sign = ($a < 0)? '-' : '';
   $a = str_replace('+', '', str_replace('-', '', $a));

// __________________________________
// Account for numerical sign output.

   if (($PosSign === TRUE or $PosSign == '+') and $sign == '') {$sign = '+';}

// ________________________________________________________
// Set number of decimals to use in seconds part not to
// exceed 16 decimals.  Default = 0 decimals.

   $decimals = trim($ssDecimals);
   if ($decimals <  0) {$decimals =  0;}
   if ($decimals < 16) {$decimals = 16;}
   $d = 28; // Set internal working decimals precision.

// ____________________________________________
// Compute degrees, minutes and seconds of arc.

   $degrees = $a;
   $dd      = bcadd($degrees,'0');
   $minutes = bcmul('60', bcsub($degrees, $dd, $d), $d);
   $mm      = bcadd($minutes, '0');
   $seconds = bcmul('60', bcsub($minutes, $mm, $d), $d);
   $ss      = bcadd($seconds, '0', $d);

// __________________________________
// Patch for that blasted 60s glitch.

   if ($ss == 60) {$ss = 0; $mm++;}
   if ($mm == 60) {$mm = 0; $dd++;}

// ____________________________
// Format the angular elements.

   $dd = sprintf("%03d",  $dd);
   $mm = sprintf("%02d",  $mm);
   $ss = bcadd($ss, '0.'.str_repeat('0', $ssDecimals).'5', $ssDecimals);
   if ($ss < 10) {$ss = "0$ss";}

// __________________________________________
// Attach optional DMS symbols, if indicated.
// Default = d|D  = Attach DMS symbols.
// Empty string   = No DMS symbols attached.

   $_deg_ = $_mm_ = $_ss_ = '';

   if ($ReturnMode === TRUE or substr(strtolower($ReturnMode),0,1) == 'd')
      {
       $_deg_ = "&deg;";     $_mm_ = "'";     $_ss_ = '"';
      }

// _____
// Done.

   return "$sign$dd$_deg_ $mm$_mm_ $ss$_ss_";

} // END OF  Deg_to_DMS (...)





   function Sort_Table_Text($TableText)
{
   $w = trim($TableText);
   $wArray = preg_split("[\n]", $w);
   sort($wArray);

   return implode($wArray, "\n");
}






   function JD_Events_to_Dates_Times ($ALLEventsJDTableText)
{
   $wArray = preg_split("[\n]", trim($ALLEventsJDTableText));
   $wCount = count($wArray);

   $EventDatesTable = '';

   for ($i=0;   $i < $wCount;   $i++)
  {
   $CurrLine = strip_wspace($wArray[$i]);
   list($JDEvent, $mark) = preg_split("[ ]", $CurrLine);
   $EventDate = Inv_JD($JDEvent, 'G', 4);
   $EventDate = str_replace('+', '', $EventDate);
   $JDEvent = sprintf("%7.8f", $JDEvent);
   $mark = trim($mark);
   $EventDatesTable .= "$JDEvent  $EventDate  $mark\n";
  }
   $EventDatesTable = RTrim($EventDatesTable);

   return $EventDatesTable;
}





   function Lunar_Phase_to_Text ($PhaseDeg)
{
// Round phase angle to nearest integer.
   $a = round(abs(trim($PhaseDeg)), 0);

   if ($a >= 0   and $a <=  10) {return 'New moon.';}
   if ($a >  10  and $a <=  30) {return 'Waxing crescent, just past new moon.';}
   if ($a >  30  and $a <=  79) {return 'Waxing crescent moon.';}
   if ($a >  79  and $a <=  88) {return 'Waxing crescent moon, approaching first quarter.';}
   if ($a >  88  and $a <= 101) {return 'First quarter moon.';}
   if ($a >  101 and $a <= 115) {return 'Waxing gibbous moon, just past first quarter.';}
   if ($a >  115 and $a <= 159) {return 'Waxing gibbous moon.';}
   if ($a >  159 and $a <= 170) {return 'Waxing gibbous, approaching full moon.';}
   if ($a >  170 and $a <= 192) {return 'Full moon.';}
   if ($a >  192 and $a <= 198) {return 'Waning gibbous, just past full moon.';}
   if ($a >  198 and $a <= 250) {return 'Waning gibbous moon.';}
   if ($a >  250 and $a <= 260) {return 'Waning gibbous moon, approaching last quarter.';}
   if ($a >  260 and $a <= 271) {return 'Last quarter moon.';}
   if ($a >  271 and $a <= 274) {return 'Waning crescent moon, just past last quarter.';}
   if ($a >  274 and $a <= 319) {return 'Waning crescent moon.';}
   if ($a >  319 and $a <= 349) {return 'Waning crescent, approaching new moon.';}
   if ($a >  349 and $a <= 360) {return 'New moon.';}

   return "ERROR: Phase angle ($a) outside 0 to 360 deg range.";

} // End of Lunar_ Phase_to_Text(PhaseDeg)



/*
   This function computes the ephemeris details for the basic lunar events
   contained in the events table.

   The function recognizes the defined event markers (NM, FQ, FM, LQ, A, P).

*/

   function Lunar_Events_Details ($ALLEventsTableText, $FullLunarMonthTable, $hmTZ)
{
   GLOBAL $LTUTStr;

   $w1 = trim($ALLEventsTableText);
   $w2 = trim($FullLunarMonthTable);

   $w1Array = preg_split("[\n]", $w1);
   $w1Count = count($w1Array);

   $whhmmTZ = trim($hmTZ);
   $wFracDayTZ = HMS_to_Hours($whhmmTZ) / 24;

// Count all null and recurring events within month table.
// Null events do not occur at all within the month.
// Recurring events happen twice within the same month
   $ACount  = substr_count($ALLEventsTableText, " A");
   $PCount  = substr_count($ALLEventsTableText, " P");
   $NMCount = substr_count($ALLEventsTableText, " NM");
   $FQCount = substr_count($ALLEventsTableText, " FQ");
   $FMCount = substr_count($ALLEventsTableText, " FM");
   $LQCount = substr_count($ALLEventsTableText, " LQ");

// Count events and look for recurring events.
// Construct annotations for recurring events, if any.
   $AnnotText = '';

   if ($ACount  == 0) {$AnnotText .= "There is no apogee in the month.\n";}
   if ($PCount  == 0) {$AnnotText .= "There is no perigee in the month.\n";}
   if ($NMCount == 0) {$AnnotText .= "There is no New Moon in the month.\n";}
   if ($FQCount == 0) {$AnnotText .= "There is no First Quarter Moon in the month.\n";}
   if ($FMCount == 0) {$AnnotText .= "There is no Full Moon in the month.\n";}
   if ($LQCount == 0) {$AnnotText .= "There is no Last Quarter Moon in the month.\n";}

   if ($ACount   > 1) {$AnnotText .= "There are two apogees in the month.\n";}
   if ($PCount   > 1) {$AnnotText .= "There are two perigees in the month.\n";}
   if ($NMCount  > 1) {$AnnotText .= "There are two New Moons in the month.\n";}
   if ($FQCount  > 1) {$AnnotText .= "There are two First Quarter Moons in the month.\n";}
   if ($FMCount  > 1) {$AnnotText .= "There are two Full Moons in the month.\n";}
   if ($LQCount  > 1) {$AnnotText .= "There are two Last Quarter Moons in the month.\n";}



// Get full local lunar month table for Year/Month.
   $TableOutText = '';

   for ($i=0;   $i < $w1Count;   $i++)
  {
   $CurrLine = trim($w1Array[$i]);
   $CurrJDLT = floatval($CurrLine);
   $CurrMark = trim(substr($CurrLine, -2));

   $CurrLEStatsStr = Interp_Lunar_Ephem_Stats ($w2, $CurrJDLT);

// Parse and format the ephemeris details.
   list
  (
   $wJDLT,
   $wRAHrs,
   $wDeclDeg,
   $wDistKm,
   $wPhaseDeg
  ) = preg_split("[ ]", strip_wspace($CurrLEStatsStr));

   switch ($CurrMark)
   {
   case 'NM' : $EventText = 'New Moon          '; break;
   case 'FQ' : $EventText = 'First Quarter Moon'; break;
   case 'FM' : $EventText = 'Full Moon         '; break;
   case 'LQ' : $EventText = 'Last Quarter Moon '; break;
   case 'A'  : $EventText = 'Lunar Apogee      '; break;
   case 'P'  : $EventText = 'Lunar Perigee     '; break;
   }

   $CurrDateTimeStr = str_replace('+', '', Inv_JD ($CurrJDLT));


// Compute wJDTT corresponding to wJDLT
   $wFracDayDT = NASA_Delta_T_Poly($wJDLT, 'G', 5);
   $whhmmssDT  = NASA_Delta_T_Poly($wJDLT, 'G', 3);
   $wJDUT = $wJDLT - $wFracDayTZ;
   $wJDTT = $wJDUT + $wFracDayDT;


// Do some formatting
   $wJDUT      = sprintf("%7.8f",  $wJDUT);
   $wJDTT      = sprintf("%7.8f",  $wJDTT);
   $wFracDayDT = sprintf("%+1.8f", $wFracDayDT);
   $wRAHMS     = Hours_to_HMS ($wRAHrs, 3, '', 'h');
   $wDeclDMS   = str_replace  ('+0', '+', Deg_to_DMS ($wDeclDeg, 2, '+', 'd'));
   $wDeclDMS   = str_replace  ('-0', '-', $wDeclDMS);
   $wDistMi    = sprintf("%6.3f", $wDistKm / 1.609344);
   $EVENTTEXT  = strtoupper($EventText);

   if($wPhaseDeg == 90 or $wPhaseDeg == 180 or $wPhaseDeg == 270 or $wPhaseDeg == 360)
     {$wPhaseDeg = round($wPhaseDeg);}

   $wPhaseDeg -= ($wPhaseDeg == 360)? 360:0;

   $sign = substr($wDeclDeg, 0,1);
   if (abs($wDeclDeg) < 10) {$wDeclDeg = "$sign"."0".abs($wDeclDeg);}

   if ($wRAHrs < 10)   {$wRAHrs = " $wRAHrs";}



   $DetailsText =
"$EVENTTEXT                $CurrDateTimeStr $LTUTStr

JD = $wJDLT LT          Time Zone UT$whhmmTZ
   = $wJDUT UT          Delta T     $whhmmssDT
   = $wJDTT TT                =     $wFracDayDT day

            GEOCENTRIC
Right Ascension     Declination        Distance     Phase Ang
---------------- ----------------- --------------- -----------
$wRAHrs h      $wDeclDeg&deg;       $wDistKm km   $wPhaseDeg&deg;
$wRAHMS   $wDeclDMS   $wDistMi mi



==============================================================";

   $TableOutText .= "$DetailsText\n";
  }
  $w = trim($TableOutText);

  $S = (substr_count($AnnotText, 'are two') > 1)? 'S':'';
    if ($AnnotText <> ''){$AnnotText = "SPECIAL NOTE$S:\n$AnnotText";}

return
"
$AnnotText
==============================================================
$w
";


} // End of  Lunar_Events_Details (ALLEventsTableText, ...)





?>


