Note: this is a exercise for my curiosity and there are existing functions / code that solve this problem (see comments too)
An algorithm for calculation of sunrise and sunset times can be found here and implementations at 1 in Tcl and 2 in javascript.
I am trying to convert the algorithm to PHP, but it's slightly over my head and takes too much time. This is my result so far:
<?php
$sunrise = sunriseSunsetTimeTest(9, 4, 2015, 46.929512, 7.565272);
$sunset = sunriseSunsetTimeTest(9, 4, 20, 46.929512, 7.565272, 'sunset');
echo $sunrise . '/' . $sunset;
/* output is: 12.314714533758/12.612340511889 (?) */
function sunriseSunsetTimeTest($day, $month, $year, $latitude, $longitude,
$which='sunrise', $localTimeOffset=1, $zenith=96){
/*
* Output:
* sunrise or sunset time depending on parameter $which
*
* Input:
* (int) day
* (int) month
* (int) year
* (float) latitude_degree
* (float) longitude_degree
* (string) which -> "sunrise" or "sunset (resp. not 'sunrise')"
* (int) localTimeOffset (0 for GMT, 1 for CET, etc.)
* (float) zenith_degree
* offical = 90 degrees 50'
* civil = 96 degrees
* nautical = 102 degrees
* astronomical = 108 degrees
*/
$dayOfYear = dayOfYear($day, $month, $year);
// convert the longitude to hour value and calculate an approximate time
$lngHour = $longitude / 15;
$approximateTime = ($which=='sunrise')
? $dayOfYear + ((6 - $lngHour) / 24)
: $dayOfYear + ((18 - $lngHour) / 24);
// calculate the Sun's mean anomaly
$sunMeanAnomaly = (0.9856 * $approximateTime) - 3.289;
// calculate the Sun's true longitude
$sunTrueLongitude = $sunMeanAnomaly + (1.916 * sin($sunMeanAnomaly)) + (0.020 * sin(2 * $sunMeanAnomaly)) + 282.634;
while ($sunTrueLongitude>360)
$sunTrueLongitude -= 360;
while ($sunTrueLongitude<0)
$sunTrueLongitude += 360;
// calculate the Sun's right ascension
$sunRightAscension = rad2deg(atan(0.91764 * tan(deg2rad($sunTrueLongitude))));
while ($sunRightAscension>360)
$sunRightAscension -= 360;
while ($sunRightAscension<0)
$sunRightAscension += 360;
// right ascension value needs to be in the same quadrant as true longitude
$sunTrueLongitudeQuadrant = (floor($sunTrueLongitude/90)) * 90;
$sunRightAscensionQuadrant = (floor($sunRightAscension/90)) * 90;
$sunRightAscension = $sunRightAscension + ($sunTrueLongitudeQuadrant - $sunRightAscensionQuadrant);
// right ascension value needs to be converted into hours
$sunRightAscension = $sunRightAscension / 15;
// calculate the Sun's declination
$sunSinDec = 0.39782 * sin(deg2rad($sunTrueLongitude));
$sunCosDec = cos(asin($sunSinDec));
// calculate the Sun's local hour angle
$cosH = (cos(deg2rad($zenith)) - ($sunSinDec * sin(deg2rad($latitude)))) / ($sunCosDec * cos(deg2rad($latitude)));
// finish calculating H and convert into hours
if ($which=='sunrise'){
if ($cosH > 1) //sun never rises on this location (on the specified date)
return false;
$H = 360 - acos($cosH);
}
else{
if ($cosH < -1) // sun never sets on this location (on the specified date)
return false;
$H = acos($cosH);
}
$H = $H / 15;
// calculate local mean time of rising/setting
$T = $H + $sunRightAscension - (0.06571 * $approximateTime) - 6.622;
// adjust back to UTC
$UT = $T - $lngHour;
while ($UT>24)
$UT -= 24;
while ($UT<0)
$UT += 24;
// convert UT value to local time zone of latitude/longitude
$localT = $UT + $localTimeOffset;
return $localT;
}
function dayOfYear($day,$month,$year){
$N1 = floor(275 * $month / 9);
$N2 = floor(($month + 9) / 12);
$N3 = (1 + floor(($year - 4 * floor($year / 4) + 2) / 3));
return $N1 - ($N2 * $N3) + $day - 30;
}
...the Input of date 09-04-2015, latitude 46.929512, longitude 7.565272 results in the outputs 12.314714533758 for sunrise and 12.612340511889 for sunset.
I would like to understand what is wrong in the code, maybe somebody can help. Thanks!
Related
I have problem in converting time which I got from speed and distance, here's my code;
$dist = 30; // Distance which is in kilometres(km)
// speed is in knots(kt), if I take speed of 40kt and convert it into kilometres(km/h) ... 40 * 1.852 = 74.08
$time = ($dist / 74.08) / 24;
// result is 0.016873650107991 which is correct, but my problem is how can this be format in H:m:s,
// tried with date('h:m:s', strtotime($time) but result is always 01:01:00 no mater the distance
echo $time;
Any ideas how to format the time into H:m:s or improve this code?
I have search the stack but did not found similar problem, if I missed sorry for duplication, but link is more than welcome.
This will also cover the case if the number of hours is > 24
$dist = 10; // Distance which is in kilometres(km)
$ts = ($dist / 74.08) * 3600; // in seconds
$h = floor($ts/3600);
$m = floor(($ts / 60) % 60);
$s = $ts % 60;
echo "$h:$m:$s";
This might help,
$dist = 10; // Distance which is in kilometres(km)
// speed is in knots(kt), if I take speed of 40kt and convert it into kilometres(km/h) ... 40 * 1.852 = 74.08
$time = ($dist / 74.08);
echo gmdate("H:i:s", $time * 3600);
This is not a duplicate question, but involves a little understanding about time.
I need a solution to the following problem
I have a number of specifically produced times (based on a date), that need to be rounded to the nearest 15 secs:
60 secs is 1 minute
meaning a regular round, floor, ceiling is to the nearest decimal (10/5)
which doesn't help me with time.
also since I'm dealing with secs, it could be that 59:59 will be rounded up to the nearest hour: e.g. 17:59:59 should be 18:00.
example:
6:17:29 rounded to 6:17:30
6:29:55 rounded to 6:30:00
20:45:34 rounded to 20:45:30
The following code does some of the job:
$hr = date('H',($resultStr));
$mn = date('i',($resultStr));
$sc = date('s',($resultStr));
$tot = ($hr * 60 * 60) + ($mn * 60) + $sc;
$totd = $tot / (60);
$totc = ceil($totd);
$totc = $totc / 60;
$hr = floor($totc);
$mn = ($totc - $hr)*60;
$mnflr = floor($mn);
$mn2 = $mn - $mnflr;
echo "$hr:$mnflr";
This results in:
18:35:17 rounded to: 18:36 (which is wrong)
18:31:49 rounded to: 18:32 (which is wrong)
As an aside:
$secs = date('U',($resultStr));
$round = ceil ( (($secs / 60 ) * 60 ));
$newtime = date('H:i:s',($round));
produces: 18:42:58 rounded to: 18:42:58 which is also incorrect
Please and thank you in advance....
You're massively overcomplicating this, just do rounding on the Unix timestamp level:
function roundMyTime($time)
{
$time = strtotime($time);
$time = 15*round($time/15);
echo date('H:i:s', $time)."\n";
}
roundMyTime('18:35:17');
roundMyTime('18:35:27');
roundMyTime('18:35:37');
roundMyTime('18:35:47');
roundMyTime('18:35:57');
roundMyTime('18:36:07');
roundMyTime('18:36:17');
Outputs:
18:35:15
18:35:30
18:35:30
18:35:45
18:36:00
18:36:00
18:36:15
Demo here.
$seconds = ($hr * 60 + $mn) * 60 + $sc; // convert to seconds
$rounded = round($seconds/15)*15; // round
$sc = $rounded % 60; // get seconds
$mn = ($rounded - $sc) / 60 % 60; // get minutes
$hr = ($rounded - $sc - $mn * 60) / 60; // get hours
Convert the date to seconds using strtotime and then just work in seconds.
$seconds = strtotime($date);
$seconds /= 15;
$seconds = round($seconds);
$seconds *= 15;
$date = date("Y-m-d H:i:s", $seconds);
This is not a duplicate question, but involves a little understanding about time.
I need a solution to the following problem
I have a number of specifically produced times (based on a date), that need to be rounded to the nearest 15 secs:
60 secs is 1 minute
meaning a regular round, floor, ceiling is to the nearest decimal (10/5)
which doesn't help me with time.
also since I'm dealing with secs, it could be that 59:59 will be rounded up to the nearest hour: e.g. 17:59:59 should be 18:00.
example:
6:17:29 rounded to 6:17:30
6:29:55 rounded to 6:30:00
20:45:34 rounded to 20:45:30
The following code does some of the job:
$hr = date('H',($resultStr));
$mn = date('i',($resultStr));
$sc = date('s',($resultStr));
$tot = ($hr * 60 * 60) + ($mn * 60) + $sc;
$totd = $tot / (60);
$totc = ceil($totd);
$totc = $totc / 60;
$hr = floor($totc);
$mn = ($totc - $hr)*60;
$mnflr = floor($mn);
$mn2 = $mn - $mnflr;
echo "$hr:$mnflr";
This results in:
18:35:17 rounded to: 18:36 (which is wrong)
18:31:49 rounded to: 18:32 (which is wrong)
As an aside:
$secs = date('U',($resultStr));
$round = ceil ( (($secs / 60 ) * 60 ));
$newtime = date('H:i:s',($round));
produces: 18:42:58 rounded to: 18:42:58 which is also incorrect
Please and thank you in advance....
You're massively overcomplicating this, just do rounding on the Unix timestamp level:
function roundMyTime($time)
{
$time = strtotime($time);
$time = 15*round($time/15);
echo date('H:i:s', $time)."\n";
}
roundMyTime('18:35:17');
roundMyTime('18:35:27');
roundMyTime('18:35:37');
roundMyTime('18:35:47');
roundMyTime('18:35:57');
roundMyTime('18:36:07');
roundMyTime('18:36:17');
Outputs:
18:35:15
18:35:30
18:35:30
18:35:45
18:36:00
18:36:00
18:36:15
Demo here.
$seconds = ($hr * 60 + $mn) * 60 + $sc; // convert to seconds
$rounded = round($seconds/15)*15; // round
$sc = $rounded % 60; // get seconds
$mn = ($rounded - $sc) / 60 % 60; // get minutes
$hr = ($rounded - $sc - $mn * 60) / 60; // get hours
Convert the date to seconds using strtotime and then just work in seconds.
$seconds = strtotime($date);
$seconds /= 15;
$seconds = round($seconds);
$seconds *= 15;
$date = date("Y-m-d H:i:s", $seconds);
This is a php implementation of Josh r code to calculate the position of the sun for a given date and time :
This is the corrected code after MvG help :
function getSunPosition($lat, $long, $year, $month, $day, $hour, $min) {
// From https://stackoverflow.com/questions/8708048/position-of-the-sun-given-time-of-day-latitude-and-longitude?rq=1
// Get Julian date for date at noon
$jd = gregoriantojd($month,$day,$year);
//correct for half-day offset
$dayfrac = $hour / 24 - .5;
//now set the fraction of a day
$frac = $dayfrac + $min / 60 / 24;
$jd = $jd + $frac;
// The input to the Atronomer's almanach is the difference between
// the Julian date and JD 2451545.0 (noon, 1 January 2000)
$time = ($jd - 2451545);
// Ecliptic coordinates
// Mean longitude
$mnlong = (280.460 + 0.9856474 * $time);
$mnlong = fmod($mnlong,360);
if ($mnlong < 0) $mnlong = ($mnlong + 360);
// Mean anomaly
$mnanom = (357.528 + 0.9856003 * $time);
$mnanom = fmod($mnanom,360);
if ($mnanom < 0) $mnanom = ($mnanom + 360);
$mnanom = deg2rad($mnanom);
// Ecliptic longitude and obliquity of ecliptic
$eclong = ($mnlong + 1.915 * sin($mnanom) + 0.020 * sin(2 * $mnanom));
$eclong = fmod($eclong,360);
if ($eclong < 0) $eclong = ($eclong + 360);
$oblqec = (23.439 - 0.0000004 * $time);
$eclong = deg2rad($eclong);
$oblqec = deg2rad($oblqec);
// Celestial coordinates
// Right ascension and declination
$num = (cos($oblqec) * sin($eclong));
$den = (cos($eclong));
$ra = (atan($num / $den));
if ($den < 0) $ra = ($ra + pi());
if ($den >= 0 && $num <0) $ra = ($ra + 2*pi());
$dec = (asin(sin($oblqec) * sin($eclong)));
// Local coordinates
// Greenwich mean sidereal time
//$h = $hour + $min / 60 + $sec / 3600;
$h = $hour + $min / 60;
$gmst = (6.697375 + .0657098242 * $time + $h);
$gmst = fmod($gmst,24);
if ($gmst < 0) $gmst = ($gmst + 24);
// Local mean sidereal time
$lmst = ($gmst + $long / 15);
$lmst = fmod($lmst,24);
if ($lmst < 0) $lmst = ($lmst + 24);
$lmst = deg2rad($lmst * 15);
// Hour angle
$ha = ($lmst - $ra);
if ($ha < pi()) $ha = ($ha + 2*pi());
if ($ha > pi()) $ha = ($ha - 2*pi());
// Latitude to radians
$lat = deg2rad($lat);
// Azimuth and elevation
$el = (asin(sin($dec) * sin($lat) + cos($dec) * cos($lat) * cos($ha)));
$az = (asin(-cos($dec) * sin($ha) / cos($el)));
// For logic and names, see Spencer, J.W. 1989. Solar Energy. 42(4):353
if ((sin($dec) - sin($el) * sin($lat)) >00) {
if(sin($az) < 0) $az = ($az + 2*pi());
} else {
$az = (pi() - $az);
}
$el = rad2deg($el);
$az = rad2deg($az);
$lat = rad2deg($lat);
return array(number_format($el,2),number_format($az,2));
}
This has been tested with Congo (near Equateur) lat/long : -4.77867 / 11.86364 for date Sept 1st 2013 at 10h00. In this case, the correct answer is :
elevation = 67.77503
azimuth = 54.51532
Thanks for your help debuging this php code !
Greg Fabre.
I believe the line
if ($dayfrac < 0) $dayfrac += 1;
is in error. If you are before noon, you don't want to refer to the same time one day later, but instead you want to specify a time before noon, i.e. subtract from the julian date which represents noon.
Removing that line, your example date corresponds to the one computed using http://www.imcce.fr/en/grandpublic/temps/jour_julien.php, namely 2456536.9166666665. The resulting
$el = 67.775028608168
$az = 54.515316112281
looks pretty good to me. In particular, it agrees with the R run
elevation = 67.77503
azimuth = 54.51532
and also with what Stellarium says (although I quoted this incorrectly in a comment above):
Alt = 67°46'30" = 67.775
Az = 54°30'60" = 45.5167
It also (almost) agrees with sunearthtools.com, so I guess you made a mistake when first entering the data there:
So I'd say that solves the problem.
I'm trying to convert the difference between two dates into a total year count, right now I'm using this:
$datetime1 = new DateTime('2009-10-11');
$datetime2 = new DateTime('2010-10-10');
$interval = $datetime1->diff($datetime2);
return $interval->format('%y');
This returns me an int (Like 0 for < than a year, 2 for two years, etc.)
I need the result to be decimal as following:
0.9 - 9 months
1.2 - 1 year and two months
3.5 - 3 years and five months
and so on..
Thanks!
If you don't care about perfect accuracy:
return $interval->days / 365;
You could also do something like return $interval->y + $interval->m / 12 + $interval->d / 365.
Didn't even notice your weird decimal convention until I saw #2unco's comment. That would look like: return $interval->y . '.' . $interval->m.
Here you can see a function that does exactly that and with many options:
http://php.net/manual/es/function.date-diff.php#98615
<?php
/*
* A mathematical decimal difference between two informed dates
*
* Author: Sergio Abreu
* Website: http://sites.sitesbr.net
*
* Features:
* Automatic conversion on dates informed as string.
* Possibility of absolute values (always +) or relative (-/+)
*/
function s_datediff( $str_interval, $dt_menor, $dt_maior, $relative=false){
if( is_string( $dt_menor)) $dt_menor = date_create( $dt_menor);
if( is_string( $dt_maior)) $dt_maior = date_create( $dt_maior);
$diff = date_diff( $dt_menor, $dt_maior, ! $relative);
switch( $str_interval){
case "y":
$total = $diff->y + $diff->m / 12 + $diff->d / 365.25; break;
case "m":
$total= $diff->y * 12 + $diff->m + $diff->d/30 + $diff->h / 24;
break;
case "d":
$total = $diff->y * 365.25 + $diff->m * 30 + $diff->d + $diff->h/24 + $diff->i / 60;
break;
case "h":
$total = ($diff->y * 365.25 + $diff->m * 30 + $diff->d) * 24 + $diff->h + $diff->i/60;
break;
case "i":
$total = (($diff->y * 365.25 + $diff->m * 30 + $diff->d) * 24 + $diff->h) * 60 + $diff->i + $diff->s/60;
break;
case "s":
$total = ((($diff->y * 365.25 + $diff->m * 30 + $diff->d) * 24 + $diff->h) * 60 + $diff->i)*60 + $diff->s;
break;
}
if( $diff->invert)
return -1 * $total;
else return $total;
}
/* Enjoy and feedback me ;-) */
?>
Simpler and more accurate interval converter to days/hours/minutes/seconds:
function DateDiffInterval($sDate1, $sDate2, $sUnit='H') {
//subtract $sDate2-$sDate1 and return the difference in $sUnit (Days,Hours,Minutes,Seconds)
$nInterval = strtotime($sDate2) - strtotime($sDate1);
if ($sUnit=='D') { // days
$nInterval = $nInterval/60/60/24;
} else if ($sUnit=='H') { // hours
$nInterval = $nInterval/60/60;
} else if ($sUnit=='M') { // minutes
$nInterval = $nInterval/60;
} else if ($sUnit=='S') { // seconds
}
return $nInterval;
} //DateDiffInterval