完成1.0版

master
宇天 2021-11-30 18:50:37 +08:00
parent 4564fb60b8
commit 0d35f290b7
12 changed files with 668 additions and 493 deletions

View File

@ -6,4 +6,5 @@ printWidth: 100
useTabs: false useTabs: false
tabWidth: 2 tabWidth: 2
trailingComma: none trailingComma: none
bracketSpacing: true bracketSpacing: true
arrowParens: avoid

View File

@ -6,25 +6,43 @@
\____\__,_|_|\___|_| |_|\__,_|\__,_|_| 终端版万年历 \____\__,_|_|\___|_| |_|\__,_|\__,_|_| 终端版万年历
``` ```
农历的计算, 只支持 1901-2100 范围内的。
![demo1.png](./img/demo1.png) ![demo1.png](./img/demo.png)
## 安装 ## 安装
```bash ```bash
npm i -g bash-calendar npm i -g bash-calendar
# 安装完之后, 可以使用 calendar命令
# 也可以使用 简写的 cal命令
``` ```
## 用法 ## 用法
> 用法: `cal [command] args...` > 用法: `cal [command] args...`
+ Commands: - Commands:
* -h - 查看帮助文档 * -y {year} - 打印指定年份的日历
* -v - 查看工具的版本 * -m - 打印指定月份的日历
* -h - 查看帮助文档
* -v - 查看程序的版本
示例:
```bash
cal # 不带参数打印当前年月
cal -y # -y不接参数, 打印当前年的所有月份
cal -y 2000 # 打印指定年份的所有月份
cal -y 2021 5 # 打印指定年份, 指定月份
cal -y 2000 -m 3 # 同上
cal -m 5 # 打印当前年份的 指定月份
```
## 更新日志 ## 更新日志
### v1.0.0
* 完成农历显示、公历节日及农历节日的显示
### v0.0.1 ### v0.0.1
* 初始化项目 * 初始化项目

14
build.js Normal file
View File

@ -0,0 +1,14 @@
const esbuild = require('esbuild')
const { version } = require('./package.json')
esbuild.build({
entryPoints: ['src/index.js'],
sourcemap: false,
bundle: true,
define: { 'process.env.APP_VERSION': `'${version}'` },
platform: 'node',
target: 'node10',
minify: true,
// external: ['chalk'],
outfile: 'index.js'
})

BIN
img/demo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

124
index.js Normal file → Executable file

File diff suppressed because one or more lines are too long

359
lunar.js
View File

