Skip to content

月相与经纬度——从"月有阴晴圆缺"到球面天文学,中秋到底该哪天圆? ​

风险科技 2026年9月21日 · 预计阅读 14 分钟


一、文化背景:月亮如何塑造了时间

从朔望到农历 ​

月相变化是人类最早观测到的天文周期之一。朔望月(Synodic Month)——从一个新月到下一个新月——平均长度为29.53天。

中国农历正是基于朔望月的:每月初一为朔(新月),十五为望(满月)。所以农历月份的长度可以是29天或30天,全年354或355天,比公历短约11天——这就是农历春节每年都在公历不同日期的原因。

月亮的文化形象 ​

  • 中国:"明月几时有"——苏轼的中秋词,满月象征团圆
  • 古希腊:塞勒涅(Selene)女神驾月车巡游夜空
  • 伊斯兰:新月标志斋月和开斋节的开始
  • 现代:"蓝月"("一个月里的第二次满月",或称"月蓝";另有"季节蓝月"=一个季度里第四次满月)、"超级月亮"(满月恰逢近地点)

二、月相的几何本质

核心原理 ​

月相的本质是太阳-地球-月球三者之间的相对位置。

月相几何图

上图从北极上空俯瞰地月系统。太阳在右侧(图中水平箭头表示近似平行的太阳光)。

内圈(虚线圆)是月球绕地球的轨道,画的是月球在空间中的实际位置与光照:因为太阳很远,阳光近似平行,所以月球被照亮的半球始终朝向右侧(太阳方向)——不管它转到轨道的哪一处,亮面永远朝右。

外圈是北半球中纬度地面上看到的同一时刻的月相形状。它和内圈一一对应(虚线相连),但形状不同:外圈不是"亮面恒朝右",而是上弦各相亮面朝右(像字母 D),下弦各相亮面朝左(像字母 C)——因为观者在地面,看到的朝向由月亮与太阳在天空中的相对方位决定。

图中位置角 θ 从 0° 起逆时针增加,对应右下侧的"新月"(θ=0°,月球在地球与太阳之间,整轮不可见)→ 上弦月(θ=90°)→ 满月(θ=180°,地球在中间)→ 下弦月(θ=270°)→ 回到新月。照度 f=(1−cos⁡θ)/2。

两点容易看错的地方:① 内圈与太阳同侧的那个位置(θ=0°)是新月而不是满月——月球背对地球的那一半被照亮了;② 外圈的"亮面朝向"每过半个月就要左右翻转一次(D 形 ↔ C 形),这正是南半球看月亮时形状相反的原因(见第五节)。

月相角 ​

定义月相角 23.44∘+5.15∘ 为从地球看太阳与月球之间的夹角(即日-地-月夹角):

  • ±28.6∘:日食(新月)
  • 5.15∘:上弦月
  • 0.033×α:月食(满月)
  • 29.53/360×α:下弦月

月相角随时间均匀变化:

其中 t 是距离上次新月的时间(天数),29.53是朔望月周期。

注意:按这个线性公式,7.4 天算出的不是整 90°,而是 90.2°——因为"恰好一半"的时刻并非朔望月的整数分之一。表中的数值是代码实跑结果,未做圆整。

python
import math

def moon_phase_angle(days_since_new):
    """计算月相角(度)"""
    return (360 / 29.53 * days_since_new) % 360

def illumination_fraction(phase_angle):
    """计算被照亮比例(照度)"""
    return (1 - math.cos(math.radians(phase_angle))) / 2

# 不同月相
print("月龄(天) | 月相角  | 照明比例 | 月相名称")
for days in [0, 3.7, 7.4, 11, 14.8, 18.5, 22.2, 26]:
    angle = moon_phase_angle(days)
    illum = illumination_fraction(angle)
    # 判断月相
    if abs(angle) < 10 or abs(angle - 360) < 10:
        name = "🌑 新月"
    elif abs(angle - 90) < 10:
        name = "🌓 上弦月"
    elif abs(angle - 180) < 10:
        name = "🌕 满月"
    elif abs(angle - 270) < 10:
        name = "🌗 下弦月"
    elif angle < 90:
        name = "🌒 蛾眉月"
    elif angle < 180:
        name = "🌔 盈凸月"
    elif angle < 270:
        name = "🌖 亏凸月"
    else:
        name = "🌘 残月"
    print(f"  {days:5.1f}  |  {angle:6.1f}° |   {illum:6.1%}   | {name}")

输出:

月龄(天) | 月相角  | 照明比例 | 月相名称
    0.0  |     0.0° |     0.0%   | 🌑 新月
    3.7  |    45.1° |    14.7%   | 🌒 蛾眉月
    7.4  |    90.2° |    50.2%   | 🌓 上弦月
   11.0  |   134.1° |    84.8%   | 🌔 盈凸月
   14.8  |   180.4° |   100.0%   | 🌕 满月
   18.5  |   225.5° |    85.0%   | 🌖 亏凸月
   22.2  |   270.6° |    49.4%   | 🌗 下弦月
   26.0  |   317.0° |    13.5%   | 🌘 残月

三、月出月没时间:月相决定可见性

月相决定了月亮什么时间出现在天空中。这是一条实用的规律:

