2026-05-01 22:38:44 +08:00
package sundial
import (
"math"
"time"
"b612.me/astro/sun"
)
// TrueSolarTime 真太阳时 / apparent solar time.
//
// 返回 date 在经度 lon 处对应的真太阳时,口径沿用 `sun.ApparentSolarTime`。
// Returns the apparent solar time for date at longitude lon.
func TrueSolarTime ( date time . Time , lon float64 ) time . Time {
return sun . ApparentSolarTime ( date , lon )
}
// MeanSolarTime 地方平太阳时 / local mean solar time.
//
// 返回 date 在经度 lon 处对应的地方平太阳时,结果时区为按经度换算的地方平太阳时区。
// 该实现直接按“真太阳时 - 均时差”构造,以与本仓库的真太阳时和均时差口径保持一致。
// Returns the local mean solar time for date at longitude lon. The result uses
// a synthetic local-mean-solar time zone derived from longitude and is built as
// apparent solar time minus equation of time to keep the package's conventions aligned.
func MeanSolarTime ( date time . Time , lon float64 ) time . Time {
return TrueSolarTime ( date , lon ) . Add ( time . Duration ( - sun . EquationTime ( date ) * float64 ( time . Hour ) ) )
}
// HourAngle 太阳时角 / solar hour angle.
//
// 返回经度 lon 处的有符号太阳时角,单位度。上午为负,下午为正,中午为 0°。
// Returns the signed apparent-solar hour angle at longitude lon, in degrees.
func HourAngle ( date time . Time , lon float64 ) float64 {
return normalizeSigned180 ( sun . HourAngle ( date , lon , 0 ) )
}
// MeanSolarHourAngle 平太阳时对应的太阳时角 / hour angle for local mean solar time.
//
// date 负责提供地方平太阳时日期与时区,原有钟面时间会被 meanSolarHours 替换;
// meanSolarHours 为地方平太阳时钟面读数,单位小时,例如 9.5 表示地方平太阳时 09:30。
// 返回对应的视太阳时角,单位度,上午为负,下午为正。
2026-05-27 16:08:11 +08:00
// date provides the local mean-solar date and timezone, while its clock fields are replaced by meanSolarHours.
// meanSolarHours is the local mean-solar clock reading in hours, for example 9.5 for 09:30.
// Returns the corresponding apparent-solar hour angle in degrees, negative in the morning and positive in the afternoon.
2026-05-01 22:38:44 +08:00
func MeanSolarHourAngle ( date time . Time , meanSolarHours float64 ) float64 {
if ! isFinite ( meanSolarHours ) {
return math . NaN ( )
}
sampleTime := dateWithClockHours ( date , meanSolarHours )
return normalizeSigned180 ( HourAngle ( sampleTime , longitudeFromTimeZone ( sampleTime ) ) )
}
// ZoneTimeHourAngle 区时对应的太阳时角 / hour angle for zone time.
//
// zoneTimeHours 为 date 所在时区下的钟面读数, 单位小时; lon 为当地经度,东正西负。
// 返回该区时在给定经度上对应的视太阳时角,单位度,上午为负,下午为正。
// date 提供民用日期和时区;其原有钟面时间会被 zoneTimeHours 替换。
2026-05-27 16:08:11 +08:00
// zoneTimeHours is the civil clock reading in the timezone carried by date, in hours; lon is east-positive longitude.
// Returns the apparent-solar hour angle for that civil time and longitude, in degrees, negative in the morning and positive in the afternoon.
// date provides the civil date and timezone, and its original clock fields are replaced by zoneTimeHours.
2026-05-01 22:38:44 +08:00
func ZoneTimeHourAngle ( date time . Time , lon , zoneTimeHours float64 ) float64 {
if ! isFinite ( zoneTimeHours ) || ! isFinite ( lon ) {
return math . NaN ( )
}
return normalizeSigned180 ( HourAngle ( dateWithClockHours ( date , zoneTimeHours ) , lon ) )
}
// HorizontalHourLineAngle 水平日晷时线角 / horizontal sundial hour-line angle.
//
// lat 为纬度( 北正南负) , hourAngle 为有符号太阳时角,单位度。返回值是相对午线的时线角,
// 上午在东侧为负,下午在西侧为正。
// lat is the observer latitude in degrees and hourAngle is the signed solar
// hour angle in degrees. The result is the hour-line angle from the noon line:
// east/morning is negative and west/afternoon is positive.
func HorizontalHourLineAngle ( lat , hourAngle float64 ) float64 {
if ! isFinite ( hourAngle ) || ! isFinite ( lat ) {
return math . NaN ( )
}
hourAngle = normalizeSigned180 ( hourAngle )
latRad := lat * math . Pi / 180
hourAngleRad := hourAngle * math . Pi / 180
return math . Atan2 ( math . Sin ( latRad ) * math . Sin ( hourAngleRad ) , math . Cos ( hourAngleRad ) ) * 180 / math . Pi
}
// HorizontalHourLineAngleAt 水平日晷时线角 / horizontal sundial hour-line angle.
//
// 先按给定时刻和经度求瞬时视太阳时角,再结合纬度返回水平日晷的时线角。
2026-05-27 16:08:11 +08:00
// First derives the apparent-solar hour angle for the supplied instant and longitude, then returns the horizontal-dial hour-line angle for the given latitude.
2026-05-01 22:38:44 +08:00
func HorizontalHourLineAngleAt ( date time . Time , lon , lat float64 ) float64 {
return HorizontalHourLineAngle ( lat , HourAngle ( date , lon ) )
}
func dateWithClockHours ( date time . Time , hours float64 ) time . Time {
startOfDay := time . Date ( date . Year ( ) , date . Month ( ) , date . Day ( ) , 0 , 0 , 0 , 0 , date . Location ( ) )
return startOfDay . Add ( time . Duration ( hours * float64 ( time . Hour ) ) )
}
func longitudeFromTimeZone ( date time . Time ) float64 {
_ , offsetSeconds := date . Zone ( )
return float64 ( offsetSeconds ) / 240.0
}
func clockHours ( date time . Time ) float64 {
return float64 ( date . Hour ( ) ) +
float64 ( date . Minute ( ) ) / 60 +
float64 ( date . Second ( ) ) / 3600 +
float64 ( date . Nanosecond ( ) ) / 3.6e12
}
func normalizeSigned180 ( value float64 ) float64 {
value = math . Mod ( value , 360 )
if value < - 180 {
value += 360
}
if value >= 180 {
value -= 360
}
return value
}
func isFinite ( value float64 ) bool {
return ! math . IsNaN ( value ) && ! math . IsInf ( value , 0 )
}