月相算法

45

有没有人知道一个算法,可以计算给定日期的月相或年龄,或者找到给定年份中新月/满月的日期?

在谷歌上搜索告诉我,答案在一些天文学书籍中,但当我只需要一页时,我不想买整本书。

更新:

我应该更好地限定我的搜索。我发现的解决方案仅适用于某些时间段(如1900年代),并且三角函数的解决方案比我想象中的要耗费更多的计算资源。

S Lott在他的Python书籍中提供了几种计算给定年份复活节的算法,其中大多数代码不到十行,并且有些可以适用于公历日历中的所有日期。找到3月满月是找到复活节的关键部分,因此我认为应该存在一种算法,它不需要三角函数,且可以适用于公历日历中的所有日期。


这个问题在这里出现了很多次,通常与确定复活节、Lint、受难节和/或逾越节的日期有关。 - nategoose
5
精确的天文模型是不可避免地复杂的;用于确定复活节日期的满月计算(请参见http://en.wikipedia.org/wiki/Paschal_full_moon)使用了一个简化模型。 - Matthew Slattery
1
moon.py 的一个很好的用途是编写一个 GUI 应用程序(我会选择 GTK),以实际显示月相和月亮图片(实际上我正在尝试做到这一点,这个问题及其答案使这变得更加容易...)。 - heltonbiker
8个回答

28

如果您和我一样,都是认真的程序员。那么当您看到散布在互联网上的随机代码声称解决了一个复杂的天文问题,但不解释其正确性时,您会感到紧张。

您相信必须有权威来源,如书籍 ,其中包含仔细而完整的解决方案。例如:

Meeus, Jean. Astronomical Algorithms. Richmond: Willmann-Bell, 1991. ISBN 0-943396-35-2.

Duffett-Smith, Peter. Practical Astronomy With Your Calculator. 3rd ed. Cambridge: Cambridge University Press, 1981. ISBN 0-521-28411-2.

您相信广泛使用、经过充分测试、可以纠正错误的开源库(与静态网页不同)。因此,这里提供了基于PyEphem库、使用月相接口的Python解决方案,以回答您的问题。

#!/usr/bin/python
import datetime
import ephem
from typing import List, Tuple

def get_phase_on_day(year: int, month: int, day: int):
  """Returns a floating-point number from 0-1. where 0=new, 0.5=full, 1=new"""
  #Ephem stores its date numbers as floating points, which the following uses
  #to conveniently extract the percent time between one new moon and the next
  #This corresponds (somewhat roughly) to the phase of the moon.

  #Use Year, Month, Day as arguments
  date = ephem.Date(datetime.date(year,month,day))

  nnm = ephem.next_new_moon(date)
  pnm = ephem.previous_new_moon(date)

  lunation = (date-pnm)/(nnm-pnm)

  #Note that there is a ephem.Moon().phase() command, but this returns the
  #percentage of the moon which is illuminated. This is not really what we want.

  return lunation

def get_moons_in_year(year: int) -> List[Tuple[ephem.Date, str]]:
  """Returns a list of the full and new moons in a year. The list contains tuples
of either the form (DATE,'full') or the form (DATE,'new')"""
  moons=[]

  date=ephem.Date(datetime.date(year,1,1))
  while date.datetime().year==year:
    date=ephem.next_full_moon(date)
    moons.append( (date,'full') )

  date=ephem.Date(datetime.date(year,1,1))
  while date.datetime().year==year:
    date=ephem.next_new_moon(date)
    moons.append( (date,'new') )

  #Note that previous_first_quarter_moon() and previous_last_quarter_moon()
  #are also methods

  moons.sort(key=lambda x: x[0])

  return moons

print(get_phase_on_day(2013,1,1))

print(get_moons_in_year(2013))

这将返回

0.632652265318

[(2013/1/11 19:43:37, 'new'), (2013/1/27 04:38:22, 'full'), (2013/2/10 07:20:06, 'new'), (2013/2/25 20:26:03, 'full'), (2013/3/11 19:51:00, 'new'), (2013/3/27 09:27:18, 'full'), (2013/4/10 09:35:17, 'new'), (2013/4/25 19:57:06, 'full'), (2013/5/10 00:28:22, 'new'), (2013/5/25 04:24:55, 'full'), (2013/6/8 15:56:19, 'new'), (2013/6/23 11:32:15, 'full'), (2013/7/8 07:14:16, 'new'), (2013/7/22 18:15:31, 'full'), (2013/8/6 21:50:40, 'new'), (2013/8/21 01:44:35, 'full'), (2013/9/5 11:36:07, 'new'), (2013/9/19 11:12:49, 'full'), (2013/10/5 00:34:31, 'new'), (2013/10/18 23:37:39, 'full'), (2013/11/3 12:49:57, 'new'), (2013/11/17 15:15:44, 'full'), (2013/12/3 00:22:22, 'new'), (2013/12/17 09:28:05, 'full'), (2014/1/1 11:14:10, 'new'), (2014/1/16 04:52:10, 'full')]

ephem.Moon() 是基于 Meeus 的书吗? - vicemagui
+1,已经在Python3.7中测试过。然而,datetime.date(year,01,01)会导致错误"invalid token",即需要更改为datetime.date(year,1,1),并且print需要使用(),即print(get_phase_on_day(2013,1,1)) - theozh

25

之前我把一些代码移植到Python上。本来想直接链接的,结果发现这段时间它消失了,所以我不得不去找回来并重新上传。请查看moon.py,它是基于约翰·沃克(John Walker)的月相工具修改而来。

我找不到关于这个算法准确性的时间跨度的参考资料,但似乎作者们非常严谨。这意味着是的,它确实使用三角函数,但我无法想象你会用它来做什么计算耗时极长的事情。Python函数调用开销可能比三角函数运算的成本还高。计算机处理速度非常快。

代码中使用的算法取自以下来源:

Meeus, Jean. Astronomical Algorithms. Richmond: Willmann-Bell, 1991. ISBN 0-943396-35-2.

必须拥有;如果你只买一本书,请确保是这本书。算法是以数学形式呈现,而不是以计算机程序形式呈现,但是可以从出版商单独订购实现书中许多算法的源代码,包括QuickBasic、Turbo Pascal或C。Meeus提供了许多计算的实例,这些实例对于调试你的代码是至关重要的,并经常提供几种具有不同精度、速度、复杂度和长期(世纪和千年)有效性的算法。

Duffett-Smith, Peter. Practical Astronomy With Your Calculator. 3rd ed. Cambridge: Cambridge University Press, 1981. ISBN 0-521-28411-2.

尽管标题中有“计算器”一词,但如果您对开发计算行星位置、轨道、食甚等软件感兴趣,这将是一份有价值的参考资料。与Meeus相比,提供了更多的背景信息,有助于那些不熟悉天文学术语的人学习。给出的算法比Meeus提供的算法更简单、精度较低,但对于大多数实际工作来说是合适的。


17
"计算机在计算方面非常快。" - 我喜欢它!我可能要引用这句话。 - Mark Ransom
这里最重要的部分当然是keturn的代码和John Walker的代码都包含了参考来源,可以对其进行检查。这些来源在此处列出:http://www.fourmilab.ch/moontoolw/moontool16.html。 - Richard
Richard,我不介意引用算法的来源,但如果你在这里粘贴那些作品的评论,请明确那些话是John Walker的,而不是你的或我的。 - keturn
moon.py对Python3的支持非常糟糕。 - Matt Joiner
“糟糕透顶”,真的吗?它只是基本的算术运算,怎么会这么糟糕呢?但考虑到它是在2001年编写的,我们很幸运它甚至还支持Python2。 - keturn
显示剩余2条评论

9

3
@Jack: WOW! +1... The memory. 我不知道我从哪里认识那个名字:“Ben Daglish”。他曾是C64上8位音乐制作人之一(我说过一个)在那个时代。令人惊讶的是,我发现了与他完全无关的链接(我不得不在他的网站上搜索,直到我找到了C64的链接才记起我是从哪里知道这个名字的)。 - SyntaxT3rr0r
9
StackOverflow的一个好处是人们可以在这里得到答案而不是链接。此外,你的顶部链接已损坏,第二个链接没有引用来说服谨慎的程序员所列出的算法是正确的,第三个链接有引用,但代码已经改变了所有的原始内容。第四个链接同样存在问题。 - Richard
1
谁的Google?对于不同的人来说,搜索结果可能会非常不同。 - jkj
1
其中一个链接已经失效,其他三个只包含粗略的近似值。例如,sci.astro FAQ 条目每 2500 年会漂移一天(这是科威特伊斯兰日历使用的相同算法)。Daglish 的可能是最好的,但它只处理公历日期,而不是时间。对于某些人来说可能足够了,但对于日历计算来说还不够好。 - dhasenan

8

太棒了,keturn,感谢你提供的链接。 - Scott Bailey

6

PyEphem现已停用,他们建议新项目使用Skyfield天文库代替PyEphem。它的现代设计鼓励更好的Python代码,并使用NumPy加速计算。

月相被定义为沿黄道测量的月亮和太阳之间的角度。这个角度被计算为月球与太阳的黄道经度的差异。

结果的角度在新月时为0°,在上弦月时为90°,在满月时为180°,在下弦月时为270°

代码取自这里

from skyfield.api import load
from skyfield.framelib import ecliptic_frame

ts = load.timescale()
t = ts.utc(2019, 12, 9, 15, 36)

eph = load('de421.bsp')
sun, moon, earth = eph['sun'], eph['moon'], eph['earth']

e = earth.at(t)
_, slon, _ = e.observe(sun).apparent().frame_latlon(ecliptic_frame)
_, mlon, _ = e.observe(moon).apparent().frame_latlon(ecliptic_frame)
phase = (mlon.degrees - slon.degrees) % 360.0

print('{0:.1f}'.format(phase))

输出

149.4

3
默认情况下,Pyephem使用协调世界时(UTC)。我想要一个能够在太平洋时区准确生成满月列表的程序。以下代码将计算给定年份的满月,然后使用ephem.localtime()方法进行校准以适应所需的时区。它还似乎正确地考虑了夏令时。感谢Richard,这段代码与他写的类似。
#!/usr/bin/python
import datetime
import ephem
import os
import time

# Set time zone to pacific
os.environ['TZ'] = 'US/Pacific'
time.tzset()

print("Time zone calibrated to", os.environ['TZ'])

def get_full_moons_in_year(year):
    """
    Generate a list of full moons for a given year calibrated to the local time zone
    :param year: year to determine the list of full moons
    :return: list of dates as strings in the format YYYY-mm-dd
    """
    moons = []

    date = ephem.Date(datetime.date(year - 1, 12, 31))
    end_date = ephem.Date(datetime.date(year + 1, 1, 1))

    while date <= end_date:
        date = ephem.next_full_moon(date)

        # Convert the moon dates to the local time zone, add to list if moon date still falls in desired year
        local_date = ephem.localtime(date)
        if local_date.year == year:
            # Append the date as a string to the list for easier comparison later
            moons.append(local_date.strftime("%Y-%m-%d"))

    return moons

moons = get_full_moons_in_year(2015)
print(moons)

上述代码将返回:

Time zone calibrated to US/Pacific
['2015-01-04', '2015-02-03', '2015-03-05', '2015-04-04', '2015-05-03', '2015-06-02', '2015-07-01', '2015-07-31', '2015-08-29', '2015-09-27', '2015-10-27', '2015-11-25', '2015-12-25']

它还会提供满月的时间吗? - Pradeep Padmanaban C

2

我知道你在寻找Python,但是如果你能理解C#,有一个开源项目叫做Chronos XP可以很好地实现这一点。


我可以阅读几乎所有编程语言,除了 Perl。哈哈。但是 Chronos XP 似乎更像占星术应用程序而不是天文学。 - Scott Bailey
3
请查看名为LunarPhase.cs的文件。这个类基本上做了它所说的事情。这是我发现的较好的实现之一,但不幸的是它仍然很复杂。如果您不想下载整个源代码,只需在Google Code上搜索该文件名即可。 - Jacobs Data Solutions

2
如果你不需要高精度,你可以随时(滥用)使用一个农历(或阴阳历)日历类(例如 Microsoft .NET 中的 HijriCalendar 或 ChineseLunisolarCalendar),来计算任何日期的(大概的)月相。因为日历的“月中日”属性是一个农历(或阴阳历)日历日,总是对应于月相(例如,第1天是新月,第15天是满月等)。

@klemens 哈哈,谢谢。我不会那么做(如果我这样做了,我就必须为Windows和Microsoft总体做出贡献 - 好吧,我已经长大成人了,这对我来说更好),但这确实是一次非常需要的笑声。 - Pryftan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接