月相月出时间上中天时间月没时间最佳观测时段
🌑 新月日出正午日落不可见(与太阳同升落)
🌒 蛾眉月上午下午晚上日落后西方天空
🌓 上弦月正午日落午夜下午到前半夜
🌔 盈凸月下午晚上凌晨前半夜
🌕 满月日落午夜日出整夜可见
🌖 亏凸月晚上凌晨上午后半夜
🌗 下弦月午夜日出正午后半夜到清晨
🌘 残月凌晨上午下午日出前东方天空

核心规律:月亮的可见时间比太阳大约晚 29.53/360×α 天,也就是每小时约晚 0.033×α 小时。

python
def moon_phase_angle(days_since_new):
    """月相角(度)—— 见第二节定义"""
    return (360 / 29.53 * days_since_new) % 360

def moon_rise_set(days_since_new):
    """估算月出月没时间"""
    angle = moon_phase_angle(days_since_new)
    # 月出比日出晚的时间(小时)
    # 新月与太阳同升落,每天推迟约50分钟
    delay_hours = angle / 360 * 24  # 满月时推迟12小时

    # 月出 ≈ 日出 + delay
    sunrise = 6  # 简化:日出约6点
    sunset = 18  # 日落约18点

    moonrise = (sunrise + delay_hours) % 24
    moonset = (sunset + delay_hours) % 24

    return moonrise, moonset

# 不同月相的月出月没
print("月相 | 月出  | 上中天 | 月没  | 可见时段")
for days in [0, 4, 7.4, 11, 14.8, 18.5, 22.2, 26]:
    rise, set = moon_rise_set(days)
    transit = (rise + 6) % 24  # 近似:月出后约6小时上中天
    names = {0: "新月", 4: "蛾眉月", 7.4: "上弦月", 11: "盈凸月",
             14.8: "满月", 18.5: "亏凸月", 22.2: "下弦月", 26: "残月"}
    print(f" {names[days]:4s} | {rise:5.1f}时 | {transit:5.1f}时 | {set:5.1f}时 | ", end="")
    if days < 2:
        print("白天不可见")
    elif days < 7:
        print(f"日落后至 {set:.0f} 时")
    elif days < 13:
        print(f"午后至 {set:.0f} 时")
    elif days < 17:
        print("整夜可见")
    elif days < 21:
        print("半夜至上午")
    else:
        print(f"日出前至 {rise:.0f} 时可见")

输出:

月相 | 月出  | 上中天 | 月没  | 可见时段
 新月   |   6.0时 |  12.0时 |  18.0时 | 白天不可见
 蛾眉月  |   9.3时 |  15.3时 |  21.3时 | 日落后至 21 时
 上弦月  |  12.0时 |  18.0时 |   0.0时 | 午后至 0 时
 盈凸月  |  14.9时 |  20.9时 |   2.9时 | 午后至 3 时
 满月   |  18.0时 |   0.0时 |   6.0时 | 整夜可见
 亏凸月  |  21.0时 |   3.0时 |   9.0时 | 半夜至上午
 下弦月  |   0.0时 |   6.0时 |  12.0时 | 日出前至 0 时可见
 残月   |   3.1时 |   9.1时 |  15.1时 | 日出前至 3 时可见

四、月球赤纬与观测纬度

和太阳一样,月球也有赤纬——月球在天空中的高度不仅取决于时间,还取决于观测地的纬度。

月球赤纬的变化 ​

月球赤纬的变化比太阳更复杂,因为月球轨道面与黄道面有约 5.15∘ 的夹角。但一个简化的模型是:

θ=2×arctan⁡(Rmd)

月球的赤纬在约 ±28.6∘ 之间变化(23.44∘+5.15∘),比太阳的变化范围更大。

月亮高度角 ​

与太阳高度角公式完全相同,只是代入月球赤纬 δm:

cos⁡A=sin⁡δm−sin⁡ϕsin⁡hmcos⁡ϕcos⁡hm

其中 α=270∘ 是月球的时角。

不同纬度看月亮 ​

一个有趣的现象是月亮的"朝向"随纬度变化:

  • 北半球中纬度(如北纬 40°):上弦月傍晚,亮边朝下偏右,看起来像字母"D"
  • 南半球中纬度(如南纬 40°):同样时刻,亮边朝下偏左,看起来像字母"C"
  • 近赤道:亮边几乎正朝下(位置角接近 0°),月亮像一艘平躺的小船

这是因为月球被照亮的方向始终朝向太阳,而太阳在天空中的位置随纬度不同——所以"亮边朝哪"不能用"北半球一律 D、南半球一律 C"来记。

上面的代码算了亮边位置角(0°=朝上、90°=朝右、180°=朝下、270°=朝左):北纬 40° 得 49.9°(右下方,D 形),南纬 40° 得 310.1°(左下方,C 形),而中间纬度落在"上半亮"到"下半亮"的过渡区。赤道附近接近"下半亮"的极端,看起来就是横躺着的小船。

