Domanda

I have been chewing on this problem for quite a while. I think I'm trying too hard and keep running around in circles.

The Problem

I need to calculate the hours of moonlight at night, i.e. the amount of time the moon is over the horizon outside the daylight hours. What is known are the sunrise/sunset and moonrise/moonset times in UTC for a given date. The easiest (hypthetical) scenario is the following:

sunrise:  06:45
sunset:   18:20
moonrise: 02:30
moonset:  19:50

The algorithm to calculate moonlight hours would be:

if(moonrise<sunset && sunrise<moonset) {
   moonlighthours = (sunrise-moonrise)-(moonset-sunset);
}

Equally easy:

sunrise:  06:45
sunset:   18:20
moonrise: 10:30
moonset:  19:50

if(moonrise>sunset && sunrise<moonset) {
   moonlighthours = (moonset-sunset);
}

But as we're dealing with UTC it can get quite complicated depending on the time zone as sunrise/set and moonrise/set times might be stretched across three different dates:

sunrise:  2014-02-05 23:30 
sunset:   2014-02-06 12:20
moonrise: 2014-02-06 11:00
moonset:  2014-03-07 00:50

So to calculate the moonlight hours for 2014-02-06 I would try something convoluted like this:

if(sunrise<midnight)    { sunrise  = midnight;    }
if(sunset>midnight+24)  { sunset   = midnight+24; }
if(moonrise<midnight)   { moonrise = midnight;    }
if(moonset>midnight+24) { moonset  = midnight+24  }

if(moonrise<sunset && sunrise<moonset) {
   moonlighthours = (sunrise-moonrise)-(moonset-sunset);
} else if (moonrise>sunset && sunrise<moonset) {
   moonlighthours = (moonset-sunset);
} else if (moonrise<sunset && sunrise>moonset) {
   moonlighthours = (sunrise-moonrise);
} else {
   moonlighthours=0;
} 

Trying to cover all possible scenarios as a result of the phase shift between moonlight and daylight hours using if... else structures is a nightmare. So I am hoping that there is someone with a fresh take on the problem. Any help would be greatly appreciated.

EDIT

Here is the solution I have come up with based on @Ilmari Karonen's suggestions. This will caclulate the moonlight hours during a given calendar date (using PHP syntax):

$mid00  = midnight;          // unixtimestamp , e.g. 1391558400 (2014-02-05 00:00:00) 
$mid24  = midnight+86399;     // 1399075199 (2014-02-05 23:59:59)

$mr     = moonrise;
$ms     = moonset;
$sr     = sunrise;
$ss     = sunset;

$mr = $mr < $mid00 ? $mid00 : $mr;
$ms = $ms > $mid24 ? $mid24 : $ms;
$sr = $sr < $mid00 ? $mid00 : $sr;
$ss = $ss > $mid24 ? $Mid24 : $ss;

$ml_morn = 0;   // moonlight hours during morning night
$ml_even = 0;   // moonlight hours during evening night

if($ms > $mr) {                                      // moon set later than moon rise 
   $ml_morn = $mr < $sr ? $sr-$mr : 0;               // moon rises before sunrise?
   $ml_even = $ms > $ss ? $ms-$ss : 0;               // moon sets after sunset?
} else {                                             // moon set before moon rise
  $ml_even = $mr > $ss ? $mid24-$mr : $mid24 - $ss;  // moon rises before sunset?
  $ml_morn = $ms < $sr ? $ms-$mid00 : $sr - $mid00;  // moon sets before sunrise? 
}

moonlight_hours = $ml_morn = $ml_even;
È stato utile?

Soluzione

When you have a complicated problem, it's often useful to break it up into simpler steps:

Step 1: If necessary, convert the sun/moonrise/set times into proper datetime values that you can do arithmetic with. Second since the Unix epoch (or minutes since the start of the millennium, or whatever) will do, but if your language has an appropriate datetime class or type, I'd recommend using that.

Since this conversion is, presumably, the same for all the timestamps, you can write a single function that will do it for an arbitrary time value, and call it for each of your inputs.

Step 2: Find the start and end times of the moonlight period(s).

If you want the moonlight hours for one night, then you can calculate the start and end of the moonlight period as:

start = max(moonrise, sunset)
end   = min(sunrise, moonset)

If you really need the moonlight hours for one calendar day, it's a bit more complicated, since there may be two separate moonlight periods. You can calculate their respective start and end times as:

start1 = max(moonrise1, sunset1, midnight)
end1   = min(sunrise1, moonset1)
start2 = max(moonrise2, sunset2)
end1   = min(sunrise2, moonset2, midnight + 24 hours)

where the variable names ending in 1 denote the respective times for the night before the given day, and the variable names ending in 2 denote the respective times for the night after the given day.

Step 3: Calculate the length(s) of the moonlight period(s).

This is just simple datetime subtraction. Note that the subtraction might give a negative value, if the moon is not visible at all on a given night, in which case you should clamp the result to zero.

If you're doing the calculation for a specific calendar date, you should repeat this for both the previous and the next night, and then add the (non-negative) results together.

Altri suggerimenti

You can convert it to seconds since 1970 and subtract the results.

tm_sec + tm_min*60 + tm_hour*3600 + tm_yday*86400 +
(tm_year-70)*31536000 + ((tm_year-69)/4)*86400 -
((tm_year-1)/100)*86400 + ((tm_year+299)/400)*86400

If you are only looking to calculate the amount of moonlight, then you can convert the entire date to a single integer representing the number of minutes after a certain date, e.g. if D is a date object and D.year, D.month, D.day, D.hour, D.minute represents the date's structure (this is just pseudo-code), and let's assume 01-01-2010 00:00 is time 0, then the minute counter should be calculated as

total_minutes = ((D.year - 2010) * 60*60*24*365) + (D.month * 60*60*24) + (D.day * 60*60) + (D.hour * 60) + D.minutes;

Do this for all 4 dates you mentioned, and from there you can perform the calculations as you mentioned in the beginning of your post.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top