lunisolar
Version:
专业农历库,支持公历阴历互转,支持各类黄历数据查询,如八字四柱、阴历、神煞宜忌、时辰吉凶、建除十二神、胎神占方、五行纳音等。支持自定义插件。
157 lines (146 loc) • 5.56 kB
text/typescript
import { FIRST_YEAR, TERM_MINIMUM_DATES, TERM_SAME_HEX, TERM_LIST } from '../constants/lunarData'
import { _GlobalConfig } from '../config'
import { parseDate, getDateData } from '../utils'
export class SolarTerm {
readonly value: number = -1
readonly _config: Required<ClassCommonConfig> = {
lang: _GlobalConfig.lang,
isUTC: false
}
constructor(value: number | string | SolarTerm, config?: ClassCommonConfig) {
if (config) {
this._config = Object.assign({}, this._config, config)
}
if (value instanceof SolarTerm) return value
if (typeof value === 'number') {
this.value = value % 24
} else if (typeof value === 'string') {
const termIndex = _GlobalConfig.locales[this._config.lang].solarTerm.indexOf(value)
if (termIndex === -1) throw new Error('Invalid term value')
this.value = termIndex
}
}
get name() {
return String(_GlobalConfig.locales[this._config.lang].solarTerm[this.value])
}
static getNames(lang?: string): string[] {
lang = lang || _GlobalConfig.lang
return [..._GlobalConfig.locales[lang].solarTerm]
}
/**
* 取得当年的节气日期列表 [d, d, d, d...]
* @param year 年份
* @returns {number[]}
*/
static getYearTermDayList(year: number): number[] {
const binData = TERM_SAME_HEX[TERM_LIST[year - FIRST_YEAR]].toString(2)
const res = []
let temp = binData.padStart(48, '0')
while (res.length < 24) {
const currDate = parseInt(temp.slice(temp.length - 2), 2)
const minDate: number = TERM_MINIMUM_DATES[res.length]
res.push(currDate + minDate)
temp = temp.slice(0, temp.length - 2)
}
return res
}
/**
* 取得某年某月的两个节气的日期
* @param year 年
* @param month 月
* @returns {[number, number]} [节, 气]
*/
static getMonthTerms(year: number, month: number): [number, number] {
// js 位运算最大只支持32位,所以要先转成字符串截取
const data = TERM_SAME_HEX[TERM_LIST[year - FIRST_YEAR]].toString(2).padStart(48, '0')
const cutLen = (month - 1) * 4
const monthTermData = parseInt(data.slice(data.length - cutLen - 4, data.length - cutLen), 2)
const term1 = (monthTermData & 3) + TERM_MINIMUM_DATES[(month - 1) * 2]
const term2 = ((monthTermData >> 2) & 3) + TERM_MINIMUM_DATES[(month - 1) * 2 + 1]
return [term1, term2]
}
// 查出指定节气的日期 [year, month, day]
static findDate(
year: number,
termValue: number | string | SolarTerm,
config?: ClassCommonConfig
): [number, number, number] {
const lang = config && config.lang ? config.lang : _GlobalConfig.lang
if (termValue instanceof SolarTerm) termValue = termValue.value
termValue =
typeof termValue === 'string'
? _GlobalConfig.locales[lang].solarTerm.indexOf(termValue)
: termValue % 24
const month = termValue >> 1
const dayList = SolarTerm.getYearTermDayList(year)
const day = dayList[termValue]
return [year, month + 1, day]
}
/**
* 查出指定日期属于哪个节或气之后,并返回该节气及该节气日期
* @param date 日期
* @param config 设置
- nodeFlag: number
- lang: string
- isUTC: boolean
* @returns {[Term | number, number]} [节气, 节气日期]
*/
static findNode<T extends boolean = false>(
date: Date,
config: TermFindNodeConfig<T>
): [T extends true ? number : SolarTerm, Date]
static findNode(date: Date, config?: TermFindNodeConfig<boolean>): [SolarTerm | number, Date] {
const configDefault: TermFindNodeConfig0 = {
lang: _GlobalConfig.lang,
returnValue: false,
nodeFlag: 0,
isUTC: false
}
const cfg = config ? Object.assign({}, configDefault, config) : configDefault
const { returnValue, nodeFlag } = cfg
if (nodeFlag > 2) throw new Error('Invalid nodeFlag')
const newSolarTermConfig = {
lang: cfg.lang || _GlobalConfig.lang
}
let year = getDateData(date, 'FullYear', cfg.isUTC)
let month = getDateData(date, 'Month', cfg.isUTC)
const d = getDateData(date, 'Date', cfg.isUTC)
const h = getDateData(date, 'Hours', cfg.isUTC)
let termValue = (month * 2 + 24) % 24 // 取得该月的节的value值
let [termDay1, termDay2] = SolarTerm.getMonthTerms(year, month + 1)
let usePreMonth = false
let beforeTerm2 = false
if (d < termDay1 && !(d === termDay1 - 1 && h >= 23)) {
// 当日期在节前, 则取上一个月
usePreMonth = true
} else if (d < termDay2 && !(d === termDay2 - 1 && h >= 23)) {
beforeTerm2 = true
// 当日期在气之前节之后,前且nodeFlag要求返回气,则取上一个月
if (nodeFlag === 1) usePreMonth = true
}
let termDay: number
let returnTerm2 = false
if (usePreMonth) {
if (month - 1 < 0) {
year--
month = 11
} else {
month--
}
termValue = (month * 2 + 24) % 24
;[termDay1, termDay2] = SolarTerm.getMonthTerms(year, month + 1)
if (nodeFlag > 0) returnTerm2 = true
} else if (nodeFlag === 1 || (nodeFlag === 2 && !beforeTerm2)) returnTerm2 = true
termDay = returnTerm2 ? termDay2 : termDay1
termValue = returnTerm2 ? (termValue + 1) % 24 : termValue
const termDate = parseDate(`${year}-${month + 1}-${termDay}`)
if (returnValue) return [termValue, termDate]
return [new SolarTerm(termValue, newSolarTermConfig), termDate]
}
valueOf() {
return this.value
}
toString() {
return this.name
}
}