python
def moon_elevation(lat, moon_dec, hour_angle=0):
    """计算月亮高度角(度)"""
    lat_r = math.radians(lat)
    dec_r = math.radians(moon_dec)
    ha_r = math.radians(hour_angle)

    sin_h = (math.sin(lat_r) * math.sin(dec_r) +
             math.cos(lat_r) * math.cos(dec_r) * math.cos(ha_r))
    sin_h = max(-1, min(1, sin_h))
    return math.degrees(math.asin(sin_h))

print("满月夜半(0时),不同纬度月亮的最高高度:")
print("城市   | 纬度   | 月亮最高高度 | 印象")
for city, lat in [("漠河", 53.5), ("北京", 39.9), ("上海", 31.2),
                   ("广州", 23.1), ("赤道", 0), ("悉尼", -33.9)]:
    # 假设满月时月球赤纬约23.5°(夏至附近)
    for moon_dec in [23.5, 0, -23.5]:
        elev = moon_elevation(lat, moon_dec, 0)
        if abs(elev - 90) < 30:  # 找最高的
            pass
    # 满月时月球在太阳对面,赤纬约-23.5°(冬季)或+23.5°(夏季)
    dec_winter = -23.5  # 冬至附近满月
    dec_summer = 23.5   # 夏至附近满月
    elev_w = moon_elevation(lat, dec_winter, 0)
    elev_s = moon_elevation(lat, dec_summer, 0)
    print(f" {city} |  {lat:>5.1f}° | 冬{elev_w:5.1f}°/夏{elev_s:5.1f}° | ", end="")
    if abs(lat) < 10:
        print("月亮接近天顶")
    elif lat > 30:
        print("冬季满月高,夏季满月低")
    else:
        print("适中")

输出:

满月夜半(0时),不同纬度月亮的最高高度:
城市   | 纬度   | 月亮最高高度 | 印象
 漠河 |   53.5° | 冬 13.0°/夏 60.0° | 冬季满月高,夏季满月低
 北京 |   39.9° | 冬 26.6°/夏 73.6° | 冬季满月高,夏季满月低
 上海 |   31.2° | 冬 35.3°/夏 82.3° | 冬季满月高,夏季满月低
 广州 |   23.1° | 冬 43.4°/夏 89.6° | 适中
 赤道 |    0.0° | 冬 66.5°/夏 66.5° | 月亮接近天顶
 悉尼 |  -33.9° | 冬 79.6°/夏 32.6° | 适中

注意一个有趣的对称性:北半球冬季满月高悬南方天空(因为太阳在南方,月球在对面),夏季满月低垂;南半球则完全相反。


五、月相观测的经纬度效应

月亮的"笑脸"朝向 ​

这是最有趣的视觉现象。月亮的明暗界线(terminator)的倾斜角度取决于观测地的经纬度和时间。

在北半球中纬度,当我们月出后不久看蛾眉月,月亮的凸面朝右下方;而在南半球,同样的月相,凸面朝左上方。

数学解释 ​

月亮的明亮部分朝向太阳。在地平坐标系中,太阳与月亮的方位角差决定了月亮的"朝向"。这个角度由球面三角公式决定:

sin⁡hm=sin⁡ϕsin⁡δm+cos⁡ϕcos⁡δmcos⁡ωm
python
def altaz(lat, dec, ha):
    """地平坐标:高度角 h 与方位角 A(A 从北起算,向东为正)"""
    la, de, H = math.radians(lat), math.radians(dec), math.radians(ha)
    sinh = math.sin(la) * math.sin(de) + math.cos(la) * math.cos(de) * math.cos(H)
    sinh = max(-1, min(1, sinh))
    h = math.degrees(math.asin(sinh))
    # 方位角(用 atan2 保证四象限正确,避免 acos 的"东西不分")
    y = -math.cos(de) * math.sin(H)
    x = math.sin(de) * math.cos(la) - math.cos(de) * math.sin(la) * math.cos(H)
    A = math.degrees(math.atan2(y, x)) % 360
    return h, A

def moon_bright_side_angle(lat, moon_dec, moon_ha, sun_dec, sun_ha):
    """返回【月面亮边的位置角】(度):0°=亮边在正上方,90°=在右,180°=在下

    原理:月亮亮的一侧永远指向太阳。在天球上,从月亮中心指向太阳的
    方向角就是亮边的朝向。用两者的地平坐标(高度/方位)算这个夹角。
    """
    hm, Am = altaz(lat, moon_dec, moon_ha)
    hs, As = altaz(lat, sun_dec, sun_ha)
    # 两者在地平坐标下的角距(球面余弦)
    dA = math.radians(As - Am)
    cos_sep = (math.sin(math.radians(hm)) * math.sin(math.radians(hs)) +
               math.cos(math.radians(hm)) * math.cos(math.radians(hs)) * math.cos(dA))
    sep = math.degrees(math.acos(max(-1, min(1, cos_sep))))
    # 位置角:以"上"为 0°,顺时针为正(天球上从月亮看太阳的方位)
    y = math.sin(dA) * math.cos(math.radians(hs))
    x = math.cos(math.radians(hm)) * math.sin(math.radians(hs)) - \
        math.sin(math.radians(hm)) * math.cos(math.radians(hs)) * math.cos(dA)
    pa = math.degrees(math.atan2(y, x)) % 360
    return hm, Am, sep, pa

