Python实现阳历转农历功能 | 迟思堂工作室
A-A+

Python实现阳历转农历功能

2017-02-14 22:15 python 评论 1 条 阅读 3,215 次

去年我家李大锤出生,办出生证明前根据老家族谱、八字、五行起名字,我起了十几个,然后再请村里大叔公算了一下。

于是心血来潮,决定研究一下阳历转换农历的算法,但网上几乎都是几种文章(或其演绎版本),笔者看了看,好像能得到正常结果,但没深入理解算法。为了练手Python,决定自己实现。

中国的农历十分复杂,涉及面也广,需要天文台计算确定,本文干脆通通舍弃这些知识,只需要确保程序运行结果与参考日历一致就行了。

结合代码,将算法概述如下:

0、阴历转农历使用查表法,确定阳历基准日、农历基准日,然后通过某种方法,计算出当前日期与基准日的差值,然后根据农历月份天数逐步计算出农历日期。

1、文中使用g_lunar_month_day存储每一年月份天数,因为农历月份大月为30天,小月为29天,没有其它值,为节省存储空间,使用1表示大月,0为小月,还需要保存闰月月份及该月份是否大小月。网上有的农历数据是从高位开始依次存储每月天数,本文从低位开始。

2、另外使用g_lunar_year_day保存春节日期(正月初一)以及该日期离元旦的天数。做此数据结构,主要是为了计算农历的快捷性,因为网上有的计算方法是从1901年开始计算日期差,直到指定日期,如今年是2017年,则要循环计算116年(2017-1901),虽然以当今CPU性能对应此事绰绰有余,但不必要进行如此计算。故本文使用g_lunar_year_day了。只需要在当前年份中进行计算即可。

3、年份的天干地支、生肖根据公式计算,以农历正月初一为分界。另外还有以立春为准的说法,这个问题待天文学家和算命先生讨论吧。

4、本文农历使用HK天文台日历数据,从1901年到2100年,地址:http://data.weather.gov.hk/gts/time/conversion1_text_c.htm。为了获取日历数据,还另外编写python程序进行分析,从而得到数据表。

5、显示日历利用python提供的calendar,其提供的itermonthdays方法返回的数据即为指定年月的日历,以周一开始。

代码如下:

#!/usr/bin/python3
# encoding: utf-8
# Author: Late Lee
# Linux + python3
# 2017.2.14

