<?php
/*
###########################################################################
Compute the dates (without times) of all
lunar quarter phases for the given month.
Built around the NASA/JPL Horizons API
AUTHOR : Jay Tanner - 2025
LANGUAGE : PHP v8.2.12
LICENSE : Public Domain
###########################################################################
*/
ob_start(); // Initialize output buffer.
// ----------------------------------
// Code to suppress program warnings.
Error_Reporting(E_ERROR | E_PARSE);
// -------------------------------------------------------------
// Get current year and define English month name abbreviations.
$cYear = date('Y');
$MONTHS = 'JanFebMarAprMayJunJulAugSepOctNovDec';
// ---------------------------------------------------------------
// Define the program cookie name and set it to expire in 30 days.
$CookieName = 'Moon-Quarter-Phases-Dates-Calculator';
$ExpiresIn30Days = time() + 30*86400;
// ---------------------------------
// Define PHP program and HTML info.
$_AUTHOR_ = "Jay Tanner";
$_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_ = "Lunar Quarter Phases Dates Calculator";
$_INTERFACE_TITLE_ = "<span style='font-size:15pt;'>Lunar Quarter Phases Dates Calculator</span><br><span>Ephemeris Span: BC 9999-Apr to AD 9999-Nov<br><br><span style='font-size:11pt;'>Built Around the NASA/JPL Horizons API</span><br><span style='font-size:8.5pt;'>Program by Jay Tanner - $cYear</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,$TimeZone,$DaySumTimeYN) = Preg_Split("[\|]", $CookieDataString);
$StepSize = '1 day ';
}
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');
$TimeZone = '-05:00'; // MUST be in '+-HH:mm' format.
$DaySumTimeYN = 'No';
$StepSize = '1 day';
// -------------------------------------------
// Store current interface settings in cookie.
$CookieDataString = "$BCAD|$Year|$Month|$TimeZone|$DaySumTimeYN";
SetCookie ($CookieName, $CookieDataString, $ExpiresIn30Days);
} // End of else {...}
} // End of if (!isset(_POST['SubmitButton']))
// ------------------------------------------
// Read values of all interface arguments and
// set any empty arguments to default values.
$w = Filter_Input(INPUT_POST, 'SubmitButton');
if (isset($w))
{
$BCAD = StrToUpper(trim(Filter_Input(INPUT_POST, 'BCAD')));
if (substr($BCAD,0,1) == 'B') {$BCAD = 'BC';}
if (substr($BCAD,0,1) == 'A') {$BCAD = 'AD';}
$Year = trim(Filter_Input(INPUT_POST, 'Year'));
/* -------------------------------------------
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;
$Month = Alt_Month($Month); if (Is_Numeric($Month)) {$Month = Alt_Month($Month);}
if ($Month === FALSE){$mErrFlag = TRUE;}else{$mErrFlag = FALSE; }
$TimeZone = trim(Filter_Input(INPUT_POST, 'TimeZone'));
if ($TimeZone == '') {$TimeZone = '-05:00';}
$TimeZoneHrs = HMS_to_Hours ($TimeZone);
$TimeZone = substr(Hours_to_HMS ($TimeZoneHrs, 0, '+', ':'),0,6);
$DaySumTimeYN = StrToUpper(substr(trim(Filter_Input(INPUT_POST, 'DaySumTimeYN')),0,1));
$DaySumTimeYN = ($DaySumTimeYN <> 'Y')? 'No':'Yes';
$StepSize = '1 day ';
// ----------------------------------
// Set default values if empty input.
if ($BCAD == '') {$BCAD = 'AD';}
if ($Year == '') {$Year = date('Y');}
if ($Month == '') {$Month = date('M');}
if ($DaySumTimeYN == '') {$DaySumTimeYN = 'No';}
// -------------------------------------
// Store interface argument in a cookie.
$CookieDataString = "$BCAD|$Year|$Month|$TimeZone|$DaySumTimeYN";
SetCookie ($CookieName, $CookieDataString, $ExpiresIn30Days);
}
// ---------------------------------------------
// Check input values for validity.
// If error, set error flag and message values.
$ErrFlag = FALSE;
$ErrMssg = '';
if ($mErrFlag === TRUE)
{
$ErrFlag = TRUE;
$ErrMssg = "Bad month number or abbreviation.\n'$xMonth'\n\nThe month must be an integer from 1 to 12 or a valid 3-letter\nEnglish month abbreviation from 'Jan' to 'Dec'.\nNOT case sensitive.";
$Month = $xMonth;
}
if (!Is_Numeric($Year) or $Year == 0 or $Year > 19999)
{
$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.";
}
// -----------------------------
// 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.
// *********************************************************
// ----------------------------------
// Get number of days in given month.
$m = Alt_Month($Month);
if (Is_Numeric($m)) {$m = Alt_Month($m);}
$mDays = Month_Days ("$BCAD $Year", $m, 'A');
// ----------------------------------------------
// Force year value into positive 4-digit format.
$Year = SPrintF("%04d", abs($Year));
// -------------------------------------------------
// Define East/West indicator string for (TimeZone).
// If (TimeZone) equates to zero, then time = UT.
$EWStr = (substr($TimeZone,0,1) == '-')? '-West' : '+East';
if ($TimeZone == '+00:00') {$EWStr = 'Greenwich';}
// QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ
/* ----------------------------------------------------
Determine last date of previous month and first date
of next month and use these for the table span.
*/
$w = Prev_and_Next_Months ("$BCAD $Year", $Month, 'A');
list($PrevMonthEnd, $NextMonthStart) = PReg_Split("[,]", $w);
$PrevMonthEnd = trim($PrevMonthEnd);
// ------------------
// Set tabular range.
$StartDateTime = "$PrevMonthEnd 00:00:00";
$StopDateTime = "$NextMonthStart 00:00:00";
// QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ
// ----------------------------------------
// Call the function to generate the table.
$QPhasesDatesTable = Lunar_Phase_Table($StartDateTime,$StopDateTime,
$StepSize,$TimeZone,$DaySumTimeYN);
// ---------------------------------------------------------
// Extract table of quarter phases from (QPhasesDatesTable).
$QPADatesTable = ''; // Extract_Phase_Dates ($QPhasesDatesTable);
$QPhasesDatesTable = trim($QPhasesDatesTable);
// ----------------------------------------------
// Determine calendar mode (Gregorian = Default)
// or Julian calendar mode.
$CalMode = ($BCAD == 'AD' and $Year < '1583')? 'Julian':'Gregorian';
/* -------------------------------------------
DROP THROUGH HERE AFTER COMPUTATIONS ABOVE
TO PRINT OUT THE RESULTS OF THE OPERATIONS.
-------------------------------------------
*/
$TextArea1Text =
"
MOON PHASE ANGLES AND QUARTER PHASE DATES
Ephemeris Span: BC 9999-Apr to AD 9999-Nov
Local Calendar Date : $BCAD $Year-$Month ($CalMode Calendar)
Local Time Zone Offset : $TimeZone ($EWStr)
Daylight/Summer Time : $DaySumTimeYN
--------------------------------------
TABLE OF CALENDAR DATES OF MOON PHASES
$QPADatesTable
$QPhasesDatesTable
";
}
// ****************************
// Define TextArea2 text block.
$TextArea2Text =
"
SEEKING THE CALENDAR DATES OF THE LUNAR PHASES
Given any month within the range of the ephemeris, this program will first
generate the table of lunar phase angles for 00:00 local time on each date
of the given month, then it will scan the table and extract ONLY the dates
of the lunar phases. The clock time of the event is NOT computed.
Seeking the phase dates within the table is actually quite simple as long
as we only want the calendar dates without the actual event times.
We scan forward through the zero-indexed table starting at array index [1]
and test the simple phase angle values in pairs to find the phase dates.
Any phase occuring near the beginning of the month may possibly be repeated
again near the end of the month, such as a second (blue) full moon if there
is a full moon near the beginning of the month.
If there are multiple similar phases, such as a blue moon or any other phase
that repeats during the month, it will also be listed.
---------------------------------------------------------
The table lines array is indexed from [0] and the pairing
works according to this algorithm working forward from [1]
up until reaching [wCount]:
PrevLine = wArray[CurrIndex - 1]
CurrLine = wArray[CurrIndex]
------------------------------------------------------------
The phase angle in degrees is at the end of each table line.
PrevPA = Trimmed last 10 characters of raw (PrevLine).
CurrPA = Trimmed last 10 characters of raw (CurrLine).
When two successive values meet the test conditions, then the
first of the two corresponding dates is the phase date on the
calendar.
-------------------------------------------------------------
Phase_Ang Phase_ID Phase Seeking Rule Within Table
--------- ------------- --------------------------------
0 New Noon (CurrPA - PrevPA) < 0
90 First Quarter (PrevPA < 90 and CurrPA >= 90)
180 Full Moon (PrevPA < 180 and CurrPA >= 180)
270 Last Quarter (PrevPA < 270 and CurrPA >= 270)
-------------------------------------------------------------
Following these rules yields the moon quarter phases date
table for the current given month as computed above.
";
/* ---------------------------------------------------------------------------
Determine number of text columns and rows to use in the output text areas.
These values vary randomly according to the text block width and length.
The idea is to eliminate the need for scroll-bars within the text areas
or worry as much about the variable dimensions of a text display area.
*/
// --------------------------------------------
// Text Area 1 - Default = At least 80 columns.
$Text1Cols = 1 + Max(Array_Map('StrLen', PReg_Split("[\n]", trim($TextArea1Text))));
if ($Text1Cols < 80) {$Text1Cols = 80;} // Default
$Text1Rows = 3 + Substr_Count($TextArea1Text, "\n");
// --------------------------------------------
// Text Area 2 - Default = At least 80 columns.
$Text2Cols = 1 + Max(Array_Map('StrLen', PReg_Split("[\n]", trim($TextArea2Text))));
if ($Text2Cols < 80) {$Text2Cols = 80;} // Default
$Text2Rows = 2 + Substr_Count($TextArea2Text, "\n");
// ******************************************
// ******************************************
// GENERATE CLIENT WEB PAGE TO DISPLAY OUTPUT
print <<< _HTML
<!DOCTYPE HTML>
<HTML>
<head>
<title>$_BROWSER_TAB_TEXT_</title>
<meta name='viewport' content='width=device-width, initial-scale=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='Hourly Geocentric Moon Distance Calculator'>
<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:12pt;
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>
<!-- 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%; border-radius:0px 0px 8px 8px;">
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">
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" title=' No = Standard Time \n Yes = Daylight Saving / Summer Time '>
</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 ">
<br><br>
<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></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;" 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 generates a table of geocentric lunar phases starting on any
given date and spanning a given interval at regular time steps.
It can also be used for a single computation for any given date and time.
It takes into account the Time Zone and Daylight Saving/Summer Time.
A table header is optional.
###########################################################################
*/
function Lunar_Phase_Table ($StartDateTimeStr,$StopDateTimeStr,$StepSize,
$TimeZone='+00:00',$DaySumYN='No',
$HeaderYN='No')
{
$StartDateTimeStr = trim($StartDateTimeStr);
$StopDateTimeStr = trim($StopDateTimeStr);
$HeaderYN = substr(StrToUpper(trim($HeaderYN)),0,1);
/* -----------------------------------------------------------
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);
$DSSTAdj = ($DaySumYN == 'N')? 0:1;
list($TZHH, $TZmm) = PReg_Split("[\:]", $TimeZone);
$TZSign = substr($TZHH,0,1);
$TZHours = (($TZSign == '-')? -1:1)*(abs($TZHH) + $TZmm/60) + $DSSTAdj;
$i = StrPos($TZHours, '.'); if ($i == FALSE) {$TZHours .= '.00';}
$i = StrPos($TZHours, '.');
$TZHH = $TZSign.SPrintF("%02d", abs(substr($TZHours,0,$i)));
$TimeZone = URLEncode("$TZHH:$TZmm");
// **********************************************************
// MOON - GEOCENTRIC ECLIPTICAL LONGITUDE AND LATITUDE (GELL)
$From_Horizons_API =
"https://ssd.jpl.nasa.gov/api/horizons.api?format=text" .
"&COMMAND='301'" .
"&OBJ_DATA='NO'" .
"&MAKE_EPHEM='YES'" .
"&EPHEM_TYPE='OBSERVER'" .
"&CAL_FORMAT='BOTH'" .
"&CAL_TYPE='MIXED'" .
"&REF_SYSTEM='ICRF'" .
"&APPARENT='AIRLESS'" .
"&RANGE_UNITS='AU'" .
"&CENTER='500@399'" .
"&TIME_DIGITS='SECONDS'" .
"&TIME_ZONE='$TimeZone'" .
"&START_TIME='$StartDateTimeStr UT'" .
"&STOP_TIME='$StopDateTimeStr'" .
"&STEP_SIZE='$StepSize'" .
"&EXTRA_PREC='YES'" .
"&CSV_FORMAT='YES'" .
"&QUANTITIES='31,29'" ;
// -----------------------------------------------------------------
// Get the geocentric ecliptical longitude and latitude of the moon.
$MoonGELL = Str_Replace(",\n", " \n", trim(File_Get_Contents($From_Horizons_API)));
/* ----------------------------------------------------
Set pointers to start and end of ephemeris table and
extract ONLY the required ephemeris CSV data lines
from between the Start/End pointers.
*/
$i = StrPos($MoonGELL, '$$SOE');
$j = StrPos($MoonGELL, '$$EOE');
$MoonTable = trim(substr($MoonGELL, $i+5, $j-$i-5));
$MoonTable = (substr($MoonTable,0,1) <> 'b')?
" $MoonTable" : $MoonTable;
// *********************************************************
// SUN - GEOCENTRIC ECLIPTICAL LONGITUDE AND LATITUDE (GELL)
$From_Horizons_API =
"https://ssd.jpl.nasa.gov/api/horizons.api?format=text" .
"&COMMAND='10'" .
"&OBJ_DATA='NO'" .
"&MAKE_EPHEM='YES'" .
"&EPHEM_TYPE='OBSERVER'" .
"&CAL_FORMAT='BOTH'" .
"&CAL_TYPE='MIXED'" .
"&REF_SYSTEM='ICRF'" .
"&APPARENT='AIRLESS'" .
"&RANGE_UNITS='AU'" .
"&CENTER='500@399'" .
"&TIME_DIGITS='SECONDS'" .
"&TIME_ZONE='$TimeZone'" .
"&START_TIME='$StartDateTimeStr UT'" .
"&STOP_TIME='$StopDateTimeStr'" .
"&STEP_SIZE='$StepSize'" .
"&EXTRA_PREC='YES'" .
"&CSV_FORMAT='YES'" .
"&QUANTITIES='31,29" ;
// ----------------------------------------------------------------
// Get the geocentric ecliptical longitude and latitude of the sun.
$SunGELL = Str_Replace(",\n", " \n", trim(File_Get_Contents($From_Horizons_API)));
/* ----------------------------------------------------
Set pointers to start and end of ephemeris table and
extract ONLY the required ephemeris CSV data lines
from between the Start/End pointers.
*/
$i = StrPos($SunGELL, '$$SOE');
$j = StrPos($SunGELL, '$$EOE');
$SunTable = trim(substr($SunGELL, $i+5, $j-$i-5));
$SunTable = (substr($SunTable,0,1) <> 'b')? " $SunTable" : $SunTable;
/* ------------------------------------------
PARSE THE TABLES AND CONSTRUCT A SINGLE
CUSTOMIZED GEOCENTRIC LUNAR PHASE TABLE.
=========================================================
Loc_Date Loc_Time Cnst Julian_Date_UT Phase_Ang
============ ======== ==== ================= =========
2025-Feb-01 00:00:00 Aqr 2460707.708333333 35.36319
...
*/
$LunarPhaseAngleTable = '';
/* --------------------------------------------
Store lunar and solar ecliptical coordinates
in their respective work arrays.
*/
$MoonLonArray = PReg_Split("[\n]", $MoonTable);
$MoonLonCount = count($MoonLonArray);
$SunLonArray = PReg_Split("[\n]", $SunTable);
$SunLonCount = count($SunLonArray);
/* -----------------------------------
Fatal error if unequal array count.
*/
if ($MoonLonCount <> $SunLonCount)
{exit ("FATAL ERROR: Unequal array count.");}
/* -------------------------------------------
Construct geocentric ecliptical coordinates
and lunar phase table.
*/
for ($i=0; $i < $MoonLonCount; $i++)
{
$CurrMoonLine = $MoonLonArray[$i];
$CurrSunLine = $SunLonArray[$i];
list ($DateTimeStr,$JDate,$w,$w,$CurrMoonLon,$w, $MoonCnst) = PReg_Split("[,]", $CurrMoonLine);
list ($w,$JDate,$w,$w,$CurrSunLon) = PReg_Split("[,]", $CurrSunLine);
$DateTimeStr = Str_Replace(' ', ' ', trim($DateTimeStr));
$DateTimeStr = (substr($DateTimeStr,0,1) <> 'b')? " $DateTimeStr" : $DateTimeStr;
// ----------------------------------------------------------
// Get IAU symbol for constellation in which moon is located.
$MoonCnst = trim($MoonCnst);
// -------------------------------
// Get longitudes of moon and sun.
$Lm = trim($CurrMoonLon);
$Ls = trim($CurrSunLon);
// -------------------------------
// Compute the simple phase angle.
$w = 360 - $Lm + $Ls;
$PhaseAng = SPrintF("% 9.5f", 360 - ($w -= ($w > 360)? 360:0));
// -----------------------------------------
// Construct current output table text line.
$LunarPhaseAngleTable .= "$DateTimeStr $MoonCnst $JDate $PhaseAng\n";
}
$LunarPhaseAngleTable = RTrim($LunarPhaseAngleTable);
// ###############################################
$LunarPhaseAngleTable = Str_Replace(' ', '|', $LunarPhaseAngleTable);
$wArray = PReg_Split("[\n]", trim($LunarPhaseAngleTable));
$wCount = count($wArray);
$wOut = '';
for ($i=0; $i < $wCount; $i++)
{
$CurrLine = trim($wArray[$i]);
if (substr($CurrLine,0,1) == 'b')
{
$wOut .= 'BC ' . substr($CurrLine,1,StrLen($CurrLine))."\n";
}
else
{
$wOut .= "AD $CurrLine\n";
}
}
$wOut = Str_Replace('|', ' ', $wOut);
$MoonPhaseAnglesTable = $wOut;
// ###############################################
// --------------------------------------------
// Return table with header text, if indicated.
if ($HeaderYN == 'Y')
{
return
"
$MoonPhaseAnglesTable
===========================================================
Loc_Date Loc_Time Cnst Julian_Date_UT Phase_Ang
============== ======== ==== ================= =========
";
}
// --------------------------------------------
// Otherwise, return table without header test.
// return $MoonPhaseAnglesTable;
/*
Append function code to extract table to the end of this function.
*/
$PATable = trim($MoonPhaseAnglesTable);
// --------------------------------------------
// Store table into text string working array.
$wArray = PReg_Split("[\n]", $PATable);
$wCount = count($wArray);
$QPADates = '';
for ($i=1; $i < $wCount; $i++)
{
$PrevLine = trim($wArray[$i-1]);
$PrevPA = substr($PrevLine, -10);
$CurrLine = trim($wArray[$i-0]);
$CurrPA = substr($CurrLine, -10);
// -------------------
// Check for New Moon.
if (($CurrPA - $PrevPA) <= 0)
{
$QPADates .= substr($PrevLine,0,14) . " New Moon\n";
}
// ------------------------
// Check for First Quarter.
if (($PrevPA < 90 and $CurrPA >= 90))
{
$QPADates .= substr($PrevLine,0,14) . " First Quarter\n";
}
// --------------------
// Check for Full Moon.
if (($PrevPA < 180 and $CurrPA >= 180))
{
$QPADates .= substr($PrevLine,0,14) . " Full Moon\n";
}
// -----------------------
// Check for Last Quarter.
if (($PrevPA < 270 and $CurrPA >= 270))
{
$QPADates .= substr($PrevLine,0,14) . " Last Quarter\n";
}
} // End of for(...)
// return $PATable;
// @@@@@@@@@@@@@@@@@@@@@@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@
//exit("<pre>942:\n$QPADates</pre>");
$PATable = trim($MoonPhaseAnglesTable);
// --------------------------------------------
// Store table into text string working array.
$wArray = PReg_Split("[\n]", $PATable);
$wCount = count($wArray);
$QPADates = '';
for ($i=1; $i < $wCount; $i++)
{
$PrevLine = trim($wArray[$i-1]);
$PrevPA = substr($PrevLine, -10);
$CurrLine = trim($wArray[$i-0]);
$CurrPA = substr($CurrLine, -10);
// -------------------
// Check for New Moon.
if (($CurrPA - $PrevPA) <= 0)
{
$QPADates .= substr($PrevLine,0,14) . " New Moon\n";
}
// ------------------------
// Check for First Quarter.
if (($PrevPA < 90 and $CurrPA >= 90))
{
$QPADates .= substr($PrevLine,0,14) . " First Quarter\n";
}
// --------------------
// Check for Full Moon.
if (($PrevPA < 180 and $CurrPA >= 180))
{
$QPADates .= substr($PrevLine,0,14) . " Full Moon\n";
}
// -----------------------
// Check for Last Quarter.
if (($PrevPA < 270 and $CurrPA >= 270))
{
$QPADates .= substr($PrevLine,0,14) . " Last Quarter\n";
}
} // End of for(...)
$MoonPhaseAnglesTable = trim($MoonPhaseAnglesTable);
// DONE:
return trim($QPADates) .
"
-----------------------------------------------------------
TABLE OF SIMPLE MOON PHASE ANGLES FOR 00:00 ON EACH DATE
===========================================================
Loc_Date Loc_Time Cnst Julian_Date_UT Phase_Ang
============== ======== ==== ================= =========
$MoonPhaseAnglesTable
============== ======== ==== ================= =========
Loc_Date Loc_Time Cnst Julian_Date_UT Phase_Ang
===========================================================";
} // End of Lunar_Phase_Table (...)
/* ###########################################################################
Given a moon phase angles table, this function will scan the table and then
extract ONLY the dates of the moon quarter phases.
When two successive values meet the test conditions, then the first of the
two corresponding dates is the phase date on the calendar.
Phase Ang Phase ID Phase Seeking Rule Within Table
--------- ------------- --------------------------------
0 New Noon (CurrPA - PrevPA) < 0
90 First Quarter (PrevPA < 90 and CurrPA >= 90)
180 Full Moon (PrevPA < 180 and CurrPA >= 180)
270 Last Quarter (PrevPA < 270 and CurrPA >= 270)
###########################################################################
*/
function Extract_Phase_Dates ($MoonPhaseAnglesTable)
{
$PATable = $xPATable = trim($MoonPhaseAnglesTable);
// --------------------------------------------
// Store table into text string working array.
$wArray = PReg_Split("[\n]", $PATable);
$wCount = count($wArray);
$QPADates = '';
for ($i=1; $i < $wCount; $i++)
{
$PrevLine = trim($wArray[$i-1]);
$PrevPA = substr($PrevLine, -10);
$CurrLine = trim($wArray[$i-0]);
$CurrPA = substr($CurrLine, -10);
// -------------------
// Check for New Moon.
if (($CurrPA - $PrevPA) <= 0)
{
$QPADates .= substr($PrevLine,0,14) . " New Moon\n";
}
// ------------------------
// Check for First Quarter.
if (($PrevPA < 90 and $CurrPA >= 90))
{
$QPADates .= substr($PrevLine,0,14) . " First Quarter\n";
}
// --------------------
// Check for Full Moon.
if (($PrevPA < 180 and $CurrPA >= 180))
{
$QPADates .= substr($PrevLine,0,14) . " Full Moon\n";
}
// -----------------------
// Check for Last Quarter.
if (($PrevPA < 270 and $CurrPA >= 270))
{
$QPADates .= substr($PrevLine,0,14) . " Last Quarter\n";
}
} // End of for(...)
return "$xPATable\n" . trim($QPADates);
}
/*
*******************************************************************
*******************************************************************
*/
/*
This is the inclusion module with all functions required for the program
to compute the calendar date (without times) of all the quarter phases of
the moon for any given year/month.
Random Uniqueness Code:
KQ6Z03qyWPu5GhofUJ09q683UF5cBzVI
*/
/*
###########################################################################
This function returns the Julian Day Number for any given calendar date in
the range from BC 19999 to AD 19999 on the old Julian calendar or on the
modern Gregorian calendar system.
It returns negative JD numbers for dates prior to mathematical origins of
the respective calendar systems.
--------------------
CALENDAR YEAR RANGE:
BC 19999 to AD 19999
There is no calendar year 0 (zero).
--------------------------------------------------------
For the mathematical year number (y) used in calendrical
computations for a BC calendar year Y = ('BC Year'):
Y = 'BC 1949' ---> y = '-1948'
We take the numerical value following the 'BC' prefix
and subtract 1 and then change the result to negative
to obtain the mathematical year value required for use
in the (JDNum) computation. This adjustment will only
apply to 'BC' date strings.
Example: 'BC 1949-May-20'
y = -(Y - 1)
= -(1949 - 1)
= -1948
So, (y,m,d) = (-1948,5,20)
-------------------------------------------------------------
Positive calendar year numbers do NOT require any adjustment.
Example: 'AD 1949-May-20'
So, (y,m,d) = (1949,5,20)
--------------------------------------------
MATHEMATICAL ORIGINS OF THE CALENDAR SYSTEMS
BC 4713-Jan-01-Mon JDNum = 0 On the old Julian calendar
BC 4714-Nov-24-Mon JDNum = 0 on the modern Gregorian calendar
NOTE:
If a year is given as a negative number, it refers to a 'BC' year
and will be converted to 'BC|AD' format internally for computations
because 'BC' years first require a special numerical adjustment not
needed for 'AD' years.
Month = 1 to 12 or as 3-letter abbreviation string ('Jan' to 'Dec').
Date strings are NOT case-sensitive.
The returned signed JD Number is left-space-padded to 8 characters
to facilitate easy columnar alignment if used for tabulation.
ARGUMENTS:
$BCADDateStr = Date string in BC|AD format.
VALID EXAMPLES:
'BC 9949-May-20'
'AD 1949-5-20'
'16959-1-29'
'-1023-Nov-15'
JGAMode = Calendar mode to apply, where:
'A' = Auto-select mode = Default
'G' = Gregorian
'J' = Julian
ERRORS:
FALSE is returned if an invalid argument is detected.
NO DEPENDENCIES
###########################################################################
*/
function JD_Num ($BCADDateStr, $JGAMode='A')
{
// --------------------------------------------------
// Read and adjust input date string argument format.
$BCADDateStr = PReg_Replace("/\s+/", " ", trim($BCADDateStr));
/* ---------------------------------------------------
If first character is a minus sign (negative year),
then convert it into a 'BC' calendar year string.
'-1949' becomes ---> 'BC 1949'
*/
if (substr($BCADDateStr,0,1) == '-')
{$BCADDateStr = 'BC ' . substr($BCADDateStr, 1, StrLen($BCADDateStr));}
// ---------------------------------------------------------------
// If no 'BC|AD' prefix at all, then attach a default 'AD' prefix.
$ww = StrToUpper(substr($BCADDateStr,0,2));
if ($ww <> 'BC' and $ww <> 'AD') {$BCADDateStr = "AD $BCADDateStr";}
// ------------------------------
// Read and parse date arguments.
list($BCADYear,$Month,$Day) = PReg_Split("[-]", $BCADDateStr);
// ------------------
// A few adjustments.
$BCADYear = trim($BCADYear);
$Month = trim($Month);
$Day = trim($Day);
// -----------------------------------
// Get BC|AD prefix and calendar year.
$BCAD = StrToUpper(substr($BCADYear, 0,2));
$Y = trim(substr($BCADYear, 2, StrLen($BCADYear)));
// ---------------------------------
// Adjust for BC year, if necessary.
if ($BCAD == 'BC') {$Y = -$Y;}
// ------------------------------------------------------------
// Read calendar year argument value and return FALSE on error.
$w = abs($Y); if ($w < -20000 or $w == 0 or $w > 20000) {return FALSE;}
// ---------------------------------------------------
// Read month argument. Could be a string or a number.
$m = UCFirst(substr(StrToLower(trim($Month)),0,3));
// ------------------------
// Read day argument value.
$d = trim($Day);
/* ----------------------------
Read calendar mode argument.
'G' = Gregorian | 'J' = Julian
'A' = Auto-select = Default
*/
$JGAMode = substr(StrToUpper(trim($JGAMode)),0,1);
if ($JGAMode == '') {$JGAMode = 'A';}
// -------------------------------------------------
// Define abbreviations for month and weekday names.
$MONTHS = 'JanFebMarAprMayJunJulAugSepOctNovDec';
$WEEKDAYS = 'SunMonTueWedThuFriSat';
$JGDiff = 0;
/* -----------------------------------------------------
If month is a 3-letter abbreviation ('Jan' to 'Dec'),
then replace it with the month number 1 to 12, if
possible. Otherwise, return FALSE if it cannot
resolve the abbreviation text.
*/
if (!Is_Numeric($m))
{
$i = StrPos($MONTHS, $m);
if ($i === FALSE) {return $i;}
$m = 1 + $i/3;
}
// ---------------------------------------
// Error if invalid month number.
if ($m < 1 or $m > 12) {return FALSE;}
/* -------------------------------------------------
Proceed to compute the Julian calendar JD Number.
This is the base JD Number value. If the Gregorian
calendar is selected, then the difference between
the calendars is applied to obtain the Gregorian
calendar JD Number.
*/
$A = floor((14-$m) / 12);
$B = (($Y < 0)? $Y+1 : $Y) - $A;
$C = floor($B/100);
$JDNum = floor(30.6001*(12*$A + $m + 1))
+ floor(365.25*($B + 4716)) - 1524 + $d;
/* ----------------------------------------------
Handle automatic calendar mode selection.
If calendar mode = 'A' = Auto-select, then the
calendar mode is automatically determined by
the computed JD Number value.
*/
if ($JGAMode == 'A')
{
$JGAMode = ($JDNum < 2299161)? 'J':'G';
}
/* ---------------------------------------------
Handle Gregorian (= default) calendar mode by
by ADDING the difference in days between the
Julian and Gregorian JD Numbers, if indicated
by the JGAMode setting. This value could be
negative or positive, depending on the given
date. Default Logic: If not 'J', then 'G'.
GregorianJDNum = JulianJDNum + (+-JGDiff)
*/
if ($JGAMode <> 'J')
{
$A = ($Y < 0)? $Y+1 : $Y;
$B = trim($m);
$C = $A - floor((14-$B) / 12);
$D = floor($C/100);
$JGDiff = (floor($D/4) - $D + 2);
}
$JDNum += $JGDiff;
/* -------------------------------------------------
Error if (JDNum) is outside of the valid calendar
range from:
BC 19999-Jan-01-Thu J -5584211
BC 19999-Jan-01-Tue G -5583059
to
AD 19999-Dec-31-Sat J 9026057
AD 19999-Dec-31-Fri G 9025909
*/
if ($JDNum < -5584211 or $JDNum > 9026908) {return FALSE;}
/* -----------------------------------------------
Left-pad the signed JD number digits field with
spaces to span exactly 8 characters width, just
in case the values are used in a table. This
helps to arrange any (JDNum) column uniformly.
The white space can be removed by performing
a simple trim(JDNum) command, if not wanted.
*/
$JDNum = SPrintF("% 8d", $JDNum);
// DONE:
return $JDNum;
} // End of JD_Num(...)
/*
###########################################################################
This function is the inverse of the JD number function. Given any signed
JD Number, it will return the corresponding calendar date string in
the same format as: 'BC|AD Yyyyy-Mmm-dd-DoW'
The year will be formatted to 5 digits padded with zeros as needed.
EXAMPLE: 'BC 17191-May-12', 'AD 01949-June-02', 'BC 00107-Oct-13'
CALENDAR YEAR RANGE:
BC 19999 Jan-01 to AD 19999-Dec-31
There is no calendar year 0 (zero).
Mathematical origins of the calendar systems:
BC 4713-Jan-01-Mon JDNum = 0 On old Julian calendar
BC 4714-Nov-24-Mon JDNum = 0 On modern Gregorian calendar
ARGUMENT:
JDNumber = Julian Day number for the calendar date to be computed.
JGAMode = Calendar mode
'G' = Gregorian
'J' = Julian
'A' = Auto-select mode = Default
RETURNS:
Calendar date string in 'BC|AD Yyyy-Mmm-dd-DoW' format
according to the selected calendar mode.
ERRORS:
FALSE is returned if JD number argument is non-numeric.
NO DEPENDENCIES
###########################################################################
*/
function Inv_JD_Num ($JDNumber, $JGAMode='A')
{
$JDNum = trim($JDNumber);
// -------------------------------------------
// Define Month and Day-Of-Week abbreviations.
$MONTHS = 'JanFebMarAprMayJunJulAugSepOctNovDec';
$WEEKDAYS = 'SunMonTueWedThuFriSat';
// ----------------------------
// Read calendar mode argument.
// 'G' = Gregorian = Default
// 'J' = Julian
// 'A' = Auto-select
$JGAMode = substr(StrToUpper(trim($JGAMode)),0,1);
if ($JGAMode == '') {$JGAMode = 'A';}
// ----------------------------------------------
// If calendar mode = 'A' = Auto-select, then the
// calendar mode is automatically determined by
// the given JD Number argument.
if ($JGAMode == 'A')
{
$CMode = ($JDNum < 2299161)? 0:1;
$JGAMode = ($CMode == 0)? 'J':'G';
}
else
{
$CMode = ($JGAMode == 'J')? 0:1;
}
// -----------------------------------------
// Compute numerical date elements (y, m, d)
// according to the calendar mode selection.
$A = floor($JDNum + 0.5);
$B = $CMode*floor(($A - 1867216.25) / 36524.25);
$C = $A + $CMode*($B - floor($B/4) + 1);
$D = $C + 1524;
$E = floor(($D - 122.1) / 365.25);
$F = floor(365.25 * $E);
$G = floor(($D - $F) / 30.6001);
$d = $D - $F - floor(30.6001 * $G); // Day num (1 to 31)
$m = $G - 12*floor($G/14) - 1; // Month num (1 to 12)
$y = $E - 4716 + floor((14 - $m) / 12); // Mathematical year
$Y = ($y > 0)? $y : $y-1; // Calendar year (Negative = BC)
// ------------------------------------------------------------
// At this point we have the numerical date elements (y, m, d).
// The next step is to construct the full calendar date text
// string for output. EXAMPLE OUTPUT: 'BC 9998-May-20-Tue'
$i = (7 + ($JDNum + 1) % 7) % 7;
$DoW = substr($WEEKDAYS, 3*$i, 3);
$Y = SPrintF("%+04d", $Y);
$Y = Str_Replace('-', 'BC ', $Y);
$Y = Str_Replace('+', 'AD ', $Y);
$Mmm = substr($MONTHS, 3*($m-1), 3);
$dd = SPrintf("%02d", $d);
$JDNum = SPrintF("% 8d", $JDNum);
// DONE.
return "$Y-$Mmm-$dd-$DoW";
} // End of Inv_JD_Num(...)
/*
###########################################################################
This function converts a HMS time string into equivalent decimal hours.
INPUT: 1, 2 or 3-element HMS (Hours Minutes Seconds) separated by spaces
as angular string as 'hrs min sec' values.
Example: Given HMS string = '19 12 50.306'
Returned = 1.2139738888888889
NO DEPENDENCIES
ERRORS:
No special error checking is done.
###########################################################################
*/
function HMS_to_Hours ($HMSstr)
{
$hms = StrToLower(trim($HMSstr));
/* ----------------------------------------------------
Normalize the spacing and then split and extract the
individual angular string elements (dh or hh, mm,ss).
*/
$hms = PReg_Replace("/\s+/", " ", trim($hms));
$whms = PReg_Split("[ ]", $hms);
$whmscount = count($whms);
$hh = ($whmscount >= 1)? bcAdd((float)$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
for reattachment to the returned output.
*/
$NumSign = (substr($HMSstr,0,1) == '-')? '-' : '';
$hh = Str_Replace('+', '', 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($HMSstr,0,1) == '+') {$NumSign = '+';}
// ----------------------------------------------
// Compute decimal hours value equivalent
// to the given HHMS argument elements.
$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 original sign.
$w = $NumSign . bcAdd(bcDiv($w2,"3600",20), "0.00000000000000005",16);
$w = RTrim(RTrim($w, '0'), '.');
return $w;
} // end of HMS_to_Hours (...)
/* ###########################################################################
This function returns the equivalent H:M:S time string with
several formatting options.
hours = Signed 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
ERRORS
No special error checking is done by this function.
*/
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 an alternate month designation.
If given a month number, it returns the 3-letter English
abbreviation of the corresponding month, if given the
3-letter English month abbreviation, it will return the
number of the corresponding month.
ERRORS:
If the month number or 3-letter abbreviation cannot be
resolved, then FALSE is returned.
*/
function Alt_Month ($MonthStr)
{
// -----------------------------------
// Define English month abbreviations.
$MONTHS = 'JanFebMarAprMayJunJulAugSepOctNovDec';
/* ---------------------------------------------------------
Read month argument. This may be a number from 1 to 12 or
a 3-letter English month abbreviation from 'Jan' to 'Dec'
*/
$mStr = substr(UCFirst(StrToLower(trim($MonthStr))),0,3);
/* ----------------------------------------------------
Handle the case of a numeric argument and return the
corresponding 3-letter English month abbreviation.
*/
if (Is_Numeric($mStr) and $mStr > 0 and $mStr < 13)
{
return substr($MONTHS, 3*($mStr-1), 3);
}
/* ---------------------------------------------------------
Check if the 3-letter English month abbreviation is found
in the MONTHS string. If not, then return FALSE. A partial
match is also allowed.
*/
$i = StrPos($MONTHS, $mStr); if ($i === FALSE) {return $i;}
/* -------------------------------------------------------------
Return the month number corresponding to the 3-letter English
month abbreviation.
*/
return 1 + StrPos($MONTHS, $mStr)/3;
}
/*
This function returns the number of days in any given month.
DEPENDENCIES:
Alt_Month()
Is_Leap_Year()
*/
function Month_Days ($YearStr, $MonthStr, $JGAMode='A')
{
// $MDAYS = '312831303130313130313031';
$Y = StrToUpper(trim($YearStr));
$m = trim($MonthStr);
if (!Is_Numeric($Y))
{
$Y = Str_Replace('BC', '-', $Y);
$Y = Str_Replace('AD', '', $Y);
}
$Y = Str_Replace(' ', '', $Y);
$m = trim($MonthStr); if (!Is_Numeric($m)) {$m = Alt_Month($m);}
// Determine the J or G calendar mode.
$JGAMode = substr(StrToUpper(trim($JGAMode)),0,1);
/* ------------------------------------------------
Get days in month assuming 28 days for February.
Any leap year adjustment is added later.
*/
$mDays = substr('312831303130313130313031', 2*($m-1), 2);
/* ----------------------------------------------
Handle automatic calendar mode selection.
If calendar mode = 'A' = Auto-select, then the
calendar mode is automatically determined by
the calendar year (Y) value.
*/
if ($JGAMode == 'A') {$JGAMode = ($Y < 1583)? 'J':'G';}
/* -------------------------------------------------------
If it is a century year AND ALSO a Gregorian year, then
an extra rule applies. A century year is a leap year
ONLY if it is a perfect multiple of 400, otherwise it
is a common year.
*/
if ($m == 2) {$mDays += ((Is_Leap_Year ($Y, $JGAMode))? 1:0);}
return $mDays;
}
/*
###########################################################################
This boolean function determines if a given year on the Gregorian calendar
or the old Julian calendar is a common year or a leap year.
Y = Calendar Year Number.
There is no year 0 (zero) on the calendar.
CalMode = 'G' for Gregorian (default) or 'J' for Julian calendar
Logic: If not Julian, then Gregorian by default.
Returns boolean FALSE if given year is a common year.
Returns boolean TRUE if given year is a leap year.
ERRORS
No special error checking is done.
NO DEPENDENCIES
###########################################################################
*/
function Is_Leap_Year ($Year, $JGAMode='A')
{
$Y = trim($Year);
if ($Y == '') {$Y = 'AD ' . date("Y-M-d");}
if (!Is_Numeric($Y))
{
$Y = Str_Replace('BC', '-', $Y);
$Y = Str_Replace('AD', '', $Y);
}
$Y = Str_Replace(' ', '', $Y);
$y = ($Y < 0)? $Y-1 : $Y;
/* ----------------------------------------------
Handle automatic calendar mode selection.
If calendar mode = 'A' = Auto-select, then
the calendar mode is automatically determined
by the calendar year (Y) value.
*/
$JGAMode = StrToUpper(substr(trim($JGAMode),0,1));
if ($JGAMode == 'A') {$JGAMode = ($y < 1583)? 'J':'G';}
return ($JGAMode == 'J')? $y % 4 == 0 :
($y % 4 == 0 and $y % 100 <> 0) or $y % 400 == 0;
}
function Prev_Month_Last_Day ($YearStr, $MonthStr, $JGAMode='A')
{
$Y = trim($YearStr);
if (!Is_Numeric($Y))
{
$Y = Str_Replace('BC ', '-', $Y);
$Y = Str_Replace('AD ', '', $Y);
}
$BCAD = ($Y < 0)? 'BC': 'AD';
$m = trim($MonthStr);
if (!Is_Numeric($m)) {$m = Alt_Month($m);}
if ($m == 1) {$Y -= 1; return "$BCAD $Y-Dec-31";}
if ($m == 12) {$Y += 1; return "$BCAD $Y-Jan-31";}
$m -= 1;
$mDays = Month_Days (trim($YearStr), $m, $JGAMode);
if (Is_Numeric($m)) {$m = Alt_Month($m);}
$Y = SPrintF("%04d", abs(IntVal($Y)));
return "$BCAD $Y-$m-$mDays";
}
/*
Get JD number of 1st day of month and add (mDays) to it to
obtain the JD number for the first date of the next month
*/
function Next_Month_First_Day ($YearStr, $MonthStr, $JGAMode='A')
{
$Y = trim($YearStr);
if (!Is_Numeric($Y))
{
$Y = Str_Replace('BC ', '-', $Y);
$Y = Str_Replace('AD ', '', $Y);
}
$BCAD = ($Y < 0)? 'BC': 'AD';
$m = trim($MonthStr);
if (!Is_Numeric($m)) {$m = Alt_Month($m);}
$mDays = Month_Days (trim("$BCAD $Y"), $m, $JGAMode);
$Y = SPrintF("%04d", abs(IntVal($Y)));
if ($m == 1) {$Y -= 1; return "$Y-Feb-$mDays";}
if ($m == 12) {$Y += 1; return "$Y-Jan-01";}
// JD number for first date of given month.
$JDNum01 = JD_Num("$Y-$m-01", $JGAMode);
$m += 1;
$Y = SPrintF("%04d", abs(IntVal($Y)));
if (Is_Numeric($m)) {$m = Alt_Month($m);}
return "$BCAD $Y-$m-01";
}
function Prev_and_Next_Months ($YearStr, $MonthStr, $JGAMode='A')
{
$Y = StrToUpper(trim($YearStr));
$JGAMode = StrToUpper(substr(trim($JGAMode),0,1));
/* --------------------------------------------------------
If year is non-numeric, assume it has a BC/AD prefix and
convert it into a signed value where negative = BC.
--------------------------------------------------------
*/
if (!Is_Numeric($Y))
{
$Y = Str_Replace('BC ', '-', $Y);
$Y = Str_Replace('AD ', '', $Y);
}
// ----------------------------
// Determine BC/AD era applies.
$BCAD = ($Y < 0)? 'BC': 'AD';
$Y = abs($Y);
// -------------------------------
// Get month as a numerical value.
$m = trim($MonthStr); if (!Is_Numeric($m)) {$m = Alt_Month($m);}
// ----------------------------------
// Get number of days in given month.
$mDays = Month_Days (trim("$BCAD $Y"), $m, $JGAMode);
// ---------------------------------
// Cast Y value into 4-digit format.
$Y = SPrintF("%04d", $Y);
// ----------------------------------------------
// Cast month as a 3-letter English abbreviation.
$m = Alt_Month($m);
// ---------------------------------------------------
// Compute JD Number of first date of the given month.
$JDNum01 = JD_Num("$BCAD $Y-$m-01", $JGAMode);
$JDNumPrev = $JDNum01 - 1;
$JDNumNext = $JDNum01 + $mDays;
/* ---------------------------------------------
Construct calendar date strings corresponding
to the JD numbers.
*/
$DatePrev = Inv_JD_Num($JDNumPrev, $JGAMode);
$DatePrev = substr($DatePrev, 0, StrLen($DatePrev)-4);
$DateNext = Inv_JD_Num($JDNumNext, $JGAMode);
$DateNext = substr($DateNext, 0, StrLen($DateNext)-4);
// Return CSV date strings.
return "$DatePrev,$DateNext";
}
?>