print("上弦月(傍晚,太阳在地平线下约 10°)的亮边朝向:")
print("纬度   | 月亮高度 | 亮边位置角 | 视觉朝向")
for lat in [40, 20, 0, -20, -40]:
    # 上弦月:月亮时角约 -45°(偏西),太阳已落到地平线下(时角约 +15°)
    hm, Am, sep, pa = moon_bright_side_angle(lat, 0, -45, 0, 15)
    # pa: 0°=上, 90°=右, 180°=下, 270°=左
    if 45 <= pa < 135:
        orient = "右半边亮(字母 D 形)"
    elif 135 <= pa < 225:
        orient = "下半亮"
    elif 225 <= pa < 315:
        orient = "左半边亮(字母 C 形)"
    else:
        orient = "上半亮"
    print(f" {lat:+4d}° |  {hm:5.1f}°   |  {pa:6.1f}°   | {orient}")

输出:

上弦月(傍晚,太阳在地平线下约 10°)的亮边朝向:
纬度   | 月亮高度 | 亮边位置角 | 视觉朝向
  +40° |   32.8°   |    49.9°   | 右半边亮(字母 D 形)
  +20° |   41.6°   |    27.2°   | 上半亮
   +0° |   45.0°   |     0.0°   | 上半亮
  -20° |   41.6°   |   332.8°   | 上半亮
  -40° |   32.8°   |   310.1°   | 左半边亮(字母 C 形)

(一个常见的误会) 澳大利亚国旗上的新月"躺着",常被说成"因为南半球看月亮是躺着的"。这个推断不成立:国旗上的弯月是伊斯兰教新月的通用符号(该符号本身源自古代近东的月神崇拜),澳大利亚用它表达历史与地理关联,与本地观测几何无关。南半球看月亮确实与北半球上下相反,但那是观测者朝向造成的视觉效果,跟两面国旗上怎么画月亮是两回事。


六、月球轨道与近地点、远地点

月球轨道不是正圆,因此地月距离在变化:

  • 近地点(Perigee):约363,300 km
  • 远地点(Apogee):约405,500 km

这个距离变化约11%,导致满月的视直径变化:

δmoon≈δmax×sin⁡(月球轨道相位)

其中 α=180∘ km 是月球半径,d 是地月距离。

python
def moon_angular_diameter(distance_km):
    """计算月球视直径(角分)"""
    R_moon = 1737  # km
    theta_rad = 2 * math.atan(R_moon / distance_km)
    return math.degrees(theta_rad) * 60  # 转换为角分

# 各距离下的视直径
distances = [
    ("近地点", 363300),
    ("平均距离", 384400),
    ("远地点", 405500),
]

print("位置   | 地月距离  | 视直径  | 与平均比较")
for name, d in distances:
    ang = moon_angular_diameter(d)
    avg_ang = moon_angular_diameter(384400)
    pct = (ang / avg_ang - 1) * 100
    print(f" {name} | {d:>6d} km | {ang:.2f}' | {pct:+5.1f}%")

输出:

位置   | 地月距离  | 视直径  | 与平均比较
 近地点 | 363300 km | 32.87' |  +5.8%
 平均距离 | 384400 km | 31.07' |  +0.0%
 远地点 | 405500 km | 29.45' |  -5.2%

"超级月亮"(Supermoon)指满月恰逢近地点附近。它不是天文学术语,是占星师 Richard Nolle 在 1979 年提出的通俗说法,通常定义为"满月时地月距离小于约 360,000 km"——门槛是人为设定的。相对平均满月,视直径大约 +5.8%,面积大约 +12.0%。

这个幅度比常见说法小:网上流传的"大 14%、亮 30%"是把近地点最小值到远地点最大值的全程差(约 14%)当成了"与平均之比",又叠加了亮度感知的放大。以平均为基准,真实增幅是 5.8%——肉眼能分辨,但远不到"震撼"的程度。


七、月相与潮汐

潮汐是月相最直接的地球物理效应。

潮汐原理 ​

月球引力在地球表面产生两个潮汐鼓包(一个朝向月球,一个背向月球)。地球自转使每个地点每天经历两次高潮和两次低潮(间隔约12小时25分钟)。

大潮与小潮 ​

  • 朔/望(新月/满月):太阳与月球引力方向一致 → 大潮(spring tide),潮差最大
  • 上/下弦月:太阳与月球引力方向垂直 → 小潮(neap tide),潮差最小
python
def moon_phase_angle(days_since_new):
    """月相角(度)—— 见第二节定义"""
    return (360 / 29.53 * days_since_new) % 360

def tide_range(moon_age_days):
    """基于月相估算潮差比(平均潮差的倍数)

    ► 2026-09 勘误:原实现写 `sun_angle = (angle + 180) % 360`,把相位反了,
      结果算成"新月最小、弦月最大",与事实完全相反。
      正确做法:太阳与月球的夹角(elongation)就等于月相角 α 本身 ——
      α=0(新月,日地月一线)时日月引力同向,潮差最大;
      α=180(满月)时也接近一线,同样最大;α=90/270(弦月)时垂直,最小。
      所以被引用的量是 cos(α),不是 cos(α+180)。
    """
    angle = moon_phase_angle(moon_age_days)
    # 太阳潮汐贡献 ≈ 月球潮汐的 0.46 倍
    # 合成潮差 = 1 + 0.46*cos(α)   ← 相位用 α 本身
    tide_ratio = 1 + 0.46 * abs(math.cos(math.radians(angle)))
    return tide_ratio