@ -1,359 +0,0 @@
/*
1建立农历年份查询表
2计算输入公历日期与公历基准的相差天数
3从农历基准开始遍历农历查询表计算自农历基准之后每一年的天数并用相差天数依次相减确定农历年份
4利用剩余相差天数以及农历每个月的天数确定农历月份
5利用剩余相差天数确定农历哪一天 */
// 农历1949-2100年查询表
const LUNAR_YEARS = [
0x0b557, //1949
0x06ca0,
0x0b550,
0x15355,
0x04da0,
0x0a5b0,
0x14573,
0x052b0,
0x0a9a8,
0x0e950,
0x06aa0, //1950-1959
0x0aea6,
0x0ab50,
0x04b60,
0x0aae4,
0x0a570,
0x05260,
0x0f263,
0x0d950,
0x05b57,
0x056a0, //1960-1969
0x096d0,
0x04dd5,
0x04ad0,
0x0a4d0,
0x0d4d4,
0x0d250,
0x0d558,
0x0b540,
0x0b6a0,
0x195a6, //1970-1979
0x095b0,
0x049b0,
0x0a974,
0x0a4b0,
0x0b27a,
0x06a50,
0x06d40,
0x0af46,
0x0ab60,
0x09570, //1980-1989
0x04af5,
0x04970,
0x064b0,
0x074a3,
0x0ea50,
0x06b58,
0x055c0,
0x0ab60,
0x096d5,
0x092e0, //1990-1999
0x0c960,
0x0d954,
0x0d4a0,
0x0da50,
0x07552,
0x056a0,
0x0abb7,
0x025d0,
0x092d0,
0x0cab5, //2000-2009
0x0a950,
0x0b4a0,
0x0baa4,
0x0ad50,
0x055d9,
0x04ba0,
0x0a5b0,
0x15176,
0x052b0,
0x0a930, //2010-2019
0x07954,
0x06aa0,
0x0ad50,
0x05b52,
0x04b60,
0x0a6e6,
0x0a4e0,
0x0d260,
0x0ea65,
0x0d530, //2020-2029
0x05aa0,
0x076a3,
0x096d0,
0x04afb,
0x04ad0,
0x0a4d0,
0x1d0b6,
0x0d250,
0x0d520,
0x0dd45, //2030-2039
0x0b5a0,
0x056d0,
0x055b2,
0x049b0,
0x0a577,
0x0a4b0,
0x0aa50,
0x1b255,
0x06d20,
0x0ada0, //2040-2049
0x14b63,
0x09370,
0x049f8,
0x04970,
0x064b0,
0x168a6,
0x0ea50,
0x06b20,
0x1a6c4,
0x0aae0, //2050-2059
0x0a2e0,
0x0d2e3,
0x0c960,
0x0d557,
0x0d4a0,
0x0da50,
0x05d55,
0x056a0,
0x0a6d0,
0x055d4, //2060-2069
0x052d0,
0x0a9b8,
0x0a950,
0x0b4a0,
0x0b6a6,
0x0ad50,
0x055a0,
0x0aba4,
0x0a5b0,
0x052b0, //2070-2079
0x0b273,
0x06930,
0x07337,
0x06aa0,
0x0ad50,
0x14b55,
0x04b60,
0x0a570,
0x054e4,
0x0d160, //2080-2089
0x0e968,
0x0d520,
0x0daa0,
0x16aa6,
0x056d0,
0x04ae0,
0x0a9d4,
0x0a2d0,
0x0d150,
0x0f252, //2090-2099
0x0d520 //2100
]
const LUNAR_MONTH = ['正', '二', '三', '四', '五', '六', '七', '八', '九', '十', '冬', '腊']
const LUNAR_DAY = {
0: '初',
1: '一',
2: '二',
3: '三',
4: '四',
5: '五',
6: '六',
7: '七',
8: '八',
9: '九',
10: '十',
20: '廿'
}
// 生肖、天干、地支, 节气
const ZODIAC = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪']
const HEAVENLY_STEMS = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']
const EARTHLY_BRANCHES = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']
const SOLAR_TERMS = [
'立春',
'雨水',
'惊蛰',
'春分',
'清明',
'谷雨',
'立夏',
'小满',
'芒种',
'夏至',
'小暑',
'大暑',
'立秋',
'处暑',
'白露',
'秋分',
'寒露',
'霜降',
'立冬',
'小雪',
'大雪',
'冬至',
'小寒',
'大寒'
]
// 公历转农历函数
export function sloar2lunar(sy, sm, sd) {
// 计算与公历基准的相差天数
let daySpan = (Date.UTC(sy, sm, sd) - Date.UTC(1949, 0, 29)) / (24 * 60 * 60 * 1000) + 1
let ly, lm, ld
// 确定输出的农历年份
for (let j = 0; j < LUNAR_YEARS.length; j++) {
daySpan -= lunarYearDays(LUNAR_YEARS[j])
if (daySpan <= 0) {
ly = 1949 + j
// 获取农历年份确定后的剩余天数
daySpan += lunarYearDays(LUNAR_YEARS[j])
break
}
}
// 确定输出的农历月份
for (let k = 0; k < lunarYearMonths(LUNAR_YEARS[ly - 1949]).length; k++) {
daySpan -= lunarYearMonths(LUNAR_YEARS[ly - 1949])[k]
if (daySpan <= 0) {
// 有闰月时月份的数组长度会变成13因此当闰月月份小于等于k时lm不需要加1
if (hasLeapMonth(LUNAR_YEARS[ly - 1949]) && hasLeapMonth(LUNAR_YEARS[ly - 1949]) <= k) {
if (hasLeapMonth(LUNAR_YEARS[ly - 1949]) < k) {
lm = k
} else if (hasLeapMonth(LUNAR_YEARS[ly - 1949]) === k) {
lm = '闰' + k
} else {
lm = k + 1
}
} else {
lm = k + 1
}
// 获取农历月份确定后的剩余天数
daySpan += lunarYearMonths(LUNAR_YEARS[ly - 1949])[k]
break
}
}
// 确定输出农历哪一天
ld = daySpan
// console.log(sm, sd, ly, lm, ld)
// 将计算出来的农历月份转换成汉字月份,闰月需要在前面加上闰字
if (hasLeapMonth(LUNAR_YEARS[ly - 1949]) && (typeof lm === 'string' && lm.indexOf('闰') > -1)) {
lm = `${LUNAR_MONTH[/\d/.exec(lm) - 1]}`
} else {
lm = LUNAR_MONTH[lm - 1]
}
// 将计算出来的农历年份转换为天干地支年
ly = getTianGan(ly) + getDiZhi(ly)
if (ld < 1) {
return ' '.repeat(4)
}
if (ld === 1) {
return lm + '月'
}
if (ld < 11) {
return LUNAR_DAY[0] + LUNAR_DAY[ld]
}
if (ld < 20) {
return LUNAR_DAY[10] + LUNAR_DAY[ld - 10]
}
if (ld === 20) {
return LUNAR_DAY[2] + LUNAR_DAY[10]
}
if (ld === 30) {
return LUNAR_DAY[3] + LUNAR_DAY[10]
}
return LUNAR_DAY[20] + LUNAR_DAY[ld - 20]
}
// 计算农历年是否有闰月参数为存储农历年的16进制
// 农历年份信息用16进制存储其中16进制的最后1位可以用于判断是否有闰月
function hasLeapMonth(ly) {
// 获取16进制的最后1位需要用到&与运算符
if (ly & 0xf) {
return ly & 0xf
} else {
return false
}
}
// 如果有闰月计算农历闰月天数参数为存储农历年的16进制
// 农历年份信息用16进制存储其中16进制的第1位0x除外可以用于表示闰月是大月还是小月
function leapMonthDays(ly) {
if (hasLeapMonth(ly)) {
// 获取16进制的第1位0x除外
return ly & 0xf0000 ? 30 : 29
} else {
return 0
}
}
// 计算农历一年的总天数参数为存储农历年的16进制
// 农历年份信息用16进制存储其中16进制的第2-4位0x除外可以用于表示正常月是大月还是小月
function lunarYearDays(ly) {
let totalDays = 0
// 获取正常月的天数,并累加
// 获取16进制的第2-4位需要用到>>移位运算符
for (let i = 0x8000; i > 0x8; i >>= 1) {
let monthDays = ly & i ? 30 : 29
totalDays += monthDays
}
// 如果有闰月,需要把闰月的天数加上
if (hasLeapMonth(ly)) {
totalDays += leapMonthDays(ly)
}
return totalDays
}
// 获取农历每个月的天数
// 参数需传入16进制数值
function lunarYearMonths(ly) {
let monthArr = []
// 获取正常月的天数并添加到monthArr数组中
// 获取16进制的第2-4位需要用到>>移位运算符
for (let i = 0x8000; i > 0x8; i >>= 1) {
monthArr.push(ly & i ? 30 : 29)
}
// 如果有闰月,需要把闰月的天数加上
if (hasLeapMonth(ly)) {
monthArr.splice(hasLeapMonth(ly), 0, leapMonthDays(ly))
}
return monthArr
}
// 将农历年转换为天干,参数为农历年
function getTianGan(ly) {
let tianGanKey = (ly - 3) % 10
if (tianGanKey === 0) tianGanKey = 10
return HEAVENLY_STEMS[tianGanKey - 1]
}
// 将农历年转换为地支,参数为农历年
function getDiZhi(ly) {
let diZhiKey = (ly - 3) % 12
if (diZhiKey === 0) diZhiKey = 12
return EARTHLY_BRANCHES[diZhiKey - 1]
}

