16c62a97d5
- 新增时标、ΔT 模型、质心时间与 UT1 支持 - 改进日月食、月掩、行星事件及路径边界计算 - 完善恒星三维自行与动态距离传播 - 扩展 SVG、GeoJSON、KML 输出与底层距离换算工具 - 整理中英文手册、示例资源及回归测试
1058 lines
50 KiB
Markdown
1058 lines
50 KiB
Markdown
# Sun and Moon
|
||
|
||
[中文](../sun-moon.md) | [Back to README](../../../README.en.md)
|
||
|
||
`sun` and `moon` are the main chains; `lite/sun` and `lite/moon` are independent approximation implementations.
|
||
|
||
Unless stated otherwise, angles are in degrees, apparent diameters and semidiameters are in arcseconds, `sun.EarthDistance` is in AU and `moon.EarthDistance` is in kilometers. Observing APIs generally use civil instants.
|
||
|
||
Apparent-solar-time results instead represent local solar readings; see [Time scales](timescale.md).
|
||
|
||
This manual covers the Sun and the Moon themselves: position, rise/set and culmination, topocentric quantities, phases and syzygies, perigee/apogee and nodes, maximum declinations, libration, apparent size, and physical ephemeris.
|
||
|
||
Eclipse geometry lives in [Solar and Lunar Eclipse Charts](eclipse.md#solar-and-lunar-eclipse-charts), occultations in [Lunar Occultation Charts](occultation.md#lunar-occultation-charts), and general topocentric and refraction conversions in [Coordinate Tools](coord.md).
|
||
|
||
## Contents
|
||
|
||
- [Sunrise, sunset and lunar phase](#sunrise-sunset-and-lunar-phase)
|
||
- [API Reference](#api-reference)
|
||
- [sun](#sun)
|
||
- [moon](#moon)
|
||
- [lite/sun](#litesun)
|
||
- [lite/moon](#litemoon)
|
||
- [Truncated ...N family](#truncated-n-family)
|
||
- [Usage examples](#usage-examples)
|
||
- [Today's sunrise, sunset and twilight](#todays-sunrise-sunset-and-twilight)
|
||
- [Moonrise, moonset and the Moon's altitude now](#moonrise-moonset-and-the-moons-altitude-now)
|
||
- [Lunar phase and the next new / full moon](#lunar-phase-and-the-next-new--full-moon)
|
||
- [Apparent size, Earth-Moon distance and libration](#apparent-size-earth-moon-distance-and-libration)
|
||
- [Geocentric vs topocentric (getting the Moon position right)](#geocentric-vs-topocentric-getting-the-moon-position-right)
|
||
- [Main chain vs lite](#main-chain-vs-lite)
|
||
- [Observing-angle semantics](#observing-angle-semantics)
|
||
- [Combined example: rise, set, and position](#combined-example-rise-set-and-position)
|
||
- [Sunrise/sunset and moonrise/moonset](#sunrisesunset-and-moonrisemoonset)
|
||
- [Sun and Moon position](#sun-and-moon-position)
|
||
- [The Sun](#the-sun)
|
||
- [Position](#position)
|
||
- [Rise, set and culmination](#rise-set-and-culmination)
|
||
- [Topocentric quantities and parallactic angle](#topocentric-quantities-and-parallactic-angle)
|
||
- [Apparent solar time and equation of time](#apparent-solar-time-and-equation-of-time)
|
||
- [Physical ephemeris and apparent size](#physical-ephemeris-and-apparent-size)
|
||
- [Earth orbit extrema](#earth-orbit-extrema)
|
||
- [The Moon](#the-moon)
|
||
- [Position](#position-1)
|
||
- [Rise, set and culmination](#rise-set-and-culmination-1)
|
||
- [Lunar phases](#lunar-phases)
|
||
- [Perigee and apogee](#perigee-and-apogee)
|
||
- [Nodes](#nodes)
|
||
- [Maximum declinations](#maximum-declinations)
|
||
- [Libration and bright-limb position angle](#libration-and-bright-limb-position-angle)
|
||
- [Apparent size and Earth-Moon distance](#apparent-size-and-earth-moon-distance)
|
||
- [Lite chains](#lite-chains)
|
||
- [lite/sun](#litesun-1)
|
||
- [lite/moon](#litemoon-1)
|
||
- [Differences from the main chain and error levels](#differences-from-the-main-chain-and-error-levels)
|
||
- [Parameter and result conventions](#parameter-and-result-conventions)
|
||
- [Units and angle conventions](#units-and-angle-conventions)
|
||
- [Time scale](#time-scale)
|
||
- [Height and aero](#height-and-aero)
|
||
- [Zero values and out-of-range](#zero-values-and-out-of-range)
|
||
- [Accuracy and scope](#accuracy-and-scope)
|
||
|
||
## Sunrise, sunset and lunar phase
|
||
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"log"
|
||
"time"
|
||
|
||
"b612.me/astro/moon"
|
||
"b612.me/astro/sun"
|
||
)
|
||
|
||
func main() {
|
||
cst := time.FixedZone("CST", 8*3600)
|
||
date := time.Date(2020, 1, 1, 8, 8, 8, 0, cst)
|
||
lon, lat, height := 108.93, 34.27, 0.0
|
||
rise, err := sun.RiseTime(date, lon, lat, height, true) // sunrise time
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
set, err := sun.SetTime(date, lon, lat, height, true) // sunset time
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
fmt.Println(rise.Format(time.RFC3339), set.Format(time.RFC3339))
|
||
fmt.Println(moon.Phase(date), moon.PhaseDesc(date)) // lunar phase (illuminated fraction) and phase description
|
||
}
|
||
```
|
||
|
||
`aero=true` uses a rise/set criterion with refraction. Rise/set functions return an error for polar conditions or a missing event on that date. `moon.Phase` is the illuminated fraction, not the lunar age.
|
||
|
||
## API Reference
|
||
|
||
The tables below group the exported entry points of all four packages. Most evaluation entry points also have a `...N` truncated variant, described together under [Truncated ...N family](#truncated-n-family).
|
||
|
||
Every `time.Time` parameter is an absolute instant, and `lon`/`lat` are east-positive and north-positive.
|
||
|
||
### sun
|
||
|
||
| Name | Purpose | Unit and convention |
|
||
| --- | --- | --- |
|
||
| `TrueLo` / `ApparentLo` | True / apparent solar longitude | degrees, geocentric |
|
||
| `TrueBo` | True solar latitude | degrees, geocentric; no separate apparent latitude |
|
||
| `GeometricLo` / `MidFunc` | Geometric solar longitude / equation of center | degrees |
|
||
| `ApparentRa` / `ApparentDec` / `ApparentRaDec` | Apparent right ascension / declination | degrees, geocentric |
|
||
| `EclipticObliquity` | Obliquity of the ecliptic | degrees; the second argument adds nutation in obliquity when `true` |
|
||
| `EclipticNutation` / `EclipticNutation1980` | Nutation in longitude | degrees, IAU 2000B / IAU 1980 |
|
||
| `AxialtiltNutation` / `AxialtiltNutation1980` | Nutation in obliquity | degrees, IAU 2000B / IAU 1980 |
|
||
| `RiseTime` / `SetTime` | Sunrise / sunset | `(time.Time, error)`; `aero` and `height` are described under Parameter and result conventions |
|
||
| `DownTime` | Sunset alias | deprecated; calls `SetTime` internally |
|
||
| `CulminationTime` | Upper culmination | `time.Time` |
|
||
| `MorningTwilight` / `EveningTwilight` | Morning / evening twilight | `(time.Time, error)`; the angle is usually -6 / -12 / -18 degrees |
|
||
| `Altitude` / `Zenith` / `Azimuth` / `HourAngle` | Geometric altitude / zenith distance / azimuth / hour angle | degrees, topocentric |
|
||
| `ApparentAltitude` / `ApparentZenith` | Apparent altitude / apparent zenith distance | degrees; requires pressure in hPa and temperature in degrees Celsius |
|
||
| `ParallacticAngle` | Parallactic angle (zenith direction angle) | degrees, signed |
|
||
| `ApparentSolarTime` | Apparent solar time | `time.Time`, with the zone derived from longitude |
|
||
| `EquationTime` | Equation of time | hours |
|
||
| `Diameter` / `Semidiameter` | Apparent diameter / semidiameter | arcseconds |
|
||
| `EarthDistance` | Earth-Sun distance | AU |
|
||
| `Physical` | Solar disk physical quantities | returns `PhysicalInfo`, fields in degrees |
|
||
|
||
### moon
|
||
|
||
| Name | Purpose | Unit and convention |
|
||
| --- | --- | --- |
|
||
| `TrueLo` / `TrueBo` / `ApparentLo` | Geocentric true longitude / true latitude / apparent longitude | degrees |
|
||
| `TrueRa` / `TrueDec` / `TrueRaDec` | Geocentric true equatorial coordinates | degrees |
|
||
| `GeocentricApparentRa` / `GeocentricApparentDec` / `GeocentricApparentRaDec` | Geocentric apparent equatorial coordinates | degrees |
|
||
| `ApparentRa` / `ApparentDec` / `ApparentRaDec` | Topocentric apparent equatorial coordinates | degrees; requires observer longitude and latitude |
|
||
| `Altitude` / `Zenith` / `Azimuth` / `HourAngle` | Topocentric altitude / zenith distance / azimuth / hour angle | degrees |
|
||
| `ApparentAltitude` / `ApparentZenith` | Apparent altitude / apparent zenith distance | degrees; requires pressure in hPa and temperature in degrees Celsius |
|
||
| `ParallacticAngle` | Parallactic angle | degrees, signed, explicitly depends on observer longitude and latitude |
|
||
| `RiseTime` / `SetTime` | Moonrise / moonset | `(time.Time, error)` |
|
||
| `DownTime` | Moonset alias | deprecated; calls `SetTime` internally |
|
||
| `CulminationTime` | Upper culmination | `time.Time`; requires longitude and latitude |
|
||
| `Phase` / `PhaseDesc` | Illuminated fraction / Chinese textual phase | fraction `[0,1]` / string |
|
||
| `SunMoonLoDiff` | Apparent Moon-Sun longitude difference | degrees, `[0,360)` |
|
||
| `ShuoYue` / `ShangXianYue` / `WangYue` / `XiaXianYue` | New / first-quarter / full / last-quarter moon solved near a decimal-year anchor | `time.Time`, UTC |
|
||
| `NewMoon` / `FullMoon` / `FirstQuarter` / `LastQuarter` | English aliases for the four phases above | `time.Time`, UTC |
|
||
| `Next*` / `Last*` / `Closest*` | Next / previous / closest phase and maximum-declination events | `time.Time`; results keep the input time zone |
|
||
| `NextConjunctionWithPlanet` / `LastConjunctionWithPlanet` / `ClosestConjunctionWithPlanet` | Moon-planet conjunction (in right ascension) | `time.Time`; the target is a `ConjunctionPlanet` constant |
|
||
| `PerigeesInMonth` / `ApogeesInMonth` | All perigees / apogees in a Gregorian month | `[]ApsisInfo`, distance in km |
|
||
| `MaximumNorthDeclinationsInMonth` / `MaximumSouthDeclinationsInMonth` | All maximum northern / southern declination events in a month | `[]MaximumDeclinationInfo`, declination in degrees |
|
||
| `AscendingNode` / `DescendingNode` | Ascending / descending node longitude | degrees |
|
||
| `Physical` / `TopocentricPhysical` | Geocentric / topocentric libration and rotation-axis position angle | returns `PhysicalInfo`, fields in degrees |
|
||
| `BrightLimbPositionAngle` / `TopocentricBrightLimbPositionAngle` | Geocentric / topocentric bright-limb position angle | degrees |
|
||
| `Diameter` / `Semidiameter` | Apparent diameter / semidiameter | arcseconds |
|
||
| `EarthDistance` | Earth-Moon distance | kilometers |
|
||
|
||
The `moon` package also exposes occultation APIs. Their parameters, results and examples are documented under [Lunar occultations](occultation.md).
|
||
|
||
| Group | Exports |
|
||
| --- | --- |
|
||
| Events and paths | `FindStarOccultations`, `FindPlanetOccultations`, `FindBestStarOccultations`, `FindBestPlanetOccultations`, `FindStarOccultationPaths`, `FindPlanetOccultationPaths` |
|
||
| Instant footprints and disk geometry | `StarOccultationFootprintAt`, `PlanetOccultationFootprintsAt`, `StarOccultationDiagram`, `PlanetOccultationDiagram` |
|
||
| UT1 label conversion | `StarOccultationInfoInUT1`, `StarOccultationPathInUT1`, `PlanetOccultationInfoInUT1`, `PlanetOccultationPathInUT1` |
|
||
| Stellar results | `StarOccultationInfo`, `StarOccultationPath`, `StarOccultationInstant` |
|
||
| Planetary results | `PlanetOccultationInfo`, `PlanetOccultationPath`, `PlanetOccultationInstant`, `PlanetOccultationFootprint` |
|
||
| Path data | `OccultationFootprint`, `OccultationPathPoint`, `OccultationGreatestTimeContour`, `OccultationRiseSetCurve` |
|
||
| Search and path options | `OccultationSearchOptions`, `OccultationPathOptions`, `OccultationPathAlgorithm` |
|
||
| Disk diagram data | `StarOccultationDiagramFrame`, `StarOccultationDiagramOptions`, `StarOccultationDiagramResult`, `PlanetOccultationDiagramFrame`, `PlanetOccultationDiagramOptions`, `PlanetOccultationDiagramResult` |
|
||
| Targets and coordinates | `StarData`, `StarCoordinate`, `StarCoordinateFromStarData`, `Observer`, `CoordinateFrame`, `CoordinateFrameICRS`, `CoordinateFrameJ2000`, `CoordinateFrameApparentOfDate` |
|
||
| Event types | `OccultationPlanet`, `OccultationType`, `OccultationTotal`, `OccultationPartial`, `OccultationGrazing` |
|
||
| Path algorithms | `OccultationPathAlgorithmOptimized`, `OccultationPathAlgorithmExact` |
|
||
| Rise/set phases | `RiseSetPhase`, `RiseSetDirection`, `RiseSetPhaseStart`, `RiseSetPhaseGreatest`, `RiseSetPhaseEnd`, `RiseSetDirectionRise`, `RiseSetDirectionSet` |
|
||
| Errors | `ErrInvalidOccultationInput`, `ErrOccultationPathSamplingLimit` |
|
||
|
||
Planet targets use constants `OccultationMercury` through `OccultationNeptune`.
|
||
|
||
### lite/sun
|
||
|
||
| Name | Purpose | Unit and convention |
|
||
| --- | --- | --- |
|
||
| `TrueLo` / `ApparentLo` | Lightweight true / apparent longitude | degrees, geocentric |
|
||
| `TrueRa` / `TrueDec` / `TrueRaDec` | Lightweight true equatorial coordinates | degrees, geocentric |
|
||
| `ApparentRa` / `ApparentDec` / `ApparentRaDec` | Lightweight apparent equatorial coordinates | degrees, geocentric |
|
||
| `Distance` | Lightweight Earth-Sun distance | AU |
|
||
| `HourAngle` / `Azimuth` / `Altitude` / `Zenith` | Lightweight hour angle / azimuth / altitude / zenith distance | degrees, topocentric |
|
||
| `RiseTime` / `SetTime` | Lightweight sunrise / sunset | `(time.Time, error)` |
|
||
| `ERR_SUN_NEVER_RISE` / `ERR_SUN_NEVER_SET` | Polar night / polar day | error values |
|
||
|
||
### lite/moon
|
||
|
||
| Name | Purpose | Unit and convention |
|
||
| --- | --- | --- |
|
||
| `TrueLo` / `TrueBo` | Lightweight geocentric true longitude / latitude | degrees |
|
||
| `TrueRa` / `TrueDec` / `TrueRaDec` | Lightweight geocentric true equatorial coordinates | degrees |
|
||
| `ApparentRa` / `ApparentDec` / `ApparentRaDec` | Lightweight topocentric apparent equatorial coordinates | degrees; requires observer longitude and latitude |
|
||
| `HourAngle` / `Azimuth` / `Altitude` / `Zenith` | Lightweight hour angle / azimuth / altitude / zenith distance | degrees, topocentric |
|
||
| `SunMoonLoDiff` / `Phase` / `PhaseAge` | Lightweight Moon-Sun longitude difference / illuminated fraction / lunar age | degrees / `[0,1]` / days |
|
||
| `RiseTime` / `SetTime` | Lightweight moonrise / moonset | `(time.Time, error)` |
|
||
| `ERR_MOON_NEVER_RISE` / `ERR_MOON_NEVER_SET` / `ERR_NOT_TODAY` | Polar night / polar day / event not on the queried date | error values |
|
||
|
||
### Truncated ...N family
|
||
|
||
Most evaluation entry points of `sun` and `moon` have a `...N` variant: the name is the base name plus `N`, one extra `n int` parameter is appended, and the return shape is unchanged.
|
||
|
||
`n < 0` keeps every analytical term embedded in this repository and matches the non-`N` version; `n >= 0` truncates the series to `n` terms, which is useful for performance comparison, bulk coarse evaluation, and error-sensitivity experiments.
|
||
|
||
- sun: `TrueLoN`, `TrueBoN`, `AltitudeN`, `ZenithN`, `AzimuthN`, `HourAngleN`, `ParallacticAngleN`, `ApparentAltitudeN`, `ApparentZenithN`, `DiameterN`, `SemidiameterN`, `PhysicalN`, `RiseTimeN`, `SetTimeN`, `DownTimeN`, `CulminationTimeN`, `MorningTwilightN`, `EveningTwilightN`, `ApparentSolarTimeN`
|
||
- moon: `TrueLoN`, `TrueBoN`, `AscendingNodeN`, `DescendingNodeN`, `DiameterN`, `SemidiameterN`, `PhysicalN`, `TopocentricPhysicalN`, `BrightLimbPositionAngleN`, `TopocentricBrightLimbPositionAngleN`
|
||
|
||
The topocentric equatorial coordinates (`ApparentRa` / `ApparentDec` / `ApparentRaDec`), the lunar rise/set entry points (`RiseTime` / `SetTime`), and the phase family have no `N` variant and are not controlled by the truncation switch.
|
||
|
||
## Usage examples
|
||
|
||
### Today's sunrise, sunset and twilight
|
||
|
||
```go
|
||
fmt.Println(sun.MorningTwilight(date, lon, lat, -6)) // civil dawn
|
||
fmt.Println(sun.RiseTime(date, lon, lat, height, true))
|
||
fmt.Println(sun.SetTime(date, lon, lat, height, true))
|
||
fmt.Println(sun.EveningTwilight(date, lon, lat, -6)) // civil dusk
|
||
```
|
||
|
||
```text
|
||
2020-01-01 07:22:28.138198256 +0800 CST <nil>
|
||
2020-01-01 07:49:52.591398954 +0800 CST <nil>
|
||
2020-01-01 17:45:09.366609156 +0800 CST <nil>
|
||
2020-01-01 18:12:33.801986575 +0800 CST <nil>
|
||
```
|
||
|
||
Passing `-12` / `-18` instead selects nautical and astronomical twilight. With `aero = true` the rise/set solution uses the horizon corrected for refraction and apparent radius; the difference from the geometric horizon is described under [Rise, set and culmination](#rise-set-and-culmination).
|
||
|
||
### Moonrise, moonset and the Moon's altitude now
|
||
|
||
```go
|
||
rise, _ := moon.RiseTime(date, lon, lat, height, true)
|
||
set, _ := moon.SetTime(date, lon, lat, height, true)
|
||
fmt.Println(rise)
|
||
fmt.Println(set)
|
||
fmt.Println(moon.Altitude(date, lon, lat), moon.Azimuth(date, lon, lat))
|
||
```
|
||
|
||
```text
|
||
2020-01-01 11:52:50.042243599 +0800 CST
|
||
2020-01-01 23:26:49.498263895 +0800 CST
|
||
-45.349728852972675 67.63824603392399
|
||
```
|
||
|
||
Lunar rise and set are computed for the local civil day and the pair is not necessarily continuous, so the full cycle after `date` follows from the order of the rise and set instants; see [Sunrise/sunset and moonrise/moonset](#sunrisesunset-and-moonrisemoonset) for the details. A negative altitude means the Moon is below the horizon, so `-45.35` degrees here means it is not visible.
|
||
|
||
### Lunar phase and the next new / full moon
|
||
|
||
```go
|
||
fmt.Println(moon.Phase(date), moon.PhaseDesc(date)) // illuminated fraction and phase name
|
||
fmt.Println(moon.NextShuoYue(date)) // next new moon
|
||
fmt.Println(moon.NextWangYue(date)) // next full moon
|
||
```
|
||
|
||
```text
|
||
0.30004130960877884 上峨眉月
|
||
2020-01-25 05:41:58.271192908 +0800 CST
|
||
2020-01-11 03:21:17.159625291 +0800 CST
|
||
```
|
||
|
||
`Next*` / `Last*` / `Closest*` are the next / previous / closest search conventions; first and last quarter are `moon.NextShangXianYue` / `moon.NextXiaXianYue`. Results keep the input time zone; the full conventions for the four phases and the synodic month are under [Lunar phases](#lunar-phases).
|
||
|
||
### Apparent size, Earth-Moon distance and libration
|
||
|
||
```go
|
||
fmt.Println(moon.Diameter(date), moon.EarthDistance(date))
|
||
p := moon.Physical(date)
|
||
fmt.Println(p.LibrationLongitude, p.LibrationLatitude, p.PositionAngle)
|
||
```
|
||
|
||
```text
|
||
1774.6658461637385 404238.6096080479
|
||
0.7655535663486027 6.382898400777244 -23.672356410246774
|
||
```
|
||
|
||
`Diameter` is in arcseconds and `EarthDistance` in kilometers; `1774.67` arcseconds (about 29.6 arcminutes) corresponds to roughly 404,000 km near apogee, about 5% smaller than the mean apparent diameter.
|
||
|
||
`Physical` gives the geocentric libration; the topocentric counterpart `TopocentricPhysical` and the field conventions are under [Libration and bright-limb position angle](#libration-and-bright-limb-position-angle).
|
||
|
||
### Geocentric vs topocentric (getting the Moon position right)
|
||
|
||
```go
|
||
geoRa, geoDec := moon.GeocentricApparentRaDec(date) // geocentric apparent position
|
||
topRa, topDec := moon.ApparentRaDec(date, lon, lat) // topocentric apparent position
|
||
fmt.Printf("geocentric %.4f %.4f\n", geoRa, geoDec)
|
||
fmt.Printf("topocentric %.4f %.4f\n", topRa, topDec)
|
||
fmt.Printf("delta dRA=%.4f dDec=%.4f\n", topRa-geoRa, topDec-geoDec)
|
||
fmt.Println(sun.ApparentRaDec(date)) // the Sun only exposes geocentric coordinates
|
||
```
|
||
|
||
```text
|
||
geocentric 349.2322 -9.9506
|
||
topocentric 349.7343 -10.3485
|
||
delta dRA=0.5021 dDec=-0.3978
|
||
280.8950939694744 -23.05840775453492
|
||
```
|
||
|
||
The Moon is close, so geocentric and topocentric positions differ by half a degree (here `0.50` degrees in right ascension and `0.40` in declination), together more than one lunar apparent diameter; anything an observer sees must go through `moon.ApparentRaDec`. The Sun, 1 AU away, has negligible parallax and is exposed geocentrically only (of course, you can also call the topocentric coordinate conversion entry points to get a topocentric position =-=). The conventions are in [Observing-angle semantics](#observing-angle-semantics).
|
||
|
||
### Main chain vs lite
|
||
|
||
```go
|
||
fmt.Println(sun.Altitude(date, lon, lat), litesun.Altitude(date, lon, lat)) // main / lightweight geometric altitude
|
||
fmt.Println(moon.Phase(date), litemoon.Phase(date)) // illuminated fraction
|
||
fmt.Println(litemoon.PhaseAge(date)) // lightweight lunar age (days)
|
||
fmt.Println(litesun.RiseTime(date, lon, lat, height, true)) // lightweight sunrise
|
||
```
|
||
|
||
```text
|
||
2.40091496867759 2.403576774819768
|
||
0.30004130960877884 0.2978124633132848
|
||
5.42608394367707
|
||
2020-01-01 07:49:51.69717729 +0800 CST <nil>
|
||
```
|
||
|
||
The lightweight chains mirror the main-chain shapes but not its accuracy: at this instant the altitude differs by about `0.0027` degrees and the phase by `0.0022`.
|
||
|
||
Over 2026 at 8 sites the mean absolute error of `lite/sun` sunrise is `0.02 min` (P95 `0.04 min`, max `0.31 min`), of `lite/moon` moonrise `0.28 min` (P95 `0.57 min`, max `1.44 min`), and `lite/moon` `Phase()` peaks at `0.00243`.
|
||
|
||
The full comparison and the truncation errors are under [Differences from the main chain and error levels](#differences-from-the-main-chain-and-error-levels) and in [Lite chains](accuracy.md#lite-lightweight-chains).
|
||
|
||
## Observing-angle semantics
|
||
|
||
- `Altitude`: altitude angle; horizon is `0°`, zenith is `+90°`
|
||
- `Zenith`: zenith distance; zenith is `0°`, horizon is `90°`
|
||
- `Zenith` and `Altitude` are complements; the two add up to `90°`
|
||
- `Azimuth`: azimuth, measured from north (`0°`) toward east, in `[0°, 360°)`
|
||
- `HourAngle`: hour angle, `0°` at upper culmination and increasing westward (afternoon), normalized to `[0°, 360°)`
|
||
- `ParallacticAngle`: parallactic angle (zenith direction angle), signed, in degrees; the shared sign convention and topocentric geometry live in [Coordinate Tools](coord.md)
|
||
- `Altitude` / `Azimuth` are geometric center altitude and azimuth without refraction or semidiameter correction; `ApparentAltitude` / `ApparentZenith` add atmospheric refraction and need pressure (hPa) and temperature (degrees Celsius)
|
||
- Solar equatorial coordinates are geocentric; for the Moon, `TrueRaDec` and `GeocentricApparentRaDec` are geocentric while `ApparentRa` / `ApparentDec` / `ApparentRaDec` are topocentric and require observer longitude and latitude
|
||
|
||
## Combined example: rise, set, and position
|
||
|
||
The two complete examples below cover the most common entry points. Their shared setup is Xi'an (`108.93°E, 34.27°N`) and `2020-01-01 08:08:08 CST`.
|
||
|
||
The later snippets omit shared variables and keep only the statements relevant to their capability.
|
||
|
||
### Sunrise/sunset and moonrise/moonset
|
||
|
||
> ⚠️ Moon rise/set times are computed for the queried civil date, so the rise and set instants need not be continuous.
|
||
>
|
||
> For example, the Moon may set at 01:00 and rise again at noon, in which case the rise time is later than the set time; the evening moonset in that scenario corresponds to the next day's date.
|
||
>
|
||
> The full rise/set cycle follows from the order of the two instants: check whether the rise time falls after the set time to pick the correct subsequent instants.
|
||
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"time"
|
||
|
||
"b612.me/astro/moon"
|
||
"b612.me/astro/sun"
|
||
)
|
||
|
||
func main() {
|
||
// Xi'an, China. Longitude east and latitude north are positive; elevation is 0 m.
|
||
var lon, lat, height float64 = 108.93, 34.27, 0
|
||
cst := time.FixedZone("CST", 8*3600)
|
||
// All "today" semantics are based on this local civil date.
|
||
date := time.Date(2020, 1, 1, 8, 8, 8, 8, cst)
|
||
// Civil morning twilight begins when the Sun is 6 degrees below the horizon.
|
||
// Civil twilight is 6 degrees below the horizon, nautical 12, astronomical 18.
|
||
fmt.Println(sun.MorningTwilight(date, lon, lat, -6))
|
||
// Sunrise: dynamic standard refraction and instantaneous solar semidiameter, upper limb.
|
||
fmt.Println(sun.RiseTime(date, lon, lat, height, true))
|
||
// Upper culmination of the Sun in Xi'an.
|
||
fmt.Println(sun.CulminationTime(date, lon))
|
||
// Sunset: dynamic standard refraction and instantaneous solar semidiameter, upper limb.
|
||
fmt.Println(sun.SetTime(date, lon, lat, height, true))
|
||
// Civil evening twilight ends when the Sun is 6 degrees below the horizon.
|
||
fmt.Println(sun.EveningTwilight(date, lon, lat, -6))
|
||
|
||
// Moonrise: dynamic standard refraction and instantaneous lunar semidiameter, upper limb.
|
||
fmt.Println(moon.RiseTime(date, lon, lat, height, true))
|
||
// Upper culmination of the Moon in Xi'an.
|
||
fmt.Println(moon.CulminationTime(date, lon, lat))
|
||
// Moonset: dynamic standard refraction and instantaneous lunar semidiameter, upper limb.
|
||
fmt.Println(moon.SetTime(date, lon, lat, height, true))
|
||
}
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
2020-01-01 07:22:27.960488498 +0800 CST <nil>
|
||
2020-01-01 07:49:52.413689196 +0800 CST <nil>
|
||
2020-01-01 12:47:35.933117866 +0800 CST
|
||
2020-01-01 17:45:09.188657999 +0800 CST <nil>
|
||
2020-01-01 18:12:33.624035418 +0800 CST <nil>
|
||
2020-01-01 11:52:49.860912859 +0800 CST <nil>
|
||
2020-01-01 17:36:48.811488747 +0800 CST
|
||
2020-01-01 23:26:49.313553571 +0800 CST <nil>
|
||
```
|
||
|
||
### Sun and Moon position
|
||
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"time"
|
||
|
||
"b612.me/astro/moon"
|
||
"b612.me/astro/star"
|
||
"b612.me/astro/sun"
|
||
"b612.me/astro/tools"
|
||
)
|
||
|
||
func main() {
|
||
// Xi'an, China.
|
||
var lon, lat float64 = 108.93, 34.27
|
||
cst := time.FixedZone("CST", 8*3600)
|
||
// Instant of observation.
|
||
date := time.Date(2020, 1, 1, 8, 8, 8, 8, cst)
|
||
// Apparent ecliptic longitude of the Sun, in degrees.
|
||
fmt.Println(sun.ApparentLo(date))
|
||
// True obliquity of the ecliptic at this instant.
|
||
fmt.Println(sun.EclipticObliquity(date, true))
|
||
// Apparent right ascension and declination of the Sun.
|
||
ra, dec := sun.ApparentRaDec(date)
|
||
fmt.Println("RA:", tools.Format(ra/15, 1), "Dec:", tools.Format(dec, 0))
|
||
// English constellation containing the Sun.
|
||
fmt.Println(star.ConstellationEN(ra, dec, date))
|
||
// Solar azimuth, altitude, and zenith distance at Xi'an.
|
||
fmt.Println("Azimuth:", sun.Azimuth(date, lon, lat), "Altitude:", sun.Altitude(date, lon, lat), "Zenith:", sun.Zenith(date, lon, lat))
|
||
// Sun-Earth distance, in AU.
|
||
fmt.Println(sun.EarthDistance(date))
|
||
|
||
// Topocentric apparent right ascension and declination of the Moon.
|
||
ra, dec = moon.ApparentRaDec(date, lon, lat)
|
||
fmt.Println("RA:", tools.Format(ra/15, 1), "Dec:", tools.Format(dec, 0))
|
||
// English constellation containing the Moon.
|
||
fmt.Println(star.ConstellationEN(ra, dec, date))
|
||
// Lunar azimuth, altitude, and zenith distance at Xi'an.
|
||
fmt.Println("Azimuth:", moon.Azimuth(date, lon, lat), "Altitude:", moon.Altitude(date, lon, lat), "Zenith:", moon.Zenith(date, lon, lat))
|
||
// Earth-Moon distance, in km.
|
||
fmt.Println(moon.EarthDistance(date))
|
||
}
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
280.01526210031136
|
||
23.4362178391013
|
||
RA: 18h43m34.82s Dec: -23°3′30.27″
|
||
Sagittarius
|
||
Azimuth: 120.19477090015224 Altitude: 2.4014437419430097 Zenith: 87.59855625805699
|
||
0.983292937163176
|
||
RA: 23h18m56.24s Dec: -10°20′54.42″
|
||
Aquarius
|
||
Azimuth: 67.63889332004852 Altitude: -45.34916937173283 Zenith: 135.34916937173284
|
||
404238.6096080479
|
||
```
|
||
|
||
## The Sun
|
||
|
||
These snippets omit the shared setup: `cst := time.FixedZone("CST", 8*3600)`, `date := time.Date(2026, 1, 1, 12, 0, 0, 0, cst)`, and `var lon, lat float64 = 108.93, 34.27` (Xi'an); `fmt`, `time`, `sun`, and `moon` are assumed to be imported.
|
||
|
||
### Position
|
||
|
||
Solar position entry points depend only on the absolute instant, not on the observer. True ecliptic latitude comes from `TrueBo`; there is no separate apparent latitude.
|
||
|
||
The second argument of `EclipticObliquity` decides whether nutation in obliquity is added.
|
||
|
||
```go
|
||
// True and apparent solar longitude, and true latitude, in degrees.
|
||
fmt.Println(sun.TrueLo(date), sun.ApparentLo(date), sun.TrueBo(date))
|
||
// Apparent right ascension and declination of the Sun.
|
||
ra, dec := sun.ApparentRaDec(date)
|
||
fmt.Println("RA:", ra, "Dec:", dec)
|
||
fmt.Println(sun.ApparentRa(date), sun.ApparentDec(date))
|
||
// Obliquity of the ecliptic, geometric longitude, and equation of center.
|
||
fmt.Println(sun.EclipticObliquity(date, true), sun.GeometricLo(date), sun.MidFunc(date))
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
280.742671383543 280.7383965677222 0.00018280886212040676
|
||
RA: 281.6786097810291 Dec: -23.00369182413533
|
||
281.6786097810291 -23.00387403948847
|
||
23.438148552330773 280.83169623098 -0.08703499790144194
|
||
```
|
||
|
||
Every entry point in this family also has a `...N` truncated variant. The comparison below truncates at `n = 8`; with `n < 0` the result matches the non-`N` version exactly:
|
||
|
||
```go
|
||
// n<0 keeps every embedded VSOP term; n>=0 truncates the series.
|
||
fmt.Println(sun.TrueLo(date), sun.TrueLoN(date, 8))
|
||
fmt.Println(sun.Altitude(date, lon, lat), sun.AltitudeN(date, lon, lat, 8))
|
||
fmt.Println(sun.Diameter(date), sun.DiameterN(date, 8))
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
280.742671383543 280.7439900413756
|
||
31.61569462953789 31.615524473491835
|
||
1950.9979407481142 1950.9994358395434
|
||
```
|
||
|
||
### Rise, set and culmination
|
||
|
||
`RiseTime` / `SetTime` anchor on the local civil day of the time zone carried by `date` and keep the same time zone in the result. `height` is the observer elevation interpreted as ellipsoidal (geodetic) height in meters.
|
||
|
||
With `aero = true`, the **upper limb** crossing is computed with dynamic standard refraction and the instantaneous semidiameter; with `aero = false`, only the geometric center crossing is tested.
|
||
|
||
Twilight entry points take the target altitude as a parameter: civil twilight `-6°`, nautical `-12°`, astronomical `-18°`, with `MorningTwilight` and `EveningTwilight` for the two sides.
|
||
|
||
```go
|
||
// Target altitudes for civil, nautical, and astronomical morning twilight.
|
||
for _, angle := range []float64{-6, -12, -18} {
|
||
t, err := sun.MorningTwilight(date, lon, lat, angle)
|
||
fmt.Println(angle, t.Format("15:04:05"), err)
|
||
}
|
||
// Upper culmination and sunrise; err is non-nil when no event exists.
|
||
fmt.Println(sun.CulminationTime(date, lon).Format("15:04:05"))
|
||
t, err := sun.RiseTime(date, lon, lat, 0, true)
|
||
fmt.Println(t.Format("15:04:05"), err)
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
-6 07:22:47 <nil>
|
||
-12 06:51:31 <nil>
|
||
-18 06:21:00 <nil>
|
||
12:47:50
|
||
07:50:10 <nil>
|
||
```
|
||
|
||
### Topocentric quantities and parallactic angle
|
||
|
||
`Altitude` / `Zenith` / `Azimuth` / `HourAngle` follow the geometric chain without refraction; `ApparentAltitude` / `ApparentZenith` add atmospheric refraction and need pressure and temperature; `ParallacticAngle` is the signed parallactic angle. General topocentric and refraction conversions (including apparent altitude and topocentric equatorial coordinates) live in [Coordinate Tools](coord.md).
|
||
|
||
```go
|
||
fmt.Println(sun.Azimuth(date, lon, lat), sun.Altitude(date, lon, lat), sun.Zenith(date, lon, lat))
|
||
fmt.Println(sun.ApparentAltitude(date, lon, lat, 1010, 10), sun.ApparentZenith(date, lon, lat, 1010, 10))
|
||
// Hour angle and signed parallactic angle.
|
||
fmt.Println(sun.HourAngle(date, lon, lat), sun.ParallacticAngle(date, lon, lat))
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
167.09774780715728 31.61569462953789 58.38430537046211
|
||
31.643010360459822 58.356989639540174
|
||
348.07820699607544 -11.564174033740159
|
||
```
|
||
|
||
### Apparent solar time and equation of time
|
||
|
||
`ApparentSolarTime` returns the apparent solar time at a longitude; the result uses a fixed-offset time zone derived from that longitude, not the zone passed by the caller.
|
||
|
||
`EquationTime` returns the equation of time at the same instant, in hours. `sundial.TrueSolarTime` uses the same convention, and `sundial` additionally provides local mean solar time and sundial geometry; see [Sundial and Apparent Solar Time](sundial.md).
|
||
|
||
```go
|
||
// Apparent solar time; the result time zone is derived from longitude.
|
||
fmt.Println(sun.ApparentSolarTime(date, lon).Format("2006-01-02 15:04:05 -0700"))
|
||
// Equation of time, in hours.
|
||
fmt.Println(sun.EquationTime(date))
|
||
// sundial.TrueSolarTime uses the same convention.
|
||
fmt.Println(sundial.TrueSolarTime(date, lon).Format("2006-01-02 15:04:05 -0700"))
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
2026-01-01 11:12:18 +0715
|
||
-0.05674946079069686
|
||
2026-01-01 11:12:18 +0715
|
||
```
|
||
|
||
### Physical ephemeris and apparent size
|
||
|
||
`sun.Physical` returns `PhysicalInfo`: `P` is the position angle of the solar north pole, `B0` is the heliographic latitude of the disk center, and `L0` is the Carrington heliographic longitude of the disk center, all in degrees.
|
||
|
||
`Diameter` / `Semidiameter` give the apparent diameter and semidiameter in arcseconds; `EarthDistance` gives the Earth-Sun distance in AU.
|
||
|
||
```go
|
||
// Apparent diameter and semidiameter (arcseconds), and Earth-Sun distance (AU).
|
||
fmt.Println(sun.Diameter(date), sun.Semidiameter(date), sun.EarthDistance(date))
|
||
// Solar physical quantities P/B0/L0, in degrees.
|
||
p := sun.Physical(date)
|
||
fmt.Println(p.P, p.B0, p.L0)
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
1950.9979407481142 975.4989703740571 0.9833237486528845
|
||
1.979086377118846 -3.0131029209723916 296.9595333604375
|
||
```
|
||
|
||
The Sun, Moon, and seven major planets share the same interface shapes, so they can be compared side by side:
|
||
|
||
```go
|
||
fmt.Println(sun.Diameter(date), sun.Semidiameter(date))
|
||
fmt.Println(sun.Physical(date))
|
||
fmt.Println(moon.Diameter(date), moon.Semidiameter(date))
|
||
fmt.Println(mars.Diameter(date), mars.Semidiameter(date))
|
||
```
|
||
|
||
### Earth orbit extrema
|
||
|
||
The extrema of the Earth-Sun distance come from the `earth` package, with UTC times and AU distances; for the orbital eccentricity at one instant, call `EarthEccentricity` directly:
|
||
|
||
```go
|
||
// Earth perihelion and aphelion in 2026; time is UTC, distance is AU.
|
||
peri := earth.Perihelion(2026)
|
||
aphe := earth.Aphelion(2026)
|
||
fmt.Printf("earth perihelion=%s distance=%.9fAU\n", peri.Time.Format(time.RFC3339), peri.Distance)
|
||
fmt.Printf("earth aphelion=%s distance=%.9fAU\n", aphe.Time.Format(time.RFC3339), aphe.Distance)
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
earth perihelion=2026-01-03T17:15:35Z distance=0.983302050AU
|
||
earth aphelion=2026-07-06T17:31:24Z distance=1.016643936AU
|
||
```
|
||
|
||
```go
|
||
fmt.Printf("earth e=%.9f\n", earth.EarthEccentricity(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)))
|
||
```
|
||
|
||
## The Moon
|
||
|
||
These snippets reuse the shared setup of the solar section: `date` is still `2026-01-01 12:00:00 CST` and the observer is still in Xi'an.
|
||
|
||
### Position
|
||
|
||
Lunar equatorial coordinates come in three layers: `TrueRaDec` is the geocentric true place, `GeocentricApparentRaDec` is the geocentric apparent place, and `ApparentRaDec` is the topocentric apparent place.
|
||
|
||
On the ecliptic side only geocentric quantities exist: `TrueLo` true longitude, `TrueBo` true latitude, and `ApparentLo` apparent longitude.
|
||
|
||
```go
|
||
// Geocentric true equatorial coordinates.
|
||
fmt.Println(moon.TrueRaDec(date))
|
||
// Geocentric apparent equatorial coordinates.
|
||
fmt.Println(moon.GeocentricApparentRaDec(date))
|
||
// Topocentric apparent equatorial coordinates.
|
||
fmt.Println(moon.ApparentRaDec(date, lon, lat))
|
||
// True longitude, true latitude, and apparent longitude.
|
||
fmt.Println(moon.TrueLo(date), moon.TrueBo(date), moon.ApparentLo(date))
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
66.66309709020791 26.830370234236764
|
||
66.66476688311091 26.830608930005567
|
||
67.0275694157259 25.982665403390747
|
||
69.21422925147913 5.060516750865828 69.21574418700706
|
||
```
|
||
|
||
### Rise, set and culmination
|
||
|
||
Lunar rise/set, like the solar one, anchors on the local civil day: `RiseTime` / `SetTime` return that day's event instants and `CulminationTime` returns that day's upper culmination.
|
||
|
||
Successive lunar rises and sets need not be continuous, and the full cycle after `date` follows from the order of the two instants. When an event falls outside the queried date, the rise/set entry points return `ERR_NOT_TODAY`.
|
||
|
||
```go
|
||
// Moonrise, upper culmination, and moonset on the local civil day.
|
||
rise, err := moon.RiseTime(date, lon, lat, 0, true)
|
||
fmt.Println(rise.Format("15:04:05"), err)
|
||
fmt.Println(moon.CulminationTime(date, lon, lat).Format("15:04:05"))
|
||
set, err := moon.SetTime(date, lon, lat, 0, true)
|
||
fmt.Println(set.Format("15:04:05"), err)
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
16:17:11 <nil>
|
||
00:03:16
|
||
06:41:37 <nil>
|
||
```
|
||
|
||
### Lunar phases
|
||
|
||
`Phase` returns the illuminated fraction in `[0,1]`, `PhaseDesc` returns a Chinese phase name, and `SunMoonLoDiff` returns the apparent Moon-Sun longitude difference normalized to `[0,360)` (near `0°` at new moon and near `180°` at full moon).
|
||
|
||
`Next*` / `Last*` / `Closest*` are the three search conventions, and results keep the input time zone.
|
||
|
||
Each of the four phases has both a pinyin name and an English alias, for example `ShuoYue` / `NewMoon`, `WangYue` / `FullMoon`, `ShangXianYue` / `FirstQuarter`, and `XiaXianYue` / `LastQuarter`.
|
||
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"time"
|
||
|
||
"b612.me/astro/moon"
|
||
)
|
||
|
||
func main() {
|
||
cst := time.FixedZone("CST", 8*3600)
|
||
// Instant of observation.
|
||
date := time.Date(2020, 1, 1, 8, 8, 8, 8, cst)
|
||
// Illuminated fraction of the lunar disk.
|
||
fmt.Println(moon.Phase(date))
|
||
// Chinese textual phase description.
|
||
fmt.Println(moon.PhaseDesc(date))
|
||
// Next new moon; moon.NextNewMoon(date) is the English alias.
|
||
fmt.Println(moon.NextShuoYue(date))
|
||
// Next first quarter; moon.NextFirstQuarter(date) is the English alias.
|
||
fmt.Println(moon.NextShangXianYue(date))
|
||
// Next full moon; moon.NextFullMoon(date) is the English alias.
|
||
fmt.Println(moon.NextWangYue(date))
|
||
// Next last quarter; moon.NextLastQuarter(date) is the English alias.
|
||
fmt.Println(moon.NextXiaXianYue(date))
|
||
}
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
0.30004130960877884 // about 30% of the lunar disk is illuminated
|
||
上峨眉月 // Chinese phase description
|
||
2020-01-25 05:41:58.271192908 +0800 CST // next new moon
|
||
2020-01-03 12:45:23.229190707 +0800 CST // next first quarter
|
||
2020-01-11 03:21:17.159625291 +0800 CST // next full moon
|
||
2020-01-17 20:58:23.396406769 +0800 CST // next last quarter
|
||
```
|
||
|
||
`Last*`, `Closest*`, and the decimal-year anchors `ShuoYue` / `FullMoon` return UTC. `ClosestConjunctionWithPlanet` finds the closest Moon-planet conjunction, with the target given by a `ConjunctionPlanet` constant:
|
||
|
||
```go
|
||
// Previous and closest new moon and full moon.
|
||
fmt.Println(moon.LastShuoYue(date), moon.ClosestShuoYue(date))
|
||
fmt.Println(moon.LastWangYue(date), moon.ClosestWangYue(date))
|
||
// First and last quarter.
|
||
fmt.Println(moon.LastFirstQuarter(date), moon.ClosestLastQuarter(date))
|
||
// Decimal-year anchors; the results are UTC.
|
||
fmt.Println(moon.ShuoYue(2025.5).Format(time.RFC3339), moon.FullMoon(2025.5).Format(time.RFC3339))
|
||
// Closest Moon-planet conjunction in right ascension.
|
||
fmt.Println(moon.ClosestConjunctionWithPlanet(date, moon.ConjunctionJupiter))
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
2025-12-20 09:43:19.074603617 +0800 CST 2025-12-20 09:43:19.074603617 +0800 CST
|
||
2025-12-05 07:14:03.670351803 +0800 CST 2026-01-03 18:02:53.55531156 +0800 CST
|
||
2025-12-28 03:09:50.141303837 +0800 CST 2026-01-10 23:48:22.03346461 +0800 CST
|
||
2025-06-25T10:31:35Z 2025-07-10T20:36:46Z
|
||
2026-01-04 05:59:19.9425897 +0800 CST
|
||
```
|
||
|
||
### Perigee and apogee
|
||
|
||
`PerigeesInMonth` / `ApogeesInMonth` return every perigee and apogee event in a Gregorian month. Each element is an `ApsisInfo` with `Time` (UTC) and `Distance` (km); a month may contain zero, one, or several events.
|
||
|
||
```go
|
||
// Lunar perigee and apogee in January 2026; distance is in km.
|
||
perigees := moon.PerigeesInMonth(2026, time.January)
|
||
apogees := moon.ApogeesInMonth(2026, time.January)
|
||
fmt.Printf("moon perigee=%s distance=%.1fkm count=%d\n", perigees[0].Time.Format(time.RFC3339), perigees[0].Distance, len(perigees))
|
||
fmt.Printf("moon apogee=%s distance=%.1fkm count=%d\n", apogees[0].Time.Format(time.RFC3339), apogees[0].Distance, len(apogees))
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
moon perigee=2026-01-01T21:44:24Z distance=360348.1km count=2
|
||
moon apogee=2026-01-13T20:47:13Z distance=405437.9km count=1
|
||
```
|
||
|
||
### Nodes
|
||
|
||
The Moon also exposes ascending-node and descending-node longitudes, which are useful for eclipse seasons, orbital geometry, and lunar-orbit studies:
|
||
|
||
```go
|
||
nodeDate := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
|
||
fmt.Println(moon.AscendingNode(nodeDate), moon.DescendingNode(nodeDate))
|
||
```
|
||
|
||
The ascending / descending node definitions here are the same as in the planets chapter:
|
||
|
||
- `AscendingNode`: ecliptic longitude where the Moon crosses from south of the ecliptic to north of it
|
||
- `DescendingNode`: ecliptic longitude where the Moon crosses from north of the ecliptic to south of it
|
||
- both values are degrees, and are usually about `180°` apart at the same instant
|
||
|
||
For the `nodeDate := 2026-01-01 00:00:00 UTC` example above, the output is:
|
||
|
||
```text
|
||
340.95708624505863 160.9570862450587
|
||
```
|
||
|
||
### Maximum declinations
|
||
|
||
Lunar declination reaches northern and southern extrema within one nodal month. `MaximumDeclinationInfo` carries `Time` (the event instant) and `Declination` (the geocentric declination at that instant, in degrees).
|
||
|
||
The monthly entry points return every event in the month, while `Next*` / `Last*` / `Closest*` search by instant:
|
||
|
||
```go
|
||
// Closest maximum northern and previous maximum southern declination.
|
||
north := moon.ClosestMaximumNorthDeclination(date)
|
||
south := moon.LastMaximumSouthDeclination(date)
|
||
fmt.Println(north.Time.Format(time.RFC3339), north.Declination)
|
||
fmt.Println(south.Time.Format(time.RFC3339), south.Declination)
|
||
// Next maximum northern declination and all events in the month.
|
||
fmt.Println(moon.NextMaximumNorthDeclination(date).Time.Format(time.RFC3339))
|
||
events := moon.MaximumNorthDeclinationsInMonth(2026, time.January)
|
||
fmt.Println(len(events))
|
||
for _, event := range events {
|
||
fmt.Println(event.Time.Format(time.RFC3339), event.Declination)
|
||
}
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
2026-01-02T16:10:49+08:00 28.266373428242343
|
||
2025-12-20T07:06:57+08:00 -28.23514705130737
|
||
2026-01-02T16:10:49+08:00
|
||
2 2026-01-02T08:10:49Z 28.266373428242343
|
||
```
|
||
|
||
The monthly-list convention for the same events looks like this (input `2026-01-01 00:00:00 UTC`):
|
||
|
||
```go
|
||
// Maximum northern and southern lunar declinations in January 2026.
|
||
north := moon.MaximumNorthDeclinationsInMonth(2026, time.January)
|
||
south := moon.MaximumSouthDeclinationsInMonth(2026, time.January)
|
||
fmt.Printf("north=%s dec=%.6f\n", north[0].Time.Format(time.RFC3339), north[0].Declination)
|
||
fmt.Printf("south=%s dec=%.6f\n", south[0].Time.Format(time.RFC3339), south[0].Declination)
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
north=2026-01-02T08:10:49Z dec=28.266373
|
||
south=2026-01-16T05:15:14Z dec=-28.304184
|
||
```
|
||
|
||
### Libration and bright-limb position angle
|
||
|
||
`Physical` returns the geocentric libration and `TopocentricPhysical` the topocentric libration.
|
||
|
||
Both are `PhysicalInfo`, carrying the optical, physical, and total libration components plus the rotation-axis position angle `PositionAngle`, in degrees.
|
||
|
||
The bright-limb position angle starts at `0°` at the lunar north point and increases eastward.
|
||
|
||
```go
|
||
// Geocentric libration and rotation-axis position angle.
|
||
p := moon.Physical(date)
|
||
fmt.Println(p.LibrationLongitude, p.LibrationLatitude, p.PositionAngle)
|
||
// Topocentric libration and rotation-axis position angle.
|
||
topo := moon.TopocentricPhysical(date, lon, lat, 0)
|
||
fmt.Println(topo.LibrationLongitude, topo.LibrationLatitude, topo.PositionAngle)
|
||
// Geocentric and topocentric bright-limb position angles.
|
||
fmt.Println(moon.BrightLimbPositionAngle(date), moon.TopocentricBrightLimbPositionAngle(date, lon, lat, 0))
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
-0.9680924808747591 -6.547834757841939 -9.025022841390472
|
||
-0.7780085060449551 -5.659649431558411 -8.883877708625544
|
||
269.08333384819935 267.8559531949645
|
||
```
|
||
|
||
A complete topocentric example (Shanghai, `height = 4 m`):
|
||
|
||
```go
|
||
// Lunar libration and rotation-axis position angle.
|
||
physical := moon.Physical(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC))
|
||
fmt.Printf("libration lon=%.6f lat=%.6f pa=%.6f\n", physical.LibrationLongitude, physical.LibrationLatitude, physical.PositionAngle)
|
||
// Bright-limb position angle; 0 degrees starts at the lunar north point and increases eastward.
|
||
fmt.Printf("bright limb=%.6f\n", moon.BrightLimbPositionAngle(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)))
|
||
// Topocentric libration, rotation-axis position angle, and bright-limb angle from Shanghai.
|
||
topo := moon.TopocentricPhysical(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), 121.4737, 31.2304, 4)
|
||
fmt.Printf("topo libration lon=%.6f lat=%.6f pa=%.6f\n", topo.LibrationLongitude, topo.LibrationLatitude, topo.PositionAngle)
|
||
fmt.Printf("topo bright limb=%.6f\n", moon.TopocentricBrightLimbPositionAngle(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), 121.4737, 31.2304, 4))
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
libration lon=-1.278902 lat=-6.531444 pa=-9.967050
|
||
bright limb=267.364849
|
||
topo libration lon=-1.736754 lat=-5.780730 pa=-10.072846
|
||
topo bright limb=266.038258
|
||
```
|
||
|
||
### Apparent size and Earth-Moon distance
|
||
|
||
`Diameter` / `Semidiameter` give the apparent diameter and semidiameter in arcseconds, and `EarthDistance` gives the Earth-Moon distance in kilometers. All three depend only on the absolute instant:
|
||
|
||
```go
|
||
// Apparent diameter and semidiameter (arcseconds), and Earth-Moon distance (km).
|
||
fmt.Println(moon.Diameter(date), moon.Semidiameter(date), moon.EarthDistance(date))
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
1986.4975069969655 993.2487534984828 360488.4234539985
|
||
```
|
||
|
||
## Lite chains
|
||
|
||
`lite/sun` and `lite/moon` are independent approximation implementations: they do not depend on VSOP87 or the main chain's ELP2000/82 series, they target CPU- and memory-constrained environments, and their calling style matches the main chain.
|
||
|
||
Rise/set search uses fixed-step scanning plus bisection, without the main chain's high-precision nutation iteration.
|
||
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"time"
|
||
|
||
litemoon "b612.me/astro/lite/moon"
|
||
litesun "b612.me/astro/lite/sun"
|
||
)
|
||
|
||
func main() {
|
||
cst := time.FixedZone("CST", 8*3600)
|
||
date := time.Date(2026, 1, 1, 20, 0, 0, 0, cst)
|
||
|
||
fmt.Println(litesun.Altitude(date, 121.4737, 31.2304))
|
||
fmt.Println(litesun.RiseTime(date, 121.4737, 31.2304, 0, true))
|
||
|
||
fmt.Println(litemoon.Phase(date))
|
||
fmt.Println(litemoon.PhaseAge(date))
|
||
fmt.Println(litemoon.RiseTime(date, 121.4737, 31.2304, 0, true))
|
||
}
|
||
```
|
||
|
||
The snippets below reuse this section's shared setup: Shanghai (`121.4737°E, 31.2304°N`) and `2026-01-01 20:00:00 CST`.
|
||
|
||
### lite/sun
|
||
|
||
The lightweight solar chain provides longitude, equatorial coordinates, distance, horizontal coordinates, and rise/set, but no apparent size, solar disk physical quantities, or twilight entry points.
|
||
|
||
`Distance` is the Earth-Sun distance in AU; note that the main chain names this capability `EarthDistance` while the lite chain names it `Distance`.
|
||
|
||
```go
|
||
// Lightweight true and apparent solar longitude, and Earth-Sun distance (AU).
|
||
fmt.Println(litesun.TrueLo(date), litesun.ApparentLo(date), litesun.Distance(date))
|
||
// Lightweight apparent right ascension and declination.
|
||
ra, dec := litesun.ApparentRaDec(date)
|
||
fmt.Println(ra, dec)
|
||
// Lightweight hour angle, azimuth, altitude, and zenith distance.
|
||
fmt.Println(litesun.HourAngle(date, 121.4737, 31.2304), litesun.Azimuth(date, 121.4737, 31.2304), litesun.Altitude(date, 121.4737, 31.2304), litesun.Zenith(date, 121.4737, 31.2304))
|
||
// Lightweight rise and set.
|
||
fmt.Println(litesun.RiseTime(date, 121.4737, 31.2304, 0, true))
|
||
fmt.Println(litesun.SetTime(date, 121.4737, 31.2304, 0, true))
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
281.0835889076667 281.0793650422643 0.9833163427233701
|
||
282.0475744673639 -22.973803828458102
|
||
120.58011725187828 263.4579582386061 -37.07676039520773 127.07676039520773
|
||
2026-01-01 06:52:24.6475178 +0800 CST <nil>
|
||
2026-01-01 17:02:44.014452695 +0800 CST <nil>
|
||
```
|
||
|
||
### lite/moon
|
||
|
||
The lightweight lunar chain provides a few-perturbation-term lunar position, light topocentric correction, phase and age, and rise/set, but no libration, apparent size, Earth-Moon distance, or nodes.
|
||
|
||
`PhaseAge` returns the lunar age in days and exists only in the lite chain:
|
||
|
||
```go
|
||
// Lightweight true longitude and latitude.
|
||
fmt.Println(litemoon.TrueLo(date), litemoon.TrueBo(date))
|
||
// Lightweight geocentric true equatorial coordinates.
|
||
ra, dec := litemoon.TrueRaDec(date)
|
||
fmt.Println(ra, dec)
|
||
// Lightweight topocentric apparent equatorial coordinates.
|
||
fmt.Println(litemoon.ApparentRaDec(date, 121.4737, 31.2304))
|
||
// Moon-Sun longitude difference, illuminated fraction, and lunar age.
|
||
fmt.Println(litemoon.SunMoonLoDiff(date), litemoon.Phase(date), litemoon.PhaseAge(date))
|
||
// Lightweight horizontal coordinates.
|
||
fmt.Println(litemoon.Altitude(date, 121.4737, 31.2304), litemoon.Azimuth(date, 121.4737, 31.2304), litemoon.Zenith(date, 121.4737, 31.2304))
|
||
```
|
||
|
||
Output:
|
||
|
||
```text
|
||
74.25630740893 5.078407838127742
|
||
72.2518040645064 27.549605139478064
|
||
72.74256784654426 27.432483413326057
|
||
153.17694236666568 0.9462021494002484 12.565014741082448
|
||
63.55513206820331 90.53047230027812 26.444867931796693
|
||
```
|
||
|
||
### Differences from the main chain and error levels
|
||
|
||
The lite chains differ from the main chain in implementation and accuracy while keeping nearly the same interface shapes.
|
||
|
||
Pure evaluation entry points such as position and phase run about `8.3-27.3x` faster than the main chain, and rise/set entry points about `1.0-3.7x`, with zero heap allocation in the computation path; the rise/set scan step is `30` minutes for `lite/sun` and `15` minutes for `lite/moon`.
|
||
|
||
Errors against `sun` / `moon` (year 2026, 8 sites) are listed below, with data from [Lite lightweight chains](accuracy.md#lite-lightweight-chains):
|
||
|
||
| Capability | Mean absolute error | P95 | Max absolute error |
|
||
| --- | --- | --- | --- |
|
||
| `lite/sun` sunrise | `0.02 min` | `0.04 min` | `0.31 min` |
|
||
| `lite/sun` sunset | `0.02 min` | `0.06 min` | `0.35 min` |
|
||
| `lite/moon` moonrise | `0.28 min` | `0.57 min` | `1.44 min` |
|
||
| `lite/moon` moonset | `0.36 min` | `0.86 min` | `1.24 min` |
|
||
| `lite/moon` `Phase()` | `0.00089` | `0.00185` | `0.00243` |
|
||
| `lite/moon` `PhaseAge()` | `0.003 d` | `0.010 d` | `0.014 d` |
|
||
| `lite/moon` geocentric longitude | `2.41'` | `6.82'` | `9.91'` |
|
||
| `lite/moon` geocentric latitude | `0.87'` | `1.83'` | `2.92'` |
|
||
|
||
Neither package provides a `...N` truncated family. On the solar side the polar-night / polar-day errors are `ERR_SUN_NEVER_RISE` / `ERR_SUN_NEVER_SET`; on the lunar side, besides `ERR_MOON_NEVER_RISE` / `ERR_MOON_NEVER_SET`, there is `ERR_NOT_TODAY`, with the same semantics as the main chain.
|
||
|
||
## Parameter and result conventions
|
||
|
||
### Units and angle conventions
|
||
|
||
- Angles are always in degrees; `RA`, `Lon`, and `Azimuth` are normalized to `[0°, 360°)`, while declination and ecliptic latitude lie in `[−90°, 90°]`
|
||
- Apparent diameters and semidiameters are in arcseconds; `sun.EarthDistance` is AU and `moon.EarthDistance` is kilometers
|
||
- `Phase` is the illuminated fraction in `[0,1]`, `PhaseAge` (lite chain) is in days, and `EquationTime` is in hours
|
||
- Distance extrema: `earth.Perihelion` / `earth.Aphelion` are AU, and `ApsisInfo.Distance` is kilometers
|
||
|
||
### Time scale
|
||
|
||
Observing inputs and event times use the civil-time convention. Before `1972-01-01`, the library treats those readings as UT1. `ApparentSolarTime` returns a local solar clock reading rather than another civil event time.
|
||
|
||
The time zone carried by a `time.Time` only affects the division of local civil days and the zone of the returned value, never the absolute instant.
|
||
|
||
The full UT1 and chart-label declaration is in the [Time Scale Declaration](map-geojson.md#time-scale-declaration).
|
||
|
||
### Height and aero
|
||
|
||
- The `height` of the rise/set entry points is the observer elevation interpreted as ellipsoidal (geodetic) height in meters, not orthometric height; see [Observer Height Convention](coord.md#observer-height)
|
||
- `aero = true`: upper-limb crossing with dynamic standard refraction and the instantaneous semidiameter; `aero = false`: only the geometric center crossing
|
||
- `pressureHPa` and `temperatureC` of `ApparentAltitude` / `ApparentZenith` are the observed pressure (hPa) and temperature (degrees Celsius)
|
||
|
||
### Zero values and out-of-range
|
||
|
||
- During polar night the rise/set entry points return `ERR_SUN_NEVER_RISE` / `ERR_MOON_NEVER_RISE`; during polar day they return `ERR_SUN_NEVER_SET` / `ERR_MOON_NEVER_SET`, with a zero `time.Time` as the first return value
|
||
- When no twilight exists, they return `ERR_TWILIGHT_NOT_EXISTS`
|
||
- When a lunar rise/set event falls outside the queried date, they return `ERR_NOT_TODAY`, matching the "moonrise/moonset are computed for the queried civil date" semantics above
|
||
- `DownTime` / `DownTimeN` are deprecated aliases of `SetTime`, and `ERR_SUN_NEVER_DOWN` / `ERR_MOON_NEVER_DOWN` are deprecated aliases of the polar-day errors; new code should not use them
|
||
- In `...N`, `n < 0` uses every embedded term and `n >= 0` truncates; `n` only changes the series length, never the returned unit or time zone
|
||
|
||
### Accuracy and scope
|
||
|
||
The Sun and planets use embedded VSOP87 analytical terms and the Moon uses an embedded ELP2000/82-style truncated series, covering roughly 4000 years around J2000 with no external ephemeris files.
|
||
|
||
Truncation errors, the lunar chain's capability boundaries, and the lite chains' quantified errors are documented in [Scope And Accuracy](accuracy.md).
|
||
|
||
The solar and lunar entry points in this manual suit calendars, observing aids, outreach, and amateur prediction; spacecraft navigation, precise occultation prediction, and rigorous dynamical integration require a professional ephemeris such as JPL DE.
|