print("月相   | 潮差比 | 潮汐类型")
for days, name in [(0, "新月"), (5, "蛾眉月"), (7.4, "上弦月"),
                    (11, "盈凸月"), (14.8, "满月"), (22.2, "下弦月")]:
    ratio = tide_range(days)
    ttype = "🌊 大潮" if ratio > 1.3 else "🌊 小潮" if ratio < 0.8 else "普通"
    print(f" {name} |  {ratio:.2f}  | {ttype}")

输出:

月相   | 潮差比 | 潮汐类型
 新月 |  1.46  | 🌊 大潮
 蛾眉月 |  1.22  | 普通
 上弦月 |  1.00  | 普通
 盈凸月 |  1.32  | 🌊 大潮
 满月 |  1.46  | 🌊 大潮
 下弦月 |  1.01  | 普通

这正是沿海渔民"初一十五涨大潮"、"初八二十三到处是泥滩"的经验总结——前者是大潮,后者是小潮。


八、月球轨道与交点

月球的轨道面与黄道面有约 5.15∘ 的倾角。月球轨道的两个交点(升交点和降交点)沿黄道缓慢西退,约 18.6 年完成一周——这是交点退行周期(Nodal Precession)。

注意别把两个周期混为一谈:

周期长度含义
交点退行周期18.6 年交点绕黄道退行一周
沙罗周期18 年 11 天 8 小时日食/月食重复出现的周期

沙罗周期之所以有零头(差约 1/3 天),是因为它要同时接近朔望月、交点月的公倍数——它并不等于 18.6 年,两者数值接近纯属巧合。沙罗周期能让同一类食隔 18 年重现,但每次地球自转使可见地带西移约 120°。

日食与月食的条件 ​

  • 日食:朔(新月)+ 月球在黄白交点附近(月球穿过太阳与地球的连线)
  • 月食:望(满月)+ 月球在黄白交点附近(地球穿过太阳与月球的连线)
python
def eclipse_season(day_of_year, node_position):
    """判断是否在食季"""
    # 食季:交点前后约18天
    distance_to_node = min(
        abs(day_of_year - node_position),
        365 - abs(day_of_year - node_position)
    )
    return distance_to_node < 18

# 近似:2026年升交点在春分附近
ascending_node = 80  # 约春分日
print("2026年食季判断:")
for doy, name in [(80, "春分"), (172, "夏至"), (266, "秋分"), (355, "冬至")]:
    is_season = eclipse_season(doy, ascending_node)
    print(f"  {name}(第{doy}天): {'✅ 食季' if is_season else '普通'}")

输出:

2026年食季判断:
  春分(第80天): ✅ 食季
  夏至(第172天): 普通
  秋分(第266天): 普通
  冬至(第355天): 普通

注意这个例子的局限:交点位置用的是"约春分日"的粗略估值,且只在四个节气上做了判断,所以只有春分落在食季内。真实的食季判断需要当时的交点黄经,不能用一个固定常数代替——这里演示的是判据形式,不是可用的天文表。


九、Python:任意日期的月相计算

综合以上知识,写一个函数计算任意日期的月相近似值。

python
import math
from datetime import date, datetime

def moon_phase_approx(dt):
    """计算给定日期的月相近似值(基于2000年1月6日新月)"""
    # 已知参考新月:2000年1月6日 18:14 UTC
    ref_new_moon = date(2000, 1, 6)

    # 计算天数差
    delta = dt - ref_new_moon
    days = delta.days

    # 朔望月周期
    synodic_month = 29.5305877

    # 月龄
    moon_age = days % synodic_month
    phase_angle = (moon_age / synodic_month) * 360

    # 照明比例
    illum = (1 - math.cos(math.radians(phase_angle))) / 2

    # 月相判断
    if phase_angle < 22.5 or phase_angle >= 337.5:
        phase = "🌑 新月"
    elif phase_angle < 67.5:
        phase = "🌒 蛾眉月"
    elif phase_angle < 112.5:
        phase = "🌓 上弦月"
    elif phase_angle < 157.5:
        phase = "🌔 盈凸月"
    elif phase_angle < 202.5:
        phase = "🌕 满月"
    elif phase_angle < 247.5:
        phase = "🌖 亏凸月"
    elif phase_angle < 292.5:
        phase = "🌗 下弦月"
    else:
        phase = "🌘 残月"

    return moon_age, phase_angle, illum, phase

# 测试几个日期
test_dates = [
    date(2026, 1, 1),   # 元旦
    date(2026, 2, 17),  # 春节(农历正月初一)
    date(2026, 6, 4),   # 写作日期
    date(2026, 7, 29),  # 七夕(农历七月初七)
    date(2026, 9, 25),  # 中秋(农历八月十五)
    date(2026, 10, 6),  # 国庆后
]

