Mosink墨溟

博客 开发手记

在浏览器里推算二十四节气

主页上曾经有一张实时星图,旁边写着今天是哪个节气、第几天、离下一个节气还有多久。这些都是在浏览器里现算的,不依赖任何接口。这篇记录一下算法。

节气是什么

很多人以为节气是按日期分的,其实不是。节气是按太阳黄经分的:太阳在黄道上每走 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());
// → "丙午年九月初三"

  1. 更高精度需要用 VSOP87 理论,但对于「今天是什么节气」这个问题,完全没有必要。 ↩

评论 · Comments3

  1. 兰亭

    原来节气是按太阳黄经分的,涨知识了。

  2. 周一舟

    Intl 的农历支持太方便了,之前还自己维护了一张农历表……

    1. 兰亭

      @周一舟 同,旧的农历表可以扔了。

写下评论

Ctrl + Enter 也可以发表