#####################################################################################
# 1901~2100年农历数据表
# powered by Late Lee,  
# 2017-02-11 10:01:48.405752 
#农历数据 每个元素的存储格式如下: 
#   16~13    12          11~0  
#  闰几月 闰月日数  1~12月份农历日数  
# 注:1、bit0表示农历1月份日数,为1表示30天,为0表示29天。bit1表示农历2月份日数,依次类推。 
#     2、bit12表示闰月日数,1为30天,0为29天。bit17~bit14表示第几月是闰月(注:为0表示该年无闰月) 
# 数据来源: http://data.weather.gov.hk/gts/time/conversion1_text_c.htm
# 由Jim Kent编写python爬虫强力分析
#####################################################################################
g_lunar_month_day = [
	0x00752, 0x00ea5, 0x0ab2a, 0x0064b, 0x00a9b, 0x09aa6, 0x0056a, 0x00b59, 0x04baa, 0x00752, # 1901 ~ 1910 
	0x0cda5, 0x00b25, 0x00a4b, 0x0ba4b, 0x002ad, 0x0056b, 0x045b5, 0x00da9, 0x0fe92, 0x00e92, # 1911 ~ 1920 
	0x00d25, 0x0ad2d, 0x00a56, 0x002b6, 0x09ad5, 0x006d4, 0x00ea9, 0x04f4a, 0x00e92, 0x0c6a6, # 1921 ~ 1930 
	0x0052b, 0x00a57, 0x0b956, 0x00b5a, 0x006d4, 0x07761, 0x00749, 0x0fb13, 0x00a93, 0x0052b, # 1931 ~ 1940 
	0x0d51b, 0x00aad, 0x0056a, 0x09da5, 0x00ba4, 0x00b49, 0x04d4b, 0x00a95, 0x0eaad, 0x00536, # 1941 ~ 1950 
	0x00aad, 0x0baca, 0x005b2, 0x00da5, 0x07ea2, 0x00d4a, 0x10595, 0x00a97, 0x00556, 0x0c575, # 1951 ~ 1960 
	0x00ad5, 0x006d2, 0x08755, 0x00ea5, 0x0064a, 0x0664f, 0x00a9b, 0x0eada, 0x0056a, 0x00b69, # 1961 ~ 1970 
	0x0abb2, 0x00b52, 0x00b25, 0x08b2b, 0x00a4b, 0x10aab, 0x002ad, 0x0056d, 0x0d5a9, 0x00da9, # 1971 ~ 1980 
	0x00d92, 0x08e95, 0x00d25, 0x14e4d, 0x00a56, 0x002b6, 0x0c2f5, 0x006d5, 0x00ea9, 0x0af52, # 1981 ~ 1990 
	0x00e92, 0x00d26, 0x0652e, 0x00a57, 0x10ad6, 0x0035a, 0x006d5, 0x0ab69, 0x00749, 0x00693, # 1991 ~ 2000 
	0x08a9b, 0x0052b, 0x00a5b, 0x04aae, 0x0056a, 0x0edd5, 0x00ba4, 0x00b49, 0x0ad53, 0x00a95, # 2001 ~ 2010 
	0x0052d, 0x0855d, 0x00ab5, 0x12baa, 0x005d2, 0x00da5, 0x0de8a, 0x00d4a, 0x00c95, 0x08a9e, # 2011 ~ 2020 
	0x00556, 0x00ab5, 0x04ada, 0x006d2, 0x0c765, 0x00725, 0x0064b, 0x0a657, 0x00cab, 0x0055a, # 2021 ~ 2030 
	0x0656e, 0x00b69, 0x16f52, 0x00b52, 0x00b25, 0x0dd0b, 0x00a4b, 0x004ab, 0x0a2bb, 0x005ad, # 2031 ~ 2040 
	0x00b6a, 0x04daa, 0x00d92, 0x0eea5, 0x00d25, 0x00a55, 0x0ba4d, 0x004b6, 0x005b5, 0x076d2, # 2041 ~ 2050 
	0x00ec9, 0x10f92, 0x00e92, 0x00d26, 0x0d516, 0x00a57, 0x00556, 0x09365, 0x00755, 0x00749, # 2051 ~ 2060 
	0x0674b, 0x00693, 0x0eaab, 0x0052b, 0x00a5b, 0x0aaba, 0x0056a, 0x00b65, 0x08baa, 0x00b4a, # 2061 ~ 2070 
	0x10d95, 0x00a95, 0x0052d, 0x0c56d, 0x00ab5, 0x005aa, 0x085d5, 0x00da5, 0x00d4a, 0x06e4d, # 2071 ~ 2080 
	0x00c96, 0x0ecce, 0x00556, 0x00ab5, 0x0bad2, 0x006d2, 0x00ea5, 0x0872a, 0x0068b, 0x10697, # 2081 ~ 2090 
	0x004ab, 0x0055b, 0x0d556, 0x00b6a, 0x00752, 0x08b95, 0x00b45, 0x00a8b, 0x04a4f, ]


