Files
moxiaonian d9da4301cd 公众授权版:normal · 1000 次 · 有效期至 2026-12-31
副本编号 LY-20261007-AC63(水印指纹已嵌入)
含 README、.gitignore;排除 .DS_Store / __pycache__ / .pytest_cache
2026-10-07 17:29:52 +08:00

942 lines
39 KiB
Python

# -*- coding:utf-8 -*-
from __future__ import annotations
import warnings
from math import floor, ceil
from typing import TYPE_CHECKING, List, Union
from tyme4py import LoopTyme
from tyme4py.culture import Twenty, Direction, KitchenGodSteed, Week, Duty, Phase, God, Taboo, PhaseDay
from tyme4py.culture.star.twentyeight import TwentyEightStar
from tyme4py.culture.fetus import FetusMonth, FetusDay
from tyme4py.culture.ren import MinorRen
from tyme4py.culture.star.nine import NineStar
from tyme4py.culture.star.six import SixStar
from tyme4py.culture.star.twelve import TwelveStar
from tyme4py.eightchar import EightChar
from tyme4py.eightchar.provider import EightCharProvider
from tyme4py.eightchar.provider.impl import DefaultEightCharProvider
from tyme4py.festival import LunarFestival
from tyme4py.jd import JulianDay
from tyme4py.unit import YearUnit, MonthUnit, WeekUnit, DayUnit, SecondUnit
from tyme4py.util import ShouXingUtil
if TYPE_CHECKING:
from tyme4py.solar import SolarDay, SolarTime, SolarTerm
from tyme4py.sixtycycle import SixtyCycle, SixtyCycleDay, SixtyCycleHour, ThreePillars
class LunarYear(YearUnit):
"""
农历年
依据国家标准《农历的编算和颁行》GB/T 33661-2017,农历年以正月初一开始,至除夕结束。
"""
_IS_INIT: bool = False
_LEAP: List[List[int]] = []
@staticmethod
def _init():
if LunarYear._IS_INIT:
return
chars: str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_@'
months: List[str] = [
'080b0r0j0j0j0C0j0j0C0j0j0j0C0j0C0j0C0F0j0V0V0V0u0j0j0C0j0j0j0j0V0C0j1v0u0C0V1v0C0b080u110u0C0j0C1v9K1v2z0j1vmZbl1veN3s1v0V0C2S1v0V0C2S2o0C0j1Z1c2S1v0j1c0j2z1v0j1c0j392H0b2_2S0C0V0j1c0j2z0C0C0j0j1c0j0N250j0C0j0b081n080b0C0C0C1c0j0N',
'0r1v1c1v0V0V0F0V0j0C0j0C0j0V0j0u1O0j0C0V0j0j0j0V0b080u0r0u080b0j0j0C0V0C0V0j0b080V0u080b0j0j0u0j1v0u080b1c0j080b0j0V0j0j0V0C0N1v0j1c0j0j1v2g1v420j1c0j2z1v0j1v5Q9z1v4l0j1vfn1v420j9z4l1v1v2S1c0j1v2S3s1v0V0C2S1v1v2S1c0j1v2S2_0b0j2_2z0j1c0j',
'0z0j0j0j0C0j0j0C0j0j0j0C0j0C0j0j0j0j0m0j0C0j0j0C0j0j0j0j0b0V0j0j0C0j0j0j0j0V0j0j0j0V0b0V0V0C0V0C0j0j0b080u110u0V0C0j0N0j0b080b080b0j0r0b0r0b0j0j0j0j0C0j0b0r0C0j0b0j0C0C0j0j0j0j0j0j0j0j0j0b110j0b0j0j0j0C0j0C0j0j0j0j0b080b080b0V080b080b0j0j0j0j0j0j0V0j0j0u1v0j0j0j0C0j0j0j0V0C0N1c0j0C0C0j0j0j1n080b0j0V0C0j0C0C2g0j1c0j0j1v2g1v0j0j1v7N0j1c0j3L0j0j1v5Q1Z5Q1v4lfn1v420j1v5Q1Z5Q1v4l1v2z1v',
'0H140r0N0r140r0u0r0V171c11140C0j0u110j0u0j1v0j0C0j0j0j0b080V0u080b0C1v0j0j0j0C0j0b080V0j0j0b080b0j0j0j0j0b080b0C080j0b080b0j0j0j0j0j0j0b080j0b080C0b080b080b080b0j0j0j0j080b0j0C0j0j0j0b0j0j080C0b0j0j0j0j0j0j0b08080b0j0C0j0j0j0b0j0j0K0b0j0C0j0j0j0b080b080j0C0b0j080b080b0j0j0j0j080b0j0b0r0j0j0j0b0j0C0r0b0j0j0j0j0j0j0j0b080j0b0r0C0j0b0j0j0j0r0b0j0C0j0j0j0u0r0b0C0j080b0j0j0j0j0j0j0j1c0j0b0j0j0j0C0j0j0j0j0j0j0j0b080j1c0u0j0j0j0C0j1c0j0u0j1c0j0j0j0j0j0j0j0j1c0j0u1v0j0j0V0j0j2g0j0j0j0C1v0C1G0j0j0V0C1Z1O0j0V0j0j2g1v0j0j0V0C2g5x1v4l1v421O7N0V0C4l1v2S1c0j1v2S2_',
'050b080C0j0j0j0C0j0j0C0j0j0j0C0j0C0j0C030j0j0j0j0j0j0j0j0j0C0j0b080u0V080b0j0j0V0j0j0j0j0j0j0j0j0j0V0N0j0C0C0j0j0j0j0j0j0j0j1c0j0u0j1v0j0j0j0j0j0b080b080j0j0j0b080b080b080b080b0j0j0j080b0j0b080j0j0j0j0b080b0j0j0r0b080b0b080j0j0j0j0b080b080j0b080j0b080b080b080b080b0j0j0r0b0j0b080j0j0j0j0b080b0j0j0C080b0b080j0j0j0j0j0j0j0b080u080j0j0b0j0j0j0C0j0b080j0j0j0j0b080b080b080b0C080b080b080b0j0j0j0j0j0j0b0C080j0j0b0j0j0j0C0j0b080j0j0C0b080b080j0b0j0j0C080b0j0j0j0j0j0j0b0j0j080C0b0j080b0j0j0j0j0j0j0j0C0j0j0j0b0j0j0C080b0j0j0j0j0j0j0b080b080b0K0b080b080b0j0j0j0j0j0j0j0C0j0j0u0j0j0V0j080b0j0C0j0j0j0b0j0r0C0b0j0j0j0j0j0j0j0j0j0C0j0b080b080b0j0C0C0j0C0j0j0j0u110u0j0j0j0j0j0j0j0j0C0j0j0u0j1c0j0j0j0j0j0j0j0j0V0C0u0j0C0C0V0C1Z0j0j0j0C0j0j0j1v0u0j1c0j0j0j0C0j0j2g0j1c1v0C1Z0V0j4l0j0V0j0j2g0j1v0j1v2S1c7N1v',
'0w0j1c0j0V0j0j0V0V0V0j0m0V0j0C1c140j0j0j0C0V0C0j1v0j0N0j0C0j0j0j0V0j0j1v0N0j0j0V0j0j0j0j0j0j080b0j0j0j0j0j0j0j080b0j0C0j0j0j0b0j0j080u080b0j0j0j0j0j0j0b080b080b080C0b0j080b080b0j0j0j0j080b0j0C0j0j0j0b0j0j080u080b0j0j0j0j0j0j0b080b080b080b0r0b0j080b080b0j0j0j0j080b0j0b0r0j0j0b080b0j0j080b0j080b0j080b080b0j0j0j0j0j0b080b0r0C0b080b0j0j0j0j080b0b080b080j0j0j0b080b080b080b0j0j0j0j080b0j0b080j0j0j0j0b080b0j0j0r0b080b0j0j0j0j0j0b080b080j0b0r0b080j0b080b0j0j0j0j080b0j0b080j0j0j0j0b080b0j080b0r0b0j080b080b0j0j0j0j0j0b080b0r0C0b080b0j0j0j0j0j0j0b080j0j0j0b080b080b080b0j0j0j0r0b0j0b080j0j0j0j0b080b0r0b0r0b0j080b080b0j0j0j0j0j0j0b0r0j0j0j0b0j0j0j0j080b0j0b080j0j0j0j0b080b080b0j0r0b0j080b0j0j0j0j0j0j0j0b0r0C0b0j0j0j0j0j0j0j080b0j0C0j0j0j0b0j0C0r0b0j0j0j0j0j0j0b080b080u0r0b0j080b0j0j0j0j0j0j0j0b0r0C0u0j0j0j0C0j080b0j0C0j0j0j0u110b0j0j0j0j0j0j0j0j0j0C0j0b080b0j0j0C0C0j0C0j0j0j0b0j1c0j080b0j0j0j0j0j0j0V0j0j0u0j1c0j0j0j0C0j0j2g0j0j0j0C0j0j0V0j0b080b1c0C0V0j0j2g0j0j0V0j0j1c0j1Z0j0j0C0C0j1v',
'160j0j0V0j1c0j0C0j0C0j1f0j0V0C0j0j0C0j0j0j1G080b080u0V080b0j0j0V0j1v0j0u0j1c0j0j0j0C0j0j0j0C0C0j1D0b0j080b0j0j0j0j0C0j0b0r0C0j0b0j0C0C0j0j0j0j0j0j0j0j0j0b0r0b0r0j0b0j0j0j0C0j0b0r0j0j0j0b080b080j0b0C0j080b080b0j0j0j0j0j0j0b0C080j0j0b0j0j0j0C0j0b080j0j0j0j0b080b080j0b0C0r0j0b0j0j0j0j0j0j0b0C080j0j0b0j0j0j0C0j0j0j0j0C0j0j0b080b0j0j0C080b0j0j0j0j0j0j0b080b080b080C0b080b080b080b0j0j0j0j0j0b080C0j0j0b080b0j0j0C080b0j0j0j0j0j0j0b080j0b0C080j0j0b0j0j0j0j0j0j0b080j0b080C0b080b080b080b0j0j0j0j080b0j0C0j0j0b080b0j0j0C080b0j0j0j0j0j0j0b080j0b080u080j0j0b0j0j0j0j0j0j0b080C0j0j0b080b0j0j0C0j0j080b0j0j0j0j0j0b080b0C0r0b080b0j0j0j0j0j0j0b080j0b080u080b080b080b0j0j0j0C0j0b080j0j0j0j0b0j0j0j0C0j0j080b0j0j0j0j0j0b080b0C0r0b080b0j0j0j0j0j0j0b080j0b0r0b080b080b080b0j0j0j0r0b0j0b0r0j0j0j0b0j0j0j0r0b0j080b0j0j0j0j0j0j0j0b0r0C0b0j0j0j0j0j0j0j0b080j0C0u080b080b0j0j0j0r0b0j0C0C0j0b0j110b0j080b0j0j0j0j0j0j0u0r0C0b0j0j0j0j0j0j0j0j0j0C0j0j0j0b0j1c0j0C0j0j0j0b0j0814080b080b0j0j0j0j0j0j1c0j0u0j0j0V0j0j0j0j0j0j0j0u110u0j0j0j',
'020b0r0C0j0j0j0C0j0j0V0j0j0j0j0j0C0j1f0j0C0j0V1G0j0j0j0j0V0C0j0C1v0u0j0j0j0V0j0j0C0j0j0j1v0N0C0V0j0j0j0K0C250b0C0V0j0j0V0j0j2g0C0V0j0j0C0j0j0b081v0N0j0j0V0V0j0j0u0j1c0j080b0j0j0j0j0j0j0V0j0j0u0j0j0V0j0j0j0C0j0b080b080V0b0j080b0j0j0j0j0j0j0j0b0r0C0j0b0j0j0j0C0j080b0j0j0j0j0j0j0u0r0C0u0j0j0j0j0j0j0b080j0C0j0b080b080b0j0C0j080b0j0j0j0j0j0j0b080b110b0j0j0j0j0j0j0j0j0j0b0r0j0j0j0b0j0j0j0r0b0j0b080j0j0j0j0b080b080b080b0r0b0j080b080b0j0j0j0j0j0j0b0r0C0b080b0j0j0j0j080b0j0b080j0j0j0j0b080b080b0j0j0j0r0b0j0j0j0j0j0j0b080b0j080C0b0j080b080b0j0j0j0j080b0j0b0r0C0b080b0j0j0j0j080b0j0j0j0j0j0b080b080b080b0j0j080b0r0b0j0j0j0j0j0j0b0j0j080C0b0j080b080b0j0j0j0j0j0b080C0j0j0b080b0j0j0C0j0b080j0j0j0j0b080b080b080b0C0C080b0j0j0j0j0j0j0b0C0C080b080b080b0j0j0j0j0j0j0b0C080j0j0b0j0j0j0C0j0b080j0b080j0j0b080b080b080b0C0r0b0j0j0j0j0j0j0b080b0r0b0r0b0j080b080b0j0j0j0j0j0j0b0r0C0j0b0j0j0j0j0j0j0b080j0C0j0b080j0b0j0j0K0b0j0C0j0j0j0b080b0j0K0b0j080b0j0j0j0j0j0j0V0j0j0b0j0j0j0C0j0j0j0j',
'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_1Z0V2o141v0j0C0C0j2z1v0j1c0j7N1O420j1c0j1v2S1c0j1v2S2_0b0j0V0j0j1v0N1v0j0j1c0j1v140j0V0j0j0C0C0b080u1v0C0V0u110u0j0j0j0C0j0j0j0C0C0N0C0V0j0j0C0j0j0b080u110u0C0j0C0u0r0C0u080b0j0j0C0j0j0j'
]
for m in months:
n: int = 0
size: int = int(m.__len__() / 2)
l: List[int] = []
for y in range(0, size):
z: int = y * 2
t: int = 0
c: int = 1
for x in range(1, -1, -1):
t += c * chars.index(m[z + x])
c *= 64
n += t
l.append(n)
LunarYear._LEAP.append(l)
LunarYear._IS_INIT = True
@staticmethod
def validate(year: int) -> None:
if year < -1 or year > 9999:
raise ValueError(f'illegal lunar year: {year}')
def __init__(self, year: int):
LunarYear._init()
LunarYear.validate(year)
super().__init__(year)
@classmethod
def from_year(cls, year: int) -> LunarYear:
return cls(year)
def get_day_count(self) -> int:
"""
当年的总天数
:return: 数字,从正月初一到除夕的总天数
"""
n: int = 0
months: List[LunarMonth] = self.get_months()
for i in range(0, months.__len__()):
n += months[i].get_day_count()
return n
def get_month_count(self) -> int:
"""
当年的总月数
:return: 数字,有闰月为13,无闰月为12
"""
return 12 if self.get_leap_month() < 1 else 13
def get_name(self) -> str:
"""
:return: 农历{六十甲子}年
"""
return f'农历{self.get_sixty_cycle().get_name()}年'
def next(self, n: int) -> LunarYear:
return LunarYear(self._year + n)
def get_leap_month(self) -> int:
"""
当年的闰月月份
:return: 数字,代表当年的闰月月份,例如:5代表闰五月,0代表当年没有闰月
"""
if self._year == -1:
return 11
for i in range(0, self._LEAP.__len__()):
if self._year in self._LEAP[i]:
return i + 1
return 0
def get_sixty_cycle(self) -> SixtyCycle:
"""
当年的干支
:return: 干支 SixtyCycle
"""
from tyme4py.sixtycycle import SixtyCycle
return SixtyCycle(self._year - 4)
def get_twenty(self) -> Twenty:
"""
:return: 运 Twenty
"""
return Twenty(floor((self._year - 1864) / 20))
def get_nine_star(self) -> NineStar:
"""
:return: 九星 NineStar
"""
return NineStar(63 + self.get_twenty().get_sixty().get_index() * 3 - self.get_sixty_cycle().get_index())
def get_jupiter_direction(self) -> Direction:
"""
太岁方位
:return: 方位 Direction
"""
return Direction([0, 7, 7, 2, 3, 3, 8, 1, 1, 6, 0, 0][self.get_sixty_cycle().get_earth_branch().get_index()])
def get_first_month(self) -> LunarMonth:
"""
首月(农历月,即一月,俗称正月)
:return: 农历月
"""
return LunarMonth.from_ym(self._year, 1)
def get_months(self) -> List[LunarMonth]:
"""
农历月列表
:return: 农历月 LunarMonth 的列表,从正月到十二月,包含闰月
"""
l: List[LunarMonth] = []
leap_month: int = self.get_leap_month()
for i in range(1, 13):
l.append(LunarMonth.from_ym(self._year, i))
if i == leap_month:
l.append(LunarMonth.from_ym(self._year, -i))
return l
def get_kitchen_god_steed(self) -> KitchenGodSteed:
"""
灶马头
:return: 灶马头
"""
return KitchenGodSteed.from_lunar_year(self._year)
class LunarSeason(LoopTyme):
"""
农历季节
从正月开始,依次为:孟春、仲春、季春、孟夏、仲夏、季夏、孟秋、仲秋、季秋、孟冬、仲冬、季冬。
"""
NAMES: List[str] = ['孟春', '仲春', '季春', '孟夏', '仲夏', '季夏', '孟秋', '仲秋', '季秋', '孟冬', '仲冬', '季冬']
"""名称"""
def __init__(self, index_or_name: Union[int, str]):
super().__init__(self.NAMES, index_or_name)
@classmethod
def from_name(cls, name: str) -> LunarSeason:
return cls(name)
@classmethod
def from_index(cls, index: int) -> LunarSeason:
return cls(index)
def next(self, n: int) -> LunarSeason:
return LunarSeason(self.next_index(n))
class LunarMonth(MonthUnit):
"""
农历月
农历月以初一开始,大月30天,小月29天
"""
NAMES: List[str] = ['正月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
@staticmethod
def validate(year: int, month: int) -> None:
if month == 0 or month > 12 or month < -12:
raise ValueError(f'illegal lunar month: {month}')
if month < 0 and -month != LunarYear.from_year(year).get_leap_month():
raise ValueError(f'illegal leap month {-month} in lunar year {year}')
def __init__(self, year: int, month: int):
"""
:param year: 农历年
:param month: 农历月,闰月为负
"""
LunarMonth.validate(year, month)
super().__init__(year, abs(month))
self._leap = month < 0
@classmethod
def from_ym(cls, year: int, month: int) -> LunarMonth:
"""
从农历年、月初始化
:param year: 农历年,支持从-1到9999年
:param month: 农历月,支持1到12,如果为闰月的,使用负数,即-3代表闰三月
:return: LunarMonth
"""
return cls(year, month)
def _get_new_moon(self):
from tyme4py.solar import SolarTerm
# 冬至
dong_zhi_jd: float = SolarTerm(self._year, 0).get_cursory_julian_day()
# 冬至前的初一,今年首朔的日月黄经差
w: int = ShouXingUtil.calc_shuo(dong_zhi_jd)
if w > dong_zhi_jd:
w -= 29.53
# 正常情况正月初一为第3个朔日,但有些特殊的
offset: int = 2
if 8 < self._year < 24:
offset = 1
elif LunarYear(self._year - 1).get_leap_month() > 10 and self._year != 239 and self._year != 240:
offset = 3
# 本月初一
return w + 29.5306 * (offset + self.get_index_in_year())
def get_lunar_year(self) -> LunarYear:
"""
农历年
:return: 农历年
"""
return LunarYear.from_year(self._year)
def get_month_with_leap(self) -> int:
"""
月(支持闰月)
:return: 月份数字,范围为1到12,闰月为负数,如闰7月返回-7
"""
return -self._month if self._leap else self._month
def get_day_count(self) -> int:
"""
当月的总天数
:return: 数字,从初一开始的总天数,大月有30天,小月有29天
"""
w: float = self._get_new_moon()
return ~~(ShouXingUtil.calc_shuo(w + 29.5306) - ShouXingUtil.calc_shuo(w))
def get_index_in_year(self) -> int:
"""
位于当年的月索引
:return: 数字,范围0到12,正月为0,依次类推,例如五月索引值为4,闰五月索引值为5
"""
# 位于当年的索引
index: int = self._month - 1
if self._leap:
index += 1
else:
leap_month: int = LunarYear.from_year(self._year).get_leap_month()
if 0 < leap_month < self._month:
index += 1
return index
def get_season(self) -> LunarSeason:
"""
:return: 农历季节
"""
return LunarSeason(self._month - 1)
def get_first_julian_day(self) -> JulianDay:
"""
初一的儒略日
:return: 儒略日
"""
from tyme4py.jd import JulianDay
return JulianDay.from_julian_day(JulianDay.J2000 + ShouXingUtil.calc_shuo(self._get_new_moon()))
def is_leap(self) -> bool:
"""
是否闰月
:return: 布尔值,闰月返回true,非闰月返回false
"""
return self._leap
def get_week_count(self, start: int) -> int:
"""
当月有几周
:param start: 参数为起始星期,1234560分别代表星期一至星期天
:return: 数字
"""
return ceil((self.index_of(self.get_first_julian_day().get_week().get_index() - start, 7) + self.get_day_count()) / 7)
def get_name(self) -> str:
"""
依据国家标准《农历的编算和颁行》GB/T 33661-2017中农历月的命名方法。
:return: 名称
"""
return ('闰' if self._leap else '') + self.NAMES[self._month - 1]
def __str__(self) -> str:
return f'{self.get_lunar_year()}{self.get_name()}'
def next(self, n: int) -> LunarMonth:
if n == 0:
return LunarMonth.from_ym(self._year, self.get_month_with_leap())
m: int = self.get_index_in_year() + 1 + n
y: LunarYear = self.get_lunar_year()
if n > 0:
month_count: int = y.get_month_count()
while m > month_count:
m -= month_count
y = y.next(1)
month_count = y.get_month_count()
else:
while m <= 0:
y = y.next(-1)
m += y.get_month_count()
leap: bool = False
leap_month: int = y.get_leap_month()
if leap_month > 0:
if m == leap_month + 1:
leap = True
if m > leap_month:
m -= 1
return LunarMonth.from_ym(y.get_year(), -m if leap else m)
def get_days(self) -> List[LunarDay]:
"""
本月的农历日列表
:return: 农历日 LunarDay 的列表,从初一开始
"""
m: int = self.get_month_with_leap()
l: List[LunarDay] = []
for i in range(1, self.get_day_count() + 1):
l.append(LunarDay(self._year, m, i))
return l
def get_first_day(self) -> LunarDay:
"""
初一
"""
return LunarDay.from_ymd(self._year, self.get_month_with_leap(), 1)
def get_weeks(self, start: int) -> List[LunarWeek]:
"""
本月的农历周列表
:param start: 参数为起始星期,1234560分别代表星期一至星期天
:return: 农历周 LunarWeek 的列表
"""
m: int = self.get_month_with_leap()
l: List[LunarWeek] = []
for i in range(0, self.get_week_count(start)):
l.append(LunarWeek(self._year, m, i, start))
return l
def get_sixty_cycle(self) -> SixtyCycle:
"""
当月的干支
:return: 干支 SixtyCycle
"""
from tyme4py.sixtycycle import SixtyCycle
return SixtyCycle.from_index(self._year * 12 + self._month - 47)
def get_nine_star(self) -> NineStar:
"""
:return: 九星 NineStar
"""
index = self.get_sixty_cycle().get_earth_branch().get_index()
if index < 2:
index += 3
return NineStar(27 - self.get_lunar_year().get_sixty_cycle().get_earth_branch().get_index() % 3 * 3 - index)
def get_jupiter_direction(self) -> Direction:
"""
太岁方位
:return: 方位 Direction
"""
from tyme4py.sixtycycle import SixtyCycle
sixty_cycle: SixtyCycle = self.get_sixty_cycle()
n: int = [7, -1, 1, 3][sixty_cycle.get_earth_branch().next(-2).get_index() % 4]
return Direction(n) if n != -1 else sixty_cycle.get_heaven_stem().get_direction()
def get_fetus(self) -> Union[FetusMonth, None]:
"""
:return: 逐月胎神 FetusMonth。闰月无胎神。
"""
return FetusMonth.from_lunar_month(self)
def get_minor_ren(self) -> MinorRen:
"""
:return: 小六壬
"""
return MinorRen((self._month - 1) % 6)
class LunarWeek(WeekUnit):
"""
农历周
农历一个月最多有6个周,分别为:第一周、第二周、第三周、第四周、第五周、第六周。
"""
@staticmethod
def validate(year: int, month: int, index: int, start: int) -> None:
WeekUnit.validate(year, month, index, start)
m: LunarMonth = LunarMonth.from_ym(year, month)
if index >= m.get_week_count(start):
raise ValueError(f'illegal lunar week index: {index} in month: {m}')
def __init__(self, year: int, month: int, index: int, start: int):
"""
:param year: 年
:param month: 月
:param index: 周索引,0-5
:param start: 起始星期 (1234560分别代表星期一至星期日)
"""
LunarWeek.validate(year, month, index, start)
super().__init__(year, month, index, start)
@classmethod
def from_ym(cls, year: int, month: int, index: int, start: int) -> LunarWeek:
return cls(year, month, index, start)
def get_lunar_month(self) -> LunarMonth:
"""
:return:农历月
"""
return LunarMonth.from_ym(self._year, self._month)
def get_name(self) -> str:
return self.NAMES[self._index]
def __str__(self) -> str:
return f'{self.get_lunar_month()}{self.get_name()}'
def next(self, n: int) -> LunarWeek:
d: int = self._index + n
m: LunarMonth = self.get_lunar_month()
if n > 0:
week_count: int = m.get_week_count(self._start)
while d >= week_count:
d -= week_count
m = m.next(1)
if m.get_first_day().get_week().get_index() != self._start:
d += 1
week_count = m.get_week_count(self._start)
else:
while d < 0:
if m.get_first_day().get_week().get_index() != self._start:
d -= 1
m = m.next(-1)
d += m.get_week_count(self._start)
return LunarWeek(m.get_year(), m.get_month_with_leap(), d, self._start)
def get_first_day(self) -> LunarDay:
"""
本周第一天的农历日
:return: 农历日 LunarDay
"""
first_day: LunarDay = LunarDay(self._year, self._month, 1)
return first_day.next(self._index * 7 - self.index_of(first_day.get_week().get_index() - self._start, 7))
def get_days(self) -> List[LunarDay]:
"""
本周农历日列表
:return: 农历日 LunarDay的列表
"""
l: List[LunarDay] = []
d: LunarDay = self.get_first_day()
l.append(d)
for i in range(1, 7):
l.append(d.next(i))
return l
def __eq__(self, other: LunarWeek) -> bool:
return other and other.get_first_day() == self.get_first_day()
class LunarDay(DayUnit):
"""农历日"""
NAMES: List[str] = ['初一', '初二', '初三', '初四', '初五', '初六', '初七', '初八', '初九', '初十', '十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十', '廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '三十']
"""名称"""
@staticmethod
def validate(year: int, month: int, day: int) -> None:
if day < 1:
raise ValueError(f'illegal lunar day {day}')
m: LunarMonth = LunarMonth.from_ym(year, month)
if day > m.get_day_count():
raise ValueError(f'illegal day {day} in {m}')
def __init__(self, year: int, month: int, day: int):
"""
:param year: 农历年
:param month: 农历月,闰月为负
:param day: 农历日
"""
LunarDay.validate(year, month, day)
super().__init__(year, month, day)
@classmethod
def from_ymd(cls, year: int, month: int, day: int) -> LunarDay:
return cls(year, month, day)
def get_lunar_month(self) -> LunarMonth:
"""
:return: 农历月
"""
return LunarMonth.from_ym(self._year, self._month)
def get_name(self) -> str:
"""
:return: 农历日名,如:初一
"""
return self.NAMES[self._day - 1]
def __str__(self) -> str:
return f'{self.get_lunar_month()}{self.get_name()}'
def next(self, n: int) -> LunarDay:
return self.get_solar_day().next(n).get_lunar_day()
def is_before(self, target: LunarDay) -> bool:
"""
是否在指定农历日之前
:param target: 农历日 LunarDay
:return: true/false
"""
y: int = target.get_year()
if self._year != y:
return self._year < y
m: int = target.get_month()
if self._month != m:
t: int = abs(m)
return self._month == t or abs(self._month) < t
return self._day < target.get_day()
def is_after(self, target: LunarDay) -> bool:
"""
是否在指定农历日之后
:param target: 农历日 LunarDay
:return:
"""
y: int = target.get_year()
if self._year != y:
return self._year > y
m: int = target.get_month()
if self._month != m:
t: int = abs(self._month)
return t == m or t > abs(m)
return self._day > target.get_day()
def get_week(self) -> Week:
"""
:return: 星期
"""
return self.get_solar_day().get_week()
def get_year_sixty_cycle(self) -> SixtyCycle:
"""
当天的年干支 非当天所属的农历年干支,以立春换年。
:return: 干支 SixtyCycle
"""
warnings.warn('get_year_sixty_cycle() is deprecated, please use SixtyCycleDay.get_year() instead.', DeprecationWarning)
return self.get_sixty_cycle_day().get_year()
def get_month_sixty_cycle(self) -> SixtyCycle:
"""
当天的月干支 非当天所属的农历月干支,以节令换月。
:return: 干支 SixtyCycle
"""
warnings.warn('get_month_sixty_cycle() is deprecated, please use SixtyCycleDay.get_month() instead.', DeprecationWarning)
return self.get_sixty_cycle_day().get_month()
def get_sixty_cycle(self) -> SixtyCycle:
"""
当天的干支
:return: 干支
"""
from tyme4py.sixtycycle import SixtyCycle
return SixtyCycle.from_index(int(self.get_lunar_month().get_first_julian_day().next(self._day - 12).get_day()))
def get_duty(self) -> Duty:
"""
建除十二值神
:return: 建除十二值神 Duty
"""
return self.get_sixty_cycle_day().get_duty()
def get_twelve_star(self) -> TwelveStar:
"""
黄道黑道十二神
:return: 黄道黑道十二神 TwelveStar
"""
return self.get_sixty_cycle_day().get_twelve_star()
def get_nine_star(self) -> NineStar:
"""
:return: 九星 NineStar
"""
return self.get_solar_day().get_nine_star()
def get_jupiter_direction(self) -> Direction:
"""
太岁方位
:return: 方位 Direction
"""
index: int = self.get_sixty_cycle().get_index()
from tyme4py.culture import Element
return Element(index // 12).get_direction() if index % 12 < 6 else self.get_lunar_month().get_lunar_year().get_jupiter_direction()
def get_fetus_day(self) -> FetusDay:
"""
:return:逐日胎神 FetusDay
"""
return FetusDay.from_lunar_day(self)
def get_phase_day(self) -> PhaseDay:
"""
月相第几天
:return: 月相第几天
"""
today = self.get_solar_day()
m = self.get_lunar_month().next(1)
p = Phase.from_index(m.get_year(), m.get_month_with_leap(), 0)
d = p.get_solar_day()
while d.is_after(today):
p = p.next(-1)
d = p.get_solar_day()
return PhaseDay(p, today.subtract(d))
def get_phase(self) -> Phase:
"""
月相
:return: 月相 Phase
"""
return self.get_phase_day().get_phase()
def get_solar_day(self) -> SolarDay:
"""
公历日
:return: 公历日 SolarDay
"""
return self.get_lunar_month().get_first_julian_day().next(self._day - 1).get_solar_day()
def get_sixty_cycle_day(self) -> SixtyCycleDay:
"""
干支日
:return: 干支日
"""
return self.get_solar_day().get_sixty_cycle_day()
def get_twenty_eight_star(self) -> TwentyEightStar:
"""
:return: 二十八宿 TwentyEightStar
"""
return TwentyEightStar(10 + 8 * self.get_week().get_index()).next(-7 * self.get_sixty_cycle().get_earth_branch().get_index())
def get_festival(self) -> Union[LunarFestival, None]:
"""
农历传统节日,如果当天不是农历传统节日,返回None
:return:农历传统节日 LunarFestival
"""
return LunarFestival.from_ymd(self._year, self._month, self._day)
def get_six_star(self) -> SixStar:
"""
:return: 六曜
"""
return SixStar((abs(self._month) + self._day - 2) % 6)
def get_gods(self) -> List[God]:
"""
神煞列表(吉神宜趋,凶神宜忌)
:return: 神煞列表
"""
return self.get_sixty_cycle_day().get_gods()
def get_recommends(self) -> List[Taboo]:
"""
今日 宜
:return: 宜忌列表
"""
return self.get_sixty_cycle_day().get_recommends()
def get_avoids(self) -> List[Taboo]:
"""
今日 忌
:return: 宜忌列表
"""
return self.get_sixty_cycle_day().get_avoids()
def get_hours(self) -> List[LunarHour]:
"""
当天的时辰列表
:return: 由于23:00-23:59、00:00-00:59均为子时,而农历日是从00:00-23:59为一天,所以获取当天的时辰列表,实际会返回13个。
"""
l: List[LunarHour] = [LunarHour(self._year, self._month, self._day, 0, 0, 0)]
for i in range(0, 24, 2):
l.append(LunarHour(self._year, self._month, self._day, i + 1, 0, 0))
return l
def get_minor_ren(self) -> MinorRen:
"""
小六壬
:return: 小六壬
"""
return self.get_lunar_month().get_minor_ren().next(self._day - 1)
def get_three_pillars(self) -> ThreePillars:
"""
三柱
@return: 三柱
"""
return self.get_sixty_cycle_day().get_three_pillars()
class LunarHour(SecondUnit):
"""
农历时辰
"""
provider: EightCharProvider = DefaultEightCharProvider()
"""八字计算接口"""
@staticmethod
def validate(year: int, month: int, day: int, hour: int, minute: int, second: int) -> None:
SecondUnit.validate(year, month, day, hour, minute, second)
LunarDay.validate(year, month, day)
def __init__(self, year: int, month: int, day: int, hour: int, minute: int = 0, second: int = 0):
"""
:param year: 农历年,支持从-1到9999年
:param month: 农历月,支持1到12,如果为闰月的,使用负数,即-3代表闰三月
:param day: 农历日,支持1到30,大月30天,小月29天
:param hour: 时为0-23
:param minute: 分为0-59
:param second: 秒为0-59
"""
LunarHour.validate(year, month, day, hour, minute, second)
super().__init__(year, month, day, hour, minute, second)
@classmethod
def from_ymd_hms(cls, year: int, month: int, day: int, hour: int, minute: int = 0, second: int = 0) -> LunarHour:
return cls(year, month, day, hour, minute, second)
def get_lunar_day(self) -> LunarDay:
"""
:return: 农历日
"""
return LunarDay.from_ymd(self._year, self._month, self._day)
def get_name(self) -> str:
from tyme4py.sixtycycle import EarthBranch
return f'{EarthBranch(self.get_index_in_day()).get_name()}时'
def __str__(self) -> str:
return f'{self.get_lunar_day()}{self.get_sixty_cycle().get_name()}时'
def get_index_in_day(self) -> int:
"""
位于当天的索引
:return: 数字,范围0到11
"""
return int((self._hour + 1) / 2)
def next(self, n: int) -> LunarHour:
if n == 0:
return LunarHour(self._year, self._month, self._day, self._hour, self._minute, self._second)
h: int = self._hour + n * 2
diff: int = -1 if h < 0 else 1
hour: int = abs(h)
days: int = int(hour / 24) * diff
hour = (hour % 24) * diff
if hour < 0:
hour += 24
days -= 1
d: LunarDay = self.get_lunar_day().next(days)
return LunarHour(d.get_year(), d.get_month(), d.get_day(), hour, self._minute, self._second)
def is_before(self, target: LunarHour) -> bool:
"""
是否在指定农历时辰之前
:param target: 农历时辰
:return: true/false
"""
a_day: LunarDay = self.get_lunar_day()
b_day: LunarDay = target.get_lunar_day()
if a_day != b_day:
return a_day.is_before(b_day)
h = target.get_hour()
if self._hour != h:
return self._hour < h
m = target.get_minute()
return self._minute < m if self._minute != m else self._second < target.get_second()
def is_after(self, target: LunarHour) -> bool:
"""
是否在指定农历时辰之后
:param target: 农历时辰
:return: true/false
"""
a_day: LunarDay = self.get_lunar_day()
b_day: LunarDay = target.get_lunar_day()
if a_day != b_day:
return a_day.is_after(b_day)
h = target.get_hour()
if self._hour != h:
return self._hour > h
m = target.get_minute()
return self._minute > m if self._minute != m else self._second > target.get_second()
def get_year_sixty_cycle(self) -> SixtyCycle:
"""
当时的年干支 非当时所属的农历年干支
:return: 干支 SixtyCycle
"""
warnings.warn('get_year_sixty_cycle() is deprecated, please use SixtyCycleHour.get_year() instead.', DeprecationWarning)
return self.get_sixty_cycle_hour().get_year()
def get_month_sixty_cycle(self) -> SixtyCycle:
"""
当时的月干支 非当天所属的农历月干支,以节令具体时刻换月。
:return: 干支 SixtyCycle
"""
warnings.warn('get_year_sixty_cycle() is deprecated, please use SixtyCycleHour.get_month() instead.', DeprecationWarning)
return self.get_sixty_cycle_hour().get_month()
def get_day_sixty_cycle(self) -> SixtyCycle:
"""
当时的日干支
:return: 干支 SixtyCycle。注意:23:00开始算做第二天。
"""
warnings.warn('get_year_sixty_cycle() is deprecated, please use SixtyCycleHour.get_day() instead.', DeprecationWarning)
return self.get_sixty_cycle_hour().get_day()
def get_sixty_cycle(self) -> SixtyCycle:
"""
时辰干支
:return: 干支 SixtyCycle
"""
from tyme4py.sixtycycle import SixtyCycle, HeavenStem
e: int = self.get_index_in_day()
h: HeavenStem = self.get_lunar_day().get_sixty_cycle().get_heaven_stem()
if self._hour >= 23:
h = h.next(1)
e = 0
return SixtyCycle.from_index(h.get_index() * 12 + e)
def get_twelve_star(self) -> TwelveStar:
"""
:return: 黄道黑道十二神 TwelveStar
"""
return TwelveStar(self.get_sixty_cycle().get_earth_branch().get_index() + (8 - self.get_sixty_cycle_hour().get_day().get_earth_branch().get_index() % 6) * 2)
def get_nine_star(self) -> NineStar:
"""
九星(时家紫白星歌诀:三元时白最为佳,冬至阳生顺莫差,孟日七宫仲一白,季日四绿发萌芽,每把时辰起甲子,本时星耀照光华,时星移入中宫去,顺飞八方逐细查。夏至阴生逆回首,孟归三碧季加六,仲在九宫时起甲,依然掌中逆轮跨。)
:return: 九星 NineStar
"""
from tyme4py.solar import SolarTerm
d: LunarDay = self.get_lunar_day()
solar: SolarDay = d.get_solar_day()
dong_zhi: SolarTerm = SolarTerm(solar.get_year(), 0)
earth_branch_index: int = self.get_index_in_day() % 12
index: int = 8 - 3 * (d.get_sixty_cycle().get_earth_branch().get_index() % 3)
if (not solar.is_before(dong_zhi.get_julian_day().get_solar_day())) and solar.is_before(dong_zhi.next(12).get_julian_day().get_solar_day()):
index = 8 + earth_branch_index - index
else:
index -= earth_branch_index
return NineStar(index)
def get_solar_time(self) -> SolarTime:
"""
公历时刻
:return: 公历时刻 SolarTime
"""
from tyme4py.solar import SolarTime
d: SolarDay = self.get_lunar_day().get_solar_day()
return SolarTime(d.get_year(), d.get_month(), d.get_day(), self._hour, self._minute, self._second)
def get_sixty_cycle_hour(self) -> SixtyCycleHour:
"""
干支时辰
:return: 干支时辰 SixtyCycleHour
"""
return self.get_solar_time().get_sixty_cycle_hour()
def get_eight_char(self) -> EightChar:
"""
八字
:return: 八字
"""
return LunarHour.provider.get_eight_char(self)
def get_recommends(self) -> List[Taboo]:
"""
宜
:return: 宜忌列表
"""
return Taboo.get_hour_recommends(self.get_sixty_cycle_hour().get_day(), self.get_sixty_cycle())
def get_avoids(self) -> List[Taboo]:
"""
忌
:return: 宜忌列表
"""
return Taboo.get_hour_avoids(self.get_sixty_cycle_hour().get_day(), self.get_sixty_cycle())
def get_minor_ren(self) -> MinorRen:
"""
:return: 小六壬
"""
return self.get_lunar_day().get_minor_ren().next(self.get_index_in_day())