lunisolar
Version:
专业农历库,支持公历阴历互转,支持各类黄历数据查询,如八字四柱、阴历、神煞宜忌、时辰吉凶、建除十二神、胎神占方、五行纳音等。支持自定义插件。
230 lines (206 loc) • 6.4 kB
text/typescript
import {
parseDate,
phaseOfTheMoon,
getLunarNewYearDay,
getYearLeapMonth,
parseFromLunar,
getDateData,
getDateOfStartOf23H
} from '../utils'
import { FIRST_YEAR, LAST_YEAR, LUNAR_MONTH_DATAS } from '../constants/lunarData'
import { _GlobalConfig } from '../config'
/**
* @param year 春節所在的公歷年
* @param dateDiff 當日與當年春節相差天數
* @returns [月, 日]
*/
function getLunarMonthDate(
year: number,
dateDiff: number,
yearLeapMonth?: [number, boolean]
): [number, number] {
const monthData = LUNAR_MONTH_DATAS[year - FIRST_YEAR]
// 取出闰月
const [leapMonth, leapMonthIsBig] = yearLeapMonth || getYearLeapMonth(year)
let month = 1
dateDiff += 1 // 因为是从正月初一开始计算,所以要加1
let isLeap = false
while (dateDiff > 29) {
const isBig = (monthData >> (month - 1)) & 1
const mLength = isBig ? 30 : 29
dateDiff = dateDiff - mLength
if (month === leapMonth && dateDiff > 0) {
// 当还有剩余有dateDiff时,并且是闰月出现的月份,检查是否在闰月
const leapMonthDateLong = leapMonthIsBig ? 30 : 29
if (dateDiff > leapMonthDateLong) {
dateDiff = dateDiff - leapMonthDateLong
} else {
isLeap = true
break
}
}
month++
}
if (isLeap) month += 100
if (dateDiff === 0) {
dateDiff = 30
month--
}
return [month, dateDiff]
}
/**
* 兩日相關天數
* @param date1 起始日
* @param date2 結束日
* @returns 天數
*/
function getDateDiff(date1: Date, date2: Date): number {
return Math.round((date2.valueOf() - date1.valueOf()) / 86400000)
}
/**
* class Lunar
*/
export class Lunar {
readonly _date: Date
readonly year: number
readonly month: number
readonly day: number
readonly hour: number
readonly leapMonth: number
readonly leapMonthIsBig: boolean
readonly _config: Required<LunarConfig> = {
lang: _GlobalConfig.lang,
isUTC: false
}
static fromLunar(param: ParseFromLunarParam, config?: LunarConfig): Lunar {
const date = parseFromLunar(param, config?.lang)
return new Lunar(date, config)
}
constructor(dateObj: DateParamType, config?: LunarConfig) {
if (config) {
this._config = Object.assign({}, this._config, config)
}
const _date = parseDate(dateObj)
this._date = _date
const isUTC = this._config.isUTC
let year = getDateData(_date, 'FullYear', isUTC)
let month = getDateData(_date, 'Month', isUTC)
let hours = getDateData(_date, 'Hours', isUTC)
const date = getDateOfStartOf23H(_date, isUTC)
const d = date.getDate()
// 計算年份
if (
year < FIRST_YEAR ||
year > LAST_YEAR
// (year === FIRST_YEAR && month < 1) ||
// (year === FIRST_YEAR && month === 1 && date.getDate() < 19)
) {
throw new Error('Invalid lunar year: out of range')
}
// 如果年份是 1901年,并且日期小于当年的农历新年,则取另一个规则
if ((year === FIRST_YEAR && month < 1) || (year === FIRST_YEAR && month === 1 && d < 19)) {
this.year = year - 1
if (month === 1 || (month < 1 && d >= 20)) {
// 大于等于 1901-01-20 时,为 十二月
this.month = 12
this.day = month === 1 ? 13 + d - 1 : d - 20 + 1
} else {
this.month = 11
this.day = 11 + d - 1
}
this.leapMonth = 8
this.leapMonthIsBig = false
} else {
// 1901年春节后的计算方式
let dateDiff = getDateDiff(getLunarNewYearDay(year), date)
if (dateDiff < 0) {
year = year - 1
dateDiff = getDateDiff(getLunarNewYearDay(year), date)
}
this.year = year
// 取得當年的闰月
const [leapMonth, leapMonthIsBig] = getYearLeapMonth(year)
this.leapMonth = leapMonth
this.leapMonthIsBig = leapMonthIsBig
// 計算年和月
;[this.month, this.day] = getLunarMonthDate(year, dateDiff, [leapMonth, leapMonthIsBig])
}
// 計算時辰 0 ~ 11
this.hour = (hours + 1) % 24 >> 1
}
get isLeapMonth(): boolean {
return this.month > 100
}
get isBigMonth(): boolean {
if (this.year === 1900 && this.month == 11) return false
if (this.year === 1900 && this.month == 12) return true
const monthData = LUNAR_MONTH_DATAS[this.year - FIRST_YEAR]
if (this.isLeapMonth) {
return ((monthData >> 12) & 1) === 1
} else {
return ((monthData >> (this.month - 1)) & 1) === 1
}
}
get isLastDayOfMonth(): boolean {
if (this.isBigMonth && this.day === 30) return true
if (!this.isBigMonth && this.day === 29) return true
return false
}
/**
* 当年正月初一的日期
*/
get lunarNewYearDay(): Date {
return getLunarNewYearDay(this.year)
}
/**
* 取得本农历年的取后一天
*/
get lastDayOfYear(): Date {
const nextNewYearDay = getLunarNewYearDay(this.year + 1)
return new Date(nextNewYearDay.valueOf() - 24 * 60 * 60 * 1000)
}
/**
* 取得月相
*/
get phaseOfTheMoon(): string {
return phaseOfTheMoon(this, _GlobalConfig.locales[this._config.lang])
}
toDate(): Date {
return new Date(this._date.valueOf())
}
getYearName(): string {
let res = ''
let year = this.year
const numerals = _GlobalConfig.locales[this._config.lang].numerals
while (year) {
const s = numerals[year % 10]
res = s + res
year = Math.floor(year / 10)
}
return res
}
getMonthName(): string {
const LunarMonthNames = _GlobalConfig.locales[this._config.lang].lunarMonths
const leapStr = _GlobalConfig.locales[this._config.lang].leap
return (this.isLeapMonth ? leapStr : '') + LunarMonthNames[(this.month % 100) - 1]
}
getDayName(): string {
const lunarDayNames = _GlobalConfig.locales[this._config.lang].lunarDays
return lunarDayNames[this.day - 1]
}
getHourName(): string {
return _GlobalConfig.locales[this._config.lang].branchs[this.hour]
}
toString(): string {
const locale = _GlobalConfig.locales[this._config.lang]
return `${this.getYearName()}${
locale.lunarYearUnit
}${this.getMonthName()}${this.getDayName()}${this.getHourName()}${locale.lunarHourUnit}`
}
valueOf(): number {
return this._date.valueOf()
}
static getLunarNewYearDay(year: number): Date {
return getLunarNewYearDay(year)
}
}