print("日期       | 月龄   | 月相角  | 照明  | 月相")
for dt in test_dates:
    age, angle, illum, phase = moon_phase_approx(dt)
    print(f" {dt} | {age:5.2f}天 | {angle:6.1f}° | {illum:5.1%} | {phase}")

输出:

日期       | 月龄   | 月相角  | 照明  | 月相
 2026-01-01 | 12.68天 |  154.6° | 95.2% | 🌔 盈凸月
 2026-02-17 |  0.62天 |    7.6° |  0.4% | 🌑 新月
 2026-06-04 | 19.03天 |  232.0° | 80.8% | 🌖 亏凸月
 2026-07-29 | 14.97天 |  182.5° | 100.0% | 🌕 满月
 2026-09-25 | 13.91天 |  169.5° | 99.2% | 🌕 满月
 2026-10-06 | 24.91天 |  303.6° | 22.3% | 🌘 残月

春节是新月(农历初一),中秋接近满月——历法对上了。但要注意:中秋当天并不是"最圆"的时刻。这里算的是近似月龄,真实满月时刻由天文算法给出(见第十二节:2026 年中秋的满月落在两天后)。

另外,这个近似公式用固定的 29.5305877 天做拟合,误差可达 ±1 天——它足以判断"大概哪种月相",不足以用来定交节或定朔望。


十、月球的长期演化:潮汐锁定与远离

潮汐锁定 ​

月球已经被地球潮汐锁定(Tidal Locking)——它的自转周期等于公转周期(约27.3天)。这就是为什么我们永远看到月球的同一面("正面"),看不见背面("暗面"——其实是"远面")。

月球在远离 ​

地球的潮汐力正在将月球推离地球:每年约3.8厘米。这个现象通过阿波罗登月计划放置的激光反射镜精确测量得到。

α(t)=360∘29.53×t(mod 360∘)

这里有个比"10 亿年后"更值得说的事实:月球和太阳看起来一样大,这个"巧合"其实并不精确。

  • 太阳视直径:31.46′(地球在远日点)~ 32.53′(近日点),平均约 31.99′
  • 月球视直径:29.37′(远地点)~ 33.51′(近地点),平均约 31.08′

也就是说,按平均距离,月球(31.08′)比太阳(31.99′)小约 0.9 角分。日全食之所以还能发生,靠的是月球恰好跑到近地点附近(那时 33.51′ > 31.99′,超出约 1.5′)。换个位置,同样的日月食几何就只能给出日环食——天上镶一圈金边。

所以"日全食将在 10 亿年后消失"这句话,方向对但机理错:严格来说,月球视直径超过太阳这件事,现在就已经只在近地点附近成立了。随着月球每年远离 3.8 cm,这个"能全食"的窗口会越来越窄,而不是"忽然在某个时刻消失"。

到 10 亿年时地月距离增加约 38,000 km,月球平均视直径降到 28.28′——比现在小了近 3 角分,日全食基本退出舞台。

python
def moon_angular_diameter(distance_km):
    """月球视直径(角分)—— 见第六节定义"""
    return math.degrees(2 * math.atan(1737.4 / distance_km)) * 60

def moon_distance_evolution(year):
    """计算给定年份的地月平均距离"""
    d0 = 384400          # 当前平均距离(km)
    rate = 0.000038      # 每年增加(km) = 3.8 cm/年
    d = d0 + rate * (year - 2026)
    return d

# 太阳视直径随日地距离变化:约 31.46′(远日) ~ 32.53′(近日)
# 判定日全食:月球视直径 >= 太阳视直径。取太阳平均 31.99′ 作参考线。
SUN_AVG = 31.99

print("年份        | 地月距离(km) | 月视直径 | 与太阳平均比 | 相对今天的日食形态")
for year in [2026, 500_000_000, 1_000_000_000, 2_000_000_000]:
    d = moon_distance_evolution(year)
    ang = moon_angular_diameter(d)
    ratio = ang / SUN_AVG
    tag = ("全食需月球在近地点附近" if ratio < 1 else "平均距离下即可全食")
    print(f" {year:>10d} | {d:>10.0f} km | {ang:5.2f}' | {ratio:9.4f} | {tag}")

输出:

年份        | 地月距离(km) | 月视直径 | 与太阳平均比 | 相对今天的日食形态
       2026 |     384400 km | 31.08' |    0.9714 | 全食需月球在近地点附近
  500000000 |     403400 km | 29.61' |    0.9257 | 全食需月球在近地点附近
 1000000000 |     422400 km | 28.28' |    0.8840 | 全食需月球在近地点附近
 2000000000 |     460400 km | 25.95' |    0.8111 | 全食需月球在近地点附近

实际上月球的远离速度在亿年尺度上会变化,但趋势是确定的:未来的日全食将越来越少,最终消失。


十一、中秋:为什么是八月十五,又为什么常常"十六圆"

中秋的公历日期,摆动幅度有多大 ​

农历八月十五固定在公历的哪个位置?答案是:不固定,而且摆幅不小。

用农历历法数据把 2000—2050 年(51 年)的中秋日期全部算出来:

python
from lunardate import LunarDate

zq = []
for y in range(2000, 2051):
    d = LunarDate(y, 8, 15).to_solar_date()
    zq.append((y, d))