#农历数据 每个元素的存储格式如下: 
#    12~7         6~5    4~0  
#  离元旦多少天  春节月  春节日  
#####################################################################################
g_lunar_year_day = [
	0x18d3, 0x1348, 0x0e3d, 0x1750, 0x1144, 0x0c39, 0x15cd, 0x1042, 0x0ab6, 0x144a, # 1901 ~ 1910 
	0x0ebe, 0x1852, 0x1246, 0x0cba, 0x164e, 0x10c3, 0x0b37, 0x14cb, 0x0fc1, 0x1954, # 1911 ~ 1920 
	0x1348, 0x0dbc, 0x1750, 0x11c5, 0x0bb8, 0x15cd, 0x1042, 0x0b37, 0x144a, 0x0ebe, # 1921 ~ 1930 
	0x17d1, 0x1246, 0x0cba, 0x164e, 0x1144, 0x0bb8, 0x14cb, 0x0f3f, 0x18d3, 0x1348, # 1931 ~ 1940 
	0x0d3b, 0x16cf, 0x11c5, 0x0c39, 0x15cd, 0x1042, 0x0ab6, 0x144a, 0x0e3d, 0x17d1, # 1941 ~ 1950 
	0x1246, 0x0d3b, 0x164e, 0x10c3, 0x0bb8, 0x154c, 0x0f3f, 0x1852, 0x1348, 0x0dbc, # 1951 ~ 1960 
	0x16cf, 0x11c5, 0x0c39, 0x15cd, 0x1042, 0x0a35, 0x13c9, 0x0ebe, 0x17d1, 0x1246, # 1961 ~ 1970 
	0x0d3b, 0x16cf, 0x10c3, 0x0b37, 0x14cb, 0x0f3f, 0x1852, 0x12c7, 0x0dbc, 0x1750, # 1971 ~ 1980 
	0x11c5, 0x0c39, 0x15cd, 0x1042, 0x1954, 0x13c9, 0x0e3d, 0x17d1, 0x1246, 0x0d3b, # 1981 ~ 1990 
	0x16cf, 0x1144, 0x0b37, 0x144a, 0x0f3f, 0x18d3, 0x12c7, 0x0dbc, 0x1750, 0x11c5, # 1991 ~ 2000 
	0x0bb8, 0x154c, 0x0fc1, 0x0ab6, 0x13c9, 0x0e3d, 0x1852, 0x12c7, 0x0cba, 0x164e, # 2001 ~ 2010 
	0x10c3, 0x0b37, 0x144a, 0x0f3f, 0x18d3, 0x1348, 0x0dbc, 0x1750, 0x11c5, 0x0c39, # 2011 ~ 2020 
	0x154c, 0x0fc1, 0x0ab6, 0x144a, 0x0e3d, 0x17d1, 0x1246, 0x0cba, 0x15cd, 0x10c3, # 2021 ~ 2030 
	0x0b37, 0x14cb, 0x0f3f, 0x18d3, 0x1348, 0x0dbc, 0x16cf, 0x1144, 0x0bb8, 0x154c, # 2031 ~ 2040 
	0x0fc1, 0x0ab6, 0x144a, 0x0ebe, 0x17d1, 0x1246, 0x0cba, 0x164e, 0x1042, 0x0b37, # 2041 ~ 2050 
	0x14cb, 0x0fc1, 0x18d3, 0x1348, 0x0dbc, 0x16cf, 0x1144, 0x0a38, 0x154c, 0x1042, # 2051 ~ 2060 
	0x0a35, 0x13c9, 0x0e3d, 0x17d1, 0x11c5, 0x0cba, 0x164e, 0x10c3, 0x0b37, 0x14cb, # 2061 ~ 2070 
	0x0f3f, 0x18d3, 0x12c7, 0x0d3b, 0x16cf, 0x11c5, 0x0bb8, 0x154c, 0x1042, 0x0ab6, # 2071 ~ 2080 
	0x13c9, 0x0e3d, 0x17d1, 0x1246, 0x0cba, 0x164e, 0x10c3, 0x0bb8, 0x144a, 0x0ebe, # 2081 ~ 2090 
	0x1852, 0x12c7, 0x0d3b, 0x16cf, 0x11c5, 0x0c39, 0x154c, 0x0fc1, 0x0a35, 0x13c9, # 2091 ~ 2100 
	]

#==================================================================================

from datetime import date, datetime
import calendar

START_YEAR = 1901

month_DAY_BIT = 12
month_NUM_BIT = 13

