<?php
/*
###########################################################################
Sun and Moon Rise, Transit and Setting Times - WITH 30-DAY COOKIE
AUTHOR : Jay Tanner - 2025
LANGUAGE : PHP v8.2.12
LICENSE : Public Domain
This program computes a table of rise, transit and setting times for only
the Sun and Moon.
###########################################################################
*/
ob_start();
$MONTHS = 'JanFebMarAprMayJunJulAugSepOctNovDec';
// ---------------------------------------------------------------
// Define the program cookie name and set it to expire in 30 days.
$CookieName = 'Object-Rising-Transit-Setting-Times';
$SetToExpireIn30Days = time() + 30*86400;
// Define JavaScript message to display while working.
$_COMPUTING_ = "TextArea1.innerHTML=' W.O.R.K.I.N.G --- This may take several seconds.';";
// ---------------------------------
// Define PHP program and HTML info.
$_AUTHOR_ = 'Jay Tanner of Waterloo, NY, USA';
$_PROGRAM_VERSION_ = 'v1.00 - '; $at = "at Local Time "; $LTC = "UTC";
$_SCRIPT_FILE_PATH_ = Filter_Input(INPUT_SERVER, 'SCRIPT_FILENAME');
$_REVISION_DATE_ = $_PROGRAM_VERSION_ .'Revised: '. date("Y-F-d-l $at h:i:s A ($LTC", FileMTime($_SCRIPT_FILE_PATH_))."−05:00)";
$_BROWSER_TAB_TEXT_ = "Sun and Moon Rise, Transit and Setting Times";
$_INTERFACE_TITLE_ = "<span style='font-size:16pt; line-height:130%;'>Sun and Moon<br>Rising, Transit and Setting Times</span><br><br><span style='font-size:12pt;'>Computations via The NASA/JPL Horizons API</span><br><span style='font-size:9pt;'>PHP Program by $_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,$TimeZone,$DaySumTimeYN,$LatDeg,$LonDeg,$AltMet) = 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';
$TimeZone = '-05:00'; // MUST be in '+-HH:MM' format.
$DaySumTimeYN = 'No';
$LatDeg = '+42.904788';
$LonDeg = '-76.862737';
$AltMet = '0';
// -------------------------------------------
// Store current interface settings in cookie.
$CookieDataString = "$BCAD|$Year|$Month|$Day|$TimeZone|$DaySumTimeYN|$LatDeg|$LonDeg|$AltMet";
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) == '') {$BCAD = 'AD';}
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');}
/* -------------------------------------------
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));
if ($Month == '') {$Month = date('M');}
$xMonth = $Month;
if (Is_Numeric($Month) and $Month == 0) {$Month = 'Jan';}
for ($ii=0; $ii < 1; $ii++)
{
if (Is_Numeric($Month))
{
$m = IntVal($Month);
if ($m < 0 or $m > 12) {$Month = 0; break;}
$Month = substr($MONTHS, 3*($m-1), 3);
}
$jj = StrPos($MONTHS, $Month);
if ($jj === FALSE) {$Month = 0; break;}
$Month = substr($MONTHS, $jj, 3);
}
$Day = trim(Filter_Input(INPUT_POST, 'Day'));
if ($Day == '') {$Day = date('d');}
if (Is_Numeric($Day)) {$Day = SPrintF('%02d', $Day);}
$TimeZone = trim(Filter_Input(INPUT_POST, 'TimeZone'));
if ($TimeZone == '') {$TimeZone = '-05:00';}
$TimeZone = HMS_to_Hours($TimeZone);
$TimeZone = substr(Hours_to_HMS ($TimeZone, 0, '+', ':'),0,6);
$DaySumTimeYN = StrToUpper(substr(trim(Filter_Input(INPUT_POST, 'DaySumTimeYN')),0,1));
$DaySumTimeYN = ($DaySumTimeYN <> 'Y')? 'No':'Yes';
$LatDeg = trim(Filter_Input(INPUT_POST, 'LatDeg'));
if ($LatDeg == '') {$LatDeg = '+42.904788';}
$LatDeg = trim(DMS_to_Degrees ($LatDeg, 9));
if ($LatDeg >= 0) {$LatDeg = "+$LatDeg";}
$LatDeg = Str_Replace('++', '+', $LatDeg);
$LonDeg = trim(Filter_Input(INPUT_POST, 'LonDeg'));
if ($LonDeg == '') {$LonDeg = '-76.862737';}
$LonDeg = trim(DMS_to_Degrees ($LonDeg, 9));
$AltMet = trim(Filter_Input(INPUT_POST, 'AltMet'));
if ($AltMet == '') {$AltMet = '0.0';}
$AltMet = SPrintF("%+1.1f", $AltMet);
// --------------------------------------
// Store interface arguments in a cookie.
$CookieDataString = "$BCAD|$Year|$Month|$Day|$TimeZone|$DaySumTimeYN|$LatDeg|$LonDeg|$AltMet";
SetCookie ($CookieName, $CookieDataString, $SetToExpireIn30Days);
}
// ---------------------------------------------
// Check input values for validity.
// If error, set error flag and message values.
$ErrFlag = FALSE;
$ErrMssg = '';
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'.";
$Month = $xMonth;
}
if (!Is_Numeric($Year) or $Year == 0 or $Year > 9999)
{
$ErrFlag = TRUE;
$ErrMssg = "Bad year number.\n'$Year'\n\nThe year must be a non-zero integer ≤ 9999. ";
}
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. ";
}
// if (Is_Numeric($Day) and $Day < 1 or $Day > 31)
if (!Is_Numeric($Day) or (Is_Numeric($Day) and $Day < 1 or $Day > 31))
{
$ErrFlag = TRUE;
$ErrMssg = "Bad day number.\n'$Day'\n\nThe day must be a positive integer ≤ 31. ";
}
// --------------------------------
// Define E/W/N/S direction labels.
$EWLable = ($LonDeg < 0)? "+East":"−West";
$NSLable = ($LatDeg < 0)? "−South":"+North";
$TZLable = (substr($TimeZone,0,1) == '-')? "+East":"−West";
// -----------------------------
// Set initial uniform width for
// tables alignment in pixels.
$TableWidth = '780';
// ******************************************
// ******************************************
// 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 4-digit format.
$Year = SPrintF("%04d", abs($Year));
/* ---------------------------------------------------------------
Define list of Body IDs to process. Processing can take several
seconds, depending on the number of bodies in the listing.
*/
$LocLabel = 'Waterloo, NY, USA';
$BodyIDList = '10 Sun, 301 Moon';
// -------------------------------------------------
// Construct calling date argument for the function.
$DateStr = "$BCAD $Year-$Month-$Day";
// --------------------------------------------------
// Generate the RTS table for the bodies in the list.
$RTSTimesTable = Bodies_RTS ($BodyIDList,$DateStr,$TimeZone,$DaySumTimeYN,
$LocLabel,$LatDeg,$LonDeg,$AltMet);
// ---------------------------------------------
// Account for certain ephemeris error messages.
$RTSTimesTable = Str_Replace('prior to ', "prior to\n", $RTSTimesTable);
$RTSTimesTable = Str_Replace('Cannot interpret date. Type "?!" or try YYYY-MMM-DD {HH:MN} format.', "ERROR:\nAt least one of the given date elements is invalid.\n\n'$BCAD $Year-$Month-$Day'", $RTSTimesTable);
$RTSTimesTable = Str_Replace(' after A.D. ', " after\nA.D. ", $RTSTimesTable);
// *******************************************
// DROP THROUGH HERE AFTER COMPUTATIONS ABOVE
// TO PRINT OUT THE RESULTS OF THE OPERATIONS.
// *******************************************
$TextArea1Text =
" SUN AND MOON
RISING, TRANSIT AND SETTING TIMES
ERA Year-Month-Day = $BCAD $Year-$Month-$Day
Time Zone = $TimeZone ($TZLable)
Daylight/Summer Time = $DaySumTimeYN
Latitude GPS = $LatDeg ° ($NSLable)
Longitude GPS = $LonDeg ° ($EWLable)
Altitude in meters = $AltMet m
$RTSTimesTable
";
}
// ****************************
// Define TextArea2 text block.
$TextArea2Text =
" GENERAL INFORMATION
Ephemeris span for the Sun : BC 9999 to AD 9999
Ephemeris span for the Moon : BC 9999 to AD 9999
See:
https://ssd.jpl.nasa.gov/horizons/time_spans.html
IMPORTANT NOTE:
Make sure that the given geographic GPS coordinates are within the given
Time Zone. The time zone and local coordinates must match or the computed
times will NOT be correct.
";
// **************************************************************************
// 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.
// ---------------------------
// TextArea1 columns and rows.
$Text1Cols = 2 + Max(Array_Map('StrLen', PReg_Split("[\n]", trim($TextArea1Text))))-9;
// if ($Text1Cols < 80) {$Text1Cols = 60;}
$Text1Rows = 3 + Substr_Count($TextArea1Text, "\n");
// ---------------------------
// TextArea2 columns and rows.
$Text2Cols = 1 + Max(Array_Map('StrLen', PReg_Split("[\n]", trim($TextArea2Text))));
if ($Text2Cols < 80) {$Text2Cols = 75;}
$Text2Rows = 2 + Substr_Count($TextArea2Text, "\n");
// ******************************************
// ******************************************
// GENERATE CLIENT WEB PAGE TO DISPLAY OUTPUT
print <<< _HTML
<!DOCTYPE HTML>
<HTML>
<head>
<title>$_BROWSER_TAB_TEXT_</title>
<meta name='viewport' content='width=device-width, initial-scale=0.8'>
<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='Solar System Rise, Transit and Setting Times'>
<meta name='keywords' content='PHPScienceLabs.com'>
<meta name='author' content='Jay Tanner - https://www.PHPScienceLabs.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:cyan; 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 red;
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>
<br>
<!-- 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 style="background:LightYellow; line-height:175%;">
Date <input name="BCAD" type="text" value="$BCAD" size="3" maxlength="2">
<input name="Year" type="text" value="$Year" size="5" maxlength="4">
<input name="Month" type="text" value="$Month" size="4" maxlength="3">
<input name="Day" type="text" value="$Day" size="3" maxlength="2">
Time Zone <input name="TimeZone" type="text" value="$TimeZone" size="7" maxlength="6">
Daylight/Summer Time <input name="DaySumTimeYN" type="text" value="$DaySumTimeYN" size="4" maxlength="3">
</td></tr>
</table>
<!-- LONGITUDE/LATITUDE/ALTITUDE SETTINGS TABLE --->
<table width="$TableWidth" align="center" border="0" cellspacing="1" cellpadding="3">
<tr title=" IMPORTANT:\n Make sure that the given Geographic Coordinates are within the \n given Time Zone or the computed times will not be accurate. \n ">
<!-- GPS Latitude in Degrees or DMS --->
<td width='33%' style='background:#E0FFE0; line-height:180%; border-radius:0px 0px 0px 8px;'>± GPS Latitude +Pos = N
<input name="LatDeg" type="text" value="$LatDeg" size="17" maxlength="16" title=' GPS Latitude in Decimal Degrees\nor as\n Deg Min Sec Separated by Spaces. \n'></td>
<!-- GPS Longitude in Degrees or DMS --->
<td width='33%' style='background:#E0FFE0; line-height:180%;'>± GPS Longitude +Pos = E
<input name="LonDeg" type="text" value="$LonDeg" size="17" maxlength="16" title=' GPS Longitude in Decimal Degrees\nor as\n Deg Min Sec Separated by Spaces \n'></td>
<!-- Sea level altitude --->
<td style='background:#E0FFE0; line-height:180%; border-radius:0px 0px 8px 0px;'>± Sea Level Altitude in Meters
<input name="AltMet" type="text" value="$AltMet" size="9" maxlength="8" title=' Altitude in meters relative to sea level. '>
</td>
</tr>
</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 " OnClick="$_COMPUTING_"></td></tr>
</table>
<!-- Yellow source code view link. --->
<table width='550' align='center' cellspacing='1' cellpadding='3'>
<!-- Yellow source code view link. --->
<tr>
<td colspan='1' style='background:transparent; color:black; font-size:10pt;
text-align:center;'>
<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;'>
<span style='font-weight:normal;'>View/Copy PHP Source Code</span> </a>
</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:GreenYellow; 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:GreenYellow; 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 constructs a table of rise, transit and setting times
for a list of any given objects for which an apparent topocentric
ephemeris can be computed.
*/
function Bodies_RTS ($BodyIDList,$DateStr,$TimeZone,$DaySumYN,
$LocLabel,$LatDeg,$LonDeg,$AltMet)
{
GLOBAL $OBJ;
$LocLabel = trim($LocLabel); if ($LocLabel == '') {$LocLabel = '---';}
$wArray = PReg_Split("[,]", trim($BodyIDList));
$wCount = count($wArray);
$out = '';
for ($i=0; $i < $wCount; $i++)
{
$CurrBodyID = trim($wArray[$i]);
$w = R_T_S_Times ($CurrBodyID,$DateStr,$TimeZone,$DaySumYN,
$LocLabel,$LatDeg,$LonDeg,$AltMet);
$out .= "$w\n\n";
}
return trim($out);
}
/*
This function returns a compass direction string
for any given azimuth (direction) angle.
*/
function Compass_Symbol ($AzimDeg)
{
$ZModeN0 = 'N NNENE ENEE ESESE SSES SSWSW WSWW WNWNW NNWN';
$a = trim($AzimDeg); if (!Is_Numeric($a)) {return FALSE;}
$a -= 360*floor($a/360);
$i = ($a < 11.25 or $a > 348.75)? 0:floor(0.5 + $a/22.5);
$w = trim(substr($ZModeN0, 3*$i, 3));
// if (StrLen($w) == 1) {$w = " $w ";} // ' N '
// if (StrLen($w) == 2) {$w = " $w ";} // 'NW '
return $w;
} // End of Compass_Symbol (...)
/*
###########################################################################
This function converts a DMS time string into equivalent decimal degrees.
INPUT: 1, 2 or 3-element DMS (Degrees Minutes Seconds) separated by spaces
as angular string as 'deg min sec' values.
Example: Given DMS string = '9 12 50.306'
Returns: 9.2139738888888889
NO DEPENDENCIES
ERRORS:
No error checking is done.
###########################################################################
*/
function DMS_to_Degrees ($DMSstr, $Decimals=16)
{
$dms = StrToLower(trim($DMSstr));
$Q = 40;
$q = trim($Decimals);
/* ----------------------------------------------------
Normalize the spacing and then split and extract the
individual angular string elements (dh or hh, mm,ss).
*/
$dms = PReg_Replace("/\s+/", " ", trim($dms));
$wdms = PReg_Split("[ ]", $dms);
$wdmscount = count($wdms);
$dd = ($wdmscount >= 1)? bcAdd($wdms[0],"0", $Q) : "0";
$mm = ($wdmscount >= 2)? bcAdd($wdms[1],"0", $Q) : "0";
$ss = ($wdmscount >= 3)? bcAdd($wdms[2],"0", $Q) : "0";
// ------------------------------------------------
// Remember and then remove any numerical (±) sign.
$NumSign = (substr($DMSstr,0,1) == '-')? '-' : '';
$dd = Str_Replace('-', '', $dd);
$dd = Str_Replace('+', '', $dd);
/* -------------------------------------------------------------
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($DMSstr,0,1) == '+') {$NumSign = '+';}
/* ----------------------------------------------
Compute decimal degrees/hours value equivalent
to the given DHMS argument elements.
*/
$w2 = bcAdd(bcAdd(bcMul($dd,"3600",$Q),bcMul($mm,"60",$Q),$Q),$ss,$Q);
// -----------------------------------------------------------
// If result equates to zero, then suppress any numerical sign.
if (bcComp($w2, '0', $Q) == 0) {$NumSign = '';}
// -------------------------------------------------------
// Return result to (q) decimals, recalling original sign.
$z = Str_Repeat('0', $q);
$w = $NumSign . bcAdd(bcDiv($w2,"3600",$Q), "0.".$z."5", $q);
// $w = RTrim(RTrim($w, '0'), '.');
return $w;
} // End of DMS_to_Degrees (...)
/*
============================================================================
This function converts a time elements string ('h m s') to decimal hours.
NO DEPENDENCIES
============================================================================
*/
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.
$w = bcAdd(bcDiv($w2,3600, 20), '0.00000000000000005', 16);
// Reattach numerical sign and trim off
// any redundant zeros/decimal point.
return $NumSign.Rtrim(Rtrim($w, '0'), '.');
} // End of HMS_to_Hours (hhmmss)
/*
===========================================================================
This function returns the equivalent H:M:S time string with
several formatting options.
hours = ± Decimal hours value
ssDecimals = Number of decimals in seconds part
posSign = Symbol to use for positive values ('' or '+')
'' = Empty = Return numbers only, no symbols.
'+' = Attach '+' sign to positive values.
Has no effect on negative values.
SymbolsMode = If or not to attach time symbols (h m s) or (:)
'h' = '01h 02m 03s' (Default)
':' = '01:02:03'
'' = '01 02 03'
ERRORS
No special error checking is done by this function.
NO DEPENDENCIES
*/
function Hours_to_HMS ($hours, $ssDec=0, $posSignSymb='', $SymbMode='h')
{
// Initialize symbol carriers.
$_h_ = $_m_ = $_s_ = '';
if (trim($posSignSymb) == '') {$posSignSymb = 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 (($posSignSymb === TRUE or $posSignSymb == '+') 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.$ssDec" . "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.$ssDec" . "f", $ss);
if ($ss < 10) {$ss = "0$ss";}
// Attach optional (h, m, s, :) symbols as indicated.
// Default = 'h'
if ($SymbMode == 0 or $SymbMode == '')
{$_h_ = $_m_ = $_s_ = ' ';}
if ($SymbMode == 1 or $SymbMode == ':')
{$_h_ = ':'; $_m_ = ':'; $_s_ = '';}
if ($SymbMode == 2 or StrToLower($SymbMode) == 'h')
{$_h_ = 'h '; $_m_ = 'm '; $_s_ = 's';}
$w = "$sign$hh$_h_$mm$_m_$ss$_s_";
// Done.
return $w;
} // End of Hours_to_HMS(...)
/*
###########################################################################
This function returns a topocentric ephemeris of rise/transit/set times
from the NASA/JPL Horizons API. A topocentric ephemeris takes the location
and altitude of the observer into account. Standard Earth refraction model
is applied.
NOTE: The BodyID MUST be an integer record # (301)
For asteroids, add a semicolon (;) to the record #. (301;)
DaySumYN = Means Daylght Saving/Summer Time 'Yes|No'
'No' = Use Standard Clock Time (Default)
'Yes' = Use Daylight Saving/Summer Clock Time
Angles are expressed in degrees.
COLUMN CONTENT
====== =======================================================================
1 Date and Time String (UT or ZONE)
2 Sp (Solar Presence Symbol)
3 Lp (Lunar Presence Symbol)
4 Azimuth Angle (Compass Direction Reckoned Clockwise From North = 0)
5 Elevation Angle (Relative to Horizon = 0 degrees)
6 Apparent Visual Magnitude or Brightness Level
7 S-brt (Brightness of 1 Square Arcsecond of Visible Disc)
NO DEPENDENCIES
###########################################################################
*/
function R_T_S_Times ($BodyID,$DateStr,$TimeZone,$DaySumYN,
$LocLabel,$LatDeg,$LonDeg,$AltMet)
{
// ===========================================================================
// Construct query URL for the NASA/JPL Horizons API.
GLOBAL $OBJ;
$OBJ = $BodyID = trim($BodyID);
if (strPos($BodyID, ';') === FALSE)
{$BodyID = IntVal(trim($BodyID));}
else
{$BodyID = IntVal(trim($BodyID)) . ';';}
$Command = URLEncode($BodyID);
$AltKm = trim($AltMet) / 1000;
$LocLabel = trim($LocLabel); if ($LocLabel == '') {$LocLabel = '---';}
/* -----------------------------------------------------------
Adjust for Daylight/Summer Time, if indicated. This assumes
that the Time Zone string is given in the standard +-HH:mm
format or an error may occur.
*/
$DaySumYN = substr(StrToUpper(trim($DaySumYN)),0,1);
$DaySumAdj = ($DaySumYN == 'N')? 0:1;
list($TZHH, $TZmm) = PReg_Split("[\:]", $TimeZone);
$TZSign = substr($TZHH,0,1);
$TZHours = (($TZSign == '-')? -1:1)*(abs($TZHH) + $TZmm/60) + $DaySumAdj;
$i = StrPos($TZHours, '.'); if ($i == FALSE) {$TZHours .= '.00';}
$i = StrPos($TZHours, '.');
$TZHH = $TZSign.SPrintF("%02d", abs(substr($TZHours,0,$i)));
$TimeZone = "$TZHH:$TZmm";
// -------------------------------------
// Construct URL for Horizons API query.
$From_Horizons_API =
"https://ssd.jpl.nasa.gov/api/horizons.api?format=text" .
"&COMMAND='$Command'" .
"&OBJ_DATA='NO'" .
"&MAKE_EPHEM='YES'" .
"&EPHEM_TYPE='OBSERVER'" .
"&CAL_FORMAT='CAL'" .
"&REF_SYSTEM='ICRF'" .
"&APPARENT='REFRACTED'" .
"&CENTER='COORD@399'" .
"&COORD_TYPE='GEODETIC'" .
"&SITE_COORD='$LonDeg,$LatDeg,$AltKm'" .
"&TIME_DIGITS='MINUTES'" .
"&TIME_ZONE='$TimeZone'" .
"&START_TIME='$DateStr%2000:00:00%20UT'" .
"&STOP_TIME='$DateStr%2023:59:59'" .
"&STEP_SIZE='1 MINUTE'" .
"&EXTRA_PREC='YES'" .
"&R_T_S_ONLY='TVH'" .
"&QUANTITIES='4,9'" .
"&CSV_FORMAT='YES'" ;
// ============================================
/* -----------------------------------------------------------------------
Send query to Horizons API to obtain the apparent topocentric ephemeris
data we need for the R-T-S times of the given body ID.
*/
$RTS = Str_Replace(",\n", " \n", File_Get_Contents($From_Horizons_API));
$RTS = trim($RTS);
/* ----------------------------------------------------------------------
If no ephemeris data is found, then return an empty string as an error
state indicator.
*/
if (StrPos($RTS, '$$SOE') === FALSE)
{
return
"=======================================================
$OBJ\n\n$RTS";
}
/* -------------------------------------------------------------------------
DO NOT TRIM HERE BECAUSE INITIAL CHARACTER MAY BE A SPACE INDICATING AN
'AD' YEAR. 'BC' YEARS BEGIN WITH A SINGLE 'b' AS THE INITIAL CHARACTER.
BAD PRACTICE. LEADING/TRAILING SPACES SHOULD NOT BE USED AS TABLE DATA.
IT CAN SOMETIMES LEAD TO CONFUSION WHEN READING A LINE OF TABULAR TEXT.
IF A DATA COLUMN IS EMPTY, A SPECIAL CHARACTER SHOULD BE ASSIGNED AS AN
INDICATOR OF AN EMPTY COLUMN. EXAMPLE (~), SIMILAR TO USING (n/a).
SOMETHING OTHER THAN SIMPLY LEAVING IT EMPTY OR USING A SPACE CHARACTER.
DIFFERENT PROGRAMMING LANGUAGES, WHEN READING THE TABLE, MAY NOT TREAT
LEADING OR TRAILING SPACES THE SAME WAY AND THEY MAY POSSIBLY BE LOST.
*/
/* -----------------------------------------------------
Set pointers to start and end of CSV ephemeris table.
A value of FALSE means no ephemeris was found within
the returned text. Instead, it could be an error or
other message.
*/
$j = StrPos($RTS, '$$SOE');
$k = StrPos($RTS, '$$EOE');
$RawRTSTable = RTrim(substr($RTS, $j+5, $k-$j-5));
// Construct custom formatted RTS table.
$wArray = PReg_Split("[\n]", trim($RawRTSTable));
$wCount = count($wArray);
$out = '';
for ($i=0; $i < $wCount; $i++)
{
$CurrLine = trim($wArray[$i]);
$out .= "$CurrLine\n";
}
$out2 = '';
$uArray = PReg_Split("[\n]", trim($out));
$uCount = count($uArray);
for($j=0; $j < $uCount; $j++)
{
$X = trim($uArray[$j]);
list($DateTimeStr, $w, $RTorS, $azim, $elev, $VMag) = PReg_Split("[,]", $X);
// -------------------
// Get RTS indicators.
$DateTimeStr = Str_Replace(' ', ' ', $DateTimeStr);
if ($RTorS == 'r') {$RTorS = 'Rises ';}
if ($RTorS == 't') {$RTorS = 'Transits';}
if ($RTorS == 's') {$RTorS = 'Sets ';}
// Handle BC/AD era symbol.
if (substr($DateTimeStr,0,1) == 'b')
{$DateTimeStr = 'BC ' . substr($DateTimeStr, 1, StrLen($DateTimeStr));}
if (Is_Numeric(substr($DateTimeStr,0,1)))
{$DateTimeStr = "AD $DateTimeStr";}
// ---------------------------------------------
// Handle azimuth and compass direction symbols
$azim = SPrintF("% 7.2f", $azim);
$AzimStr = trim(Compass_Symbol($azim));
if (StrLen($AzimStr) == 1) {$AzimStr = " $AzimStr ";}
if (StrLen($AzimStr) == 2) {$AzimStr = "$AzimStr ";}
// ------------------------------------------
// Handle 'Rise', 'Transit' and 'Set' labels.
if ($RTorS == 'Transits') {$azim = ' | '; $AzimStr = "$AzimStr ";}
// -----------------------
// Handle elevation angle.
if ($RTorS <> 'Transits')
{
$elev = ' - ';
}
else
{
$elev = SPrintF("% 5.2f", $elev);
}
/* ------------------------------------------------------
Attach numerical + sign to positive visual magnitudes
unless the value is non-numeric
*/
$VMag = trim($VMag);
if (Is_Numeric($VMag))
{$VMag = SPrintF("%+7.3f", $VMag);}
else
{$VMag = " - ";}
// --------------------------------
// Append line to constructd table.
$out2 .= "$DateTimeStr $RTorS $azim $AzimStr $elev $VMag\n";
}
// ----------------------------------------
// Return customized RTS table with header.
$w =
"===============================================================
$OBJ
Calendar_Date Time Event Azim° Dir Elev° Vis_Mag
-------------- ----- -------- ------ --- ----- -------
$out2
";
return trim($w);
} // End of R_T_S_Times(...)
// END OF PROGRAM
?>