万年历计算之节气

一、基本知识
二十四节气起源于黄河流域。远在春秋时代,就定出仲春、仲夏、仲秋和仲冬等四个节气。以后不断地改进与完善,到秦汉年间,二十四节气已完全确立。公元前104年,由邓平等制定的《太初历》,正式把二十四节气订于历法,明确了二十四节气的天文位置。
  太阳从黄经零度起,沿黄经每运行15度所经历的时日称为“一个节气”。每年运行360度,共经历24个节气,每月2个。其中,每月第一个节气为“节气”,即:立春、惊蛰、清明、立夏、芒种、小暑、立秋、白露、寒露、立冬、大雪和小寒等12个节气;每月的第二个节气为“中气”,即:雨水、春分、谷雨、小满、夏至、大暑、处暑、秋分、霜降、小雪、冬至和大寒等12个节气。“节气” 和“中气”交替出现,各历时15天,现在人们已经把“节气”和“中气”统称为“节气”。
  二十四节气反映了太阳的周年视运动,所以节气在现行的公历中日期基本固定,上半年在6日、21日,下半年在8日、23日,前后不差1~2天。
二、基本概念
儒略日:Julian Day number 或 Julian Day 或 JD。是指从公元前 4712 年开始连续计算日数得出的天数及不满一日的尾数。传统上儒略日的计数是从格林尼治平午,即世界时间 12 点开始的。
格里历:即公历或格列高利历,是现行国际通行的历法,属于阳历的一种,通称阳历,其前身是奥古斯都历,而奥古斯都历的前身是儒略历。其历年为一个回归年(365.2425日),划分为12个历月。是教皇格里高利13世(也译格雷果里)在公元1582年改革儒略历制定的历法,儒略历的回归年为365.25,与实际的回归365.2422相差甚多,当时儒略历和地球实际位置的误差已达14天,格里历将误差纠正,确定所有整数世纪年除了可被400整除的外一律不设闰年,同时规定1582年10月4日之后的那天为1582年10月15日,但原有星期不变。新颁布的历法理论上可以达到两万年内误差不超过一天,但由于地球自转的变化,实际到公元4909年误差就可达一天。与儒略历相比,公历是新制定的历法,所以有时候,包括中华民国《中国国家标准》CNS 7648《数据元及交换格式–信息交换–日期及时间的表示法》,又称新历。
黄经:地球绕太阳运转一周约365天5小时多,运转94,000万公里。地球的公转在地球上人看来就表现为太阳周年视运动,其运行线路(即地球公转轨道在天球上的反映)称为黄道。黄经就是黄道上的度量坐标(经度),按天文学惯例,以春分点为起点自西向东度量,分360度。我国古人把太阳黄经的360度划分成24等份,每份15度,为一个节气。两个节气间相隔日数为15天左右,全年即有二十四节气。二十四节气的太阳黄经度分别为:立春315度,雨水330度,惊蛰345度,春分360度,清明15度,谷雨30度,立夏45度,小满60度,芒种75度,夏至90度,小暑105度,大暑120度,立秋135度,处暑150度,白露165度,秋分180度,寒露195度,霜降210度,立冬225度,小雪240度,大雪255度,冬至270度,小寒285度,大寒300度。
黄纬:黄道上的纬度。
三、基本算法:
3.1:儒略日的计算(算法描述请参考相关书籍,为了节省空间和时间,这里请罗列出 C++ 实现代码)

// ------------------------------------------------------------------------
//
// 儒略日转换为格历日
//
// dbGD[IN]:儒略日
// year[OUT]:年
// month[OUT]:月
// day[OUT]:日
// hour[OUT]:时
// minute[OUT]:分
// second[OUT]:秒
// ------------------------------------------------------------------------
void w_JDToGD(const double &dbJD, int &year, int &month, int &day, int &hour, int &minute, int &second)
{
double dJDM = dbJD + 0.5;
unsigned long ulZ = static_cast(floor(dJDM));
double dF = dJDM - floor(dJDM);
unsigned long ulA, ulB, ulC, ulD;
int nE, nQ;
if (dbJD < 2299161)
ulA = ulZ;
else
{
nQ = static_cast((ulZ - 1867216.25) / 36524.25);
ulA = ulZ + 1 + nQ - static_cast(nQ >> 2);
}
ulB = ulA + 1524 ;
ulC = static_cast(( ulB - 122.1) / 365.25);
ulD = static_cast(365.25 * ulC);
nE = static_cast((ulB - ulD) / 30.6001);
// 计算日
day = ulB - ulD - int ( 30.6001 * nE ) ;
// 计算时
hour = static_cast(floor(dF * 24.0));
// 计算分
minute = static_cast(((dF * 24.0) - floor(dF * 24.0)) * 60.0);
// 计算秒
second = static_cast((((dF * 24.0) * 60.0) - floor(( dF * 24.0) * 60.0)) * 60.0);
// 计算月
if(nE < 14)
month = nE - 1;
if(nE == 14 || nE == 15)
month = nE - 13;
// 计算年
if(month > 2)
year = ulC - 4716;
if(month == 1 || month == 2)
year = ulC - 4715;
}

