moonsuncalc

    Pure MoonBit port of SunCalc 2.0.1: solar and lunar observer calculations

    astronomy
    suncalc
    sunrise
    moon
    Download zip
    Author
    Version
    0.1.0
    License
    BSD-2-Clause
    Last updated
    yesterday
    Downloads
    4

    #MoonSunCalc

    SunCalc 2.0.1 的纯 MoonBit 移植。输入 UTC 时刻和地球观测点,计算太阳、月亮的位置、升落时刻和月相;不依赖 JavaScript 运行时、网络星历或系统时钟。

    适合摄影时刻表、太阳路径图、月亮观察工具。它不是气象预报或高精度导航星历。采用 SunCalc v2 约定:角度单位为度,方位角从正北顺时针增加;不是 v1 的弧度接口。

    #在本地运行

    需要 MoonBit 工具链;对照测试另需 Node.js 22+、Python 3.8+。本地验证版本与误差见 质量报告。模块名为 Zh666-lab/moonsuncalc,版本 0.1.0。通过 MoonCakes 安装:

    moon add Zh666-lab/moonsuncalc@0.1.0

    moon check --target all --deny-warn moon test --target wasm-gc moon run examples/photography --target wasm-gc moon run examples/solar_track --target wasm-gc moon run examples/moon_watch --target wasm-gc

    在本模块的消费包 moon.pkg 中导入:

    import { "Zh666-lab/moonsuncalc" @sun }

    let date = @sun.utc(2026, 9, 14).unwrap()
    let site = @sun.observer(31.23, 121.47).unwrap()
    let times = @sun.sun_times(date, site)
    match times.event("sunrise") {
    Some(time) => println(time.to_iso_utc())
    None => println("该太阳日没有日出事件")
    }

    这里的 unwrap 只用于写死的合法输入。外部输入应先处理构造函数返回的 Result。完整程序见三个示例目录,各有说明和回归测试。

    #三个示例

    示例输入与处理可检查的输出
    摄影时刻表上海、2026-09-14、20 m 地平线高度,加一个 -4° 自定义事件日出 2026-09-13T21:36:43.120Z;北极圈案例返回极昼而不是无效日期
    太阳轨迹上海 UTC 日内每 10 分钟批量采样,144 点CSV;样本最高点 03:50Z,视高度约 62.2024°;与求解中天区分
    月亮观察上海 2026-09-14T10:00Z,计算位置、照明及升落视高度约 16.6636°、照明比例约 11.9448%;包含上游极区问题回归

    输出均为 UTC。上海民用时间需加 8 小时;月亮升落按 UTC 日扫描,不能把它直接当成当地午夜到午夜的结果。

    #API 与边界

    • instantutcparse_utc:校验 1900—2150 年 UTC 输入;to_iso_utc 输出完整毫秒。
    • observer:纬度 [-90,90]、经度 [-180,180],拒绝非有限数。
    • sun_positionmoon_positionmoon_illumination:纯函数位置和月相。
    • sun_times:六组标准事件、中天、下中天;sun_times_with 加高度和自定义角度。
    • moon_times:可选月出/月落,以及全天在地平线上/下状态。
    • sample_observations:预检时间范围、1—4096 点的固定步长批量观测。

    详细语义、返回字段和移植差异见 API 文档,算法分层见 架构。不提供时区数据库、天气/地形遮挡、闰秒、卫星轨道或光伏发电量估算。

    #复现验证

    python tools/verify.py # Windows 没有 Moon 可用的系统 C 编译器时,仅显式跳过 native 执行;仍做 native 严格检查: python tools/verify.py --defer-native

    默认覆盖 wasm-gc、wasm、js、native 的检查、构建、测试、三个示例和两类对照。报告写入被 Git 忽略的 output/。持续集成配置在 .github/workflows/ci.yml;四后端已通过远端检查、构建、测试、三个示例和两类对照。历史运行和本地环境分别记录在质量报告。

    #来源和许可证

    原项目 mourner/suncalc,固定版本 2.0.1、提交 bbc91f689ede3ff7173011947d435b3fb6c0485d,BSD-2-Clause。本项目保留原版权及许可;公式、系数和主要事件算法均来自上游,不作为独创算法申报。

    测试用上游源码与归档 JPL/USNO 数据保存在 tools/upstream/,不参与库运行。来源和改动见 THIRD_PARTY.md,AI 辅助范围见 AI_USE.md

    HorizonState

    pub(all) enum HorizonState {
    Crosses
    AlwaysAbove
    AlwaysBelow
    } derive(Eq,
    Debug
    )

    Illumination

    pub(all) struct Illumination {
    fraction : Double
    phase : Double
    angle : Double
    waxing : Bool
    } derive(Eq,
    Debug
    )

    InputError

    pub(all) enum InputError {
    InvalidInstant
    InvalidDate
    InvalidLatitude
    InvalidLongitude
    InvalidHeight
    InvalidAngle
    InvalidSamplingStep
    InvalidSampleCount
    TooManyEvents
    } derive(Eq,
    Debug
    )

    Structured input failures; no NaN/Infinity is accepted at the public boundary.

    Instant

    pub struct Instant {
    // private fields
    } derive(Eq,
    Debug
    )

    UTC Unix milliseconds, whole milliseconds, supported years 1900 through 2150.

    Instant::julian_day

    fn Instant::julian_day(self : Instant) -> Double

    Julian date on the UTC scale (not TT).

    Instant::to_iso_utc

    fn Instant::to_iso_utc(self : Instant) -> String

    Always YYYY-MM-DDTHH:MM:SS.sssZ; no machine-local timezone conversion.

    Instant::to_utc

    fn Instant::to_utc(self : Instant) -> UtcDateTime

    UTC breakdown. Event solvers can return neighbouring days outside the input domain.

    Instant::unix_ms

    fn Instant::unix_ms(self : Instant) -> Double

    Instant::utc_midnight

    fn Instant::utc_midnight(self : Instant) -> Instant

    The UTC calendar day containing the instant; not an observer's civil timezone.

    MoonPosition

    pub(all) struct MoonPosition {
    azimuth : Double
    altitude : Double
    distance : Double
    parallactic_angle : Double
    } derive(Eq,
    Debug
    )

    MoonTimes

    pub(all) struct MoonTimes {
    rise : Instant?
    set : Instant?
    state : HorizonState
    } derive(Eq,
    Debug
    )

    Observation

    pub(all) struct Observation {
    time : Instant
    sun : Position
    moon : MoonPosition
    illumination : Illumination
    } derive(Eq,
    Debug
    )

    One observation at an explicit UTC instant; no clock or I/O is consulted.

    Observer

    pub struct Observer {
    // private fields
    } derive(Eq,
    Debug
    )

    Earth latitude north-positive and longitude east-positive in degrees.

    Observer::latitude

    fn Observer::latitude(self : Observer) -> Double

    Observer::longitude

    fn Observer::longitude(self : Observer) -> Double

    Position

    pub(all) struct Position {
    azimuth : Double
    altitude : Double
    } derive(Eq,
    Debug
    )

    Apparent altitude and azimuth in degrees. Azimuth: north=0, east=90.

    SolarAngle

    pub struct SolarAngle {
    // private fields
    } derive(Eq,
    Debug
    )

    SolarConfig

    pub struct SolarConfig {
    // private fields
    } derive(Eq,
    Debug
    )

    Config is immutable to callers; constructor copies the extra-angle array.

    SolarEvent

    pub(all) struct SolarEvent {
    angle : Double
    rise_name : String
    set_name : String
    rise : Instant?
    set : Instant?
    state : HorizonState
    } derive(Eq,
    Debug
    )

    SunTimes

    pub(all) struct SunTimes {
    solar_noon : Instant
    nadir : Instant
    events : Array[SolarEvent]
    state : HorizonState
    } derive(Eq,
    Debug
    )

    SunTimes::event

    fn SunTimes::event(self : SunTimes, name : String) -> Instant?

    Lookup a standard or configured event by its case-sensitive upstream name.

    UtcDateTime

    pub(all) struct UtcDateTime {
    year : Int
    month : Int
    day : Int
    hour : Int
    minute : Int
    second : Int
    millisecond : Int
    } derive(Eq,
    Debug
    )

    Gregorian UTC components. Not a timezone or leap-second model.

    instant

    fn instant(unix_ms : Double) -> Result[Instant, InputError]

    moon_illumination

    fn moon_illumination(date : Instant) -> Illumination

    Deterministic: an explicit instant is required, never the host's current date. Fraction measures lit area; phase runs new=0, first quarter=.25, full=.5.

    moon_position

    fn moon_position(date : Instant, site : Observer) -> MoonPosition

    Topocentric apparent Moon position; distance is geocentric kilometres.

    moon_times

    fn moon_times(date : Instant, site : Observer) -> MoonTimes

    Scan the UTC calendar day containing date, independent of host timezone. One-sided days keep the missing event as None. Does not accept a local civil day.

    observer

    fn observer(latitude_deg : Double, longitude_deg : Double) -> Result[Observer, InputError]

    parse_utc

    fn parse_utc(s : String) -> Result[Instant, InputError]

    Strict UTC forms YYYY-MM-DDTHH:MM:SSZ and YYYY-MM-DDTHH:MM:SS.sssZ only.

    sample_observations

    fn sample_observations(start : Instant, step_ms : Double, count : Int, site : Observer) -> Result[Array[Observation], InputError]

    Fixed-step, bounded sampling. All timestamps are validated before allocation. Step must be a positive whole number of milliseconds; count is 1..4096.

    solar_angle

    fn solar_angle(angle : Double, rise_name : String, set_name : String) -> Result[SolarAngle, InputError]

    A geometric solar-centre angle, not the refracted altitude of sun_position.

    solar_config

    fn solar_config(height_m : Double, extra : Array[SolarAngle]) -> Result[SolarConfig, InputError]

    Height above the visible horizon, 0..10000 m; up to 32 extra angle pairs. Duplicate/reserved names are rejected instead of overwriting standard results.

    sun_position

    fn sun_position(date : Instant, site : Observer) -> Position

    SunCalc v2 getPosition. Apparent refracted altitude; degrees, north clockwise.

    sun_times

    fn sun_times(date : Instant, site : Observer) -> SunTimes

    Six standard pairs at sea-level horizon.

    sun_times_with

    fn sun_times_with(date : Instant, site : Observer, config : SolarConfig) -> SunTimes

    Six standard event pairs. Missing polar events are None, never invalid dates.

    utc

    fn utc(year : Int, month : Int, day : Int, hour? : Int, minute? : Int, second? : Int, millisecond? : Int) -> Result[Instant, InputError]

    Construct a UTC instant. Rejects nonexistent dates, leap seconds, and rollover.