【问题标题】:Algorithm to calculate nightly hours of moonlight计算每晚月光小时数的算法
【发布时间】:2014-03-03 12:29:35
【问题描述】:

我一直在研究这个问题很长一段时间。我觉得我太努力了,一直在兜圈子。

问题

我需要计算夜间月光的小时数,即月亮在白天以外的地平线上的时间。已知的是给定日期的 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

所以要计算 2014-02-06 的月光时间,我会尝试这样复杂的事情:

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;

【问题讨论】:

  • ??? - 我不明白那个。
  • 直到今天我才知道夏令时......认为太阳总是重要的,与每 28 天一次的罕见月亮相比......;)
  • 您的输入是否实际上也包含日期,或者您只是获取一天中的时间并且必须猜测它是哪一天?
  • 另外,您真的需要某个特定日历日期(可能包括两个时段,一个在早上和另一个在晚上)或一个晚上的月光小时数吗?您是否真的需要足够的精度才能有所作为?
  • @Ilmari Karonen - 是的,是的。所有输入的格式为datetime。是的,我需要特定日历日期的月光,因为我正在处理时间序列数据,其中需要测试其他日期特定参数与夜间可见度的相关性。

标签: php algorithm datetime timezone date-arithmetic


【解决方案1】:

当您遇到复杂问题时,将其分解为更简单的步骤通常很有用:

第 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 步:计算月光周期的长度。

这只是简单的日期时间减法。请注意,如果在给定的夜晚根本看不到月亮,则减法可能会给出负值,在这种情况下,您应该将结果钳制为零。

如果您要针对特定​​日历日期进行计算,则应在前一晚和下一晚重复此操作,然后将(非负)结果加在一起。

【讨论】:

  • 它比第 2 步下的第二个代码 sn-p 还要复杂。因为你的解假设每天我们都有同步的月亮和太阳升起。但事实上,如果您查看日历日期(UTC 时区!),您可能会遇到按时间顺序排列的情况,例如 moonset&gt;sunrise&gt;sunset&gt;moonrise 甚至 sunset&gt;moonrise&gt;sunrise&gt;moonset。但是您的建议使我走上了正轨(我将编辑我的帖子以展示我提出的算法)。感谢您的努力,非常感谢!
【解决方案2】:

您可以将其转换为自 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

【讨论】:

    【解决方案3】:

    如果您只想计算月光的,那么您可以将整个日期转换为一个整数,表示某个日期之后的分钟数,例如如果D 是日期对象,D.yearD.monthD.dayD.hourD.minute 表示日期的结构(这只是伪代码),假设01-01-2010 00:00 是时间@ 987654328@,那么分钟计数器应该计算为

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

    对您提到的所有 4 个日期执行此操作,然后您可以从那里执行您在帖子开头提到的计算。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-06
      • 1970-01-01
      • 2021-11-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多