// ------------------------------------------------------------------------
//
// 格历日转换为儒略日
//
// year[IN]:年
// month[IN]:月
// day[IN]:日
// hour[IN]:时
// minute[IN]:分
// second[IN]:秒
//
// 返回对应的儒略日
// ------------------------------------------------------------------------
double w_GDToJD(const int &year, const int &month, const int &day, const int &hour, const int &minute, const int &second)
{
int nY = year, nM = month;
double dD = static_cast(day) + hour / 24.0 + (minute / 60.0) / 24.0 + ((second / 60.0) / 60.0) / 24.0;
if(month == 1 || month == 2)
{
nY = year - 1;
nM = month + 12;
}
int nA = nY / 100;
int nB = 2 - nA + (nA >> 2);
return static_cast(static_cast(365.25 * (nY + 4716)) + static_cast(30.6001 * (nM + 1)) + dD + nB - 1524.5);
}
3.2、角度调整
// ------------------------------------------------------------------------
//
// 调整角度到 0-360 之间
//
// dbDegrees[IN]:角度
//
// 返回调整后的角度
// ------------------------------------------------------------------------
double w_MapTo0To360Range(const double &dbDegrees)
{
double dbValue = dbDegrees;
// map it to the range 0 - 360
while(dbValue < 0.0)
dbValue += 360.0;
while(dbValue > 360.0)
dbValue -= 360.0;
return dbValue;
}
四、步骤简述
4.1、按照 5.2 和 5.3 节介绍方法计算出某一时刻太阳黄经和黄纬。
4.2、按照 5.4 、5.5、5.6 节介绍的三次校正方法对黄经进行校正,即可求得某时刻太阳黄经度数。
4.3、综合步骤演示在第 六 节。
4.4、第 七 节演示如何使用二分法求指定节气的时间。
4.5、第 八 节介绍了 本地时间(LST)和格林尼治时间(UTC)之间互换的函数。
4.6、第 九 节罗列出了本代码计算出的 2008年24节气时间(北京时间)
五、太阳黄经、黄纬、向量半径以及校正量的计算
5.1、为了代码可读性,定义类型变量。
//
// 圆周率
const double PI = 3.1415926535897932384626433832795;
//
// 一度代表的弧度
const double dbUnitRadian = PI / 180.0;
//
// 计算太阳黄经赤纬所需类型变量
typedef struct
{
double dA;
double dB;
double dC;
} VSOP87COEFFICIENT, *PVSOP87COEFFICIENT
// 二次修正黄经赤纬所需的天体章动系数类型变量
typedef struct
{
int nD;
int nM;
int nMprime;
int nF;
int nOmega;
int nSincoeff1;
double dSincoeff2;
int nCoscoeff1;
double dCoscoeff2;
} NUTATIONCOEFFICIENT, *PNUTATIONCOEFFICIENT;
//
// 节气枚举
enum SOLARTERMS
{
ST_VERNAL_EQUINOX = 0, // 春分
ST_CLEAR_AND_BRIGHT = 15, // 清明
ST_GRAIN_RAIN = 30, // 谷雨
ST_SUMMER_BEGINS = 45, // 立夏
ST_GRAIN_BUDS = 60, // 小满
ST_GRAIN_IN_EAR = 75, // 芒种
ST_SUMMER_SOLSTICE = 90, // 夏至
ST_SLIGHT_HEAT = 105, // 小暑
ST_GREAT_HEAT = 120, // 大暑
ST_AUTUMN_BEGINS = 135, // 立秋
ST_STOPPING_THE_HEAT = 150, // 处暑
ST_WHITE_DEWS = 165, // 白露
ST_AUTUMN_EQUINOX = 180, // 秋分
ST_COLD_DEWS = 195, // 寒露
ST_HOAR_FROST_FALLS = 210, // 霜降
ST_WINTER_BEGINS = 225, // 立冬
ST_LIGHT_SNOW = 240, // 小雪
ST_HEAVY_SNOW = 255, // 大雪
ST_WINTER_SOLSTICE = 270, // 冬至
ST_SLIGHT_COLD = 285, // 小寒
ST_GREAT_COLD = 300, // 大寒
ST_SPRING_BEGINS = 315, // 立春
ST_THE_RAINS = 330, // 雨水
ST_INSECTS_AWAKEN = 345 // 惊蛰
};
5.2、黄经:(算法描述请参考相关书籍,为了节省空间和时间,这里请罗列出 C++ 实现代码)
//
// 计算太阳黄经用参数
const VSOP87COEFFICIENT Earth_SLG0[64] =
{
{ 175347046.0 , 0.0000000 , 000000.0000000 } ,
{ 3341656.0 , 4.6692568 , 6283.0758500 } ,
{ 34894.0 , 4.6261000 , 12566.1517000 } ,
{ 3497.0 , 2.7441000 , 5753.3849000 } ,
{ 3418.0 , 2.8289000 , 3.5231000 } ,
{ 3136.0 , 3.6277000 , 77713.7715000 } ,
{ 2676.0 , 4.4181000 , 7860.4194000 } ,
{ 2343.0 , 6.1352000 , 3930.2097000 } ,
{ 1324.0 , 0.7425000 , 11506.7698000 } ,
{ 1273.0 , 2.0371000 , 529.6910000 } ,
{ 1199.0 , 1.1096000 , 1577.3435000 } ,
{ 990.0 , 5.2330000 , 5884.9270000 } ,
{ 902.0 , 2.0450000 , 26.2980000 } ,
{ 857.0 , 3.5080000 , 398.1490000 } ,
{ 780.0 , 1.1790000 , 5223.6940000 } ,
{ 753.0 , 2.5330000 , 5507.5530000 } ,
{ 505.0 , 4.5830000 , 18849.2280000 } ,
{ 492.0 , 4.2050000 , 775.5230000 } ,
{ 357.0 , 2.9200000 , 000000.0670000 } ,
{ 317.0 , 5.8490000 , 11790.6290000 } ,
{ 284.0 , 1.8990000 , 796.2880000 } ,
{ 271.0 , 0.3150000 , 10977.0790000 } ,
{ 243.0 , 0.3450000 , 5486.7780000 } ,
{ 206.0 , 4.8060000 , 2544.3140000 } ,
{ 205.0 , 1.8690000 , 5573.1430000 } ,
{ 202.0 , 2.4580000 , 6069.7770000 } ,
{ 156.0 , 0.8330000 , 213.2990000 } ,
{ 132.0 , 3.4110000 , 2942.4630000 } ,
{ 126.0 , 1.0830000 , 20.7750000 } ,
{ 115.0 , 0.6450000 , 000000.9800000 } ,
{ 103.0 , 0.6360000 , 4694.0030000 } ,
{ 102.0 , 0.9760000 , 15720.8390000 } ,
{ 102.0 , 4.2670000 , 7.1140000 } ,
{ 99.0 , 6.2100000 , 2146.1700000 } ,
{ 98.0 , 0.6800000 , 155.4200000 } ,
{ 86.0 , 5.9800000 , 161000.6900000 } ,
{ 85.0 , 1.3000000 , 6275.9600000 } ,
{ 85.0 , 3.6700000 , 71430.7000000 } ,
{ 80.0 , 1.8100000 , 17260.1500000 } ,
{ 79.0 , 3.0400000 , 12036.4600000 } ,
{ 75.0 , 1.7600000 , 5088.6300000 } ,
{ 74.0 , 3.5000000 , 3154.6900000 } ,
{ 74.0 , 4.6800000 , 801.8200000 } ,
{ 70.0 , 0.8300000 , 9437.7600000 } ,
{ 62.0 , 3.9800000 , 8827.3900000 } ,
{ 61.0 , 1.8200000 , 7084.9000000 } ,
{ 57.0 , 2.7800000 , 6286.6000000 } ,
{ 56.0 , 4.3900000 , 14143.5000000 } ,
{ 56.0 , 3.4700000 , 6279.5500000 } ,
{ 52.0 , 0.1900000 , 12139.5500000 } ,
{ 52.0 , 1.3300000 , 1748.0200000 } ,
{ 51.0 , 0.2800000 , 5856.4800000 } ,
{ 49.0 , 0.4900000 , 1194.4500000 } ,
{ 41.0 , 5.3700000 , 8429.2400000 } ,
{ 41.0 , 2.4000000 , 19651.0500000 } ,
{ 39.0 , 6.1700000 , 10447.3900000 } ,
{ 37.0 , 6.0400000 , 10213.2900000 } ,
{ 37.0 , 2.5700000 , 1059.3800000 } ,
{ 36.0 , 1.7100000 , 2352.8700000 } ,
{ 36.0 , 1.7800000 , 6812.7700000 } ,
{ 33.0 , 0.5900000 , 17789.8500000 } ,
{ 30.0 , 0.4400000 , 83996.8500000 } ,
{ 30.0 , 2.7400000 , 1349.8700000 } ,
{ 25.0 , 3.1600000 , 4690.4800000 }
};
const VSOP87COEFFICIENT Earth_SLG1[34] =
{
{ 628331966747.0 , 0.000000 , 00000.0000000 } ,
{ 206059.0 , 2.678235 , 6283.0758500 } ,
{ 4303.0 , 2.635100 , 12566.1517000 } ,
{ 425.0 , 1.590000 , 3.5230000 } ,
{ 119.0 , 5.796000 , 26.2980000 } ,
{ 109.0 , 2.966000 , 1577.3440000 } ,
{ 93.0 , 2.590000 , 18849.2300000 } ,
{ 72.0 , 1.140000 , 529.6900000 } ,
{ 68.0 , 1.870000 , 398.1500000 } ,
{ 67.0 , 4.410000 , 5507.5500000 } ,
{ 59.0 , 2.890000 , 5223.6900000 } ,
{ 56.0 , 2.170000 , 155.4200000 } ,
{ 45.0 , 0.400000 , 796.3000000 } ,
{ 36.0 , 0.470000 , 775.5200000 } ,
{ 29.0 , 2.650000 , 7.1100000 } ,
{ 21.0 , 5.430000 , 00000.9800000 } ,
{ 19.0 , 1.850000 , 5486.7800000 } ,
{ 19.0 , 4.970000 , 213.3000000 } ,
{ 17.0 , 2.990000 , 6275.9600000 } ,
{ 16.0 , 0.030000 , 2544.3100000 } ,
{ 16.0 , 1.430000 , 2146.1700000 } ,
{ 15.0 , 1.210000 , 10977.0800000 } ,
{ 12.0 , 2.830000 , 1748.0200000 } ,
{ 12.0 , 3.260000 , 5088.6300000 } ,
{ 12.0 , 5.270000 , 1194.4500000 } ,
{ 12.0 , 2.080000 , 4694.0000000 } ,
{ 11.0 , 0.770000 , 553.5700000 } ,
{ 10.0 , 1.300000 , 6286.6000000 } ,
{ 10.0 , 4.240000 , 1349.8700000 } ,
{ 9.0 , 2.700000 , 242.7300000 } ,
{ 9.0 , 5.640000 , 951.7200000 } ,
{ 8.0 , 5.300000 , 2352.8700000 } ,
{ 6.0 , 2.650000 , 9437.7600000 } ,
{ 6.0 , 4.670000 , 4690.4800000 }
};
const VSOP87COEFFICIENT Earth_SLG2[20] =
{
{ 52919.0 , 0.0000 , 00000.0000 } ,
{ 8720.0 , 1.0721 , 6283.0758 } ,
{ 309.0 , 0.8670 , 12566.1520 } ,
{ 27.0 , 0.0500 , 3.5200 } ,
{ 16.0 , 5.1900 , 26.3000 } ,
{ 16.0 , 3.6800 , 155.4200 } ,
{ 10.0 , 0.7600 , 18849.2300 } ,
{ 9.0 , 2.0600 , 77713.7700 } ,
{ 7.0 , 0.8300 , 775.5200 } ,
{ 5.0 , 4.6600 , 1577.3400 } ,
{ 4.0 , 1.0300 , 7.1100 } ,
{ 4.0 , 3.4400 , 5573.1400 } ,
{ 3.0 , 5.1400 , 796.3000 } ,
{ 3.0 , 6.0500 , 5507.5500 } ,
{ 3.0 , 1.1900 , 242.7300 } ,
{ 3.0 , 6.1200 , 529.6900 } ,
{ 3.0 , 0.3100 , 398.1500 } ,
{ 3.0 , 2.2800 , 553.5700 } ,
{ 2.0 , 4.3800 , 5223.6900 } ,
{ 2.0 , 3.7500 , 00000.9800 }
};
const VSOP87COEFFICIENT Earth_SLG3[ 7] =
{
{ 289.0 , 5.844 , 6283.076 } ,
{ 35.0 , 0.000 , 00000.000 } ,
{ 17.0 , 5.490 , 12566.150 } ,
{ 3.0 , 5.200 , 155.420 } ,
{ 1.0 , 4.720 , 3.520 } ,
{ 1.0 , 5.300 , 18849.230 } ,
{ 1.0 , 5.970 , 242.730 }
};
const VSOP87COEFFICIENT Earth_SLG4[ 3] =
{
{ 114.0 , 3.142 , 00000.00 } ,
{ 8.0 , 4.130 , 6283.08 } ,
{ 1.0 , 3.840 , 12566.15 }
};
const VSOP87COEFFICIENT Earth_SLG5[ 1] =
{
{ 1.0 , 3.14 , 0.0 }
};
// ------------------------------------------------------------------------
//
// 计算太阳在黄道面上的经度(单位:度)
//
// dbJD[IN]:儒略日(计算该时刻太阳在黄道面上的经度)
//
// 返回太阳黄经度数
// ------------------------------------------------------------------------
double w_GetSunLongitude(const double & dbJD)
{
// 计算τ
double dt = (dbJD - 2451545.0) / 365250.0;
double dL = 0.0, dL0 = 0.0, dL1 = 0.0, dL2 = 0.0, dL3 = 0.0, dL4 = 0.0, dL5 = 0.0;
// L0 38x3
for(int i = 0; i < sizeof(Earth_SLG0) / sizeof(VSOP87COEFFICIENT); i++)
dL0 += (Earth_SLG0[i].dA * cos((Earth_SLG0[i].dB + Earth_SLG0[i].dC * dt)));
// L1 16x3
for(int i = 0; i < sizeof(Earth_SLG1) / sizeof(VSOP87COEFFICIENT); i++)
dL1 += (Earth_SLG1[i].dA * cos((Earth_SLG1[i].dB + Earth_SLG1[i].dC * dt)));
// L2 10x3
for(int i = 0; i < sizeof(Earth_SLG2) / sizeof(VSOP87COEFFICIENT); i++)
dL2 += (Earth_SLG2[i].dA * cos((Earth_SLG2[i].dB + Earth_SLG2[i].dC * dt)));
// L3 8x3
for(int i = 0; i < sizeof(Earth_SLG3) / sizeof(VSOP87COEFFICIENT); i++)
dL3 += (Earth_SLG3[i].dA * cos((Earth_SLG3[i].dB + Earth_SLG3[i].dC * dt)));
// L4 6x3
for(int i = 0; i < sizeof(Earth_SLG4) / sizeof(VSOP87COEFFICIENT); i++)
dL4 += (Earth_SLG4[i].dA * cos((Earth_SLG4[i].dB + Earth_SLG4[i].dC * dt)));
// L5 1x3
for(int i = 0; i < sizeof(Earth_SLG5) / sizeof(VSOP87COEFFICIENT); i++)
dL5 += (Earth_SLG5[i].dA * cos((Earth_SLG5[i].dB + Earth_SLG5[i].dC * dt)));
// 计算 L = ( L0 + L1 * τ^1 + L2 * τ^2 + L3 * τ^3 + L4 * τ^4 + L5 * τ^5 ) / 10^8 ;(单位弧度)
dL = (dL0 + (dL1 * dt) + (dL2 * (dt * dt)) + (dL3 * (dt * dt * dt)) * (dL4 * (dt * dt * dt * dt)) + (dL5 * (dt * dt * dt * dt * dt))) / 100000000.0;
// 转化为度θ = L + 180 (单位度);
return (w_MapTo0To360Range(w_MapTo0To360Range(dL / dbUnitRadian) + 180.0));
}
5.3、黄纬:(算法描述请参考相关书籍,为了节省空间和时间,这里请罗列出 C++ 实现代码)
//
// 计算太阳黄纬用参数
const VSOP87COEFFICIENT Earth_SLT0[ 5] =
{
{ 280.0 , 3.199 , 84334.662 } ,
{ 102.0 , 5.422 , 5507.553 } ,
{ 80.0 , 3.880 , 5223.690 } ,
{ 44.0 , 3.700 , 2352.870 } ,
{ 32.0 , 4.000 , 1577.340 }
};
const VSOP87COEFFICIENT Earth_SLT1[ 2] =
{
{ 9.0 , 3.90 , 5507.55 } ,
{ 6.0 , 1.73 , 5223.69 }
};
const VSOP87COEFFICIENT Earth_SLT2[ 4] =
{
{ 22378.0 , 3.38509 , 10213.28555 } ,
{ 282.0 , 0.00000 , 00000.00000 } ,
{ 173.0 , 5.25600 , 20426.57100 } ,
{ 27.0 , 3.87000 , 30639.86000 }
};
const VSOP87COEFFICIENT Earth_SLT3[ 4] =
{
{ 647.0 , 4.992 , 10213.286 } ,
{ 20.0 , 3.140 , 00000.000 } ,
{ 6.0 , 0.770 , 20426.570 } ,
{ 3.0 , 5.440 , 30639.860 }
};
const VSOP87COEFFICIENT CWDCalendarEngine::Earth_SLT4[ 1] =
{
{ 14.0 , 0.32 , 10213.29 }
};
// ------------------------------------------------------------------------
//
// 计算太阳在黄道面上的纬度(单位:度)
//
// dbJD[IN]:儒略日(计算该时刻太阳在黄道面上的纬度)
//
// 返回太阳黄纬度数
// ------------------------------------------------------------------------
double w_GetSunLatitude(const double & dbJD)
{
// 计算τ
double dbt = (dbJD - 2451545.0) / 365250.0;
double dbB = 0.0, dbB0 = 0.0, dbB1 = 0.0, dbB2 = 0.0, dbB3 = 0.0, dbB4 = 0.0;
// B0 5x3
for(int i = 0; i < sizeof(Earth_SLT0) / sizeof(VSOP87COEFFICIENT); i++)
dbB0 += (Earth_SLT0[i].dA * cos((Earth_SLT0[i].dB + Earth_SLT0[i].dC * dbt)));
// B1 2x3
for(int i = 0; i < sizeof(Earth_SLT1) / sizeof(VSOP87COEFFICIENT); i++)
dbB1 += (Earth_SLT1[i].dA * cos((Earth_SLT1[i].dB + Earth_SLT1[i].dC * dbt)));
// B2 4x3
for(int i = 0; i < sizeof(Earth_SLT2) / sizeof(VSOP87COEFFICIENT); i++)
dbB2 += (Earth_SLT2[i].dA * cos((Earth_SLT2[i].dB + Earth_SLT2[i].dC * dbt)));

// B3 4x3
for(int i = 0; i < sizeof(Earth_SLT3) / sizeof(VSOP87COEFFICIENT); i++)
dbB3 += (Earth_SLT3[i].dA * cos((Earth_SLT3[i].dB + Earth_SLT3[i].dC * dbt)));

// B4 1x3
for(int i = 0; i < sizeof(Earth_SLT4) / sizeof(VSOP87COEFFICIENT); i++)
dbB4 += (Earth_SLT4[i].dA * cos((Earth_SLT4[i].dB + Earth_SLT4[i].dC * dbt)));

// 计算 B = ( B0 + B1 * τ^1 + B2 * τ^2 + B3 * τ^3 + B4 * τ^4 ) / 10^8 ;(单位弧度)
dbB = (dbB0 + (dbB1 * dbt) + (dbB2 * (dbt * dbt)) + (dbB3 * (dbt * dbt * dbt)) * (dbB4 * (dbt * dbt * dbt * dbt))) / 100000000.0;
// 计算 θ(单位度);
return -(dbB / dbUnitRadian);
}


