鸿蒙多功能工具箱开发实战(十二)-二十四节气与黄历数据展示
·
鸿蒙多功能工具箱开发实战(十二)-二十四节气与黄历数据展示
前言
二十四节气是中国传统历法的重要组成部分,黄历则包含宜忌、冲煞等信息。本文将讲解节气计算和黄历数据展示的实现。
一、二十四节气计算
1.1 节气算法
export class SolarTerms {
// 节气名称
private static TERMS = [
'小寒', '大寒', '立春', '雨水', '惊蛰', '春分',
'清明', '谷雨', '立夏', '小满', '芒种', '夏至',
'小暑', '大暑', '立秋', '处暑', '白露', '秋分',
'寒露', '霜降', '立冬', '小雪', '大雪', '冬至'
]
// 节气计算常数
private static TERM_INFO = [
0, 21208, 42467, 63836, 85337, 107014,
128867, 150921, 173149, 195551, 218072, 240693,
263343, 285989, 308563, 331033, 353350, 375494,
397447, 419210, 440795, 462224, 483532, 504758
]
/**
* 获取某年的节气日期
*/
static getTermDate(year: number, termIndex: number): Date {
const baseDate = new Date(1900, 0, 6, 2, 5) // 1900年1月6日02:05
const offset = this.TERM_INFO[termIndex] * 60000 // 毫秒
const termDate = new Date(baseDate.getTime() + offset)
// 按年份调整
const yearOffset = (year - 1900) * 365.2422 * 24 * 60 * 60 * 1000
return new Date(baseDate.getTime() + yearOffset + offset)
}
/**
* 获取当前节气
*/
static getCurrentTerm(date: Date): { name: string, date: Date, next: { name: string, date: Date } } {
const year = date.getFullYear()
for (let i = 0; i < 24; i++) {
const termDate = this.getTermDate(year, i)
const nextTermDate = this.getTermDate(year, (i + 1) % 24)
if (date >= termDate && date < nextTermDate) {
return {
name: this.TERMS[i],
date: termDate,
next: {
name: this.TERMS[(i + 1) % 24],
date: nextTermDate
}
}
}
}
return null
}
}
二、黄历数据
2.1 黄历信息结构
export interface HuangLiInfo {
// 基本信息
yearGanZhi: string // 年干支
monthGanZhi: string // 月干支
dayGanZhi: string // 日干支
shengxiao: string // 生肖
// 宜忌
yi: string[] // 宜
ji: string[] // 忌
// 冲煞
chong: string // 冲
sha: string // 煞
// 五行
wuxing: string // 五行
// 值神
zhishen: string // 值神
// 胎神
taishen: string // 胎神
// 彭祖百忌
pengzu: string // 彭祖百忌
}
2.2 黄历计算
export class HuangLiCalculator {
// 宜事列表
private static YI_LIST = [
'祭祀', '祈福', '求嗣', '开光', '出行', '解除',
'纳采', '冠笄', '嫁娶', '纳婿', '安床', '移徙',
'修造', '动土', '竖柱', '上梁', '纳畜', '入宅'
]
// 忌事列表
private static JI_LIST = [
'安葬', '破土', '启攒', '开仓', '出货财', '纳财',
'伐木', '作梁', '作灶', '掘井', '造桥', '盖屋'
]
/**
* 计算黄历信息
*/
static calculate(year: number, month: number, day: number): HuangLiInfo {
// 计算日干支
const dayGanZhi = this.getDayGanZhi(year, month, day)
// 计算月干支
const monthGanZhi = this.getMonthGanZhi(year, month)
// 计算年干支
const yearGanZhi = this.getYearGanZhi(year)
// 计算冲煞
const chong = this.getChong(dayGanZhi)
const sha = this.getSha(dayGanZhi)
// 计算宜忌(简化版,实际需要更复杂的算法)
const yi = this.calculateYi(day, month)
const ji = this.calculateJi(day, month)
return {
yearGanZhi,
monthGanZhi,
dayGanZhi,
shengxiao: this.getShengxiao(year),
yi,
ji,
chong,
sha,
wuxing: this.getWuxing(dayGanZhi),
zhishen: this.getZhiShen(day),
taishen: this.getTaiShen(day),
pengzu: this.getPengZu(dayGanZhi)
}
}
private static getDayGanZhi(year: number, month: number, day: number): string {
const baseDate = new Date(1900, 0, 1)
const targetDate = new Date(year, month - 1, day)
const offset = Math.floor((targetDate.getTime() - baseDate.getTime()) / 86400000)
const ganIndex = (offset + 10) % 10
const zhiIndex = (offset + 12) % 12
return this.TIANGAN[ganIndex] + this.DIZHI[zhiIndex]
}
private static calculateYi(day: number, month: number): string[] {
// 简化版:根据日期返回宜事
const yiCount = (day + month) % 8 + 3
return this.YI_LIST.slice(0, yiCount)
}
private static calculateJi(day: number, month: number): string[] {
// 简化版:根据日期返回忌事
const jiCount = (day + month) % 5 + 2
return this.JI_LIST.slice(0, jiCount)
}
}
三、界面实现
@Entry
@Component
struct HuangLiPage {
@State selectedDate: Date = new Date()
@State huangLiInfo: HuangLiInfo = null
build() {
Column() {
NavBar({ title: '黄历查询' })
// 日期选择
DatePicker({
selected: this.selectedDate,
onChange: (value) => {
this.selectedDate = new Date(value.year, value.month, value.day)
this.loadHuangLi()
}
})
if (this.huangLiInfo) {
// 黄历信息展示
Scroll() {
Column() {
// 干支信息
this.GanZhiSection()
// 宜忌
this.YiJiSection()
// 冲煞五行
this.ChongShaSection()
}
}
}
}
}
@Builder
GanZhiSection() {
Column() {
Text('干支纪年')
.fontSize(16)
.fontWeight(FontWeight.Bold)
Row() {
Text(`年: ${this.huangLiInfo.yearGanZhi}`)
Text(`月: ${this.huangLiInfo.monthGanZhi}`)
Text(`日: ${this.huangLiInfo.dayGanZhi}`)
}
}
}
@Builder
YiJiSection() {
Column() {
// 宜
Row() {
Text('宜')
.fontColor('#27AE60')
.width(40)
Text(this.huangLiInfo.yi.join(' '))
.layoutWeight(1)
}
// 忌
Row() {
Text('忌')
.fontColor('#E74C3C')
.width(40)
Text(this.huangLiInfo.ji.join(' '))
.layoutWeight(1)
}
}
}
private loadHuangLi() {
this.huangLiInfo = HuangLiCalculator.calculate(
this.selectedDate.getFullYear(),
this.selectedDate.getMonth() + 1,
this.selectedDate.getDate()
)
}
}
四、节气算法详解
4.1 节气计算流程图
4.2 节气数据精度优化
// 使用高精度算法计算节气时刻
class SolarTermPrecise {
// 简化儒略日计算
private static toJD(year: number, month: number, day: number): number {
if (month <= 2) {
year -= 1
month += 12
}
const A = Math.floor(year / 100)
const B = 2 - A + Math.floor(A / 4)
return Math.floor(365.25 * (year + 4716)) +
Math.floor(30.6001 * (month + 1)) +
day + B - 1524.5
}
// 计算太阳黄经
private static sunLongitude(jd: number): number {
const T = (jd - 2451545) / 36525
const L0 = 280.46646 + 36000.76983 * T + 0.0003032 * T * T
// 简化计算,实际需要更多项
return L0 % 360
}
}
五、黄历宜忌算法
5.1 宜忌规则引擎
// 黄历规则定义
interface HuangLiRule {
name: string // 规则名称
condition: (date: DateInfo) => boolean // 条件
yi?: string[] // 宜的事项
ji?: string[] // 忌的事项
}
// 规则库
const HUANG_LI_RULES: HuangLiRule[] = [
{
name: '建日',
condition: (date) => date.zhi === '建',
yi: ['出行', '上任', '会友'],
ji: ['动土', '开仓']
},
{
name: '除日',
condition: (date) => date.zhi === '除',
yi: ['沐浴', '求医', '治病'],
ji: ['嫁娶', '出行']
},
// 更多规则...
]
// 应用规则
function applyRules(dateInfo: DateInfo): { yi: string[], ji: string[] } {
const yi: string[] = []
const ji: string[] = []
for (const rule of HUANG_LI_RULES) {
if (rule.condition(dateInfo)) {
if (rule.yi) yi.push(...rule.yi)
if (rule.ji) ji.push(...rule.ji)
}
}
return { yi: [...new Set(yi)], ji: [...new Set(ji)] }
}
5.2 彭祖百忌
// 彭祖百忌表
const PENG_ZU_JI: Record<string, string> = {
'甲不开仓财物耗散': '甲日不宜开仓',
'乙不栽植千株不长': '乙日不宜栽植',
'丙不修灶必见灾殃': '丙日不宜修灶',
// ... 更多条目
}
function getPengZuJi(gan: string, zhi: string): string[] {
const result: string[] = []
// 天干忌
const ganJi = PENG_ZU_JI[`${gan}不`]
if (ganJi) result.push(ganJi)
// 地支忌
const zhiJi = PENG_ZU_JI[`${zhi}不`]
if (zhiJi) result.push(zhiJi)
return result
}
六、界面优化
6.1 节气动画效果
@Component
struct SolarTermAnimation {
@Prop term: SolarTerm
@State scale: number = 1
build() {
Column() {
Text(this.term.name)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.scale({ x: this.scale, y: this.scale })
.animation({
duration: 1000,
curve: Curve.EaseInOut,
iterations: -1
})
Text(this.term.date)
.fontSize(14)
.fontColor('#666666')
}
.onAppear(() => {
this.scale = 1.1
})
}
}
6.2 黄历卡片设计
@Component
struct HuangLiCard {
@Prop info: HuangLiInfo
build() {
Column() {
// 日期头部
this.Header()
// 宜忌内容
this.Content()
// 底部信息
this.Footer()
}
.padding(16)
.borderRadius(12)
.backgroundColor('#FFFFFF')
.shadow({ radius: 8, color: '#1A000000' })
}
@Builder
Header() {
Row() {
Column() {
Text(`${this.info.month}月${this.info.day}日`)
.fontSize(18)
Text(this.info.ganZhi)
.fontSize(12)
.fontColor('#666666')
}
Blank()
Text(this.info.zodiac)
.fontSize(14)
.fontColor('#E74C3C')
}
.width('100%')
}
@Builder
Content() {
Row() {
// 宜
Column() {
Text('宜')
.fontColor('#27AE60')
.fontSize(16)
ForEach(this.info.yi.slice(0, 5), (item: string) => {
Text(item)
.fontSize(12)
})
}
.layoutWeight(1)
Divider().vertical(true).height(80)
// 忌
Column() {
Text('忌')
.fontColor('#E74C3C')
.fontSize(16)
ForEach(this.info.ji.slice(0, 5), (item: string) => {
Text(item)
.fontSize(12)
})
}
.layoutWeight(1)
}
.width('100%')
.margin({ top: 16 })
}
}
图 1 黄历查询界面展示
七、数据缓存策略
// 节气数据缓存
class SolarTermCache {
private static cache: Map<number, SolarTerm[]> = new Map()
static getTerms(year: number): SolarTerm[] {
if (this.cache.has(year)) {
return this.cache.get(year)!
}
const terms = SolarTermCalculator.calculate(year)
this.cache.set(year, terms)
return terms
}
// 预加载相邻年份
static preload(year: number) {
this.getTerms(year - 1)
this.getTerms(year)
this.getTerms(year + 1)
}
}
八、单元测试
describe('SolarTermCalculator', () => {
it('should calculate Spring Equinox correctly', () => {
const terms = SolarTermCalculator.calculate(2024)
const springEquinox = terms.find(t => t.name === '春分')
expect(springEquinox?.date).toBe('2024-03-20')
})
it('should have 24 terms per year', () => {
const terms = SolarTermCalculator.calculate(2024)
expect(terms.length).toBe(24)
})
})
describe('HuangLiCalculator', () => {
it('should calculate Yi and Ji', () => {
const result = HuangLiCalculator.calculate(2024, 1, 1)
expect(result.yi.length).toBeGreaterThan(0)
expect(result.ji.length).toBeGreaterThan(0)
})
})
九、小结
本文详细讲解了节气和黄历功能:
- ✅ 二十四节气计算算法
- ✅ 黄历信息结构设计
- ✅ 干支计算原理
- ✅ 宜忌推算规则
- ✅ 界面展示优化
- ✅ 数据缓存策略
- ✅ 单元测试设计
9.1 关键要点
- 节气计算基于太阳黄经
- 黄历宜忌使用规则引擎
- 界面设计注重信息层次
- 缓存提升性能体验
系列文章导航:
下期预告:鸿蒙多功能工具箱开发实战(十三)-单位换算工具开发
更多推荐




所有评论(0)