102 lines
3.6 KiB
Python
102 lines
3.6 KiB
Python
#!/usr/bin/env python
|
||
"""广寒计划发射窗口计算
|
||
|
||
广寒基地往返标准 LLO,每月有 2 个标准窗口。
|
||
地月转移耗时 5 天。给定任意时间,计算最近的地球出发发射窗口
|
||
及对应的月球着陆窗口。
|
||
"""
|
||
|
||
import math
|
||
import sys
|
||
from datetime import datetime, timedelta, timezone
|
||
|
||
MOON_PERIOD = timedelta(days=27.32166) # 月球自转周期
|
||
TRANSIT_DAYS = timedelta(days=5) # 地月转移耗时
|
||
|
||
# 参考着陆窗口(UTC)—— 发射窗口 = 着陆窗口 − 5 天地月转移
|
||
# A窗: 2025-05-14 04:09 着陆 校准 (k=8)
|
||
# B窗: 2025-03-15 02:22 着陆 校准 (k=5)
|
||
REF_LANDING_A = datetime(2024, 10, 7, 14, 23, tzinfo=timezone.utc)
|
||
REF_LANDING_B = datetime(2024, 10, 29, 11, 46, tzinfo=timezone.utc)
|
||
REF_LAUNCH_A = REF_LANDING_A - TRANSIT_DAYS
|
||
REF_LAUNCH_B = REF_LANDING_B - TRANSIT_DAYS
|
||
|
||
|
||
def parse_dt(s: str) -> datetime:
|
||
"""解析用户输入的日期时间字符串,自动补全缺失部分。"""
|
||
s = s.strip()
|
||
formats = [
|
||
"%Y-%m-%d %H:%M",
|
||
"%Y-%m-%d %H",
|
||
"%Y-%m-%d",
|
||
"%Y/%m/%d %H:%M",
|
||
"%Y/%m/%d %H",
|
||
"%Y/%m/%d",
|
||
]
|
||
for fmt in formats:
|
||
try:
|
||
return datetime.strptime(s, fmt).replace(tzinfo=timezone.utc)
|
||
except ValueError:
|
||
continue
|
||
raise ValueError(f"无法解析日期: {s}(支持格式: YYYY-MM-DD HH:MM 或 YYYY/MM/DD)")
|
||
|
||
|
||
def next_occurrences(ref: datetime, period: timedelta, after: datetime, count: int = 3):
|
||
"""计算从 after 之后最近的 count 个窗口(以 ref 为相位零点)。"""
|
||
n = math.floor((after - ref) / period) # 已完成周期数(floor确保负数时正确)
|
||
results = []
|
||
for i in range(1, count + 1):
|
||
results.append(ref + (n + i) * period)
|
||
return results
|
||
|
||
|
||
def guanghan_windows(target: datetime, count: int = 4):
|
||
"""返回最近 count 个广寒发射/着陆窗口。
|
||
|
||
以着陆窗口为相位零点,发射窗口 = 着陆窗口 − 5 天地月转移。
|
||
"""
|
||
landing_a = next_occurrences(REF_LANDING_A, MOON_PERIOD, target, count)
|
||
landing_b = next_occurrences(REF_LANDING_B, MOON_PERIOD, target, count)
|
||
|
||
# 合并着陆窗口,去重排序
|
||
all_landings = sorted(set(landing_a + landing_b))
|
||
|
||
lines = []
|
||
lines.append(f"查询时间: {target.strftime('%Y-%m-%d %H:%M UTC')}")
|
||
lines.append(f"参考着陆窗 A: {REF_LANDING_A.strftime('%Y-%m-%d %H:%M UTC')}")
|
||
lines.append(f"参考着陆窗 B: {REF_LANDING_B.strftime('%Y-%m-%d %H:%M UTC')}")
|
||
lines.append(f"月球周期: {MOON_PERIOD.days}.{MOON_PERIOD.seconds/3600:.2f} 天")
|
||
lines.append(f"地月转移: 5 天")
|
||
lines.append("")
|
||
|
||
header = f"{'#':<4} {'发射 (UTC)':<22} {'着陆 (UTC)':<22} {'距现在':>10}"
|
||
lines.append(header)
|
||
lines.append("-" * len(header))
|
||
|
||
for i, landing in enumerate(all_landings[:count], 1):
|
||
launch = landing - TRANSIT_DAYS
|
||
delta = launch - target
|
||
if delta.days >= 0:
|
||
delta_str = f"{delta.days}d {delta.seconds // 3600}h"
|
||
else:
|
||
delta_str = f"已过"
|
||
|
||
lines.append(f"{i:<4} {launch.strftime('%Y-%m-%d %H:%M'):<22} {landing.strftime('%Y-%m-%d %H:%M'):<22} {delta_str:>10}")
|
||
|
||
return "\n".join(lines)
|
||
|
||
|
||
def main():
|
||
if len(sys.argv) < 2:
|
||
print("用法: python guanghan_windows.py <日期时间>")
|
||
print("示例: python guanghan_windows.py '2024-07-20'")
|
||
print(" python guanghan_windows.py '2024-08-15 12:00'")
|
||
sys.exit(1)
|
||
|
||
target = parse_dt(sys.argv[1])
|
||
print(guanghan_windows(target))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|