5.4、第一次校正黄经:(算法描述请参考相关书籍,为了节省空间和时间,这里请罗列出 C++ 实现代码)
// ------------------------------------------------------------------------
//
// 修正某时刻太阳在黄道上的经度(黄经)
//
// dbSrcLongitude[IN]:黄经
// dbSrcLatitude[IN]:黄纬
// dbJD[IN]:儒略日(修正在该时刻太阳的黄经)
//
// 返回太阳黄经度数
// ------------------------------------------------------------------------
double w_CorrectionCalcSunLongitude(const double &dbSrcLongitude, const double &dbSrcLatitude, const double &dbJD)
{
double dbT = (dbJD - 2451545.0) / 36525.0;
double dbLdash = dbSrcLongitude - 1.397 * dbT - 0.00031 * dbT * dbT;
// 转换为弧度
dbLdash *= dbUnitRadian;
return (-0.09033 + 0.03916 * (cos(dbLdash) + sin(dbLdash)) * tan(dbSrcLatitude * dbUnitRadian)) / 3600.0;
}
5.5、第二次校正黄经(章动):(算法描述请参考相关书籍,为了节省空间和时间,这里请罗列出 C++ 实现代码)
//
// 二次修正黄经黄纬所需的天体章动系数
const NUTATIONCOEFFICIENT Nutation_Gene[63] =
{
{ 0, 0, 0, 0, 1, -171996, -174.2, 92025, 8.9 },
{ -2, 0, 0, 2, 2, -13187, -1.6, 5736, -3.1 },
{ 0, 0, 0, 2, 2, -2274, -0.2, 977, -0.5 },
{ 0, 0, 0, 0, 2, 2062, 0.2, -895, 0.5 },
{ 0, 1, 0, 0, 0, 1426, -3.4, 54, -0.1 },
{ 0, 0, 1, 0, 0, 712, 0.1, -7, 0 },
{ -2, 1, 0, 2, 2, -517, 1.2, 224, -0.6 },
{ 0, 0, 0, 2, 1, -386, -0.4, 200, 0 },
{ 0, 0, 1, 2, 2, -301, 0, 129, -0.1 },
{ -2, -1, 0, 2, 2, 217, -0.5, -95, 0.3 },
{ -2, 0, 1, 0, 0, -158, 0, 0, 0 },
{ -2, 0, 0, 2, 1, 129, 0.1, -70, 0 },
{ 0, 0, -1, 2, 2, 123, 0, -53, 0 },
{ 2, 0, 0, 0, 0, 63, 0, 0, 0 },
{ 0, 0, 1, 0, 1, 63, 0.1, -33, 0 },
{ 2, 0, -1, 2, 2, -59, 0, 26, 0 },
{ 0, 0, -1, 0, 1, -58, -0.1, 32, 0 },
{ 0, 0, 1, 2, 1, -51, 0, 27, 0 },
{ -2, 0, 2, 0, 0, 48, 0, 0, 0 },
{ 0, 0, -2, 2, 1, 46, 0, -24, 0 },
{ 2, 0, 0, 2, 2, -38, 0, 16, 0 },
{ 0, 0, 2, 2, 2, -31, 0, 13, 0 },
{ 0, 0, 2, 0, 0, 29, 0, 0, 0 },
{ -2, 0, 1, 2, 2, 29, 0, -12, 0 },
{ 0, 0, 0, 2, 0, 26, 0, 0, 0 },
{ -2, 0, 0, 2, 0, -22, 0, 0, 0 },
{ 0, 0, -1, 2, 1, 21, 0, -10, 0 },
{ 0, 2, 0, 0, 0, 17, -0.1, 0, 0 },
{ 2, 0, -1, 0, 1, 16, 0, -8, 0 },
{ -2, 2, 0, 2, 2, -16, 0.1, 7, 0 },
{ 0, 1, 0, 0, 1, -15, 0, 9, 0 },
{ -2, 0, 1, 0, 1, -13, 0, 7, 0 },
{ 0, -1, 0, 0, 1, -12, 0, 6, 0 },
{ 0, 0, 2,-2, 0, 11, 0, 0, 0 },
{ 2, 0, -1, 2, 1, -10, 0, 5, 0 },
{ 2, 0, 1, 2, 2, -8, 0, 3, 0 },
{ 0, 1, 0, 2, 2, 7, 0, -3, 0 },
{ -2, 1, 1, 0, 0, -7, 0, 0, 0 },
{ 0, -1, 0, 2, 2, -7, 0, 3, 0 },
{ 2, 0, 0, 2, 1, -7, 0, 3, 0 },
{ 2, 0, 1, 0, 0, 6, 0, 0, 0 },
{ -2, 0, 2, 2, 2, 6, 0, -3, 0 },
{ -2, 0, 1, 2, 1, 6, 0, -3, 0 },
{ 2, 0, -2, 0, 1, -6, 0, 3, 0 },
{ 2, 0, 0, 0, 1, -6, 0, 3, 0 },
{ 0, -1, 1, 0, 0, 5, 0, 0, 0 },
{ -2, -1, 0, 2, 1, -5, 0, 3, 0 },
{ -2, 0, 0, 0, 1, -5, 0, 3, 0 },
{ 0, 0, 2, 2, 1, -5, 0, 3, 0 },
{ -2, 0, 2, 0, 1, 4, 0, 0, 0 },
{ -2, 1, 0, 2, 1, 4, 0, 0, 0 },
{ 0, 0, 1,-2, 0, 4, 0, 0, 0 },
{ -1, 0, 1, 0, 0, -4, 0, 0, 0 },
{ -2, 1, 0, 0, 0, -4, 0, 0, 0 },
{ 1, 0, 0, 0, 0, -4, 0, 0, 0 },
{ 0, 0, 1, 2, 0, 3, 0, 0, 0 },
{ 0, 0, -2, 2, 2, -3, 0, 0, 0 },
{ -1, -1, 1, 0, 0, -3, 0, 0, 0 },
{ 0, 1, 1, 0, 0, -3, 0, 0, 0 },
{ 0, -1, 1, 2, 2, -3, 0, 0, 0 },
{ 2, -1, -1, 2, 2, -3, 0, 0, 0 },
{ 0, 0, 3, 2, 2, -3, 0, 0, 0 },
{ 2, -1, 0, 2, 2, -3, 0, 0, 0 }
};
// ------------------------------------------------------------------------
//
// 计算天体章动
// 二次修正某时刻太阳在黄道上的纬度(单位:度)
// 使用天体章动系数修正,消除扰动影响
//
// dbJD[IN]:儒略日(计算该时刻天体章动补偿量)
//
// 返回天体扰动干扰量
// ------------------------------------------------------------------------
double w_GetNutationJamScalar(const double &dbJD)
{
double dbT = (dbJD - 2451545.0) / 36525.0;
double dbTsquared = dbT * dbT;
double dbTcubed = dbTsquared * dbT;
double dbD = 297.85036 + 445267.111480 * dbT - 0.0019142 * dbTsquared + dbTcubed / 189474.0;
dbD = w_MapTo0To360Range(dbD);
double dbM = 357.52772 + 35999.050340 * dbT - 0.0001603 * dbTsquared - dbTcubed / 300000.0;
dbM = w_MapTo0To360Range(dbM);
double dbMprime = 134.96298 + 477198.867398 * dbT + 0.0086972 * dbTsquared + dbTcubed / 56250.0;
dbMprime = w_MapTo0To360Range(dbMprime);
double dbF = 93.27191 + 483202.017538 * dbT - 0.0036825 * dbTsquared + dbTcubed / 327270.0;
dbF = w_MapTo0To360Range(dbF);
double dbOmega = 125.04452 - 1934.136261 * dbT + 0.0020708 * dbTsquared + dbTcubed / 450000.0;
dbOmega = w_MapTo0To360Range(dbOmega);
double dbResulte = 0.0 ;
for(int i = 0; i < sizeof(Nutation_Gene) / sizeof(NUTATIONCOEFFICIENT); i++)
{
double dbRadargument = (Nutation_Gene[i].nD * dbD + Nutation_Gene[i].nM * dbM + Nutation_Gene[i].nMprime * dbMprime + Nutation_Gene[i].nF * dbF + Nutation_Gene[i].nOmega * dbOmega) * dbUnitRadian;
dbResulte += (Nutation_Gene[i].nSincoeff1 + Nutation_Gene[i].dSincoeff2 * dbT ) * sin(dbRadargument) * 0.0001;
}
return dbResulte;
}