# todo:正月初一 == 春节   腊月二十九/三十 == 除夕
yuefeng = ["正月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "冬月", "腊月"]
riqi = ["初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十",
    "十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "廿十",
    "廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十"]

xingqi = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"]

tiangan   = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"]
dizhi     = ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"]
shengxiao = ["鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"]

# todo:添加节气
jieqi = [
    "小寒", "大寒",  # 1月
    "立春", "雨水",  # 2月
    "惊蛰", "春分",  # 3月
    "清明", "谷雨",  # 4月
    "立夏", "小满",  # 5月
    "芒种", "夏至",  # 6月
    "小暑", "大暑",  # 7月
    "立秋", "处暑",  # 8月
    "白露", "秋分",  # 9月
    "寒露", "霜降",  # 10月
    "立冬", "小雪",  # 11月
    "大雪", "冬至"]  # 12月

def change_year(num):
    dx = ["零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十"]
    tmp_str = ""
    for i in str(num):
        tmp_str += dx[int(i)]
    return tmp_str

def week_str(tm):
    return xingqi[tm.weekday()]

def lunar_day(day):
    return riqi[(day - 1) % 30]

def lunar_day1(month, day):
    if day == 1:
        return lunar_month(month)
    else:
        return riqi[day - 1]

def lunar_month(month):
    leap = (month>>4)&0xf
    m = month&0xf
    month = yuefeng[(m - 1) % 12]
    if leap == m:
        month = "闰" + month
    return month

def lunar_year(year):
    return tiangan[(year - 4) % 10] + dizhi[(year - 4) % 12] + '[' + shengxiao[(year - 4) % 12] + ']'

# 返回:
# a b c
# 闰几月,该闰月多少天 传入月份多少天
def lunar_month_days(lunar_year, lunar_month):
    if (lunar_year < START_YEAR):
        return 30

    leap_month, leap_day, month_day = 0, 0, 0 # 闰几月,该月多少天 传入月份多少天

    tmp = g_lunar_month_day[lunar_year - START_YEAR]

    if tmp & (1<<(lunar_month-1)):
        month_day = 30
    else:
        month_day = 29

    # 闰月
    leap_month = (tmp >> month_NUM_BIT) & 0xf
    if leap_month:
        if (tmp & (1<<month_DAY_BIT)):
            leap_day = 30
        else:
            leap_day = 29

    return (leap_month, leap_day, month_day)

# 算农历日期
# 返回的月份中,高4bit为闰月月份,低4bit为其它正常月份
def get_ludar_date(tm):
    year, month, day = tm.year, 1, 1
    code_data = g_lunar_year_day[year - START_YEAR]
    days_tmp = (code_data >> 7) & 0x3f
    chunjie_d = (code_data >> 0) & 0x1f
    chunjie_m = (code_data >> 5) & 0x3
    span_days = (tm - datetime(year, chunjie_m, chunjie_d)).days
    #print("span_day: ", days_tmp, span_days, chunjie_m, chunjie_d)

    # 日期在该年农历之后
    if (span_days >= 0):
        (leap_month, foo, tmp) = lunar_month_days(year, month)
        while span_days >= tmp:
            span_days -= tmp
            if (month == leap_month):
                (leap_month, tmp, foo) = lunar_month_days(year, month) # 注:tmp变为闰月日数
                if (span_days < tmp): # 指定日期在闰月中
                    month = (leap_month<<4) | month
                    break
                span_days -= tmp
            month += 1 # 此处累加得到当前是第几个月
            (leap_month, foo, tmp) = lunar_month_days(year, month)
        day += span_days
        return year, month, day
    # 倒算日历
    else:
        month = 12
        year -= 1
        (leap_month, foo, tmp) = lunar_month_days(year, month)
        while abs(span_days) >= tmp:
            span_days += tmp
            month -= 1
            if (month == leap_month):
                (leap_month, tmp, foo) = lunar_month_days(year, month)
                if (abs(span_days) < tmp): # 指定日期在闰月中
                    month = (leap_month<<4) | month
                    break
                span_days += tmp
            (leap_month, foo, tmp) = lunar_month_days(year, month)
        day += (tmp + span_days) # 从月份总数中倒扣 得到天数
        return year, month, day

def _show_month(tm):
    (year, month, day) = get_ludar_date(tm)
    print("%d年%d月%d日" % (tm.year, tm.month, tm.day), week_str(tm), end='')
    print("\t农历 %s年 %s年%s%s " % (lunar_year(year), change_year(year), lunar_month(month), lunar_day(day))) # 根据数组索引确定
    print("一\t二\t三\t四\t五\t六\t日")

    c = calendar.Calendar(0)
    ds = [d for d in c.itermonthdays(tm.year, tm.month)]

    #print(len(ds), ds)
    # 利用calendar直接获取指定年月日期
    count = 0
    for d in ds:
        if d == 0:
            print("\t", end='')
            count += 1
            continue

        (year, month, day) = get_ludar_date(datetime(tm.year, tm.month, d))

        if count % 7 == 0:
            print("\n", end='')
        d_str = str(d)
        if d == tm.day:
            d_str = "*" + d_str
        print("%s\t" % (d_str + lunar_day1(month, day)), end='')
        count += 1
    print("")


def show_month(year, month, day):
    if year > 2100 or year < 1901:
        return
    if month > 13 or month < 1:
        return

    tmp = datetime(year, month, day)
    _show_month(tmp)

def this_month():
    #print(calendar.month(datetime.now().year, datetime.now().month))
    #print('--------------------------')
    show_month(datetime.now().year, datetime.now().month, datetime.now().day)

this_month()
show_month(2034, 1, 1)

运行结果:

2017年2月14日 星期三    农历 丁酉[鸡]年 二零一七年正月十八 
一      二      三      四      五      六      日
                1初五   2初六   3初七   4初八   5初九
6初十   7十一   8十二   9十三   10十四  11十五  12十六
13十七  *14十八  15十九 16廿十  17廿一  18廿二  19廿三
20廿四  21廿五  22廿六  23廿七  24廿八  25廿九  26二月
27初二  28初三


2034年1月1日 星期日     农历 癸丑[牛]年 二零三三年闰冬月十一 
一      二      三      四      五      六      日
                                                *1十一
2十二   3十三   4十四   5十五   6十六   7十七   8十八
9十九   10廿十  11廿一  12廿二  13廿三  14廿四  15廿五
16廿六  17廿七  18廿八  19廿九  20三十  21初二  22初三
23初四  24初五  25初六  26初七  27初八  28初九  29初十
30十一  31十二

 

参考:

http://www.cnblogs.com/chjbbs/p/5704326.html

http://www.jb51.net/article/49368.htm

李迟 2017年2月14日 周二 晚



如果本文对阁下有帮助,不妨赞助笔者以输出更多好文章,谢谢!
donate




1 条留言  访客:0 条  博主:1 条

  1. 李 迟

给我留言