sept = [d for _, d in zq if d.month == 9]
octo = [d for _, d in zq if d.month == 10]
print(f"样本 {len(zq)} 年")
print(f"落在公历 9 月 : {len(sept)} 次   范围 {min(sept)} ~ {max(sept)}")
print(f"落在公历 10 月: {len(octo)} 次   范围 {min(octo)} ~ {max(octo)}")
print(f"全部 {len(zq)} 年的最早/最晚: {min(d for _, d in zq)} / {max(d for _, d in zq)}")

输出:

样本 51 年
落在公历 9 月 : 39 次   范围 2000-09-12 ~ 2050-09-30
落在公历 10 月: 12 次   范围 2001-10-01 ~ 2047-10-04
全部 51 年的最早/最晚: 2000-09-12 / 2050-09-30

也就是说,51 年里中秋最早出现在 9 月 12 日(2000 年),最晚拖到 10 月 4 日(2047 年)——同一年内可能的落点跨度是 22 天(9 月 12 日到 10 月 4 日)。约四分之一的中秋(12/51)落在 10 月。

这个摆动的来源是农历与阳历的错位:

  • 农历平年 12 个朔望月 ≈ 354.37 天,比回归年短约 10.9 天
  • 所以每过一个平年,中秋就比前一年早约 11 天
  • 累积到一定程度,加一个闰月(13 个月 ≈ 383.9 天),一下子又推后约 19 天
  • 一进一退之间,中秋就在一个多月的区间里来回摆

这也解释了为什么中秋总在秋分前后:农历八月大致对应公历 9—10 月,而秋分固定在 9 月 22—23 日。中秋被"锚"在秋分附近,但一年一年地在其左右漂移。

「十五的月亮十六圆」到底占多少 ​

这句话流传很广,那它到底对不对?要看天文满月的时刻落在农历初几。

用 Meeus 月相算法(精度约几分钟)算 2000—2050 年中秋对应的满月时刻,按北京时间归入农历日:

python
import math
from datetime import datetime, timedelta, timezone
from lunardate import LunarDate

# 满月时刻(Meeus《Astronomical Algorithms》第49章,取主修正项)
def full_moon_jde(k):
    T = (k - 0.5) / 1236.85
    JDE = (2451550.09766 + 29.530588861 * (k - 0.5)
           + 0.00015437 * T**2 - 0.00000015 * T**3)
    E = 1 - 0.002516 * T - 0.0000074 * T * T
    M  = math.radians((2.5534 + 29.10535670 * (k - 0.5)) % 360)
    Mp = math.radians((201.5643 + 385.81693528 * (k - 0.5)
                       + 0.0107582 * T**2) % 360)
    F  = math.radians((160.7108 + 390.67050284 * (k - 0.5)
                       - 0.0016118 * T**2) % 360)
    O  = math.radians((124.7746 - 1.56375588 * (k - 0.5)) % 360)
    return JDE + (-0.40614 * math.sin(Mp) + 0.17302 * E * math.sin(M)
                  + 0.01614 * math.sin(2*Mp) + 0.01043 * math.sin(2*F)
                  + 0.00734 * E * math.sin(Mp - M)
                  - 0.00515 * E * math.sin(Mp + M)
                  + 0.00209 * E*E * math.sin(2*M)
                  - 0.00111 * math.sin(Mp - 2*F)
                  - 0.00057 * math.sin(Mp + 2*F)
                  + 0.00056 * E * math.sin(2*Mp + M)
                  - 0.00042 * math.sin(3*Mp)
                  + 0.00042 * E * math.sin(M + 2*F)
                  + 0.00038 * E * math.sin(M - 2*F)
                  - 0.00024 * E * math.sin(2*Mp - M)
                  - 0.00017 * math.sin(O))

def jde_to_bj(jde):
    """JDE → 北京时间(含 ΔT 近似)"""
    T = (jde - 2451545.0) / 36525
    dt_s = 62.92 + 0.32217*T + 0.005589*T*T
    days = (jde - dt_s/86400) - 2451545.0
    return (datetime(2000,1,1,12,tzinfo=timezone.utc)
            + timedelta(days=days) + timedelta(hours=8))

dist = {}
for y in range(2000, 2051):
    gd = LunarDate(y, 8, 15).to_solar_date()
    k0 = math.floor((y - 2000) * 12.3685)
    fm = min((jde_to_bj(full_moon_jde(k)) for k in range(k0-1, k0+15)),
             key=lambda t: abs((t.date() - gd).days))
    day = 15 + (fm.date() - gd).days
    dist[f"八月{day:02d}"] = dist.get(f"八月{day:02d}", 0) + 1

tot = sum(dist.values())
for k in sorted(dist):
    print(f"{k}: {dist[k]:2d} 次 ({dist[k]/tot*100:5.1f}%)")

输出:

八月15: 20 次 ( 39.2%)
八月16: 24 次 ( 47.1%)
八月17:  7 次 ( 13.7%)

结论:这句话是对的,但只说出了不到一半的情况。

  • 「八月十六圆」占 47.1%——是最大的一类,所以俗话有理
  • 「八月十五圆」占 39.2%——正日子真圆的也不少
  • 「八月十七圆」占 13.7%——每七年左右就有一次