5.6、第三次校正黄经(太阳半径向量):(算法描述请参考相关书籍,为了节省空间和时间,这里请罗列出 C++ 实现代码)
//
// 计算太阳向量半径用参数
const VSOP87COEFFICIENT Earth_SRV0[40] =
{
{ 100013989 , 0 , 0 },
{ 1670700 , 3.0984635 , 6283.0758500 },
{ 13956 , 3.05525 , 12566.15170 },
{ 3084 , 5.1985 , 77713.7715 },
{ 1628 , 1.1739 , 5753.3849 },
{ 1576 , 2.8469 , 7860.4194 },
{ 925 , 5.453 , 11506.770 },
{ 542 , 4.564 , 3930.210 },
{ 472 , 3.661 , 5884.927 },
{ 346 , 0.964 , 5507.553 },
{ 329 , 5.900 , 5223.694 },
{ 307 , 0.299 , 5573.143 },
{ 243 , 4.273 , 11790.629 },
{ 212 , 5.847 , 1577.344 },
{ 186 , 5.022 , 10977.079 },
{ 175 , 3.012 , 18849.228 },
{ 110 , 5.055 , 5486.778 },
{ 98 , 0.89 , 6069.78 },
{ 86 , 5.69 , 15720.84 },
{ 86 , 1.27 , 161000.69 },
{ 65 , 0.27 , 17260.15 },
{ 63 , 0.92 , 529.69 },
{ 57 , 2.01 , 83996.85 },
{ 56 , 5.24 , 71430.70 },
{ 49 , 3.25 , 2544.31 },
{ 47 , 2.58 , 775.52 },
{ 45 , 5.54 , 9437.76 },
{ 43 , 6.01 , 6275.96 },
{ 39 , 5.36 , 4694.00 },
{ 38 , 2.39 , 8827.39 },
{ 37 , 0.83 , 19651.05 },
{ 37 , 4.90 , 12139.55 },
{ 36 , 1.67 , 12036.46 },
{ 35 , 1.84 , 2942.46 },
{ 33 , 0.24 , 7084.90 },
{ 32 , 0.18 , 5088.63 },
{ 32 , 1.78 , 398.15 },
{ 28 , 1.21 , 6286.60 },
{ 28 , 1.90 , 6279.55 },
{ 26 , 4.59 , 10447.39 }
};
const VSOP87COEFFICIENT Earth_SRV1[10] =
{
{ 103019 , 1.107490 , 6283.075850 },
{ 1721 , 1.0644 , 12566.1517 },
{ 702 , 3.142 , 0 },
{ 32 , 1.02 , 18849.23 },
{ 31 , 2.84 , 5507.55 },
{ 25 , 1.32 , 5223.69 },
{ 18 , 1.42 , 1577.34 },
{ 10 , 5.91 , 10977.08 },
{ 9 , 1.42 , 6275.96 },
{ 9 , 0.27 , 5486.78 }
};
const VSOP87COEFFICIENT Earth_SRV2[ 6] =
{
{ 4359 , 5.7846 , 6283.0758 },
{ 124 , 5.579 , 12566.152 },
{ 12 , 3.14 , 0 },
{ 9 , 3.63 , 77713.77 },
{ 6 , 1.87 , 5573.14 },
{ 3 , 5.47 , 18849.23 }
};
const VSOP87COEFFICIENT Earth_SRV3[ 2] =
{
{ 145 , 4.273 , 6283.076 },
{ 7 , 3.92 , 12566.15 }
};
const VSOP87COEFFICIENT Earth_SRV4[ 1] =
{
{ 4 , 2.56 , 6283.08 }
};
// ------------------------------------------------------------------------
//
// 计算某时刻太阳半径向量
// 三次修正某时刻太阳在黄道上的经度(单位:弧度)
//
// dbJD[IN]:儒略日(计算该时刻太阳半径向量)
//
// 返回太阳半径向量
// ------------------------------------------------------------------------
double w_GetSunRadiusVector(const double &dbJD)
{
// 计算τ
double dbt = (dbJD - 2451545.0) / 365250.0;
double dbR = 0.0, dbR0 = 0.0, dbR1 = 0.0, dbR2 = 0.0, dbR3 = 0.0, dbR4 = 0.0;
// R0 40x3
for(int i = 0; i < sizeof(Earth_SRV0) / sizeof(VSOP87COEFFICIENT); i++)
dbR0 += (Earth_SRV0[i].dA * cos((Earth_SRV0[i].dB + Earth_SRV0[i].dC * dbt)));
// R1 10x3
for(int i = 0; i < sizeof(Earth_SRV1) / sizeof(VSOP87COEFFICIENT); i++)
dbR1 += (Earth_SRV1[i].dA * cos((Earth_SRV1[i].dB + Earth_SRV1[i].dC * dbt)));
// R2 6x3
for(int i = 0; i < sizeof(Earth_SRV2) / sizeof(VSOP87COEFFICIENT); i++)
dbR2 += (Earth_SRV2[i].dA * cos((Earth_SRV2[i].dB + Earth_SRV2[i].dC * dbt)));
// R3 2x3
for(int i = 0; i < sizeof(Earth_SRV3) / sizeof(VSOP87COEFFICIENT); i++)
dbR3 += (Earth_SRV3[i].dA * cos((Earth_SRV3[i].dB + Earth_SRV3[i].dC * dbt)));
// R4 1x3
for(int i = 0; i < sizeof(Earth_SRV4) / sizeof(VSOP87COEFFICIENT); i++)
dbR4 += (Earth_SRV4[i].dA * cos((Earth_SRV4[i].dB + Earth_SRV4[i].dC * dbt)));
// 计算 R = ( R0 + R1 * τ^1 + R2 * τ^2 + R3 * τ^3 + R4 * τ^4 ) / 10^8 ;(单位弧度)
return ((dbR0 + (dbR1 * dbt) + (dbR2 * (dbt * dbt)) + (dbR3 * (dbt * dbt * dbt)) * (dbR4 * (dbt * dbt * dbt * dbt))) / 100000000.0);
}
六、计算指定时刻太阳黄道经度度数(算法描述请参考相关书籍,为了节省空间和时间,这里请罗列出 C++ 实现代码)
利用以上介绍算法,如下:
// ------------------------------------------------------------------------
//
// 计算某时刻太阳黄经黄纬
//
// dbJD[IN]:儒略日(计算该时刻太阳在黄道面上的经度和纬度)
// dbLongitude[OUT]:黄经
// dbLatitude[OUT]:黄纬
// ------------------------------------------------------------------------
void w_CalcEclipticLongLat(const double & dbJD, double &dbLongitude, double &dbLatitude)
{
// 计算太阳黄经
dbLongitude = w_GetSunLongitude(dbJD);
// 计算太阳黄纬
dbLatitude = w_GetSunLatitude(dbJD);
// 一次校正经度
dbLongitude += w_CorrectionCalcSunLongitude(dbLongitude, dbLatitude, dbJD);
// 二次校正天体章动
dbLongitude += w_GetNutationJamScalar(dbJD) / 3600.0;
// 三次校正太阳半径向量
dbLongitude -= (20.4898 / w_GetSunRadiusVector(dbJD)) /3600.0;
// 校正太阳黄纬
dbLatitude += w_CorrectionCalcSunLatitude(dbLongitude, dbJD);
}
七、使用二分法计算指定节气的时间
// ------------------------------------------------------------------------
//
// 计算节气
//
// year[IN]:公历年份(计算该年份,指定节气的时间)
// ST_SolarTerms[IN]:节气指定的节气
//
// 返回指定节气的儒略日时间
// ------------------------------------------------------------------------
double w_CalcSolarTerms(const int &year, const SOLARTERMS &ST_SolarTerms)
{
// 节气月份
int SolarTermsMonth = static_cast(ceil(static_cast((ST_SolarTerms + 90.0) / 30.0)));
SolarTermsMonth = SolarTermsMonth > 12 ? SolarTermsMonth - 12 : SolarTermsMonth;
// 节令的发生日期基本都在每月 4 - 9 号间
int LowerLimitSolarTermsDay = ST_SolarTerms % 15 == 0 && ST_SolarTerms % 30 != 0 ? 4 : 16;
// 节气的发生日期基本都在每月 16 - 24 号间
int UpperLimitSolarTermsDay = ST_SolarTerms % 15 == 0 && ST_SolarTerms % 30 != 0 ? 9 : 24;
// 采用二分法逼近计算
double dbLowerLinit = w_GDToJD(year, SolarTermsMonth, LowerLimitSolarTermsDay, 0, 0, 0);
double dbUpperLinit = w_GDToJD(year, SolarTermsMonth, UpperLimitSolarTermsDay, 23, 59, 59);
// 二分法分界点日期
double dbDichotomyDivisionDayJD = 0;
// 太阳黄经角度
double dbLongitude = 0;
// 对比二分法精度是否达到要求
for(; fabs(dbLongitude - static_cast(ST_SolarTerms)) >= 0.00001;)
{
dbDichotomyDivisionDayJD = ((dbUpperLinit - dbLowerLinit) / 2.0) + dbLowerLinit;
// 计算太阳黄经
dbLongitude = w_GetSunLongitude(dbDichotomyDivisionDayJD);
// 一次校正经度
dbLongitude += w_CorrectionCalcSunLongitude(dbLongitude, w_GetSunLatitude(dbDichotomyDivisionDayJD), dbDichotomyDivisionDayJD);
// 二次校正天体章动
dbLongitude += w_GetNutationJamScalar(dbDichotomyDivisionDayJD) / 3600.0;
// 三次校正太阳半径向量
dbLongitude -= (20.4898 / w_GetSunRadiusVector(dbDichotomyDivisionDayJD)) /3600.0;
// 由于春分这天黄经为 0 度,比较特殊,因此专门判断(如不加以特殊对待则会导致计算范围覆盖整个 360 度角)
dbLongitude = ((ST_SolarTerms == ST_VERNAL_EQUINOX) && (dbLongitude > 345.0)) ? -dbLongitude : dbLongitude;
// 调整二分法上下限
(dbLongitude > static_cast(ST_SolarTerms)) ? dbUpperLinit = dbDichotomyDivisionDayJD : dbLowerLinit = dbDichotomyDivisionDayJD;
}
return dbDichotomyDivisionDayJD;
}
八、本地时间(LST)和格林尼治时间(UTC)的互换(仅限于 VC 代码)
// ------------------------------------------------------------------------
//
// 格林威治时间转本地时间(以格里历表示本地时间)
//
// ------------------------------------------------------------------------
void w_UTCToLST(int &year, int &month, int &day, int &hour, int &minute, int &second)
{
_tzset () ;
// 计算本地时间和标准时间的时差(单位:秒)
int nDifference_hour = static_cast(_timezone / 3600);
int nDifference_minute = static_cast((_timezone - nDifference_hour * 3600) / 60);
int nDifference_second = static_cast((_timezone - nDifference_hour * 3600) - nDifference_minute * 60);
// 格林威治时间 + 时差 = 本地时间
// 秒
second = second - nDifference_second;
if(second >= 60 || second < 0)
{
minute = second > 0 ? minute + 1 : minute - 1 ;
second = abs(abs(second) - 60);
}
// 分
minute = minute - nDifference_minute;
if(minute >= 60 || minute < 0)
{
hour = minute > 0 ? hour + 1 : hour - 1;
minute = abs(abs(minute) - 60);
}
// 时
hour = hour - nDifference_hour;
if(hour >= 24 || hour < 0)
{
day = (hour >= 24 || hour == 0) ? day + 1 : day - 1;
hour = abs(abs(hour) - 24);
}
// 日
int nDaysOfMonth = w_GetDaysOfMonth(year, month);
if(day > nDaysOfMonth || day <= 0)
{
if(day > nDaysOfMonth)
month++;
if(day < nDaysOfMonth || day <= 0)
month--;
day = abs(abs(day) - nDaysOfMonth);
}
// 月
if(month > 12 || month <= 0)
{
year = month > 0 ? year + 1 : year - 1;
month = month > 0 ? abs(month - 12) : abs(12 + month);
}
}
// ------------------------------------------------------------------------
//
// 本地时间转格林威治时间(以格里历表示)
//
// ------------------------------------------------------------------------
void w_LSTToUTC(int &year, int &month, int &day, int &hour, int &minute, int &second)
{
_tzset () ;
// 计算本地时间和标准时间的时差(单位:秒)
int nDifference_hour = static_cast(_timezone / 3600);
int nDifference_minute = static_cast((_timezone - nDifference_hour * 3600) / 60);
int nDifference_second = static_cast((_timezone - nDifference_hour * 3600) - nDifference_minute * 60);
// 本地时间 - 时差 = 格林威治时间
// 秒
second = second + nDifference_second;
if(second >= 60 || second < 0)
{
minute = second > 0 ? minute + 1 : minute - 1 ;
second = abs(abs(second) - 60);
}
// 分
minute = minute + nDifference_minute;
if(minute >= 60 || minute < 0)
{
hour = minute > 0 ? hour + 1 : hour - 1;
minute = abs(abs(minute) - 60);
}
// 时
hour = hour + nDifference_hour;
if(hour >= 24 || hour < 0)
{
day = (hour >= 24 || hour == 0) ? day + 1 : day - 1;
hour = abs(abs(hour) - 24);
}
// 日
int nDaysOfMonth = w_GetDaysOfMonth(year, month);
if(day > nDaysOfMonth || day <= 0)
{
if(day > nDaysOfMonth)
month++;
if(day < nDaysOfMonth || day <= 0)
month--;
day = abs(abs(day) - nDaysOfMonth);
}
// 月
if(month > 12 || month <= 0)
{
year = month > 0 ? year + 1 : year - 1;
month = month > 0 ? abs(month - 12) : abs(12 + month);
}
}


