<?php
/*
EARTH - PERIHELIONS AND APHELIONS
FOR THE YEARS FROM 1000 TO 9998.
Based on the NASA/JPL Horizons API v1.1
The NASA/JPL Horizons went on-line in July
of 2021. This program implementS that API.
PHP program by Jay Tanner
License: Public Domain
*/
// Initialize output buffer.
ob_start();
// Set note of PHP version.
$PHPVersionStr = 'PHP 7.4.9';
// Get script path and filename.
$_AUTHOR_ = 'Jay Tanner';
$_PROGRAM_VERSION_ = ''; $at = "at"; $UTC = "UTC";
$_SCRIPT_PATH_ = Filter_Input(INPUT_SERVER, 'SCRIPT_FILENAME');
$_RUN_ = Filter_Input(INPUT_SERVER, 'SCRIPT_NAME');
// Set ephemeris year range.
$MinYear = 200;
$MaxYear = 9998;
$EphSpan = $MaxYear - $MinYear + 1;
// Define internal document page title, HTML
// page heading text and revision date.
$_INTERNAL_TITLE_ = "Earth Perihelion and Aphelion Calculator";
$_INTERFACE_TITLE_ = "<b style='font-size:160%; font-weight:normal;'>Earth Perihelion and Aphelion Calculator</b><br><span style='font-size:100%;'>For the $EphSpan-Year Span From $MinYear AD to $MaxYear AD<br>
<b style='font-weight:normal;'>Built Around the NASA/JPL Horizons API<br>PHP Program by Jay Tanner</b></span>";
$_THIS_SCRIPT_NAME_ = BaseName($_SERVER["SCRIPT_FILENAME"]);
$at = "at"; $UTC = "UTC";
$_REVISION_DATE_ = 'Revised: '.GMDate("l - F d, Y $at H:i:s $UTC", FileMTime($_THIS_SCRIPT_NAME_));
// Define the program cookie name and set it to expire in 7 days.
// This cookie can be shared by other programs in the same common
// home folder together.
$_COOKIE_NAME_ = 'Perigees_and_Apogees_Calculator';
$SetToExpireIn7Days = time() + 7*86400;
// Define main TextArea text, background
// colors and HTML table row span.
$TxColor = 'black';
$BgColor = 'white';
// Initialize time scale strings.
$UT1C = $TimeScale = $TScaleStr = '';
// Do this only if [COMPUTE] button was clicked.
// If [COMPUTE] button was clicked and an active cookie
// exists, then restore the previous interface settings
// from it.
$w = Filter_Input(INPUT_POST, 'ComputeButton');
if (!IsSet($w))
{
$w = Filter_Input(INPUT_COOKIE, $_COOKIE_NAME_);
if (IsSet($w))
{
$CookieDataString = Filter_Input(INPUT_COOKIE, $_COOKIE_NAME_);
list
(
$Year, $TZhhmm, $kmmi, $LocLabel
) = Preg_Split("[\|]", $CookieDataString);
}
else
// Set the initial default interface startup values.
{
$Year = GMDate('Y');
$TZhhmm = '+00:00';
$kmmi = 'km';
$LocLabel = '';
// Store interface settings in cookie.
$CookieDataString = "$Year|$TZhhmm|$kmmi|$LocLabel";
setcookie ($_COOKIE_NAME_, $CookieDataString, $SetToExpireIn7Days);
}
} // End of if (!isset(_POST['ComputeButton']))
// =======================================
// Read values of all interface arguments.
// Empty values are set to defaults.
$w = Filter_Input(INPUT_POST, 'ComputeButton');
if (IsSet($w))
{
// Read given year value.
$Year = trim(Filter_Input(INPUT_POST, 'Year'));
if ($Year == '') {$Year = GMDate('Y');}
$Year = bcAdd($Year, 0);
// Read local time zone offset.
$TZhhmm = trim(Filter_Input(INPUT_POST, 'TZhhmm'));
if ($TZhhmm == '') {$TZhhmm = '+00:00';}
$w = Num_to_HMS($TZhhmm);
$w = HMS_to_Hours($w);
$w = Hours_to_HMS($w, 0, '+', ':');
$TZhhmm = substr($w, 0, 6);
// Select distance units if moon (ID 301)
// is the body. Can be either 'km' or 'mi'.
// Otherwise, 'AU' will be used for other
// bodies.
$kmmi = 'km';
// Read optional location label string. This location should
// match the given time zone. For example, TZ = -05:00
// would not apply to London, UK (TZ = 00:00) and would
// be wrong and look abnormal. It is simply an optional
// reference description to apply to the returned table
// and can consist of any printable text.
$LocLabel = trim(Filter_Input(INPUT_POST, 'LocLabel'));
// -----------------------------------
// Store interface values in a cookie.
$CookieDataString = "$Year|$TZhhmm|$kmmi|$LocLabel";
SetCookie ($_COOKIE_NAME_, $CookieDataString, $SetToExpireIn7Days);
}
// =========================================
// Check for errors. FALSE = Error detected.
// Change this code to detect errors.
$ErrFlag = TRUE;
// Check for valid year argument.
if (!Is_Numeric($Year) or $Year < $MinYear or $Year > $MaxYear)
{
$ErrFlag = FALSE;
$ErrMssg =
"Invalid Year: '$Year'
The year must be a positive integer in the range from $MinYear to $MaxYear.
";
}
// Check for invalid time zone argument.
if (abs(HMS_to_Hours($TZhhmm)) > 14)
{
$ErrFlag = FALSE;
$ErrMssg =
"Invalid Time Zone Offset: '$TZhhmm'
The time zone offset must be in the range from 00:00 to ± 14:00
";
}
// =====================================
// Check if any errors (FALSE) detected.
// If so, display error in RED/WHITE.
if ($ErrFlag === FALSE)
{
$TxColor = 'white';
$BgColor = '#CC0000';
$TextArea2Text = '';
$TextArea1Text =
"= ERROR =============================================================
$ErrMssg";
}
else
// BEGIN MAIN COMPUTATIONS BELOW IF
// NO ERRORS DETECTED AT THIS POINT.
// -----------------------------------
// DEFINE EPHEMERIS PARAMETER SETTINGS
// AND QUANTITIES TO BE COMPUTED.
//
// UT is used for internal computations and
// then the output converted for the given
// time zone offset from UT.
{
// Set solar system body ID
$body_id = 10; // = Sun
// Set start/stop times for a full-year
// Earth perihelion and aphelion ephemeris.
$PrevYear = $Year - 1;
$NextYear = $Year + 1;
$TScale = 'UT';
// Set starting and stopping times for the ephemeris.
// This range may vary depending on what computations
// are being done. Table computed at 3-hour intervals.
$start_time = "$PrevYear-Nov-30"; // Start
$stop_time = "$NextYear-Jan-02"; // Stop
$step_size = '3h';
// ==============================================================
// CONSTRUCT CALLING URL FOR JPL HORIZONS API. IN THE CASE OF THE
// MOON, USING KILOMETERS AS RANGE (DISTANCE) UNITS MAY BE MORE
// CONVENIENT THAN THE DEFAULT AUs.
$FROM_NASA_JPL_HORIZONS_API =
"https://ssd.jpl.nasa.gov/api/horizons.api?format=text" .
"&COMMAND='$body_id'" .
"&OBJ_DATA='NO'" .
"&MAKE_EPHEM='YES'" .
"&EPHEM_TYPE='OBSERVER'" .
"&CENTER='500@399'" .
"&START_TIME='$start_time'" .
"&STOP_TIME='$stop_time'" .
"&STEP_SIZE='$step_size'" .
"&QUANTITIES='20'" .
"&CAL_FORMAT='BOTH'" .
"&SUPPRESS_RANGE_RATE='NO'" .
"&RANGE_UNITS='AU'";
// ==============================================================
// Determine if Julian or Gregorian calendar
// applies or both (1582-Oct only).
// Julian = Dates before 1582-Oct-15-Fri
// Gregorian = Dates since 1582-Oct-15-Fri
// Both = Only the month 1582-Oct
if ($Year < 1583)
{$JGMessage = "\n Dates refer to the old Julian calendar.";}
if ($Year == 1582)
{$JGMessage = "\n Julian calendar used for dates < 1582-Oct-05-Thu\n and Gregorian calendar used for all later dates.";}
if ($Year > 1582)
{$JGMessage = "\n Dates refer to the modern Gregorian calendar.";}
// Determine time scale (UT or LT)
// UT = Universal Time
// LT = Local Time for given TZ
$TimeZoneHM = HMS_to_Hours($TZhhmm);
$TimeScale = ($TimeZoneHM <> 0)? 'LT':'UT';
// Adjust time scale label string.
// UT1 before 1962
// UTC from 1962
$UT1C = ($Year < 1962)? '1':'C';
$TScaleStr = "$TimeScale$UT1C";
$TScaleStr =($TScaleStr == 'LT1' or $TScaleStr == 'LTC')? 'LT ':"UT$UT1C";
// Determinee DST (Daylight Saving Time) rules for
// the given year >= 1975
$DSTRules = USA_DST_Rules_Text ($Year);
// ===========================
// CREATE INTERNAL WORK-TABLES
// -------------------------------------------------------------
// Compute RAW ephemeris text table using NASA/JPL Horizons API.
// This raw text includes the header info, the ephemeris itself
// and the footer explaining the columns.
$RawEphemerisTable = trim(File_Get_Contents($FROM_NASA_JPL_HORIZONS_API));
// --------------------------------------------------------------------
// Extract ONLY the ephemeris part between the $$SOE and $$EOE markers.
// $$SOE = Start Of Ephemeris
// $$EOE = End Of Ephemeris
$EphemerisWorkTable = Extract_Ephemeris($RawEphemerisTable);
// Construct P/A work table(s). It may be possible for
// multiple A/P events to occur in a year, depending on
// the selected body.
$PAWorkTable = Make_PA_Work_Table($EphemerisWorkTable);
$PAEventsTableLT = Compute_PA_Events ($PAWorkTable, $TimeZoneHM);
// Patch for the sun for perihelion and aphelion.
$PAEventsTableLT = Str_Replace('Perigee', 'Perihelion', $PAEventsTableLT);
$PAEventsTableLT = Str_Replace('Apogee', 'Aphelion ', $PAEventsTableLT);
// ====================================
// Define output ephemeris header text.
$HeaderText =
"===========================================================================
EARTH PERIHELION AND APHELION FOR THE YEAR $Year
===============================================
Time Zone UT$UT1C$TZhhmm
$LocLabel
$JGMessage";
// Construct tabulated computations for display by client web browser.
$TextArea1Text =
"$HeaderText
===========================================================================
EVENT Julian Date $TScaleStr Calendar Date Time $TScaleStr Dist. AU/mi/km
---------- ----------------- --------------- ----------- ----------------
$PAEventsTableLT
===========================================================================
Event(s) listed may not occur on the same date in another time zone.";
}
// -----------------------------
// Define content for TextArea2.
$TextArea2Text =
"
NOTES:
[1] Occasionally a time-out may occur if the JPL Horizons server
is temporarily unavailable or too busy and an error occurs.
If such a crash or hang-up does occur, simply refresh and try
again.
[2] This Earth perihelion and aphelion calculator spans the $EphSpan
year period from $MinYear AD to $MaxYear AD.
It was written in $PHPVersionStr and makes background calls to the
JPL Horizons API. If run on a desktop server, it will need a
connection to the Internet to access the API.
[3] Julian Dates, Day Numbers and Calendar Dates:
Julian Dates (JD) and Calendar Dates:
JD < 2299160.5 = Refers to Old-Style Julian Calendar Dates
JD >= 2299160.5 = Refers to Modern Gregorian Calendar Dates
Dates up to 1582-Oct-04-Thu refer to the Julian calendar.
Dates from 1582-Oct-15-Fri refer to the Gregorian calendar.
The date following 1582-Oct-04-Thu was 1582-Oct-15-Fri, the
official first date on our modern Gregorian calendar system.
10 days were dropped from the calendar during the Julian to
Gregorian calendar transition to bring dates of the seasons
back into alignment with the sun and the rule for leap year
was changed to prevent the previous calendar error from re-
curring.
Given JD = General Julian Date, then the Julian Day Number
corresponding to that date on the Calendar is:
JDNum = floor(JD + 0.5)
The Julian Day Number is always a positive integer value serving
as a unique serial number for every date on the calendar and
holds the calendar date and the day of the week information.
The Julian Date holds the calendar date and the day of the week
with the fractional part holding the time of day information.
For the day of the week (DoW) index corresponding to any Julian
Date (JD), or Julian Day Number (JDNum), let the day of the week
be indicated by a numerical index DoW in the range from 0=Sun to
6=Sat.
Then:
DoW = (floor(JD + 0.5) + 1) mod 7
or
DoW = (JDNum + 1) mod 7
Where DoW: 0=Sun, 1=Mon, 2=Tue, 3=Wed, 4=Thu, 5=Fri and 6=Sat
The JDNum and DoW formulas apply to both the old Julian and
the modern Gregorian calendar systems.
[4] Standard times are assumed. To adjust for Daylight Saving or
Summer Time, add 1 hour to the standard times taking care to
watch for any change of date -OR- subtract 1 hour from the
time zone offset, which will automatically handle any date
change.
For example, the time zone offset for Eastern Standard Time
(EST) is normally -5 hours. However, when Eastern Daylight Time
(EDT) is in effect, use a time zone offset of -4 hours instead.
The computed times will be in EDT and automatically handle any
date changes.
[5] There is an optional location label that can be applied to the
computed table for reference. However, any label should match
the indicated time zone so as to be accurate and make sense.
For example, if using the time zone offset for India, putting
'New York City, USA' as the location, would be rather absurd
and an inaccurate match between location name and time zone.
The label can consist of any printable plain-text string and
defaults to Time Zone Offset +00:00 (Greenwich).
[6] This program implements a cookie to store and recall the year
and other interface settings between calls. It does not track,
monitor or perform any other activity. If you navigate away
and come back later, the interface settings stored in the
cookie will be recalled from your last visit.
Each call will refresh the cookie for up to 7 days retention
unless deleted sooner. There is no harm in deleting the cookie.
The program will simply revert to the original default values.
";
// -----------------------------------------------------------------
// Determine the number of text rows to use in the output TextAreas.
$TextArea1Rows = 2 + Substr_Count($TextArea1Text, "\n");
$TextArea2Rows = 2 + Substr_Count($TextArea2Text, "\n");
// Generate client webpage to display the computations.
print <<< _HTML
<!DOCTYPE HTML>
<html lang='en'>
<head>
<title>Earth - Perihelion and Aphelion Calculator</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<meta http-equiv='content-type' content='text/html; charset=UTF-8'>
<meta name='description' content='Earth Perihelion and Aphelion Calculator - Based on JPL Horizons API'>
<meta name='keywords' content='Earth,Perihelion,aphelion,JPL Horizons API, Horizons, NASA'>
<meta name='author' content='Jay Tanner - PHP Science Labs'>
<meta http-equiv='pragma' content='no-cache'>
<meta http-equiv='expires' content='-1'>
<meta name='robots' content='index,follow'>
<meta name='googlebot' content='index,follow'>
<link rel="canonical" href="https://www.phpsciencelabs.com/nasa-jpl-horizons-earth-perihelion-and-aphelion-calculator/">
<style type='text/css'>
BODY
{color:silver; background-color:black; font-family:Verdana; font-size:100%;}
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;}
TEXTAREA
{
color:black; background:white; font-family:monospace; font-size:150%;
font-weight:bold; border-radius: 8px; border:1px solid black; padding:4px;
white-space:pre;
}
INPUT[type='text']::-ms-clear {width:0; height:0;}
INPUT[type='text']
{
font-family:monospace; color:black; background:white; font-size:150%;
font-weight:bold; text-align:center; box-shadow:2px 2px 3px #666666;
border:1px solid black; border-radius:0px;
}
INPUT[type='text']:focus
{
font-family:monospace; background:white; box-shadow:2px 2px 3px #666666;
font-size:150%; border:2px solid blue; text-align:center; font-weight:bold;
}
INPUT[type='submit']{font-weight:bold;}
INPUT[type='submit']:hover
{font-weight:bold; background:cyan; border-radius:4px;}
HR {background:black; height:2px; border:0px;}
A:link
{
font-size:110%; background:transparent; color:cyan; font-family:Verdana;
font-weight:bold; text-decoration:none; line-height:175%; padding:3px;
border:1px solid transparent;
}
A:visited {font-size:110%; background:transparent; color:silver;}
A:hover
{
font-size:110%; background:yellow; color:black; border:1px solid black;
box-shadow:1px 1px 3px #222222;
}
A:active {font-size:110%; background:yellow; color:black;}
::selection {background-color:yellow; color:black;}
::-moz-selection {background-color:yellow; color:black;}
</style>
</head>
<body>
<form name='form1' method='post' action="$_RUN_">
<table width='700' align='top' border='0' cellspacing='1' cellpadding='3'>
<tr><td style='color:white; background:#000066; border:2px solid white; border-radius:8px 8px 0px 0px;'><b>$_INTERFACE_TITLE_</b></td></tr>
<tr><td>
Year <input name='Year' type='text' value="$Year" size='5' maxlength='4' title=' Span: $MinYear to $MaxYear '>
Time Zone <span style='font-size:140%; font-family:monospace;'><b>UT$UT1C</b><sub> </sub></span><input name='TZhhmm' type='text' value="$TZhhmm" size='7' maxlength='6' title=' − West or East + ' style='border-width:1px 1px 1px 0px; text-align:left;'>
<br><br>
<input name='LocLabel' type='text' value="$LocLabel" size='49' maxlength='48' title=' Optional Label Should Match the Given Time Zone '><br>Optional Location Label
<!-- PHANTOM FILLER FOR KMMI - -->
<input name='kmmi' type='hidden' value="$kmmi">
</td></tr>
<tr><td style='background:black;'>
<input name='ComputeButton' type='submit' value=' C O M P U T E '>
</td></tr>
<tr>
<td style='color:silver; background:black;'>
Double-Click Within Text Area to Select ALL Text<br>
<textarea name='TextArea1' style='color:$TxColor; background:$BgColor; padding:6px; border:2px solid white;' cols='78' rows="$TextArea1Rows" ReadOnly OnDblClick='this.select();' OnMouseUp='return true;'>
$TextArea1Text
</textarea></td>
</tr>
<tr>
<td style='color:silver; background:black;'>
Double-Click Within Text Area to Select ALL Text<br>
<textarea name='TextArea2' style='color:black; background:white; padding:6px;' cols='68' rows="$TextArea2Rows" ReadOnly OnDblClick='this.select();' OnMouseUp='return true;'>
$TextArea2Text
</textarea></td>
</tr>
<tr>
<td colspan="1" style="background:transparent; color:black; font-size:10pt;
text-align:center;">
<b><a href='View-Source-Code.php' target='_blank'
style='font-family:Verdana; color:black; background:yellow;
text-decoration:none; border:1px solid black; padding:4px;
border-radius:4px;'>
View/Copy PHP Source Code </a></b>
</td>
</tr>
<tr><td style='color:gray; background:black;'>
<br>Program by $_AUTHOR_<br>$_REVISION_DATE_ - $PHPVersionStr
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
</td></tr>
</table>
</form>
</body>
_HTML;
/*
###########################################################################
BELOW ARE UTILITY FUNCTIONS FOR USE WITH THIS PROGRAM. SOME FUNCTIONS ARE
CUSTOMIZED JUST FOR THIS APPLICATION AND MAY NOT TRANSPORT TO OTHER WORKS
OUT OF CONTEXT.
###########################################################################
*/
/*
============================================================================-
This function returns the time of an extremum (minimum or maximum), if
any, within a given time vs event table. For example, this function
can be used to find the times of perihelion, aphelion, perigee, apogee
or any general periapsis or apoapsis times. It is based on a 5-point
data table and the extremum is computed from a polynomial derived from
the given data.
ARGUMENT:
DataTableStr = 5-point paired numerical data table.
ERRORS:
No error checking is done and the function assumes that an extremum
exists within the given data table.
possible Uses:
XY-Data = JDTT vs Distance
NO DEPENDENCIES
============================================================================-
*/
function Extremum_5 ($DataTableStr)
{
// -----------------------------------------
// Read data table and parse numeric 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 $x3 + $q*$interval;
} // end of Extremum_5(...)
/*
---------------------------------------------------------------------------
This function performs LaGrange interpolation within a 2-column
data table. Data intervals can be linear or non-linear forward.
Given (x) value within the given range, this function interpolates
the corresponding (y) value.
Revised, stand-alone version.
Table XY column format example:
$XYTable =
'
481 10.012
482 10.503
483 11.083
';
---------------------------------------------------------------------------
*/
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 matching data column pairs.
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.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 (XYDataTable, xArg)
// ---------------------------------------------------------------------------
/*
===========================================================================
This is the inverse Julian date function. Given any general Julian date
on the old Julian or modern Gregorian calendar, it returns the correspond-
ing full date and time string. The fractional part of the Julian date
indicates the time of day.
RE-ENGINEERED FOR THIS PROGRAM TO NEAREST WHOLE SECOND.
In this program, this function is used to compute the date and time of
a perigee or apogee event given its computed JD (Julian Date).
INPUT ARGUMENTS: (JDStr, AMPM24, ssDecimals)
JDStr = Julian date as numeric string, like '2432959.1843657209'
AMPM24 = time Mode
'24h' = 24h military time mode = Default
'A|P|AM|PM|AMPM|PMAM' All = 12h AM/PM civil time mode
ssDecimals = Decimals at which to round off seconds part.
Default = 0
----------------------
INPUT/OUTPUT EXAMPLES:
print Inv_JD ('2433057.13271234131', '', '');
// = G+1949-May-20-Fri 15:11:06
print Inv_JD ('2433057.13271234131', 'AP');
// = G+1949-May-20-Fri 03:11:06 PM
print Inv_JD ('1433057.13271234131', '');
// = J+1949-May-07-Fri 15:11:06
NO DEPENDENCIES
===========================================================================
*/
function Inv_JD ($JDStr, $AMPM24='24h', $ssDecimals=0)
{
$Q = 32; // Internal working decimals.
$q = floor(trim($ssDecimals));
$ssFmt = ($q == 0)? "%02d" : "%0".(3 + $q).".$q".'f';
$ssAdj = ($q == 0)? 0.5 : 0;
$JD = trim($JDStr);
// Automatically determine calendar mode to use
// according to the JD value. If not the Julian
// calendar, then default to Gregorian calendar.
// Julian calendar if JD < 2299160.5
// Gregorian calendar if JD >= 2299160.5
$JG = ($JD < 2299160.5)? 'J':'G';
$J = bcAdd($JD, '0.5', $Q);
$timeMode = substr(StrToUpper(trim($AMPM24)),0,1);
// Compute time of day in hours since beginning of date.
$timeHours = bcMul('24', bcSub($J, bcAdd($J, '0'), $Q), $Q);
// Compute Julian Day Number from Julian date value.
$JDNum = bcAdd($J, '0');
// Compute date elements (m,d,y) according to (JDMode).
$MDYstr = ($JG == 'J')? JDtoJulian($JDNum) : JDtoGregorian($JDNum);
list($m,$d,$y) = PReg_Split("[\/]", $MDYstr);
$y = SPrintF("%+d", $y);
$dd = SPrintF("%02d", $d);
// Get month name and day of week abbreviations.
$Mmm = ($JG == 'J')? JDMonthName($JDNum, 2) : JDMonthName($JDNum, 0);
$DoW = JDDayOfWeek($JDNum, 2);
// Compute time of day elements (hh,mm,ss) = 00h to 24h
$hours = $timeHours;
$hh = bcAdd($hours, '0');
$minutes = bcMul('60', bcSub($hours, $hh, $Q), $Q);
$mm = bcAdd($minutes, '0');
$seconds = bcMul('60', bcSub($minutes, $mm, $Q), $Q);
$ss = SPrintF("%06.3f", $seconds); // 0x.xxx
// Construct time of day string.
$hh = SPrintF("%02d", $hh);
$mm = SPrintF("%02d", $mm);
$ss = SPrintF($ssFmt, $seconds + $ssAdj); // 0x.xxx
// Account for that blasted 60s glitch.
if ($ss == 60) {$mm += 1; $ss = 0;}
if ($mm == 60) {$hh += 1; $mm = 0;}
// Reconstruct time of day string.
$hh = SPrintF("%02d", $hh);
$mm = SPrintF("%02d", $mm);
$ss = SPrintF("%02d", $ss); // ss
// Adjust for AM/PM/24h time return mode.
// If not AM/PM mode, then default to 24h mode.
$AMPM = ''; $hh = floor($hh);
if ($timeMode == 'A' or $timeMode == 'P')
{
if ($hh > 0 and $hh < 12) {$AMPM = ' AM';}
if ($hh == 12) {$AMPM = ' PM';}
if ($hh > 12) {$AMPM = ' PM'; $hh -= 12;}
if ($hh == 0) {$AMPM = ' AM'; $hh = 12;}
}
$hh = SPrintF("%02d", $hh);
$BCAD = ($y < 0)? 'BC':'AD';
$y = SPrintf("% 4d", abs($y));
// Done. JG marker removed from output. +y sign removed.
return "$y-$Mmm-$dd-$DoW $hh:$mm:$ss$AMPM";
} // end of Inv_JD (...)
/* ######################################################################## */
/*
This function computes all the perigee and apogee events in the PA
work-tables. The time zone should have already been taken into
account within the PA work-tables prior to calling this function.
*/
function Compute_All_PA_Events ($PAWorkTables)
{
GLOBAL $Year;
$PAWTables = trim($PAWorkTables);
$wArray = PReg_Split("[\*]", $PAWTables);
$wCount = count($wArray);
// Process each PA work-table within array
// and compute each PA event date and time
// and distance.
$OutTable = '';
for($i=0; $i < $wCount; $i++)
{
$wTable = trim($wArray[$i]);
// Determine if perigee or apogee table
// and then remove PA marker before
// performing computations.
if (StrPos($wTable, 'P') !== FALSE )
{$PAMarker = 'Perigee';}
else
{$PAMarker = 'Apogee';}
// $wTable = Str_Replace ('P', '', $wTable);
// $wTable = Str_Replace ('A', '', $wTable);
// Compute the PA event date and time string for the local TZ
$PAJDLT = Extremum_5 ($wTable);
$PAJDLT = SPrintF("%1.9f", $PAJDLT);
$PADateTimeLT = Inv_JD($PAJDLT, 'AP');
// Compute the distance of the PA event in AU.
$DistAU = Lagrange_Interp($wTable, $PAJDLT);
$DistAU = SPrintF("%16.13f", $DistAU); // 12.1234567890123
// Handle special overflow case(s) at ends.
$w = "$PAJDLT $PADateTimeLT $DistAU\n";
if (IntVal($PADateTimeLT) == $Year) {$OutTable .= "$w";}
}
return trim($OutTable);
}
/* ######################################################################## */
/*
This function filters the table and separates
the months of the year for easier readability.
PATCHED AND MODIFIED FOR PERIHELION/APHELION
*/
function Final_Filter ($FinalTableText)
{
$wArray = Preg_Split("[\n]", trim($FinalTableText));
$wCount = count($wArray);
// Determine if initial event is a perigee or apogee event.
$u = PReg_Replace("/\s+/", " ", trim($wArray[0]));
list($q,$q,$q,$q,$d1) = PReg_Split("[ ]", $u);
$w = PReg_Replace("/\s+/", " ", trim($wArray[1]));
list($q,$q,$q,$q,$d2) = PReg_Split("[ ]", $w);
$OutTable = '';
for ($i=1; $i < $wCount; $i++)
{
$PrevLine = trim($wArray[$i-1]);
$PrevMmm = substr($PrevLine, 23, 3); // Get PrevMmm
$pp = trim(substr($PrevLine, 46, StrLen($PrevLine)));
$CurrLine = trim($wArray[$i]);
$CurrMmm = substr($CurrLine, 23, 3); // Get CurrMmm
$qq = trim(substr($CurrLine, 46, StrLen($CurrLine)));
if ($pp < $qq) {$PAM = 'Perigee';}else{$PAM = 'Apogee ';}
if ($PrevMmm <> $CurrMmm) {$LineBreak = "\n";} else {$LineBreak = '';}
$OutTable .= "$PAM $PrevLine$LineBreak\n";
}
// Determine final AP symbol to prepend to final line.
$APFinalMarker = substr(trim($wArray[$i-1]),0,1);
$APFinalMarker = ($APFinalMarker == 'A')? 'Perigee' : 'Apogee ';
$OutTable = $OutTable.$APFinalMarker.$wArray[$i-1];
// Patch to adjust spacing.
$OutTable = Str_Replace(' AM',' AM ', $OutTable);
$OutTable = Str_Replace(' PM',' PM ', $OutTable);
return trim($OutTable);
}
/*
===========================================================================
This function translates a purely numeric time string into standard form.
Applies ONLY to purely numerical time strings.
time format used = 00 to 24 hours
----------------------------------
EXAMPLES
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
This function does NOT check for errors.
NO DEPENDENCIES
===========================================================================
*/
function Num_to_HMS ($NumHMSVal)
{
$w = trim($NumHMSVal);
if ($w == '') {return '00:00:00';}
if (!Is_Numeric($w)) {return $NumHMSVal;}
$FirstChar = substr($w,0,1);
$NumSign = (IntVal($w) < 0)? '-' : '+';
$w = Str_Replace($NumSign, '', $w);
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";}
// if ($FirstChar <> '+' and $NumSign == '+') {$NumSign = '';}
$hh = SPrintF("%02d", IntVal(substr($w,0,2)));
return $NumSign.$hh.':'.substr($w,2,2).':'.substr($w,4,2);
} // end of Num_to_HMS (...)
/*
===========================================================================
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'
NO DEPENDENCIES
===========================================================================
*/
function Hours_to_HMS ($hours, $ssDec=0, $posSignSymb='', $SymbMode='h')
{
$_h_ = $_m_ = $_s_ = '';
if (trim($posSignSymb) == '') {$posSignSymb = FALSE;}
$sign = ($hours < 0)? '-' : '';
$hours = abs($hours);
if (($posSignSymb === TRUE or $posSignSymb == '+') and $sign == '')
{$sign = '+';}
$hh = floor($hours);
$minutes = 60*($hours - $hh); $mm = floor($minutes);
$seconds = 60*($minutes - $mm); $ss = SPrintF("%1.3f", $seconds);
$hh = SPrintF("%02d", $hh);
$mm = SPrintF("%02d", $mm);
$ss = SPrintF("%1.$ssDec" . "f", $ss);
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";}
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_";
return $w;
} // end of Hours_to_HMS(...)
/* ######################################################################## */
/*
============================================================================
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 extracts ONLY the ephemeris portion between
the $$SOE and $$EOE markers from the raw ephemeris table.
*/
function Extract_Ephemeris ($RawEphemerisTableText)
{
// Split text file into a string line array.
$wArray = explode("\n", trim($RawEphemerisTableText));
$wCount = count($wArray);
$wText = '';
$NumColumns = $ii = 0;
// Collect ephemeris table between
// the $$SOE and $$EOE markers.
for ($i=0; $i < $wCount; $i++)
{
$CurrLine = trim($wArray[$i]);
if ($CurrLine == '$$SOE') {$ii=1;}
if ($CurrLine == '$$EOE') {break;}
if ($ii == 1 and $CurrLine <> '$$SOE')
{
if ($NumColumns == 0)
{
$NumColumns = 1 + Substr_Count(PReg_Replace("/\s+/", " ", trim($CurrLine)), ' ');
}
$wText .= "$CurrLine\n";
}
}
return trim($wText);
}
/*
This function uses the +/- transitions markers to
construct all tables used for the computation of
the perigees and apogees.
Read forward looking for a change of +/- signs at
the end of the lines.
Change from - to + means approaching perigee
Change from + to - means approaching apogee
Collect up lines from 2 before to 2 lines after
the trasition points to make 5-line tables.
The first line in each table contains a marker
to indicate a perigee (P) or apogee (A) event.
*/
function Make_PA_Work_Table ($WorkTable)
{
$wTable = trim($WorkTable);
$wArray = explode("\n", $wTable);
$wCount = count($wArray);
$OutTable = $PAMarker = '';
for($i=1; $i < $wCount; $i++)
{
$PrevLine = trim($wArray[$i-1]);
$PrevLine = PReg_Replace("/\s+/", " ", trim($PrevLine));
$CurrLine = trim($wArray[$i]);
$CurrLine = PReg_Replace("/\s+/", " ", trim($CurrLine));
// PReg_Replace("/\s+/", " ", trim($TextString));
// Get numerical sign from (radial velocity) column 5 at end.
// 2020-Dec-11 12:00 2459195.000000000 0.00242737191826 -0.0225830
$PrevRadVel = Get_Nth_Element ($PrevLine, 5);
$PrevSign = ($PrevRadVel < 0)? '-':'+';
$CurrRadVel = Get_Nth_Element ($CurrLine, 5);
$CurrSign = ($CurrRadVel < 0)? '-' : '+';
// If radial velocity signs differ, then
// create 5-line PA work-table.
if ($PrevSign <> $CurrSign)
{
$k = 0;
for($j=$i-2; $j < $i+3; $j++)
{
$PAMarker = ($PrevSign == '+' and $CurrSign == '-')? 'A' : 'P';
$k += 1;
$wLine = trim($wArray[$j]);
$wLine = PReg_Replace("/\s+/", " ", trim($wLine)) ;
$JDLT = Get_Nth_Element ($wLine, 3);
$DistAU = Get_Nth_Element ($wLine, 4);
if ($k == 1){$PAM = "$PAMarker\n";} else $PAM = '';
$ww = "$PAM$JDLT $DistAU";
$OutTable .= "$ww\n";
if ($k % 5 == 0 and $k <> 0) {$OutTable .= "*\n";}
}
}
}
return RTrim(trim($OutTable), '*');
} // End of Make_Perigee_Apogee_Tables (...)
/* ######################################################################## */
/*
Given a table text string, this function extracts the Nth
element within the string where the elements are delimited
by at least one space.
EXAMPLE TABLE TEXT LINE
2020-Nov-30 00:00 2459183.500000000 0.00269100735899 -0.0235562
The table columns are indexed 1, 2, 3, ...
ERRORS:
If N < 1 or N > number of elements in the string.
An empty string is returned on error.
*/
function Get_Nth_Element ($TableTextString, $ColumnNum)
{
$T = PReg_Replace("/\s+/", ' ', trim($TableTextString));
$N = trim($ColumnNum);
$wArray = PReg_Split("[ ]", $T);
$wCount = count($wArray);
if ($N < 1 or $N > $wCount){return '';}
$NthElement = trim($wArray[$N - 1]);
return $NthElement;
}
function Compute_PA_Events ($PAWorkTable, $TZhhmm='+00:00')
{
GLOBAL $Year;
$PAWT = trim($PAWorkTable);
$TZHM = trim($TZhhmm);
$TZFracDay = HMS_to_Hours($TZHM) / 24;
$wArray = PReg_Split("[\*]", $PAWT);
$wCount = count($wArray);
$EventsTable = '';
for($i=0; $i < $wCount; $i++)
{
$CurrPATable = trim($wArray[$i]);
$PAMarker = (StrPos($CurrPATable, 'P') !== FALSE)? 'Perigee':'Apogee ';
$CurrPATable = trim(Str_Replace('P', '', $CurrPATable));
$CurrPATable = trim(Str_Replace('A', '', $CurrPATable));
$PAJDUT = Extremum_5($CurrPATable);
$PADist = Lagrange_Interp($CurrPATable, $PAJDUT);
$PAJDLT = $PAJDUT + $TZFracDay;
$PADateTimeLT = Inv_JD($PAJDLT, 'AMPM');
// Ignore the event if it occurs in a different year
// other than the given GLOBAL year in the interface.
if (IntVal(trim($PADateTimeLT)) == $Year)
{
$PAJDLT = SPrintF("%17.9f", $PAJDLT);
$PADist = SPrintF("% 16.13f", $PADist);
$PADistMkm = Number_Format($PADist*149597870.7, 0);
$PADistMmi = Number_Format($PADist*92955807.273, 0);
$PALightTimeSec = Str_Replace(',', '', $PADistMkm) /299792.458;
$PALightTimeSec = round($PALightTimeSec, 3);
$PALightTimeHMS = Hours_to_HMS($PALightTimeSec /3600, 2,'', 'h');
// If lunar distance range < 0.005 AU,
// then convert distance from AU to km
// assuming the body is the moon (301).
if ($PADist < 0.005)
{
$PADist *= 149597870.7;
$PADist = SPrintF("%10.3f", $PADist);
}
$EventsTable .= "$PAMarker $PAJDLT $PADateTimeLT $PADist
$PADistMmi mi
$PADistMkm km
Light Time $PALightTimeHMS\n\n";
}
}
return trim($EventsTable);
} // End of Compute_PA_Events (...)
/*
This function returns the rules for daylight US saving time
since 1975 (3 rules are implemented, depending on the year.)
A plain text string is returned. It does NOT display the
date span for DST, but only the rule used for the given
year.
DEPENDENCIES: NONE
*/
function USA_DST_Rules_Text ($year)
{
$y = IntVal(trim($year));
// $wText = "ERROR: Invalid year '$y'\nYear must be 1975 or later.";
$wText = '';
if ($y > 1974 and $y < 1987) // 1975 to 1986
{
$wText =
"
U.S. Daylight Saving Time For Year $y:
Starts on last Sunday in April at 02:00 AM Loc Time - Spring ahead 1 hour
Ends on last Sunday in October at 02:00 AM Loc Time - Fall back 1 hour
";
}
if ($y > 1986 and $y < 2007) // 1987 to 2006
{
$wText =
"
U.S. Daylight Saving Time For Year $y:
Starts on first Sunday in April at 02:00 AM Loc Time - Spring ahead 1 hour
Ends on last Sunday in October at 02:00 AM Loc Time - Fall back 1 hour
";
}
if ($y > 2006) // To present
{
$wText =
"
U.S. Daylight Saving Time For Year $y:
Starts on second Sunday in March at 02:00 AM Loc Time - Spring ahead 1 hour
Ends on first Sunday in November at 02:00 AM Loc Time - Fall back 1 hour
";
}
return trim($wText);
} // End of USA_DST_Rules_Text
?>