django-vue3-admin-web/node_modules/date-chinese/lib/Chinese.cjs
2025-10-20 21:21:14 +08:00

403 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
var astronomia = require('astronomia');
var vsop87Bearth = require('./vsop87Bearth.cjs');
/**
* @copyright 2016 commenthol
* @license MIT
*/
const earth = new astronomia.planetposition.Planet(vsop87Bearth.vsop87Bearth);
const lunarOffset = astronomia.moonphase.meanLunarMonth / 2;
const p = 180 / Math.PI;
// Start of Chinese Calendar in 2636 BCE by Chalmers
const epochY = -2636;
const epoch = new astronomia.julian.CalendarGregorian(epochY, 2, 15).toJDE();
function toYear (jde) {
return new astronomia.julian.CalendarGregorian().fromJDE(jde).toYear()
}
// prevent rounding errors
function toFixed (val, e) {
return parseFloat(val.toFixed(e), 10)
}
class CalendarChinese {
/**
* constructor
*
* @param {Number|Array|Object} cycle - chinese 60 year cicle; if `{Array}` than `[cycle, year, ..., day]`
* @param {Number} year - chinese year of cycle
* @param {Number} month - chinese month
* @param {Number} leap - `true` if leap month
* @param {Number} day - chinese day
*/
constructor (cycle, year, month, leap, day) {
this.set(cycle, year, month, leap, day);
this._epochY = epochY;
this._epoch = epoch;
this._cache = { // cache for results
lon: {},
sue: {},
ny: {}
};
}
/**
* set a new chinese date
*
* @param {Number|Array|Object} cycle - chinese 60 year cicle; if `{Array}` than `[cycle, year, ..., day]`
* @param {Number} year - chinese year of cycle
* @param {Number} month - chinese month
* @param {Number} leap - `true` if leap month
* @param {Number} day - chinese day
*/
set (cycle, year, month, leap, day) {
if (cycle instanceof CalendarChinese) {
this.cycle = cycle.cycle;
this.year = cycle.year;
this.month = cycle.month;
this.leap = cycle.leap;
this.day = cycle.day;
} else if (Array.isArray(cycle)) {
this.cycle = cycle[0];
this.year = cycle[1];
this.month = cycle[2];
this.leap = cycle[3];
this.day = cycle[4];
} else {
this.cycle = cycle;
this.year = year;
this.month = month;
this.leap = leap;
this.day = day;
}
return this
}
/**
* returns chinese date
* @returns {Array}
*/
get () {
return [this.cycle, this.year, this.month, this.leap, this.day]
}
/**
* get Gregorian year from Epoch / Cycle
* @return {Number} year
*/
yearFromEpochCycle () {
return this._epochY + (this.cycle - 1) * 60 + (this.year - 1)
}
/**
* convert gregorian date to chinese calendar date
*
* @param {Number} year - (int) year in Gregorian or Julian Calendar
* @param {Number} month - (int)
* @param {Number} day - needs to be in correct (chinese) timezone
* @return {Object} this
*/
fromGregorian (year, month, day) {
const j = this.midnight(new astronomia.julian.CalendarGregorian(year, month, day).toJDE());
if (month === 1 && day <= 20) year--; // chinese new year never starts before 20/01
this._from(j, year);
return this
}
/**
* convert date to chinese calendar date
*
* @param {Date} date - javascript date object
* @return {Object} this
*/
fromDate (date) {
const j = this.midnight(new astronomia.julian.CalendarGregorian().fromDate(date).toJDE());
this._from(j, date.getFullYear());
return this
}
/**
* convert JDE to chinese calendar date
*
* @param {Number} jde - date in JDE
* @return {Object} this
*/
fromJDE (jde) {
const j = this.midnight(jde);
const gc = new astronomia.julian.CalendarGregorian().fromJDE(j);
if (gc.month === 1 && gc.day < 20) gc.year--; // chinese new year never starts before 20/01
this._from(j, gc.year);
return this
}
/**
* common conversion from JDE, year to chinese date
*
* @private
* @param {Number} j - date in JDE
* @param {Number} year - gregorian year
*/
_from (j, year) {
let ny = this.newYear(year);
if (ny > j) {
ny = this.newYear(year - 1);
}
let nm = this.previousNewMoon(j);
if (nm < ny) {
nm = ny;
}
const years = 1.5 + (ny - this._epoch) / astronomia.base.BesselianYear;
this.cycle = 1 + Math.trunc((years - 1) / 60);
this.year = 1 + Math.trunc((years - 1) % 60);
this.month = this.inMajorSolarTerm(nm).term;
const m = Math.round((nm - ny) / astronomia.moonphase.meanLunarMonth);
if (m === 0) {
this.month = 1;
this.leap = false;
} else {
this.leap = this.isLeapMonth(nm);
}
if (m > this.month) {
this.month = m;
} else if (this.leap) {
this.month--;
}
this.day = 1 + Math.trunc(toFixed(j, 3) - toFixed(nm, 3));
}
/**
* convert chinese date to gregorian date
*
* @param {Number} [gyear] - (int) gregorian year
* @return {Object} date in gregorian (preleptic) calendar; Timezone is Standard Chinese / Bejing Time
* {Number} year - (int)
* {Number} month - (int)
* {Number} day - (int)
*/
toGregorian (gyear) {
const jde = this.toJDE(gyear);
const gc = new astronomia.julian.CalendarGregorian().fromJDE(jde + 0.5); // add 0.5 as day get truncated
return {
year: gc.year,
month: gc.month,
day: Math.trunc(gc.day)
}
}
/**
* convert chinese date to Date
*
* @param {Number} [gyear] - (int) gregorian year
* @return {Date} javascript date object in gregorian (preleptic) calendar
*/
toDate (gyear) {
const jde = this.toJDE(gyear);
return new astronomia.julian.CalendarGregorian().fromJDE(toFixed(jde, 4)).toDate()
}
/**
* convert chinese date to JDE
*
* @param {Number} [gyear] - (int) gregorian year
* @return {Number} date in JDE
*/
toJDE (gyear) {
const years = gyear || this.yearFromEpochCycle();
const ny = this.newYear(years);
let nm = ny;
if (this.month > 1) {
nm = this.previousNewMoon(ny + this.month * 29);
const st = this.inMajorSolarTerm(nm).term;
const lm = this.isLeapMonth(nm);
if (st > this.month) {
nm = this.previousNewMoon(nm - 1);
} else if (st < this.month || (lm && !this.leap)) {
nm = this.nextNewMoon(nm + 1);
}
}
if (this.leap) {
nm = this.nextNewMoon(nm + 1);
}
const jde = nm + this.day - 1;
return jde
}
/**
* timeshift to UTC
*
* @param {CalendarGregorian} gcal - gregorian calendar date
* @return {Number} timeshift in fraction of day
*/
timeshiftUTC (gcal) {
if (gcal.toYear() >= 1929) {
return 8 / 24 // +8:00:00h Standard China time zone (120° East)
}
return 1397 / 180 / 24 // +7:45:40h Beijing (116°25´ East)
}
/**
* time/date at midnight - truncate `jde` to actual day
*
* @param {Number} jde - julian ephemeris day
* @return {Number} truncated jde
*/
midnight (jde) {
const gcal = new astronomia.julian.CalendarGregorian().fromJDE(jde);
const ts = 0.5 - this.timeshiftUTC(gcal);
let mn = Math.trunc(gcal.toJD() - ts) + ts;
mn = gcal.fromJD(mn).toJDE();
if (toFixed(jde, 5) === toFixed(mn, 5) + 1) {
return jde
}
return mn
}
/**
* get major solar term `Z1...Z12` for a given date in JDE
*
* @param {Number} jde - date of new moon
* @returns {Number} major solar term part of that month
*/
inMajorSolarTerm (jde) {
const lon = this._cache.lon[jde] || astronomia.solar.apparentVSOP87(earth, jde).lon;
this._cache.lon[jde] = lon;
const lonDeg = lon * p - 1e-13;
const term = (2 + Math.floor(lonDeg / 30)) % 12 + 1;
return { term, lon: lonDeg }
}
/**
* Test if date `jde` is inside a leap month
* `jde` and previous new moon need to have the same major solar term
*
* @param {Number} jde - date of new moon
* @returns {Boolean} `true` if previous new moon falls into same solar term
*/
isLeapMonth (jde) {
const t1 = this.inMajorSolarTerm(jde);
const next = this.nextNewMoon(this.midnight(jde + lunarOffset));
const t2 = this.inMajorSolarTerm(next);
const r = (t1.term === t2.term);
return r
}
/**
* next new moon since `jde`
*
* @param {Number} jde - date in julian ephemeris days
* @return {Number} jde at midnight
*/
nextNewMoon (jde) {
let nm = this.midnight(astronomia.moonphase.newMoon(toYear(jde)));
let cnt = 0;
while (nm < jde && cnt++ < 4) {
nm = this.midnight(astronomia.moonphase.newMoon(toYear(jde + cnt * lunarOffset)));
}
return nm
}
/**
* next new moon since `jde`
*
* @param {Number} jde - date in julian ephemeris days
* @return {Number} jde at midnight
*/
previousNewMoon (jde) {
let nm = this.midnight(astronomia.moonphase.newMoon(toYear(jde)));
let cnt = 0;
while (nm > jde && cnt++ < 4) {
nm = this.midnight(astronomia.moonphase.newMoon(toYear(jde - cnt * lunarOffset)));
}
return nm
}
/**
* chinese new year for a given gregorian year
*
* @param {Number} gyear - gregorian year (int)
* @param {Number} jde at midnight
*/
newYear (gyear) {
gyear = Math.trunc(gyear);
if (this._cache.ny[gyear]) return this._cache.ny[gyear]
const sue1 = this._cache.sue[gyear - 1] || astronomia.solstice.december2(gyear - 1, earth);
const sue2 = this._cache.sue[gyear] || astronomia.solstice.december2(gyear, earth);
this._cache.sue[gyear - 1] = sue1;
this._cache.sue[gyear] = sue2;
const m11n = this.previousNewMoon(this.midnight(sue2 + 1));
const m12 = this.nextNewMoon(this.midnight(sue1 + 1));
const m13 = this.nextNewMoon(this.midnight(m12 + lunarOffset));
this.leapSui = Math.round((m11n - m12) / astronomia.moonphase.meanLunarMonth) === 12;
let ny = m13;
if (this.leapSui && (this.isLeapMonth(m12) || this.isLeapMonth(m13))) {
ny = this.nextNewMoon(this.midnight(m13 + astronomia.moonphase.meanLunarMonth / 2));
}
this._cache.ny[gyear] = ny;
return ny
}
/**
* get solar term from solar longitude
*
* @param {Number} term - jiéqì solar term 1 .. 24
* @param {Number} [gyear] - (int) gregorian year
* @returns {Number} jde at midnight
*/
solarTerm (term, gyear) {
if (gyear && term <= 3) gyear--;
const years = gyear || this.yearFromEpochCycle();
const lon = (((term + 20) % 24) * 15) % 360;
let st = astronomia.solstice.longitude(years, earth, lon / p);
st = this.midnight(st);
return st
}
/**
* get major solar term
*
* @param {Number} term - zhōngqì solar term Z1 .. Z12
* @param {Number} [gyear] - (int) gregorian year
* @returns {Number} jde at midnight
*/
majorSolarTerm (term, gyear) {
return this.solarTerm(term * 2, gyear)
}
/**
* get minor solar term
*
* @param {Number} term - jiéqì solar term J1 .. J12
* @param {Number} [gyear] - (int) gregorian year
* @returns {Number} jde at midnight
*/
minorSolarTerm (term, gyear) {
return this.solarTerm(term * 2 - 1, gyear)
}
/**
* Qı̄ngmíng - Pure brightness Festival
*
* @param {Number} [gyear] - (int) gregorian year
* @returns {Number} jde at midnight
*/
qingming (gyear) {
return this.solarTerm(5, gyear)
}
}
module.exports = CalendarChinese;