九、2008年全年 24 节气发生时间(北京时间)
春分:03月20日 13:49:24
清明:04月04日 17:46:58
谷雨:04月20日 00:52:17
立夏:05月05日 11:04:35
小满:05月21日 00:02:03
芒种:06月05日 15:12:53
夏至:06月21日 08:00:30
小暑:07月07日 01:27:59
大暑:07月22日 18:55:58
立秋:08月07日 11:17:19
处暑:08月23日 02:03:22
白露:09月07日 14:15:15
秋分:09月22日 23:45:36
寒露:10月08日 05:57:46
霜降:10月23日 09:09:49
立冬:11月07日 09:11:45
小雪:11月22日 06:45:32
大雪:12月07日 02:03:28
冬至:12月21日 20:04:54
小寒:01月06日 07:25:55
大寒:01月21日 00:44:38
立春:02月04日 19:01:30
雨水:02月19日 14:50:40
惊蛰:03月05日 12:59:54
本文给出了一种通过计算得到某一年的节气发生日期,相比之下比通过查表的方法的优点不言而喻,经本人比对(与日梭万年历)在范围公元300-3000年间误差很小,如果对历法进一步熟悉,你会发现历法的计算非常困难,计算的误差也随时间跨度的增加增长的很快,即便是所有查阅到的最权威的书籍数据,也只能保证在前后200年间的准确。

你可能感兴趣的:(历算)