Алгоритм расчета ночных часов лунного света

Я довольно долго размышлял над этой проблемой. Думаю, я слишком стараюсь и продолжаю бегать кругами.

Проблема

Мне нужно рассчитать часы лунного света ночью, т.е. количество времени, в течение которого луна находится над горизонтом вне дневного времени. Что известно, так это время восхода/захода солнца и восхода/захода луны в UTC для данной даты. Самый простой (гипотетический) сценарий выглядит следующим образом:

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

Алгоритм расчета лунных часов будет следующим:

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

Так же легко:

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

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

Но поскольку мы имеем дело с UTC, это может стать довольно сложным в зависимости от часового пояса, поскольку время восхода/захода и восхода/захода луны может быть растянуто на три разные даты:

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

Таким образом, чтобы рассчитать лунные часы на 06.02.2014, я бы попробовал что-то запутанное, например:

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;
} 

Попытка охватить все возможные сценарии в результате фазового сдвига между лунным и дневным светом с использованием структур if... else — это кошмар. Поэтому я надеюсь, что есть кто-то со свежим взглядом на проблему. Любая помощь будет принята с благодарностью.

ИЗМЕНИТЬ

Вот решение, которое я придумал на основе предложений @Ilmari Karonen. Это подсчитает лунные часы в течение заданной даты календаря (используя синтаксис PHP):

$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;

person Tomm    schedule 06.02.2014    source источник
comment
??? - Я не понимаю этого.   -  person Tomm    schedule 06.02.2014
comment
До сегодняшнего дня я знал только переход на летнее время ... думал, что Солнце всегда было важным по сравнению с редкими оборотами Луны раз в 28 дней .. ;)   -  person bonCodigo    schedule 06.02.2014
comment
Содержит ли ваш ввод также и дату, или вы просто получаете время суток и должны угадать, на какой день он приходится?   -  person Ilmari Karonen    schedule 06.02.2014
comment
Кроме того, вам действительно нужны часы лунного света для одной конкретной календарной даты (которая может состоять из двух периодов, одного утром и другого вечером) или для одной ночи? И действительно ли вам нужна достаточная точность, чтобы это имело значение?   -  person Ilmari Karonen    schedule 06.02.2014
comment
@ Ильмари Каронен - ​​Да и да. Все входные данные имеют формат datetime. И да, мне нужен лунный свет для определенных дат календаря, поскольку я имею дело с данными временных рядов, где необходимо проверить другие параметры, относящиеся к дате, на корреляцию с видимостью в ночное время.   -  person Tomm    schedule 06.02.2014


Ответы (3)


Когда у вас есть сложная проблема, часто бывает полезно разбить ее на более простые шаги:

Шаг 1. При необходимости преобразуйте время восхода/захода солнца/захода солнца в правильные значения даты и времени, с которыми можно выполнять арифметические действия. Во-вторых, с эпохи Unix (или минут с начала тысячелетия или что-то еще) подойдет, но если ваш язык имеет соответствующий класс или тип даты и времени, я бы рекомендовал использовать это.

Поскольку это преобразование, по-видимому, одинаково для всех меток времени, вы можете написать одну функцию, которая будет делать это для произвольного значения времени, и вызывать ее для каждого из ваших входных данных.

Шаг 2. Найдите время начала и окончания лунного периода.

Если вам нужны часы лунного света для одной ночи, вы можете рассчитать начало и конец периода лунного света следующим образом:

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

Если вам действительно нужны лунные часы для одного календарного дня, это немного сложнее, так как могут быть два отдельных лунных периода. Вы можете рассчитать их соответствующие времена начала и окончания как:

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

где имена переменных, заканчивающиеся на 1, обозначают соответствующее время ночи перед заданным днем, а имена переменных, заканчивающиеся на 2, обозначают соответствующее время ночи после заданного дня. день.

Шаг 3. Рассчитайте продолжительность лунного периода.

Это простое вычитание даты и времени. Обратите внимание, что вычитание может дать отрицательное значение, если луна вообще не видна в данную ночь, и в этом случае вы должны зафиксировать результат до нуля.

Если вы выполняете расчет для определенной календарной даты, вы должны повторить это как для предыдущей, так и для следующей ночи, а затем сложить (неотрицательные) результаты вместе.

person Ilmari Karonen    schedule 06.02.2014
comment
Это еще сложнее, чем второй фрагмент кода на шаге 2. Потому что ваше решение предполагает, что каждый день у нас есть синхронные восходы луны и солнца. Но на самом деле, если вы посмотрите на календарные даты (часовой пояс UTC!), у вас могут быть случаи, когда в хронологическом порядке у вас есть что-то вроде moonset>sunrise>sunset>moonrise или даже sunset>moonrise>sunrise>moonset. Но ваше предложение поставило меня на правильный путь (я отредактирую свой пост, чтобы показать алгоритм, который я придумал). Спасибо за ваши усилия, очень признателен! - person Tomm; 06.02.2014

Вы можете преобразовать его в секунды с 1970 года и вычесть результаты.

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
person mclc    schedule 06.02.2014

Если вы хотите рассчитать только количество лунного света, вы можете преобразовать всю дату в одно целое число, представляющее количество минут после определенной даты, например если D является объектом даты, а D.year, D.month, D.day, D.hour, D.minute представляют структуру даты (это всего лишь псевдокод), и предположим, что 01-01-2010 00:00 является временем 0, то счетчик минут должен рассчитываться как

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

Сделайте это для всех 4 дат, которые вы упомянули, и оттуда вы можете выполнить расчеты, как вы упомянули в начале своего поста.

person Ron Teller    schedule 06.02.2014