shiren-calendar
Version:
941 lines (858 loc) • 33.7 kB
JavaScript
"use strict";
// 朔望月平均时长(mean length of synodic month)
const synMonth = 29.530588853;
// 因子
const ptsA = [485, 203, 199, 182, 156, 136, 77, 74, 70, 58, 52, 50, 45, 44, 29, 18, 17, 16, 14, 12, 12, 12, 9, 8];
const ptsB = [324.96, 337.23, 342.08, 27.85, 73.14, 171.52, 222.54, 296.72, 243.58, 119.81, 297.17, 21.02, 247.54, 325.15, 60.93, 155.12, 288.79, 198.04, 199.76, 95.39, 287.11, 320.81, 227.73, 15.45];
const ptsC = [1934.136, 32964.467, 20.186, 445267.112, 45036.886, 22518.443, 65928.934, 3034.906, 9037.513, 33718.147, 150.678, 2281.226, 29929.562, 31555.956, 4443.417, 67555.328, 4562.452, 62894.029, 31436.921, 14577.848, 31931.756, 34777.259, 1222.114, 16859.074];
// 检查1582年
function check1582(year, month, day) {
return year === 1582 && month === 10 && day >= 5 && day < 15
}
function timeIsOk(hour, minute, second) {
return 0 <= hour && hour <= 24 && 0 <= minute && minute <= 60 && 0 <= second && second <= 60
}
function dateIsOk(year, month, day) {
if (year < -1000 || year > 3000 || month < 1 || month > 12 || check1582(year, month, day)) {
return false;
}
const ndf1 = -(year % 4 === 0);
const ndf2 = ((year % 400 === 0) - (year % 100 === 0)) && (year > 1582);
const ndf = ndf1 + ndf2;
const dom = 30 + ((Math.abs(month - 7.5) + 0.5) % 2) - (month === 2) * (2 + ndf);
return 0 < day && day <= dom
}
/**
* 将公历时间转换为儒略日历时间
* @return boolean|number
* @param year
* @param month
* @param day
* @param hour
* @param minute
* @param second
*/
export function solar2julian(year, month, day, hour = 0, minute = 0, second = 0) {
if (!(dateIsOk(year, month, day) && timeIsOk(hour, minute, second))) {
return false;
}
const yp = year + Math.floor((month - 3) / 10);
let yearJD;
let init;
if ((year > 1582) || (year === 1582 && month > 10) || (year === 1582 && month === 10 && day >= 15)) { //这一年有十天是不存在的
init = 1721119.5;
yearJD = Math.floor(yp * 365.25) - Math.floor(yp / 100) + Math.floor(yp / 400);
} else if ((year < 1582) || (year === 1582 && month < 10) || (year === 1582 && month === 10 && day <= 4)) {
init = 1721117.5;
yearJD = Math.floor(yp * 365.25);
} else {
return false
}
const mp = Math.floor(month + 9) % 12;
const monthJD = mp * 30 + Math.floor((mp + 1) * 34 / 57);
const dayJD = day - 1;
const hourJd = (hour + (minute + (second / 60)) / 60) / 24;
return yearJD + monthJD + dayJD + hourJd + init;
}
/**
* 将儒略日历时间转换为公历(格里高利历)时间
* @param jd
* @return array(年,月,日,时,分,秒)
*/
export function julian2solar(jd) {
jd = Number(jd);
let init, y4h;
if (jd >= 2299160.5) {
y4h = 146097;
init = 1721119.5;
} else {
y4h = 146100;
init = 1721117.5;
}
const jdr = Math.floor(jd - init);
const yh = y4h / 4;
const cen = Math.floor((jdr + 0.75) / yh);
let d = Math.floor(jdr + 0.75 - cen * yh);
const ywl = 1461 / 4;
const jy = Math.floor((d + 0.75) / ywl);
d = Math.floor(d + 0.75 - ywl * jy + 1);
const ml = 153 / 5;
const mp = Math.floor((d - 0.5) / ml);
d = Math.floor((d - 0.5) - 30.6 * mp + 1);
let y = (100 * cen) + jy;
const m = (mp + 2) % 12 + 1;
if (m < 3) {
y = y + 1;
}
const sd = Math.floor((jd + 0.5 - Math.floor(jd + 0.5)) * 24 * 60 * 60 + 0.00005);
let mt = Math.floor(sd / 60);
const ss = sd % 60;
const hh = Math.floor(mt / 60);
mt = mt % 60;
const yy = Math.floor(y);
const mm = Math.floor(m);
const dd = Math.floor(d);
return [yy, mm, dd, hh, mt, ss];
}
/**
* 对于指定日期时刻所属的朔望月,求出其均值新月点的月序数
* @param jd
* @return (number|number)[]
*/
export function meanNewMoon(jd) {
const kn = Math.floor((jd - 2451550.09765) / synMonth);
const jdt = 2451550.09765 + kn * synMonth;
const t = (jdt - 2451545) / 36525;
const theJD = jdt + 0.0001337 * t * t - 0.00000015 * t * t * t + 0.00000000073 * t * t * t * t;
return [kn, theJD];
}
/**
* 获取指定年的春分开始的24节气,另外多取2个确保覆盖完一个公历年
* 大致原理是:先用此方法得到理论值,再用摄动值(Perturbation)和固定参数DeltaT做调整
* @return *[]
* @param year
*/
export function meanJQJD(year) {
let i;
const jd = VE(year);
if (!jd) { return []; } //该年的春分点
const ty = VE(year + 1) - jd; //该年的回归年長
const num = 26;
const ath = 2 * Math.PI / 24;
const tx = (jd - 2451545) / 365250;
const e = 0.0167086342 - 0.0004203654 * tx - 0.0000126734 * tx * tx + 0.0000001444 * tx * tx * tx - 0.0000000002 * tx * tx * tx * tx + 0.0000000003 * tx * tx * tx * tx * tx;
const tt = year / 1000;
const vp = 111.25586939 - 17.0119934518333 * tt - 0.044091890166673 * tt * tt - 4.37356166661345E-04 * tt * tt * tt + 8.16716666602386E-06 * tt * tt * tt * tt;
const rvp = vp * 2 * Math.PI / 360;
const peri = [];
for (i = 0; i < num; i++) {
let flag = 0;
let th = ath * i + rvp;
if (Math.PI < th && th <= 3 * Math.PI) {
th = 2 * Math.PI - th;
flag = 1;
} else if (3 * Math.PI < th) {
th = 4 * Math.PI - th;
flag = 2;
}
const f1 = 2 * Math.atan((Math.sqrt((1 - e) / (1 + e)) * Math.tan(th / 2)));
const f2 = (e * Math.sqrt(1 - e * e) * Math.sin(th)) / (1 + e * Math.cos(th));
let f = (f1 - f2) * ty / 2 / Math.PI;
if (flag === 1) {
f = ty - f;
} else if (flag === 2) {
f = 2 * ty - f;
}
peri[i] = f;
}
const JDs = [];
for (i = 0; i < num; i++) {
JDs[i] = jd + peri[i] - peri[0];
}
return JDs;
}
/**
* 求出实际新月点
* 以2000年初的第一个均值新月点为0点求出的均值新月点和其朔望月之序数 k 代入此副程式來求算实际新月点
* @param k
* @return number
*/
export function trueNewMoon(k) {
const jdt = 2451550.09765 + k * synMonth;
const t = (jdt - 2451545) / 36525;
const t2 = t * t;
const t3 = t2 * t;
const t4 = t3 * t;
const pt = jdt + 0.0001337 * t2 - 0.00000015 * t3 + 0.00000000073 * t4;
const m = 2.5534 + 29.10535669 * k - 0.0000218 * t2 - 0.00000011 * t3;
const mprime = 201.5643 + 385.81693528 * k + 0.0107438 * t2 + 0.00001239 * t3 - 0.000000058 * t4;
const f = 160.7108 + 390.67050274 * k - 0.0016341 * t2 - 0.00000227 * t3 + 0.000000011 * t4;
const omega = 124.7746 - 1.5637558 * k + 0.0020691 * t2 + 0.00000215 * t3;
const es = 1 - 0.002516 * t - 0.0000074 * t2;
const pi180 = Math.PI / 180
let apt1 = -0.4072 * Math.sin(pi180 * mprime);
apt1 += 0.17241 * es * Math.sin(pi180 * m);
apt1 += 0.01608 * Math.sin(pi180 * 2 * mprime);
apt1 += 0.01039 * Math.sin(pi180 * 2 * f);
apt1 += 0.00739 * es * Math.sin(pi180 * (mprime - m));
apt1 -= 0.00514 * es * Math.sin(pi180 * (mprime + m));
apt1 += 0.00208 * es * es * Math.sin(pi180 * (2 * m));
apt1 -= 0.00111 * Math.sin(pi180 * (mprime - 2 * f));
apt1 -= 0.00057 * Math.sin(pi180 * (mprime + 2 * f));
apt1 += 0.00056 * es * Math.sin(pi180 * (2 * mprime + m));
apt1 -= 0.00042 * Math.sin(pi180 * 3 * mprime);
apt1 += 0.00042 * es * Math.sin(pi180 * (m + 2 * f));
apt1 += 0.00038 * es * Math.sin(pi180 * (m - 2 * f));
apt1 -= 0.00024 * es * Math.sin(pi180 * (2 * mprime - m));
apt1 -= 0.00017 * Math.sin(pi180 * omega);
apt1 -= 0.00007 * Math.sin(pi180 * (mprime + 2 * m));
apt1 += 0.00004 * Math.sin(pi180 * (2 * mprime - 2 * f));
apt1 += 0.00004 * Math.sin(pi180 * (3 * m));
apt1 += 0.00003 * Math.sin(pi180 * (mprime + m - 2 * f));
apt1 += 0.00003 * Math.sin(pi180 * (2 * mprime + 2 * f));
apt1 -= 0.00003 * Math.sin(pi180 * (mprime + m + 2 * f));
apt1 += 0.00003 * Math.sin(pi180 * (mprime - m + 2 * f));
apt1 -= 0.00002 * Math.sin(pi180 * (mprime - m - 2 * f));
apt1 -= 0.00002 * Math.sin(pi180 * (3 * mprime + m));
apt1 += 0.00002 * Math.sin(pi180 * (4 * mprime));
let apt2 = 0.000325 * Math.sin(pi180 * (299.77 + 0.107408 * k - 0.009173 * t2));
apt2 += 0.000165 * Math.sin(pi180 * (251.88 + 0.016321 * k));
apt2 += 0.000164 * Math.sin(pi180 * (251.83 + 26.651886 * k));
apt2 += 0.000126 * Math.sin(pi180 * (349.42 + 36.412478 * k));
apt2 += 0.00011 * Math.sin(pi180 * (84.66 + 18.206239 * k));
apt2 += 0.000062 * Math.sin(pi180 * (141.74 + 53.303771 * k));
apt2 += 0.00006 * Math.sin(pi180 * (207.14 + 2.453732 * k));
apt2 += 0.000056 * Math.sin(pi180 * (154.84 + 7.30686 * k));
apt2 += 0.000047 * Math.sin(pi180 * (34.52 + 27.261239 * k));
apt2 += 0.000042 * Math.sin(pi180 * (207.19 + 0.121824 * k));
apt2 += 0.00004 * Math.sin(pi180 * (291.34 + 1.844379 * k));
apt2 += 0.000037 * Math.sin(pi180 * (161.72 + 24.198154 * k));
apt2 += 0.000035 * Math.sin(pi180 * (239.56 + 25.513099 * k));
apt2 += 0.000023 * Math.sin(pi180 * (331.55 + 3.592518 * k));
return pt + apt1 + apt2;
}
/**
* 求∆t
* @return number
* @param yy
* @param mm
*/
export function DeltaT(yy, mm) {
let u, t, dt;
const y = yy + (mm - 0.5) / 12;
if (y <= -500) {
u = (y - 1820) / 100;
dt = (-20 + 32 * u * u);
} else {
if (y < 500) {
u = y / 100;
dt = (10583.6 - 1014.41 * u + 33.78311 * u * u - 5.952053 * u * u * u - 0.1798452 * u * u * u * u + 0.022174192 * u * u * u * u * u + 0.0090316521 * u * u * u * u * u * u);
} else {
if (y < 1600) {
u = (y - 1000) / 100;
dt = (1574.2 - 556.01 * u + 71.23472 * u * u + 0.319781 * u * u * u - 0.8503463 * u * u * u * u - 0.005050998 * u * u * u * u * u + 0.0083572073 * u * u * u * u * u * u);
} else {
if (y < 1700) {
t = y - 1600;
dt = (120 - 0.9808 * t - 0.01532 * t * t + t * t * t / 7129);
} else {
if (y < 1800) {
t = y - 1700;
dt = (8.83 + 0.1603 * t - 0.0059285 * t * t + 0.00013336 * t * t * t - t * t * t * t / 1174000);
} else {
if (y < 1860) {
t = y - 1800;
dt = (13.72 - 0.332447 * t + 0.0068612 * t * t + 0.0041116 * t * t * t - 0.00037436 * t * t * t * t + 0.0000121272 * t * t * t * t * t - 0.0000001699 * t * t * t * t * t * t + 0.000000000875 * t * t * t * t * t * t * t);
} else {
if (y < 1900) {
t = y - 1860;
dt = (7.62 + 0.5737 * t - 0.251754 * t * t + 0.01680668 * t * t * t - 0.0004473624 * t * t * t * t + t * t * t * t * t / 233174);
} else {
if (y < 1920) {
t = y - 1900;
dt = (-2.79 + 1.494119 * t - 0.0598939 * t * t + 0.0061966 * t * t * t - 0.000197 * t * t * t * t);
} else {
if (y < 1941) {
t = y - 1920;
dt = (21.2 + 0.84493 * t - 0.0761 * t * t + 0.0020936 * t * t * t);
} else {
if (y < 1961) {
t = y - 1950;
dt = (29.07 + 0.407 * t - t * t / 233 + t * t * t / 2547);
} else {
if (y < 1986) {
t = y - 1975;
dt = (45.45 + 1.067 * t - t * t / 260 - t * t * t / 718);
} else {
if (y < 2005) {
t = y - 2000;
dt = (63.86 + 0.3345 * t - 0.060374 * t * t + 0.0017275 * t * t * t + 0.000651814 * t * t * t * t + 0.00002373599 * t * t * t * t * t);
} else {
if (y < 2050) {
t = y - 2000;
dt = (62.92 + 0.32217 * t + 0.005589 * t * t);
} else {
if (y < 2150) {
u = (y - 1820) / 100;
dt = (-20 + 32 * u * u - 0.5628 * (2150 - y));
} else {
u = (y - 1820) / 100;
dt = (-20 + 32 * u * u);
}
}
}
}
}
}
}
}
}
}
}
}
}
}
if (y < 1955 || y >= 2005) {
dt = dt - (0.000012932 * (y - 1955) * (y - 1955));
}
return dt / 60;
}
/**
* 获取某年的立春
* @param year
* @returns {*}
*/
export function spring(year) {
return adjustedJQ(year - 1, 21, 21)[21]
}
/**
* 计算指定年(公历)的春分点(vernal equinox),但因地球在绕日运行时会因受到其他星球之影响而产生摄动(perturbation),必须将此现象产生的偏移量加入.
* @return boolean|number 返回儒略日历格林威治时间
* @param year
*/
export function VE(year) {
let m;
if (year < -8000 && 8001 < year) {
return false;
}
if (1000 <= year && year <= 8001) {
m = (year - 2000) / 1000;
return 2451623.80984 + 365242.37404 * m + 0.05169 * m * m - 0.00411 * m * m * m - 0.00057 * m * m * m * m;
} else {
m = year / 1000;
return 1721139.29189 + 365242.1374 * m + 0.06134 * m * m + 0.00111 * m * m * m - 0.00071 * m * m * m * m;
}
}
/**
* 地球在绕日运行时會因受到其他星球之影响而產生摄动(perturbation)
* @return number 返回某时刻(儒略日历)的摄动偏移量
* @param jd
*/
export function perturbation(jd) {
const t = (jd - 2451545) / 36525;
let s = 0;
for (let k = 0; k <= 23; k++) {
s = s + ptsA[k] * Math.cos(ptsB[k] * 2 * Math.PI / 360 + ptsC[k] * 2 * Math.PI / 360 * t);
}
const w = 35999.373 * t - 2.47;
const l = 1 + 0.0334 * Math.cos(w * 2 * Math.PI / 360) + 0.0007 * Math.cos(2 * w * 2 * Math.PI / 360);
return 0.00001 * s / l;
}
/**
* 求算以含冬至中气为阴历11月开始的连续16个朔望月
* @param year 年份
* @param wJD 冬至的儒略日历时间
* @return array
*/
export function SMSinceWinterSolstice(year, wJD) {
let j, k;
const tjd = [];
const jd = solar2julian(year - 1, 11, 1, 0, 0, 0);
const nm = meanNewMoon(jd);
const kn = nm[0];
for (let i = 0; i <= 19; i++) {
k = kn + i;
tjd[i] = trueNewMoon(k) + 1 / 3;
tjd[i] = tjd[i] - DeltaT(year, i - 1) / 1440;
}
for (j = 0; j <= 18; j++) {
if (Math.floor(tjd[j] + 0.5) > Math.floor(wJD + 0.5)) {
break;
}
}
const JDs = [];
for (k = 0; k <= 15; k++) {
JDs.push(tjd[j - 1 + k])
}
return JDs;
}
/**
* 求出以某年立春点开始的节(注意:为了方便计算起运数,此处第0位为上一年的小寒)
* @param year
* @return array jq[(2*k+21)%24]
*/
export function pureJQSinceSpring(year) {
let k;
const jdpjq = [];
let dj = adjustedJQ(year - 1, 19, 23); //求出含指定年立春开始之3个节气JD值,以前一年的年值代入
for (k in dj) {
if (k < 19 || k > 23 || k % 2 === 0) {
continue;
}
jdpjq.push(dj[k]); //19小寒;20大寒;21立春;22雨水;23惊蛰
}
dj = adjustedJQ(year, 0, 25); //求出指定年节气之JD值,从春分开始,到大寒,多取两个确保覆盖一个公历年,也方便计算起运数
for (k in dj) {
if (k % 2 === 0) {
continue;
}
jdpjq.push(dj[k]);
}
return jdpjq;
}
/**
* 获取指定年的春分开始作perturbation調整後的24节气,可以多取2个
* @param year
* @param start 0-25
* @param end 0-25
* @return array
*/
export function adjustedJQ(year, start, end) {
if (start < 0 || 25 < start || end < 0 || 25 < end) {
return [];
}
const jq = [];
const Jd4JQ = meanJQJD(year);
for (let k = 0; k < Jd4JQ.length; k++) {
if (k < start || k > end) {
continue;
}
const ptb = perturbation(Jd4JQ[k]);
const dt = DeltaT(year, Math.floor((k + 1) / 2) + 3);
jq[k] = Jd4JQ[k] + ptb - dt / 60 / 24;
jq[k] = jq[k] + 1 / 3;
}
return jq;
}
/**
* 求出自冬至点为起点的连续15个中气
* @param year
* @return array jq[(2*k+18)%24]
*/
export function ZQSinceWinterSolstice(year) {
const JD4ZQ = [];
let dj = adjustedJQ(year - 1, 18, 23);
JD4ZQ[0] = dj[18]; //冬至
JD4ZQ[1] = dj[20]; //大寒
JD4ZQ[2] = dj[22]; //雨水
dj = adjustedJQ(year, 0, 23);
for (const k in dj) {
if (k % 2 !== 0) {
continue;
}
JD4ZQ.push(dj[k]);
}
return JD4ZQ;
}
/**
* 以比较日期法求算冬月及其余各月名称代码,包含闰月,冬月为0,腊月为1,正月为2,余类推.闰月多加0.5
* @param year
*/
export function ZQAndSMandLunarMonthCode(year) {
let i;
const mc = [];
const jd4zq = ZQSinceWinterSolstice(year);
const jd4sm = SMSinceWinterSolstice(year, jd4zq[0]);
let yz = 0;
if (Math.floor(jd4zq[12] + 0.5) >= Math.floor(jd4sm[13] + 0.5)) {
for (i = 1; i <= 14; i++) {
if (Math.floor((jd4sm[i] + 0.5) > Math.floor(jd4zq[i - 1 - yz] + 0.5) && Math.floor(jd4sm[i + 1] + 0.5) <= Math.floor(jd4zq[i - yz] + 0.5))) {
mc[i] = i - 0.5;
yz = 1;
} else {
mc[i] = i - yz;
}
}
} else {
for (i = 0; i <= 12; i++) {
mc[i] = i;
}
for (i = 13; i <= 14; i++) {
if (Math.floor((jd4sm[i] + 0.5) > Math.floor(jd4zq[i - 1 - yz] + 0.5) && Math.floor(jd4sm[i + 1] + 0.5) <= Math.floor(jd4zq[i - yz] + 0.5))) {
mc[i] = i - 0.5;
yz = 1;
} else {
mc[i] = i - yz;
}
}
}
return [jd4zq, jd4sm, mc];
}
// 公历转农历
export function solar2lunar(year, month, day) {
if (!dateIsOk(year, month, day)) return undefined;
let mData = ZQAndSMandLunarMonthCode(year);
let jd4sm = mData[1];
let mc = mData[2];
const jd = solar2julian(year, month, day, 12, 0, 0); //求出指定年月日之JD值
let mi = 0;
let prev = 0;
if (Math.floor(jd) < Math.floor(jd4sm[0] + 0.5)) {
prev = 1;
mData = ZQAndSMandLunarMonthCode(year - 1);
jd4sm = mData[1];
mc = mData[2];
}
for (let i = 0; i <= 14; i++) {
if (Math.floor(jd) >= Math.floor(jd4sm[i] + 0.5) && Math.floor(jd) < Math.floor(jd4sm[i + 1] + 0.5)) {
mi = i;
break;
}
}
if (mc[0] === undefined ) {
mc[0] = 0
}
if (mc[mi] < 2 || prev === 1) {
year = year - 1;
}
month = (Math.floor(mc[mi] + 10) % 12) + 1;
day = Math.floor(jd) - Math.floor(jd4sm[mi] + 0.5) + 1;
let isLeap = (mc[mi] - Math.floor(mc[mi])) * 2 + 1 !== 1;
return [year, month, day, isLeap];
}
/**
* 农历转公历
* @param year
* @param month
* @param day
* @param isLeap
* @returns {boolean|boolean|*}
*/
export function lunar2solar(year, month, day, isLeap=false) {
if (year < -1000 || 3000 < year || month < 1 || 12 < month || day < 1 || 30 < day) {
return false;
}
const lm = ZQAndSMandLunarMonthCode(year);
const jd4sm = lm[1];
const mc = lm[2];
let leap = 0;
for (let j = 1; j <= 14; j++) {
if (mc[j] - Math.floor(mc[j]) > 0) {
leap = Math.floor(mc[j] + 0.5);
break;
}
}
month = month + 2;
const nofd = [];
for (let i = 0; i <= 14; i++) {
nofd[i] = Math.floor(jd4sm[i + 1] + 0.5) - Math.floor(jd4sm[i] + 0.5);
}
let jd = null;
if (isLeap) {
if (leap >= 3 && leap === month && day <= nofd[month]) {
jd = jd4sm[month] + day - 1;
}
} else {
const rate = leap === 0 ? 0 : 1;
if (day <= nofd[month - 1 + rate * (month > leap)]) {
jd = jd4sm[month - 1 + rate * (month > leap)] + day - 1;
}
}
return jd === null ? false : julian2solar(jd).slice(0, 3);
}
/**
* 获取公历某个月有多少天
* @param year
* @param month
* @returns {number}
*/
export function solarMonthHasDays(year, month) {
if (year < -1000 || year > 3000 || month < 1 || month > 12) {
return 0;
}
const ndf1 = -(year % 4 === 0);
const ndf2 = ((year % 400 === 0) - (year % 100 === 0)) && (year > 1582);
const ndf = ndf1 + ndf2;
return 30 + ((Math.abs(month - 7.5) + 0.5) % 2) - (month === 2) * (2 + ndf);
}
/**
* 获取农历某个月有多少天
* @param year
* @param month
* @param isLeap
* @returns {number|*}
*/
export function lunarMonthHasDays(year, month, isLeap) {
if (year < -1000 || year > 3000 || month < 1 || month > 12) {
return 0;
}
const lm = ZQAndSMandLunarMonthCode(year);
const jdnm = lm[1];
const mc = lm[2];
let leap = 0;
for (let j = 1; j <= 14; j++) {
if (mc[j] - Math.floor(mc[j]) > 0) {
leap = Math.floor(mc[j] + 0.5);
break;
}
}
month = month + 2;
const nofd = [];
for (let i = 0; i <= 14; i++) {
nofd[i] = Math.floor(jdnm[i + 1] + 0.5) - Math.floor(jdnm[i] + 0.5); //每月天数,加0.5是因JD以正午起算
}
if (isLeap) {
if (leap >= 3 && leap === month) {
return nofd[month]
} else {
return 0
}
} else {
return nofd[month - 1 + (leap !== 0 && month > leap)]
}
}
/**
* 获取一年的12个节气['立春', '惊蛰', '清明', '立夏', '芒种', '小暑', '立秋', '白露', '寒露', '立冬', '大雪', '小寒']对应的公历日期
* @param year
* @returns {*[]}
*/
export function yearJieQi(year) {
const jds = pureJQSinceSpring(year)
const dates = []
for (let i = 1; i < 14; i++) {
dates.push([...julian2solar(jds[i]), jds[i], jds[i + 1]])
}
return dates.map((d, i, all) => {
return {
jd: d[6], // 这个月的开始时间
nextjd: d[7], // 这个月的结束时间
year: d[0],
month: d[1],
day: d[2],
hour: d[3],
minute: d[4],
second: d[5],
dm: i < 12 ? (all[i+1][1] + 12 - all[i][1]) % 12 : null, // 当前月 - 下一个月,得出两个月的差值
dd: i < 12 ? all[i+1][2] : null
}
}).slice(0, 12)
}
/**
* 获取干支信息
* @param year
* @param month
* @param day
* @param hour
* @param minute
* @param second
* @param zwz 区分早晚子时
* @returns {{jqi: number, g: *[], jq: *[], z: *[], jd: number}|{}}
*/
export function gzi(year, month, day, hour, minute = 0, second = 0, zwz = false) {
const info = {
g: [], // 天干
z: [], // 地址
jd: 0, // 对应的儒略日
jq: [], // 日期前后节气的儒略日
jqi: 0, // 对应的节气索引
}
info.jd = solar2julian(year, month, day, hour, minute, Math.max(1, second));
if (!info.jd) return {};
let jq = pureJQSinceSpring(year);
if (info.jd < jq[1]) {
year = year - 1;
jq = pureJQSinceSpring(year);
}
const yearGZ = ((year + 4712 + 24) % 60 + 60) % 60;
info.g[0] = yearGZ % 10; //年干
info.z[0] = yearGZ % 12; //年支
for (let j = 0; j <= 15; j++) {
if (jq[j] >= info.jd) {
info.jqi = j - 1;
break;
}
}
info.jq = [jq[info.jqi], jq[info.jqi + 1]]
const monthGZ = (((year + 4712) * 12 + (info.jqi - 1) + 60) % 60 + 50) % 60;
info.g[1] = monthGZ % 10; //月干
info.z[1] = monthGZ % 12; //月支
const jda = info.jd + 0.5;
const dayJD = Math.floor(jda) + (((jda - Math.floor(jda)) * 86400) + 3600) / 86400;
const dgz = (Math.floor(dayJD + 49) % 60 + 60) % 60;
info.g[2] = dgz % 10; //日干
info.z[2] = dgz % 12; //日支
if (zwz && (hour >= 23)) { //区分早晚子时,日柱前移一柱
info.g[2] = (info.g[2] + 10 - 1) % 10;
info.z[2] = (info.z[2] + 12 - 1) % 12;
}
const dh = dayJD * 12;
const hgz = (Math.floor(dh + 48) % 60 + 60) % 60;
info.g[3] = hgz % 10; //时干
info.z[3] = hgz % 12; //时支
return info;
}
/**
* 根据公历年月日排盘
* @param male true男false女
* @param year
* @param month
* @param day
* @param hour
* @param minute
* @param second
* @param zwz 区分早晚子时
* @returns {{lucky: {datetime: *[], g: *[], z: *[], desc: string}, basic: {g: *[], z: *[]}, shiren: {datetime: string, year: number, rang: *[]}}}
*/
export const plate = function (male, year, month, day, hour, minute = 0, second = 0, zwz=false) {
let i, span;
const plate = {
basic: { // 四柱天干地址
g: [],
z: [],
},
lucky: { // 大运天干地址
desc: '', // 起运日期描述
g: [],
z: [],
datetime: [] // 每个大运对应的起运具体时间
},
shiren: { // 实仁排盘的起运时间
datetime: '',
year: 0,
rang: [], // 每个大运对应的的具体起运日期
}
};
const info = gzi(year, month, day, hour, minute, second, zwz);
plate.basic.g = info.g
plate.basic.z = info.z
const JQs = ['小寒', '立春', '惊蛰', '清明', '立夏', '芒种', '小暑', '立秋', '白露', '寒露', '立冬', '大雪'] // 12节气,不包含另外12中气
plate['birth'] = {
front: {
name: JQs[info.jqi % 12],
time: datetime2string(julian2solar(info.jq[0]))
},
back: {
name: JQs[(info.jqi + 1) % 12],
time: datetime2string(julian2solar(info.jq[1]))
}
}
const pn = plate.basic.g[0] % 2;
let srSpan;
if ((male && pn === 0) || (!male && pn === 1)) { //起大运时间,阳男阴女顺排
srSpan = shirenSpan(info.jd, info.jq[1], true)
span = info.jq[1] - info.jd; //往后数一个节,计算时间跨度
for (i = 1; i <= 12; i++) {
plate.lucky.g.push((plate.basic.g[1] + i) % 10);
plate.lucky.z.push((plate.basic.z[1] + i) % 12);
}
} else { // 阴男阳女逆排,往前数一个节
srSpan = shirenSpan(info.jd, info.jq[0], false)
span = info.jd - info.jq[0];
for (i = 1; i <= 12; i++) {
plate.lucky.g.push((plate.basic.g[1] + 20 - i) % 10);
plate.lucky.z.push((plate.basic.z[1] + 24 - i) % 12);
}
}
const days = Math.floor(span * 4 * 30);
const y = Math.floor(days / 360);
const m = Math.floor(days % 360 / 30);
const d = Math.floor(days % 360 % 30);
plate.lucky.desc = y + "年" + m + "月" + d + "天起运";
const startJDTime = info.jd + span * 120;
for (i = 0; i < 12; i++) {
plate.lucky.datetime.push(datetime2string(julian2solar(startJDTime + i * 10 * 360)));
}
// 公历A转为农历B,农历B年份加上起运年龄,月、天不变,则新的农历B1日期时间则为起运日期,如果B1对应的公历A1不存在,则进行闰月和减一天的操作,让A1存在
// 计算实仁起运时间
const startAge = parseInt((srSpan / 3).toFixed())
const lunarDate = solar2lunar(year, month, day)
lunarDate[0] += startAge // 出生日期农历年平移到起运年
let shirenLuckyDay = offsetLunar2solar(lunarDate)
plate.shiren.datetime = datetime2string(shirenLuckyDay).slice(0, 10)
plate.shiren.year = shirenLuckyDay[0]
for (let i = 0; i < 11; i++) {
lunarDate[0] += 10
plate.shiren.rang.push(datetime2string(offsetLunar2solar(lunarDate)).trimRight())
}
return plate;
}
/**
* 实仁计算span
* @param birthtimeJD 出生时间儒略日
* @param nearbyJD 靠近的节气儒略日
* @param forward 顺排还是逆排 无效参数
* @returns {number}
*/
function shirenSpan(birthtimeJD, nearbyJD, forward) {
// 去掉时分秒 TODO 优化掉中间变量
const birthday =julian2solar(birthtimeJD).slice(0, 3)
const nearbyDay = julian2solar(nearbyJD).slice(0, 3)
birthtimeJD = solar2julian(...birthday)
nearbyJD = solar2julian(...nearbyDay)
return Math.abs(birthtimeJD - nearbyJD)
}
/**
* 偏移农历到公历
* @param lunarDate
* @returns {*}
*/
export function offsetLunar2solar(lunarDate) {
let ld = [...lunarDate]
let shirenLuckyDay = lunar2solar(...ld)
if (shirenLuckyDay === false) { // 如果平移后的日期不存在
if (ld[3] === true) { // 平移的日期为闰月,转为非闰月重试
ld[3] = false
shirenLuckyDay = lunar2solar(...ld)
if (shirenLuckyDay === false) { // 平移后的日期不存在(平移后,大月(30)变小月(29))
ld[2] -= 1
shirenLuckyDay = lunar2solar(...ld)
}
} else { // 平移日期不存在,大小月问题
ld[2] -= 1
shirenLuckyDay = lunar2solar(...ld)
}
}
return shirenLuckyDay
}
/**
* 根据八字干支查找对应的公历日期
* @param yearColumn 年柱的60甲子年索引
* @param monthColumn 月柱的60甲子年索引
* @param dayColumn 日柱的60甲子年索引
* @param hourColumn 时柱的60甲子年索引
* @param zzs 早(true)/晚(false)子时,时柱为X子才会生效
* @param startYear
* @param mx
*/
export function gz2datetime(yearColumn, monthColumn, dayColumn, hourColumn, zzs = true, startYear = 1500, mx = 17) {
const CycleIndex = (startYear + 56) % 60 // 求出开始那年对应60甲子年的索引
const diff = (yearColumn + 60 - CycleIndex) % 60 // 计算输入年柱与1500对应年柱的差值,yearColumn+60把输入的年柱切换到下一个周期,以保证比1500年的甲子年索引大,再通过取余60回到60甲子年内
let sii = (monthColumn + 10) % 12 // SolarItermIndex 因为一年12个月与12地支一一对应(索引上偏差2,所以使用12-2=10进行矫正),12个地支跟12节气对应
const datetime = []
for (let m = 0; m <= mx - 1; m++) {
let sis = pureJQSinceSpring(startYear + diff + 60 * m).slice(1) // 因为需要从立春开始算,去掉第一个去年的小寒
let headSi = sis[sii] // 月头对应的节气
let footSi = sis[sii + 1] // 月尾对应的节气
let headCycleIndex = (Math.floor(headSi) + 49) % 60 // 儒略日历时间0日为癸丑日,六十甲子代码为49
let dayDiff = (dayColumn + 60 - headCycleIndex) % 60 // 输入的日期到月头间隔的日子(一般不超过30,因为一个月30天,只能循环60甲子中的一半)
let theDayJd = Math.floor(headSi + dayDiff) // 计算出输入四柱对应的日期
let theHour = hourColumn % 12 // 计算出时支
let id, fd
if (theHour === 0) {
if (zzs) { // 早
id = theDayJd - 12 / 24
fd = theDayJd - 11 / 24
} else { // 晚
id = theDayJd + 11 / 24
fd = theDayJd + 12 / 24 - 0.00000001
}
} else {
id = theDayJd + (theHour * 2 - 13) / 24
fd = theDayJd + (theHour * 2 - 11) / 24
}
if (fd < headSi || footSi < id) continue // 此八字在此60年中不存在
let startJd, endJd
if (headSi < id && fd < footSi) { // 没有跨节
startJd = id
endJd = fd
}
if (id < headSi && headSi < fd) { // 同一个时辰跨越了节:在节气月头,只包含时辰后段
startJd = headSi
endJd = fd
}
if (id < footSi && footSi < fd) { // 同一个时辰跨越了节:在节气月尾,只包含时辰前段
startJd = id
endJd = footSi
}
datetime.push([julian2solar(startJd), julian2solar(endJd)]) // 儒略日历时间转成公历时间
}
return datetime
}
/**
* 日期时间数组转为字符串
* @param data
* @returns {string}
*/
export function datetime2string(data) {
return data.slice(0, 3).map((v) => { return `${v}`.padStart(2, '0') }).join('-') + ' ' + data.slice(3, 6).map((v) => { return `${v}`.padStart(2, '0') }).join(':')
}