在浏览器里推算二十四节气
主页上曾经有一张实时星图,旁边写着今天是哪个节气、第几天、离下一个节气还有多久。这些都是在浏览器里现算的,不依赖任何接口。这篇记录一下算法。
节气是什么
很多人以为节气是按日期分的,其实不是。节气是按太阳黄经分的:太阳在黄道上每走 15°,就是一个节气。春分是 0°,清明 15°,谷雨 30°……一圈 360°,正好二十四个。
所以问题变成了:给定一个时刻,太阳的黄经是多少?
Meeus 的低精度算法
Jean Meeus 在《天文算法》里给了一个足够好用的近似公式,精度大约 0.01°,对应节气时刻误差在十几分钟以内1。
function sunLongitude(jd) {
const T = (jd - 2451545) / 36525; // 儒略世纪
const L0 = 280.46646 + 36000.76983 * T; // 平黄经
const M = 357.52911 + 35999.05029 * T; // 平近点角
const C = (1.914602 - 0.004817 * T) * sin(M)
+ 0.019993 * sin(2 * M)
+ 0.000289 * sin(3 * M); // 中心差
const omega = 125.04 - 1934.136 * T;
return norm(L0 + C - 0.00569 - 0.00478 * sin(omega));
}反过来求时刻
知道了怎么从时刻算黄经,反过来求「黄经等于 195° 的时刻」(寒露)就是一个求根问题。太阳每天大约走 0.9856°,先用这个速度估一个初值,再迭代两三次牛顿法就收敛了。
function termTime(targetLon, jdGuess) {
let jd = jdGuess;
for (let i = 0; i < 4; i++) {
const diff = ((targetLon - sunLongitude(jd) + 540) % 360) - 180;
jd += diff / 0.9856;
}
return jd;
}时区与「第几天」
算出来的是世界时,要加 8 小时换成北京时间。「寒露第四日」的意思是:从寒露那个时刻所在的那一天算起,今天是第四天。注意是按日期数,不是按 24 小时数。
农历
农历就不自己算了。现代浏览器的 Intl.DateTimeFormat 支持 calendar: 'chinese':
new Intl.DateTimeFormat('zh-CN-u-ca-chinese', {
year: 'numeric', month: 'long', day: 'numeric'
}).format(new Date());
// → "丙午年九月初三"- 更高精度需要用 VSOP87 理论,但对于「今天是什么节气」这个问题,完全没有必要。 ↩
原来节气是按太阳黄经分的,涨知识了。
Intl 的农历支持太方便了,之前还自己维护了一张农历表……
@周一舟 同,旧的农历表可以扔了。