feat: 实现生辰信息存储与真太阳时计算

- 新增 ADR 文档记录生辰信息存储设计决策
- 新增真太阳时计算模块 (solarTime.ts),基于 Spencer 1971 时差方程
- 新增城市经纬度内置数据库,覆盖全国主要区县,系统自动匹配坐标
- 更新紫微页面:移除手动经纬度输入,改为 500ms 防抖自动定位
- 更新所有 9 个语言文件,替换经纬度提示为定位提示
This commit is contained in:
2026-08-12 20:44:45 +08:00
parent 3a428d8977
commit 34e9c3721d
13 changed files with 869 additions and 76 deletions
@@ -0,0 +1,117 @@
/**
* 真太阳时计算模块
*
* 真太阳时 = 平太阳时 + 经度修正 + 时差方程修正
* - 经度修正:经度每差 1°,时间差 4 分钟
* - 时差方程:基于地球公转轨道离心率和黄赤交角的天文修正
*
* @example
* ```typescript
* const result = calculateTrueSolarTime({
* date: '2026-08-13',
* hour: 14,
* minute: 30,
* longitude: 116.4,
* timezoneOffset: 8,
* })
* // result.trueSolarTime => '2026-08-13T14:24:30'
* // result.correctionMinutes => -5.5
* ```
*/
export interface TrueSolarTimeParams {
/** 公历日期 YYYY-MM-DD */
date: string
/** 小时 (0-23) */
hour: number
/** 分钟 (0-59) */
minute: number
/** 出生地经度(东经为正,西经为负) */
longitude: number
/** 时区偏移小时数(东八区为 8,东七区为 7) */
timezoneOffset: number
}
export interface TrueSolarTimeResult {
/** 真太阳时字符串 HH:mm:ss */
trueSolarTime: string
/** 总修正分钟数(正数=提前,负数=推迟) */
correctionMinutes: number
/** 经度修正分钟数 */
longitudeCorrection: number
/** 时差方程修正分钟数 */
equationOfTime: number
}
/**
* 计算真太阳时
*
* 算法来源:
* - 经度修正:时区中央经线与本地经度的差值 × 4 分钟/度
* - 中国标准时间 (UTC+8) 中央经线为 120°E
* - 时差方程:基于 Spencer (1971) 的近似公式
* - 参考:https://www.esrl.noaa.gov/gmd/grad/solcalc/
*/
export function calculateTrueSolarTime(params: TrueSolarTimeParams): TrueSolarTimeResult {
const { date, hour, minute, longitude, timezoneOffset } = params
// 1. 经度修正
const standardMeridian = timezoneOffset * 15
const longitudeCorrection = (longitude - standardMeridian) * 4
// 2. 时差方程修正(基于 Spencer 1971 近似公式)
const dateObj = new Date(`${date}T00:00:00`)
const dayOfYear = getDayOfYear(dateObj)
const fractionalYear = (2 * Math.PI / 365) * (dayOfYear - 1 + (hour - 12) / 24)
const equationOfTime = calculateEquationOfTime(fractionalYear)
// 3. 总修正
const totalCorrection = longitudeCorrection + equationOfTime
// 4. 计算真太阳时
const totalMinutes = hour * 60 + minute + totalCorrection
const adjustedHours = Math.floor(totalMinutes / 60) % 24
const adjustedMinutes = Math.floor(totalMinutes % 60)
const adjustedSeconds = Math.round((totalMinutes % 1) * 60)
const trueSolarTime = `${String(adjustedHours).padStart(2, '0')}:${String(adjustedMinutes).padStart(2, '0')}:${String(adjustedSeconds).padStart(2, '0')}`
return {
trueSolarTime,
correctionMinutes: Math.round(totalCorrection * 100) / 100,
longitudeCorrection: Math.round(longitudeCorrection * 100) / 100,
equationOfTime: Math.round(equationOfTime * 100) / 100,
}
}
/**
* 计算一年中的第几天(1 月 1 日为第 1 天)
*/
function getDayOfYear(date: Date): number {
const start = new Date(date.getFullYear(), 0, 0)
const diff = date.getTime() - start.getTime()
return Math.floor(diff / (1000 * 60 * 60 * 24))
}
/**
* 计算时差方程(分钟)
*
* 基于 Spencer (1971) 傅里叶级数近似:
* EoT = 229.2 × (0.000075 + 0.001868×cos(γ) - 0.032077×sin(γ)
* - 0.014615×cos(2γ) - 0.04089×sin(2γ))
*
* 其中 γ = 2π/365 × (N - 1 + (t - 12) / 24),N 为一年中的第几天
*
* 结果范围约为 -16 到 +14 分钟
*/
function calculateEquationOfTime(fractionalYear: number): number {
const coeff = 229.2
const eot = coeff * (
0.000075 +
0.001868 * Math.cos(fractionalYear) -
0.032077 * Math.sin(fractionalYear) -
0.014615 * Math.cos(2 * fractionalYear) -
0.04089 * Math.sin(2 * fractionalYear)
)
return eot
}