feat: 实现生辰信息存储与真太阳时计算
- 新增 ADR 文档记录生辰信息存储设计决策 - 新增真太阳时计算模块 (solarTime.ts),基于 Spencer 1971 时差方程 - 新增城市经纬度内置数据库,覆盖全国主要区县,系统自动匹配坐标 - 更新紫微页面:移除手动经纬度输入,改为 500ms 防抖自动定位 - 更新所有 9 个语言文件,替换经纬度提示为定位提示
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user