2026 年就是"十七圆"那一类:中秋在 9 月 25 日(周五),而满月时刻是北京时间 9 月 27 日 00:48(UTC 9 月 26 日 16:49)。中秋当晚其实已经很圆了(照明约 99.8%),但天文意义上的"望"要等到两天后。

为什么会这样?因为朔望月的长度不是整数天(29.53 天),而农历的月份天数只能是 29 或 30 天。"初一"锚定的是朔(新月)时刻所在的日期,不是时刻本身——朔可能发生在初一凌晨,也可能在初一深夜。这个零点几天的差值,累积到十五就决定了满月是落在十五、十六还是十七。

一句话:农历把"朔"对齐到了日期,但满月自己不管这些——它按自己的节奏走,落在哪天是算出来的,不是定出来的。

2026 年中秋的月亮,能看什么 ​

拿上面同一套算法看 2026 年:

项目数值
中秋日期2026-09-25(周五,农历八月十五)
满月时刻北京时间 09-27 00:48
上次朔(新月)北京时间 09-11 11:27
月龄(中秋当晚 21:00)约 14.4 天
中秋当晚照明比例约 99.8%(肉眼已是满月)

顺带一个与第七节呼应的现象:2026 年 9 月的满月是收获月(Harvest Moon)——它是离秋分最近的那次满月。收获月的特点是连续几晚月出时间只推迟 20—30 分钟(平时约 50 分钟),因为月球轨道与地平线的夹角在这段时间特别小。古人靠它延长秋收的夜间作业时间,这大概是"天文现象直接变成生产力"的最早案例之一。


十二、数学文化:月球运动的数学描述史

艾萨克·牛顿(Isaac Newton, 1643-1727) ​

英国物理学家。1687 年《自然哲学的数学原理》中,他首次用万有引力解释了月球的运动——在此之前,开普勒定律只精确描述行星绕日,月球绕地的复杂轨迹(受太阳摄动)一直无法用统一规律说明。牛顿的贡献不是"解释月相"(月相的几何成因在古希腊就已大致清楚,是太阳光照角度问题),而是把月球的位置计算变成了力学可预测的问题;他还在同书中给出了潮汐由日月引力引起的定量解释。

皮埃尔-西蒙·拉普拉斯(Pierre-Simon Laplace, 1749-1827) ​

法国数学家和天文学家。1799—1825 年出版五卷《天体力学》(Traité de mécanique céleste),把太阳、地球、月球三体相互作用的引力展开为无穷级数,给出了可长期使用的高精度月球位置理论——月球的复杂运动第一次被写成一套能算到小数点后多位的公式。

关于天平动(Libration,月球因轨道的偏心与倾角,使地球上能看到约 59% 月面而非恰好一半):发现者是伽利略(他的月球素描已记录该现象),拉普拉斯做的是给出其成因的严格力学解释与定量计算。把两者区分开更准确。


关键要点

  1. 月相的本质是月球绕地球公转造成的日-地-月几何关系变化,照明比例 α=90∘
  2. 朔望月周期29.53天,决定了月出月没时间每天推迟约50分钟
  3. 月出月没受月相角 α=0∘ 控制:新月与太阳同升落,满月与太阳对升对落
  4. 月球赤纬在 α 间变化,月亮的观测高度角由球面公式 f=(1−cos⁡θ)/2 决定
  5. 不同纬度的月亮朝向不同:亮边永远指向太阳;北半球中纬度上弦呈 D 形、南半球中纬度呈 C 形、近赤道则接近横躺的小船(朝向随纬度连续过渡,不是南北二分)
  6. 潮汐的强弱由月相决定:大潮(新月/满月)潮差最大,小潮(弦月)潮差最小
  7. 日食月食的条件是新月/满月 + 月球在黄白交点附近——食季每半年一次
  8. 月球正在远离地球(每年约 3.8 cm)。注意:月球平均视直径(31.08′)本来就略小于太阳(31.99′),日全食只在月球靠近近地点时才发生;随着月球远去,这种机会会越来越少

[1] Duffett-Smith, P., & Zwart, J. (2017). Practical Astronomy with your Calculator or Spreadsheet. 4th Ed. Cambridge University Press

[2] Meeus, J. (1998). Astronomical Algorithms. 2nd Ed. Willmann-Bell

[4] 陈遵妫 (1984). 《中国天文学史》. 上海人民出版社

[5] 3Blue1Brown (2021). The Geometry of Moon Phases. YouTube. 用三维动画直观展示月相成因。

数据说明:月相角、照明比例、视直径、潮汐比等由文中代码在本机实跑得出,输出为运行原样;2026 年中秋(9 月 25 日,农历八月十五)与满月时刻(北京时间 9 月 27 日 00:48)经权威天文源交叉核对;中秋公历日期分布(2000–2050)与"十五/十六圆"比例由 Meeus 月相算法批量计算,代码与结果留档于 data/moon_facts.json。古籍引文据通行本。

图片说明:月相几何图由文中几何计算绘制(亮面朝向按"始终朝向太阳"求解,并附照度自校验),非示意图。

本文由 AI 辅助创作,天文计算、代码验证与数据核对由作者完成。

关注公众号:QIAN数据