466 lines
15 KiB
Python
466 lines
15 KiB
Python
# -*- coding: utf-8 -*-
|
|
from datetime import datetime
|
|
from math import ceil
|
|
|
|
from .util import SolarUtil, LunarUtil, HolidayUtil
|
|
|
|
|
|
class Solar:
|
|
"""
|
|
阳历日期
|
|
"""
|
|
|
|
# 2000年儒略日数(2000-1-1 12:00:00 UTC)
|
|
J2000 = 2451545
|
|
|
|
def __init__(self, year, month, day, hour, minute, second):
|
|
if year == 1582 and month == 10:
|
|
if 4 < day < 15:
|
|
raise Exception("wrong solar year %d month %d day %d" % (year, month, day))
|
|
if month < 1 or month > 12:
|
|
raise Exception("wrong month %d" % month)
|
|
if day < 1 or month > 31:
|
|
raise Exception("wrong day %d" % day)
|
|
if hour < 0 or hour > 23:
|
|
raise Exception("wrong hour %d" % hour)
|
|
if minute < 0 or minute > 59:
|
|
raise Exception("wrong minute %d" % minute)
|
|
if second < 0 or second > 59:
|
|
raise Exception("wrong second %d" % second)
|
|
self.__year = year
|
|
self.__month = month
|
|
self.__day = day
|
|
self.__hour = hour
|
|
self.__minute = minute
|
|
self.__second = second
|
|
|
|
@staticmethod
|
|
def fromDate(date):
|
|
return Solar(date.year, date.month, date.day, date.hour, date.minute, date.second)
|
|
|
|
@staticmethod
|
|
def fromJulianDay(julian_day):
|
|
d = int(julian_day + 0.5)
|
|
f = julian_day + 0.5 - d
|
|
if d >= 2299161:
|
|
c = int((d - 1867216.25) / 36524.25)
|
|
d += 1 + c - int(c / 4)
|
|
d += 1524
|
|
year = int((d - 122.1) / 365.25)
|
|
d -= int(365.25 * year)
|
|
month = int(d / 30.601)
|
|
d -= int(30.601 * month)
|
|
day = d
|
|
if month > 13:
|
|
month -= 13
|
|
year -= 4715
|
|
else:
|
|
month -= 1
|
|
year -= 4716
|
|
f *= 24
|
|
hour = int(f)
|
|
|
|
f -= hour
|
|
f *= 60
|
|
minute = int(f)
|
|
|
|
f -= minute
|
|
f *= 60
|
|
second = int(round(f))
|
|
if second > 59:
|
|
second -= 60
|
|
minute += 1
|
|
if minute > 59:
|
|
minute -= 60
|
|
hour += 1
|
|
if hour > 23:
|
|
hour -= 24
|
|
day += 1
|
|
return Solar(year, month, day, hour, minute, second)
|
|
|
|
@staticmethod
|
|
def fromYmdHms(year, month, day, hour, minute, second):
|
|
return Solar(year, month, day, hour, minute, second)
|
|
|
|
@staticmethod
|
|
def fromYmd(year, month, day):
|
|
return Solar(year, month, day, 0, 0, 0)
|
|
|
|
@staticmethod
|
|
def fromBaZi(year_gan_zhi, month_gan_zhi, day_gan_zhi, time_gan_zhi, sect=2, base_year=1900):
|
|
from . import Lunar
|
|
sect = 1 if 1 == sect else 2
|
|
solar_list = []
|
|
# 月地支距寅月的偏移值
|
|
m = LunarUtil.find(month_gan_zhi[1:], LunarUtil.ZHI, -1) - 2
|
|
if m < 0:
|
|
m += 12
|
|
# 月天干要一致
|
|
if ((LunarUtil.find(year_gan_zhi[:1], LunarUtil.GAN, -1) + 1) * 2 + m) % 10 != LunarUtil.find(month_gan_zhi[:1], LunarUtil.GAN, -1):
|
|
return solar_list
|
|
# 1年的立春是辛酉,序号57
|
|
y = LunarUtil.getJiaZiIndex(year_gan_zhi) - 57
|
|
if y < 0:
|
|
y += 60
|
|
y += 1
|
|
# 节令偏移值
|
|
m *= 2
|
|
# 时辰地支转时刻,子时按零点算
|
|
h = LunarUtil.find(time_gan_zhi[1:], LunarUtil.ZHI, -1) * 2
|
|
hours = [h]
|
|
if 0 == h and 2 == sect:
|
|
hours.append(23)
|
|
start_year = base_year - 1
|
|
|
|
# 结束年
|
|
end_year = datetime.now().year
|
|
|
|
while y <= end_year:
|
|
if y >= start_year:
|
|
# 立春为寅月的开始
|
|
jie_qi_table = Lunar.fromYmd(y, 1, 1).getJieQiTable()
|
|
# 节令推移,年干支和月干支就都匹配上了
|
|
solar_time = jie_qi_table[Lunar.JIE_QI_IN_USE[4 + m]]
|
|
if solar_time.getYear() >= base_year:
|
|
# 日干支和节令干支的偏移值
|
|
d = LunarUtil.getJiaZiIndex(day_gan_zhi) - LunarUtil.getJiaZiIndex(solar_time.getLunar().getDayInGanZhiExact2())
|
|
if d < 0:
|
|
d += 60
|
|
if d > 0:
|
|
# 从节令推移天数
|
|
solar_time = solar_time.next(d)
|
|
for hour in hours:
|
|
mi = 0
|
|
s = 0
|
|
if d == 0 and hour == solar_time.getHour():
|
|
# 如果正好是节令当天,且小时和节令的小时数相等的极端情况,把分钟和秒钟带上
|
|
mi = solar_time.getMinute()
|
|
s = solar_time.getSecond()
|
|
# 验证一下
|
|
solar = Solar.fromYmdHms(solar_time.getYear(), solar_time.getMonth(), solar_time.getDay(), hour, mi, s)
|
|
if d == 30:
|
|
solar = solar.nextHour(-1)
|
|
lunar = solar.getLunar()
|
|
dgz = lunar.getDayInGanZhiExact2() if 2 == sect else lunar.getDayInGanZhiExact()
|
|
if lunar.getYearInGanZhiExact() == year_gan_zhi and lunar.getMonthInGanZhiExact() == month_gan_zhi and dgz == day_gan_zhi and lunar.getTimeInGanZhi() == time_gan_zhi:
|
|
solar_list.append(solar)
|
|
y += 60
|
|
return solar_list
|
|
|
|
def isLeapYear(self):
|
|
"""
|
|
是否闰年
|
|
:return: True/False 闰年/非闰年
|
|
"""
|
|
return SolarUtil.isLeapYear(self.__year)
|
|
|
|
def getWeek(self):
|
|
"""
|
|
获取星期,0代表周日,1代表周一
|
|
:return: 0123456
|
|
"""
|
|
return (int(self.getJulianDay() + 0.5) + 7000001) % 7
|
|
|
|
def getWeekInChinese(self):
|
|
"""
|
|
获取星期的中文
|
|
:return: 日一二三四五六
|
|
"""
|
|
return SolarUtil.WEEK[self.getWeek()]
|
|
|
|
def getFestivals(self):
|
|
"""
|
|
获取节日,有可能一天会有多个节日
|
|
:return: 劳动节等
|
|
"""
|
|
festivals = []
|
|
key = "%d-%d" % (self.__month, self.__day)
|
|
if key in SolarUtil.FESTIVAL:
|
|
festivals.append(SolarUtil.FESTIVAL[key])
|
|
week = self.getWeek()
|
|
key = "%d-%d-%d" % (self.__month, int(ceil(self.__day / 7.0)), week)
|
|
if key in SolarUtil.WEEK_FESTIVAL:
|
|
festivals.append(SolarUtil.WEEK_FESTIVAL[key])
|
|
if self.__day + 7 > SolarUtil.getDaysOfMonth(self.__year, self.__month):
|
|
key = "%d-0-%d" % (self.__month, week)
|
|
if key in SolarUtil.WEEK_FESTIVAL:
|
|
festivals.append(SolarUtil.WEEK_FESTIVAL[key])
|
|
return festivals
|
|
|
|
def getOtherFestivals(self):
|
|
"""
|
|
获取非正式的节日,有可能一天会有多个节日
|
|
:return: 非正式的节日列表,如中元节
|
|
"""
|
|
festivals = []
|
|
key = "%d-%d" % (self.__month, self.__day)
|
|
if key in SolarUtil.OTHER_FESTIVAL:
|
|
for f in SolarUtil.OTHER_FESTIVAL[key]:
|
|
festivals.append(f)
|
|
return festivals
|
|
|
|
def getXingZuo(self):
|
|
"""
|
|
获取星座
|
|
:return: 星座
|
|
"""
|
|
index = 11
|
|
y = self.__month * 100 + self.__day
|
|
if 321 <= y <= 419:
|
|
index = 0
|
|
elif 420 <= y <= 520:
|
|
index = 1
|
|
elif 521 <= y <= 621:
|
|
index = 2
|
|
elif 622 <= y <= 722:
|
|
index = 3
|
|
elif 723 <= y <= 822:
|
|
index = 4
|
|
elif 823 <= y <= 922:
|
|
index = 5
|
|
elif 923 <= y <= 1023:
|
|
index = 6
|
|
elif 1024 <= y <= 1122:
|
|
index = 7
|
|
elif 1123 <= y <= 1221:
|
|
index = 8
|
|
elif y >= 1222 or y <= 119:
|
|
index = 9
|
|
elif y <= 218:
|
|
index = 10
|
|
return SolarUtil.XING_ZUO[index]
|
|
|
|
def getJulianDay(self):
|
|
"""
|
|
获取儒略日
|
|
:return: 儒略日
|
|
"""
|
|
y = self.__year
|
|
m = self.__month
|
|
d = self.__day + ((self.__second / 60.0 + self.__minute) / 60 + self.__hour) / 24
|
|
n = 0
|
|
g = False
|
|
if y * 372 + m * 31 + int(d) >= 588829:
|
|
g = True
|
|
if m <= 2:
|
|
m += 12
|
|
y -= 1
|
|
if g:
|
|
n = int(y / 100)
|
|
n = 2 - n + int(n / 4)
|
|
return int(365.25 * (y + 4716)) + int(30.6001 * (m + 1)) + d + n - 1524.5
|
|
|
|
def getLunar(self):
|
|
"""
|
|
获取农历
|
|
:return: 农历
|
|
"""
|
|
from .Lunar import Lunar
|
|
return Lunar.fromSolar(self)
|
|
|
|
def nextDay(self, days):
|
|
y = self.__year
|
|
m = self.__month
|
|
d = self.__day
|
|
if 1582 == y and 10 == m:
|
|
if d > 4:
|
|
d -= 10
|
|
if days > 0:
|
|
d += days
|
|
days_in_month = SolarUtil.getDaysOfMonth(y, m)
|
|
while d > days_in_month:
|
|
d -= days_in_month
|
|
m += 1
|
|
if m > 12:
|
|
m = 1
|
|
y += 1
|
|
days_in_month = SolarUtil.getDaysOfMonth(y, m)
|
|
elif days < 0:
|
|
while d + days <= 0:
|
|
m -= 1
|
|
if m < 1:
|
|
m = 12
|
|
y -= 1
|
|
d += SolarUtil.getDaysOfMonth(y, m)
|
|
d += days
|
|
if 1582 == y and 10 == m:
|
|
if d > 4:
|
|
d += 10
|
|
return Solar.fromYmdHms(y, m, d, self.__hour, self.__minute, self.__second)
|
|
|
|
def next(self, days, only_work_day=False):
|
|
"""
|
|
获取往后推几天的阳历日期,如果要往前推,则天数用负数
|
|
:param days: 天数
|
|
:param only_work_day: 是否仅工作日
|
|
:return: 阳历日期
|
|
"""
|
|
if not only_work_day:
|
|
return self.nextDay(days)
|
|
solar = Solar.fromYmdHms(self.__year, self.__month, self.__day, self.__hour, self.__minute, self.__second)
|
|
if days != 0:
|
|
rest = abs(days)
|
|
add = 1
|
|
if days < 0:
|
|
add = -1
|
|
while rest > 0:
|
|
solar = solar.next(add)
|
|
work = True
|
|
holiday = HolidayUtil.getHoliday(solar.getYear(), solar.getMonth(), solar.getDay())
|
|
if holiday is None:
|
|
week = solar.getWeek()
|
|
if 0 == week or 6 == week:
|
|
work = False
|
|
else:
|
|
work = holiday.isWork()
|
|
if work:
|
|
rest -= 1
|
|
return solar
|
|
|
|
def getYear(self):
|
|
return self.__year
|
|
|
|
def getMonth(self):
|
|
return self.__month
|
|
|
|
def getDay(self):
|
|
return self.__day
|
|
|
|
def getHour(self):
|
|
return self.__hour
|
|
|
|
def getMinute(self):
|
|
return self.__minute
|
|
|
|
def getSecond(self):
|
|
return self.__second
|
|
|
|
def toYmd(self):
|
|
return "%04d-%02d-%02d" % (self.__year, self.__month, self.__day)
|
|
|
|
def toYmdHms(self):
|
|
return "%s %02d:%02d:%02d" % (self.toYmd(), self.__hour, self.__minute, self.__second)
|
|
|
|
def toFullString(self):
|
|
s = self.toYmdHms()
|
|
if self.isLeapYear():
|
|
s += " 闰年"
|
|
s += " 星期"
|
|
s += self.getWeekInChinese()
|
|
for f in self.getFestivals():
|
|
s += " (" + f + ")"
|
|
for f in self.getOtherFestivals():
|
|
s += " (" + f + ")"
|
|
s += " "
|
|
s += self.getXingZuo()
|
|
s += "座"
|
|
return s
|
|
|
|
def toString(self):
|
|
return self.toYmd()
|
|
|
|
def __str__(self):
|
|
return self.toString()
|
|
|
|
def subtract(self, solar):
|
|
return SolarUtil.getDaysBetween(solar.getYear(), solar.getMonth(), solar.getDay(), self.__year, self.__month, self.__day)
|
|
|
|
def subtractMinute(self, solar):
|
|
days = self.subtract(solar)
|
|
cm = self.__hour * 60 + self.__minute
|
|
sm = solar.getHour() * 60 + solar.getMinute()
|
|
m = cm - sm
|
|
if m < 0:
|
|
m += 1440
|
|
days -= 1
|
|
m += days * 1440
|
|
return m
|
|
|
|
def isAfter(self, solar):
|
|
if self.__year > solar.getYear():
|
|
return True
|
|
if self.__year < solar.getYear():
|
|
return False
|
|
if self.__month > solar.getMonth():
|
|
return True
|
|
if self.__month < solar.getMonth():
|
|
return False
|
|
if self.__day > solar.getDay():
|
|
return True
|
|
if self.__day < solar.getDay():
|
|
return False
|
|
if self.__hour > solar.getHour():
|
|
return True
|
|
if self.__hour < solar.getHour():
|
|
return False
|
|
if self.__minute > solar.getMinute():
|
|
return True
|
|
if self.__minute < solar.getMinute():
|
|
return False
|
|
return self.__second > solar.getSecond()
|
|
|
|
def isBefore(self, solar):
|
|
if self.__year > solar.getYear():
|
|
return False
|
|
if self.__year < solar.getYear():
|
|
return True
|
|
if self.__month > solar.getMonth():
|
|
return False
|
|
if self.__month < solar.getMonth():
|
|
return True
|
|
if self.__day > solar.getDay():
|
|
return False
|
|
if self.__day < solar.getDay():
|
|
return True
|
|
if self.__hour > solar.getHour():
|
|
return False
|
|
if self.__hour < solar.getHour():
|
|
return True
|
|
if self.__minute > solar.getMinute():
|
|
return False
|
|
if self.__minute < solar.getMinute():
|
|
return True
|
|
return self.__second < solar.getSecond()
|
|
|
|
def nextYear(self, years):
|
|
y = self.__year + years
|
|
m = self.__month
|
|
d = self.__day
|
|
if 1582 == y and 10 == m:
|
|
if 4 < d < 15:
|
|
d += 10
|
|
elif 2 == m:
|
|
if d > 28:
|
|
if not SolarUtil.isLeapYear(y):
|
|
d = 28
|
|
return Solar.fromYmdHms(y, m, d, self.__hour, self.__minute, self.__second)
|
|
|
|
def nextMonth(self, months):
|
|
from . import SolarMonth
|
|
month = SolarMonth.fromYm(self.__year, self.__month).next(months)
|
|
y = month.getYear()
|
|
m = month.getMonth()
|
|
d = self.__day
|
|
if 1582 == y and 10 == m:
|
|
if 4 < d < 15:
|
|
d += 10
|
|
else:
|
|
days = SolarUtil.getDaysOfMonth(y, m)
|
|
if d > days:
|
|
d = days
|
|
return Solar.fromYmdHms(y, m, d, self.__hour, self.__minute, self.__second)
|
|
|
|
def nextHour(self, hours):
|
|
h = self.__hour + hours
|
|
n = 1
|
|
if h < 0:
|
|
n = -1
|
|
hour = abs(h)
|
|
days = int(hour / 24) * n
|
|
hour = (hour % 24) * n
|
|
if hour < 0:
|
|
hour += 24
|
|
days -= 1
|
|
solar = self.next(days)
|
|
return Solar.fromYmdHms(solar.getYear(), solar.getMonth(), solar.getDay(), hour, solar.getMinute(), solar.getSecond())
|