View File

@ -1,15 +1,14 @@
{ {
"name": "bash-calendar", "name": "bash-calendar",
"description": "终端版万年历", "description": "终端版万年历",
"version": "0.0.1", "version": "1.0.0",
"type": "module",
"author": "yutent <yutent.io@gmail.com>", "author": "yutent <yutent.io@gmail.com>",
"bin": { "bin": {
"calendar": "index.js", "calendar": "index.js",
"cal": "index.js" "cal": "index.js"
}, },
"dependencies": { "engines": {
"chalk": "^4.0.0" "node": ">=10.0.0"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -5,7 +5,7 @@
*/ */
import chalk from 'chalk' import chalk from 'chalk'
import { sloar2lunar } from './lunar.js' import { solar2lunar, getHSEBYear } from './lunar/index.js'
const CAL_HEAD = ['日', '一', '二', '三', '四', '五', '六'].map((s, i) => { const CAL_HEAD = ['日', '一', '二', '三', '四', '五', '六'].map((s, i) => {
s = '星期' + s s = '星期' + s
@ -63,13 +63,15 @@ export function getCalendarTable(year, month) {
} }
if (i > 0) { if (i > 0) {
let week = getFirstDay(year, month, i) let week = getFirstDay(year, month, i)
let lunar = solar2lunar(year, month, i)
tmp.weekend = week === 0 || week === 6 tmp.weekend = week === 0 || week === 6
tmp.picked = !!isPicked({ year, month, day: i }, today) tmp.picked = !!isPicked({ year, month, day: i }, today)
tmp.lunar = sloar2lunar(year, month, i) tmp.lunar = lunar.short
tmp.highlight = !!lunar.festival
} else { } else {
// 从上个月中补齐第1周 // 从上个月中补齐第1周
tmp.grey = 1 tmp.grey = 1
tmp.lunar = sloar2lunar(year, month - 1, lnums - -i) tmp.lunar = solar2lunar(year, month - 1, lnums + i).short
} }
list.push(tmp) list.push(tmp)
} }
@ -81,7 +83,7 @@ export function getCalendarTable(year, month) {
for (let day = 1; day <= nd; day++) { for (let day = 1; day <= nd; day++) {
list.push({ list.push({
day: (day + '').padStart(2, '0'), day: (day + '').padStart(2, '0'),
lunar: sloar2lunar(year, month + 1, day), lunar: solar2lunar(year, month + 1, day).short,
grey: 1 grey: 1
}) })
} }
@ -90,7 +92,7 @@ export function getCalendarTable(year, month) {
// 画表头 // 画表头
function drawThead(year, month) { function drawThead(year, month) {
var dateStr = `${year}${month + 1}` var dateStr = `${year}${month + 1}${' '.repeat(10)}${getHSEBYear(year, month)}`
dateStr = dateStr =
chalk.grey('| ') + chalk.cyan(dateStr) + ' '.repeat(75 - dateStr.length - 2) + chalk.grey('|') chalk.grey('| ') + chalk.cyan(dateStr) + ' '.repeat(75 - dateStr.length - 2) + chalk.grey('|')
@ -155,9 +157,12 @@ function drawTbody(year, month) {
if (tmp.picked) { if (tmp.picked) {
tr += chalk.bgRed.white.bold(' '.repeat(3) + tmp.lunar + ' '.repeat(3)) + VLINE tr += chalk.bgRed.white.bold(' '.repeat(3) + tmp.lunar + ' '.repeat(3)) + VLINE
} else { } else {
tmp.lunar = chalk.grey(tmp.lunar) let pad = 5
if (tmp.lunar) {
tr += ' '.repeat(3) + tmp.lunar + ' '.repeat(3) + VLINE pad = (10 - tmp.lunar.length * 2) / 2
tmp.lunar = tmp.highlight ? chalk.cyan.dim(tmp.lunar) : chalk.grey(tmp.lunar)
}
tr += ' '.repeat(pad) + tmp.lunar + ' '.repeat(pad) + VLINE
} }
break break
} }

129
src/index.js Normal file
View File

@ -0,0 +1,129 @@
#!/usr/bin/env node
/**
* {终端版万年历}
* @author yutent<yutent.io@gmail.com>
* @date 2021/11/26 17:20:02
*/
import chalk from 'chalk'
import { getThisYearMonth, drawCalendar } from './calendar.js'
var version = process.env.APP_VERSION
var argvs = process.argv.slice(2)
var action = argvs.shift()
var [year, month] = getThisYearMonth()
function drawOneYear(y) {
for (let i = 0; i < 12; i++) {
drawCalendar(y, i)
}
}
function print(...args) {
args[0] = args[0].padEnd(20, ' ')
if (args.length > 1) {
args.splice(1, 0, ' - ')
}
console.log.apply(null, args)
}
function print_help() {
print('='.repeat(64))
print(`终端版万年历 v${version}, 作者: 宇天`)
print('='.repeat(64))
print('用法: cal [command] args...')
print('Commands:')
print(' -y {year}', '打印指定年份的日历')
print(' -m', '打印指定月份的日历')
print(' -h', '查看帮助文档')
print(' -v', '查看程序的版本\n')
print('示例: ')
print(' cal ' + chalk.grey('# 不带参数打印当前年月'))
print(' cal -y ' + chalk.grey('# -y 不接参数, 打印当前年的所有月份'))
print(' cal -y 2000 ' + chalk.grey('# 打印指定年份的所有月份'))
print(' cal -y 2021 5 ' + chalk.grey('# 打印指定年份, 指定月份'))
print(' cal -y 2000 -m 3 ' + chalk.grey('# 同上'))
print(' cal -m 5 ' + chalk.grey('# 打印当前年份的 指定月份'))
process.exit()
}
switch (action) {
case '-y':
switch (argvs.length) {
// 再无其他参数, 由打印当前年份所有的日历
case 0:
drawOneYear(year)
break
// 有1~2个参数, cal -y 2020 2, cal -y 2020 5
case 1:
case 2:
year = +argvs.shift()
month = +argvs.shift()
if (year === year) {
if (month < 13 && month > 0) {
drawCalendar(year, month - 1)
} else {
drawOneYear(year)
}
} else {
console.log('-y 参数异常')
}
break
// 3个参数 cal -y 2020 -m 1
case 3:
action = argvs.shift()
month = +argvs.shift()
if (action === '-m' && month < 13 && month > 0) {
drawCalendar(year, month - 1)
} else {
console.log('-m 参数异常')
}
break
default:
console.log('-y 参数异常')
break
}
break
case '-m':
month = +argvs.shift()
if (month < 13 && month > 0) {
drawCalendar(year, month - 1)
} else {
console.log('-m 参数异常')
}
break
case '-v':
print(version)
break
case '-h':
print_help()
break
default:
if (action) {
year = +action
month = +argvs.shift()
if (year === year) {
if (month < 13 && month > 0) {
drawCalendar(year, month - 1)
} else {
drawOneYear(year)
}
} else {
console.log('参数异常')
}
} else {
drawCalendar(year, month)
}
break
}
process.exit()

303
src/lunar/config.js Normal file
View File

@ -0,0 +1,303 @@
// 农历1900-2100年查询表
export const LUNAR_YEARS = [
0x04bd8, // 1900
0x04ae0,
0x0a570,
0x054d5,
0x0d260,
0x0d950,
0x16554,
0x056a0,
0x09ad0,
0x055d2, //1909
0x04ae0,
0x0a5b6,
0x0a4d0,
0x0d250,
0x1d255,
0x0b540,
0x0d6a0,
0x0ada2,
0x095b0,
0x14977, //1919
0x04970,
0x0a4b0,
0x0b4b5,
0x06a50,
0x06d40,
0x1ab54,
0x02b60,
0x09570,
0x052f2,
0x04970, //1929
0x06566,
0x0d4a0,
0x0ea50,
0x16a95,
0x05ad0,
0x02b60,
0x186e3,
0x092e0,
0x1c8d7,
0x0c950, //1939
0x0d4a0,
0x1d8a6,
0x0b550,
0x056a0,
0x1a5b4,
0x025d0,
0x092d0,
0x0d2b2,
0x0a950,
0x0b557, //1949
0x06ca0,
0x0b550,
0x15355,
0x04da0,
0x0a5b0,
0x14573,
0x052b0,
0x0a9a8,
0x0e950,
0x06aa0, //1959
0x0aea6,
0x0ab50,
0x04b60,
0x0aae4,
0x0a570,
0x05260,
0x0f263,
0x0d950,
0x05b57,
0x056a0, //1969
0x096d0,
0x04dd5,
0x04ad0,
0x0a4d0,
0x0d4d4,
0x0d250,
0x0d558,
0x0b540,
0x0b6a0,
0x195a6, //1979
0x095b0,
0x049b0,
0x0a974,
0x0a4b0,
0x0b27a,
0x06a50,
0x06d40,
0x0af46,
0x0ab60,
0x09570, //1989
0x04af5,
0x04970,
0x064b0,
0x074a3,
0x0ea50,
0x06b58,
0x05ac0,
0x0ab60,
0x096d5,
0x092e0, //1999
0x0c960,
0x0d954,
0x0d4a0,
0x0da50,
0x07552,
0x056a0,
0x0abb7,
0x025d0,
0x092d0,
0x0cab5, //2009
0x0a950,
0x0b4a0,
0x0baa4,
0x0ad50,
0x055d9,
0x04ba0,
0x0a5b0,
0x15176,
0x052b0,
0x0a930, //2019
0x07954,
0x06aa0,
0x0ad50,
0x05b52,
0x04b60,
0x0a6e6,
0x0a4e0,
0x0d260,
0x0ea65,
0x0d530, //2029
0x05aa0,
0x076a3,
0x096d0,
0x04afb,
0x04ad0,
0x0a4d0,
0x1d0b6,
0x0d250,
0x0d520,
0x0dd45, //2039
0x0b5a0,
0x056d0,
0x055b2,
0x049b0,
0x0a577,
0x0a4b0,
0x0aa50,
0x1b255,
0x06d20,
0x0ada0, //2049
0x14b63,
0x09370,
0x049f8,
0x04970,
0x064b0,
0x168a6,
0x0ea50,
0x06b20,
0x1a6c4,
0x0aae0, //2059
0x092e0,
0x0d2e3,
0x0c960,
0x0d557,
0x0d4a0,
0x0da50,
0x05d55,
0x056a0,
0x0a6d0,
0x055d4, //2069
0x052d0,
0x0a9b8,
0x0a950,
0x0b4a0,
0x0b6a6,
0x0ad50,
0x055a0,
0x0aba4,
0x0a5b0,
0x052b0, //2079
0x0b273,
0x06930,
0x07337,
0x06aa0,
0x0ad50,
0x14b55,
0x04b60,
0x0a570,
0x054e4,
0x0d160, //2089
0x0e968,
0x0d520,
0x0daa0,
0x16aa6,
0x056d0,
0x04ae0,
0x0a9d4,
0x0a2d0,
0x0d150,
0x0f252, //2099
0x0d520 // 2100
]
export const LUNAR_MONTH = ['正', '二', '三', '四', '五', '六', '七', '八', '九', '十', '冬', '腊']
export const LUNAR_DAY = {
0: '初',
1: '一',
2: '二',
3: '三',
4: '四',
5: '五',
6: '六',
7: '七',
8: '八',
9: '九',
10: '十',
20: '廿'
}
// 生肖、天干、地支, 节气
export const ZODIAC = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪']
export const HEAVENLY_STEMS = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']
export const EARTHLY_BRANCHES = [
'子',
'丑',
'寅',
'卯',
'辰',
'巳',
'午',
'未',
'申',
'酉',
'戌',
'亥'
]
// 24节气表和C值
export const SOLAR_TERMS = [
'小寒',
'大寒',
'立春',
'雨水',
'惊蛰',
'春分',
'清明',
'谷雨',
'立夏',
'小满',
'芒种',
'夏至',
'小暑',
'大暑',
'立秋',
'处暑',
'白露',
'秋分',
'寒露',
'霜降',
'立冬',
'小雪',
'大雪',
'冬至'
]
// 农历节日(仅为常规节日, 地方节日、教派节日未收录)
export const FESTIVALS = {
'1.1': '春节',
'1.15': '元宵节',
'2.2': '龙抬头',
'3.3': '上巳节',
'5.5': '端午节',
'7.7': '七夕节',
'7.15': '中元节',
'8.15': '中秋节',
'9.9': '重阳节',
'10.1': '寒衣节',
'10.15': '下元节',
'12.8': '腊八节',
'12.23': '北方小年',
'12.24': '南方小年',
'12.30': '除夕' // 不一定有年三十, 计算时会修正
}
// 公历节日(不全, 仅国内常用的)
export const SOLAR_FESTIVALS = {
'1.1': '元旦节',
'2.14': '情人节',
'3.8': '妇女节',
'3.12': '植树节',
'4.1': '愚人节',
'5.1': '劳动节',
'5.4': '青年节',
'5.12': '护士节',
'6.1': '儿童节',
'7.1': '建党节',
'8.1': '建军节',
'9.10': '教师节',
'10.1': '国庆节',
'12.24': '平安夜',
'12.25': '圣诞节'
}

175
src/lunar/index.js Normal file
View File

@ -0,0 +1,175 @@
/**
* {农历转换算法}
* @author yutent<yutent.io@gmail.com>
* @date 2021/11/30 13:31:34
*/
import {
LUNAR_YEARS,
LUNAR_MONTH,
LUNAR_DAY,
HEAVENLY_STEMS,
EARTHLY_BRANCHES,
ZODIAC,
FESTIVALS,
SOLAR_FESTIVALS
} from './config.js'
/**
* 公历转农历函数
* 传入公历{年月日}, 返回农历信息, 范围支持 1901/01/01 ~ 2100/12/31
*/
export function solar2lunar(year = 1901, month = 0, day = 1) {
var baseDate = Date.UTC(1900, 0, 31) // 公历基准(1900年春节)
var timestamp = Date.UTC(year, month, day) // 传入日期的时间戳
var offset = (timestamp - baseDate) / (24 * 60 * 60 * 1000) + 1 // 计算与的相差天数, 有1天的修正
var months, leap
var result = { short: '' }
if (year < 1901 || year > 2100) {
return result
}
// 计算给出的公历对应的农历年份
for (let y = 1900; y <= 2100; y++) {
let days = getLunarYearDays(y)
offset -= days
// 确定之后, 得出农历天数(距离正月初一)
if (offset <= 0) {
result.year = y
offset += days
break
}
}
// 获取当年的农历月份天数
months = getLunarMonthsDays(result.year)
leap = getLeapMonth(result.year)
// 计算农历月份
for (let m = 0; m < months.length; m++) {
offset -= months[m] // 一个一个月的天数相减,直到小于0
if (offset <= 0) {
result.month = m
result.day = offset + months[m] // 补回天数
// 有闰月, 且当前月份大于等于闰月, 那得到的月份要减1
if (leap > 0 && m >= leap) {
result.leap = m === leap
result.month -= 1
}
break
}
}
// 公历节日
result.festival = SOLAR_FESTIVALS[`${month + 1}.${day}`]
// 非闰月才有农历节日
if (!result.leap) {
// 修正没有年三十的除夕
if (result.month === 11 && result.day === months.pop()) {
result.festival = FESTIVALS['12.30']
} else {
result.festival = FESTIVALS[`${result.month + 1}.${result.day}`] || result.festival
}
}
result.yearCN = getHSEBYear(result.year)
result.monthCN = (result.leap > 0 ? '闰' : '') + LUNAR_MONTH[result.month] + '月'
if (result.day < 11) {
result.dayCN = LUNAR_DAY[0] + LUNAR_DAY[result.day]
} else if (result.day < 20) {
result.dayCN = LUNAR_DAY[10] + LUNAR_DAY[result.day - 10]
} else if (result.day === 20) {
result.dayCN = LUNAR_DAY[2] + LUNAR_DAY[10]
} else if (result.day < 30) {
result.dayCN = LUNAR_DAY[20] + LUNAR_DAY[result.day - 20]
} else if (result.day === 30) {
result.dayCN = LUNAR_DAY[3] + LUNAR_DAY[10]
}
result.short = result.festival
? result.festival
: result.day === 1
? result.monthCN
: result.dayCN
return result
}
/**
* 获取指定年份的农历天数
* @param year {Number} 1901 ~ 2100
* @return {Number}
*/
function getLunarYearDays(year) {
var months = getLunarMonthsDays(year)
return months.reduce((sum, n) => (sum += n), 0)
}
/**
* 判断指定年份是否有闰月
* 16进制最后一位表示闰月的月份 (0-12), 0表示无闰月
* @param year {Number} 1901 - 2100
* @return {Number} 0-12
*/
function getLeapMonth(year) {
var hexYear = LUNAR_YEARS[year - 1900] // 转16进制年份
return hexYear & 0xf
}
/**
* 获取闰月的天数
* 16进制年份的第1位表示闰月是大小月
* @param year {Number} 1901 - 2100
* @return {Number}
*/
function getLeapMonthDays(year) {
var hexYear = LUNAR_YEARS[year - 1900] // 转16进制年份
var month = getLeapMonth(year)
if (month > 0) {
return hexYear & 0xf0000 ? 30 : 29
}
return 0
}
/**
* 获取指定年份的所有月份天数
* @param year {Number} 1901 - 2100
* @return {Array}
*/
function getLunarMonthsDays(year) {
var hexYear = LUNAR_YEARS[year - 1900] // 转16进制年份
var leap = getLeapMonth(year)
var leapDays = getLeapMonthDays(year)
var months = []
// 16进制年份的2-4位用于表示大小月
for (let i = 0x8000; i > 0x8; i >>= 1) {
months.push(hexYear & i ? 30 : 29)
}
if (leap > 0) {
months.splice(leap, 0, leapDays)
}
return months
}
/**
* 获取指定年份的天干地支生肖
* @param year {Number} 1901 - 2100
* @return {String}
*/
export function getHSEBYear(year) {
var hy = (year - 3) % 10
var eb = (year - 3) % 12
hy = hy === 0 ? 10 : hy
eb = eb === 0 ? 12 : eb
return HEAVENLY_STEMS[hy - 1] + EARTHLY_BRANCHES[eb - 1] + ZODIAC[(year - 4) % 12] + '年'
}