调休日历
前些天,使用python做了一个日历,可以打印出调休的节假日。效果还可以,但是遇到了一些小问题。
有一个朋友提出了这样一个问题:“日历确实是很好,但是,使用控制台print不怎么样,别人的日历都是很好看的,而我们的日历还只能用print。这太丢人了,我出门说自己是你的粉丝,都没什么面子了啊!”
还有一个朋友提出了另外一个问题:“在我们国家,‘农历’也是很重要的,比如说‘农历新年’是一年中最重要的日子。所以,我们的日历也应该能看到农历才比较好。”
好吧,这次就来解决一下这些问题!
农历
农历介绍
农历,也称为“阳历”,“月亮历”,“阴阳历”,是一种重要的历法,起源于中国的古代,这种历法同时结合了太阳运动,和月亮周期。其中月球围绕地球旋转的周期,大约为29.5天,这构成了农历的一个月,而一年通常是12个月。然而,一个太阳年(太阳公转一周)大概是365.24天,显然月亮的12个月不够这个数字,因此,为了填补到太阳年的时长,就会加入“闰月“,大概每3年就会出现一个”闰月“,”闰月“和公历中的“闰年”有异曲同工之妙。
那么农历有什么用呢?农历有很大的作用!节日安排,就是农历决定的(新年,中秋,端午等)。农业活动也和农历有很大的关系,比如说,仍然有很多农民,根据农历的指导,进行农业活动,什么时候播种,什么时候收割,都是农历决定的。还有很多人过生日,都会选择过农历生日。除此之外,像“季节变化”也和农历有微妙的关系,我们经常可以听到,“现在已经立秋了,已经是秋天了,马上就要凉快了。”,诸如此类的话,可见,农历在日常生活中发挥了重要作用。是我们祖先的伟大发明。
农历实现
那么,农历到底是怎么确定日子的呢?这和天文观测有很大的关系。我们要通过观察天象,来确定农历的准确性。比如说,在中国古代,就有专门的机构,如皇家天文台负责观测天文,然后调整历法,这是一个重要的活动。在历史上,历法也经过了多次修订。
到了现代,对于天文的观测,不再是必须的了。因为现代的科技较为发达,已经能够通过数学计算,历史天文数据推导等,精确的计算出农历。因此,即使我们没有“夜观星象”,也可以知道未来上百年的农历运作。
但是,我们既不会观察天象,也不会科学计算月亮运动,怎么办呢?当然没关系啦,因为,别人已经算好了,我们直接引用就可以了!
安装:pip install lunarcalendar
from lunarcalendar import Converter, Solar, Lunar# 将公历日期转换为农历
solar = Solar(2024, 9, 17)
lunar = Converter.Solar2Lunar(solar)# 输出结果为农历日期
print(lunar)# 将农历日期转换为公历
lunar = Lunar(2024, 8, 15)
solar = Converter.Lunar2Solar(lunar)# 输出结果为公历日期
print(solar)
转换为汉字日期
一般在农历中,我们并不使用阿拉伯数字的记录,而是常说,“正月初三”,“八月廿二”,这样的表达方式。因此,我们还需要将数字的农历,转为常见的汉字日期:
from lunarcalendar import Converter, Solardef lunar_date_str(year, month, day):lunar = Converter.Solar2Lunar(Solar(year, month, day))leap_month = lunar.isleapmonths = ["正月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "冬月", "腊月"]days = ["初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十","十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十","廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十"]month_str = months[lunar.month - 1]if leap_month:month_str = "闰" + month_strday_str = days[lunar.day - 1]# 该实现是特别为了符合日历的实现,仅在每个月的第一天返回月份,例如正月初一,返回“正月”# 而其他日子,不返回月份,仅返回日期,例如正月初三,返回“初三”if lunar.day == 1:return month_strelse:return day_str
如果要更广泛意义的月份加日期,只需要简单的修改返回值即可:f"{month_str}{day_str}"
日历实现
假期判断
因为“调休”的存在,所以我们的放假日期不总是固定的,每年都会有很大的变化,那么如何判断某个日子是不是节假日呢?是不是需要调休呢?
实际上,这是一件困难的事情,没有办法提前知道,只能等到每一年,国家公布了放假安排以后,我们才能够知道准确的放假调休日期。比如说,一些日历等不及,还没等公布放假安排,就已经开始提前印刷了,那么这样的日历,其上包含的信息,就是不完整的,他只能告诉你常规的节日和星期,没办法告诉你调休。
看过我上一期关于日历文章的,应该知道在当时,我是使用了“标记调休”的方式,实现这一点的,大概像这样:
这当然是简单有效,且可行的,只不过一次标记只能管一年,到了明年就不能用了,还得自己重新标记,况且,标记也是一件麻烦的事情,有没有什么更好的办法呢?
当然是有的,我们让别人给我们标记好了,自己等着用现成的,不就好了吗?那么哪里能找到这样的好心人呢?当然是有的,python有一个库叫做chinese-calendar,其中维护了每年的中国节假日,我们只需要使用这个库,让他告诉我们,今天休不休息,就好了。
安装:pip install chinese_calendar
import chinese_calendar as cc
from datetime import date# 检查某一天是否是工作日或节假日
on_holiday, holiday_name = cc.get_holiday_detail(date(2024, 10, 1))# 输出是否为假日,假日名称
print(on_holiday, holiday_name)
唉,世界上还是好人多啊!用上了这个,我们可以省很多事,真是好东西啊。
matplotlib绘制日历
matplotlib非常常用,今天就不主要介绍了,虽然它不常用于绘制日历,但是,它的功能其实是很广泛的,包括我们今天的绘制日历。
在下面的实现中,允许提供一个额外信息表,来覆盖默认的农历。也就是你可以通过extra_info_days来增加节日,纪念日的提示。
import datetime
import chinese_calendar as cc
import matplotlib.pyplot as pltfrom calendar import monthrange
from lunarcalendar import Converter, Solarclass CalendarDrawer:plt.rcParams['font.family'] = 'SimHei' # 设置显示字体为黑体months = ["正月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"]days = ["初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十","十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十","廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十"]def __init__(self, year, month, extra_info_days=None):self.year = yearself.month = monthself.extra_info_days = extra_info_days or {}self.fig, self.ax = plt.subplots()def ax_init(self):self.ax.axis([0, 7, 0, 7])self.ax.axis("on")self.ax.grid(True)self.ax.set_xticks([])self.ax.set_yticks([])@staticmethoddef lunar_date_str(year, month, day):lunar = Converter.Solar2Lunar(Solar(year, month, day))month_str = "闰" + CalendarDrawer.months[lunar.month - 1] if lunar.isleap else CalendarDrawer.months[lunar.month - 1]return month_str if lunar.day == 1 else CalendarDrawer.days[lunar.day - 1]def plot_month(self):self.ax.text(3.5, 7.5, f"{self.year}年{self.month}月", color="black", ha="center", va="center")def plot_weekday_headers(self):for i, weekday in enumerate(["周一", "周二", "周三", "周四", "周五", "周六", "周日"]):x = i + 0.5self.ax.text(x, 6.5, weekday, ha="center", va="center", color="black")def plot_day(self, day, x, y, color):ex_day = datetime.date(self.year, self.month, day)day_info = f"{day}\n{self.extra_info_days.get(ex_day, self.lunar_date_str(self.year, self.month, day))}"self.ax.text(x, y, day_info, ha="center", va="center", color=color)def check_color_day(self, day):date = datetime.date(self.year, self.month, day)return "red" if cc.get_holiday_detail(date)[0] else "black"def save(self):self.ax_init()self.plot_month()self.plot_weekday_headers()weekday, num_days = monthrange(self.year, self.month)y = 5.5x = weekday + 0.5for day in range(1, num_days + 1):color = self.check_color_day(day)self.plot_day(day, x, y, color)weekday = (weekday + 1) % 7if weekday == 0:y -= 1x = weekday + 0.5plt.savefig(f"日历{self.year}-{self.month}.png")if __name__ == "__main__":extra_info_days = {datetime.date(2024, 1, 1): "元旦",datetime.date(2024, 2, 10): "春节",datetime.date(2024, 2, 14): "情人节",datetime.date(2024, 2, 24): "元宵节",datetime.date(2024, 3, 8): "妇女节",datetime.date(2024, 4, 4): "清明节",datetime.date(2024, 5, 1): "劳动节",datetime.date(2024, 5, 12): "母亲节",datetime.date(2024, 5, 20): "520",datetime.date(2024, 6, 1): "儿童节",datetime.date(2024, 6, 10): "端午节",datetime.date(2024, 6, 16): "父亲节",datetime.date(2024, 8, 10): "七夕节",datetime.date(2024, 9, 17): "中秋节",datetime.date(2024, 10, 1): "国庆节",datetime.date(2024, 11, 1): "万圣节",datetime.date(2024, 11, 11): "双十一",datetime.date(2024, 12, 12): "双十二",datetime.date(2024, 12, 24): "平安夜",datetime.date(2024, 12, 25): "圣诞节"}calendar_drawer = CalendarDrawer(2024, 12, extra_info_days) # 第一个参数为年份,第二个参数为月份,第三个参数为额外信息字典calendar_drawer.save()
绘制结果,2024-09:
2024-10:
2024-11:
2024-12:
引用与致谢
日历样式,部分参考了便民查询:https://wannianrili.bmcx.com/
matplotlib绘制,部分参考了shimo164:https://medium.com/@shimo164/
详细的中国节假日,不再需要个人手动标记了,chinese-calendar:https://github.com/LKI/chinese-calendar
快速将公历转为农历,支持转换到2100年,LunarCalendar:https://github.com/wolfhong/LunarCalendar
总结
从我们的日历中,可以清晰的看出,中秋到国庆,我们经历了:
- 上6休3
- 上3休2
- 上5休1
- 上2休7
- 上5休1
嗯,确实是太烧脑了,没有日历,很难算的清楚啊,最后,那么问题来了,3+7到底等于几呢?