一概述
公歷是全世界通用的歷法以地球繞太陽的一周為一年一年 365 天分為 12 個月 1 3 5 7 8 10 12 月為31 天
2 月為 28 天其余月份為 30 天事實上地球繞太陽一周共 365 天 5 小時 48 分 46 秒比公歷一年多出 5 小時 48分 46 秒
為使年誤差不累積公歷年用閏年法來消除年誤差由于每年多出 5 小時 48 分 46 秒每 4 年累計多出 23 小時 15 分 4 秒接近 1 天
天文學家就規定每 4 年有一個閏年把 2 月由 28 天改為 29 天凡是公歷年代能被 4 整除
的那一年就是閏年但是這樣一來每 4 年又少了 44 分 56 秒為了更準確地計時天文學家又規定凡能被 100 整除
的年份只有能被 400 整除才是閏年即每 400 年要減掉 3 個閏年經過這樣處理后實際上每 400 年的誤差只有 2 小時
53 分 20 秒已相當準確了
農歷與公歷不同農歷把月亮繞地球一周作為一月因為月亮繞地球一周不是一整天所以農歷把月分為大月和小
月大月 30 天小月 29 天通過設置大小月使農歷日始終與月亮與地球的位置相對應為了使農歷的年份與公歷年
相對應農歷通過設置閏月的辦法使它的平均年長度與公歷年相等農歷是中國傳統文化的代表之一并與農業生產聯
系密切中國人民特別是廣大農民十分熟悉并喜愛農歷
公歷與農歷是我國目前并存的兩種歷法各有其固有的規律農歷與月球的運行相對應其影響因素多它的大小
月和閏月與天體運行有關計算十分復雜且每年都不一致因此要用單片機實現公歷與農歷的轉換用查表法是方
便實用的辦法
51 系列單片機因其在功能上能滿足大部份對速度要求不高的應用場合的要求且價格低廉開發工具普及程度高
是目前應用多的單片機之一本文介紹一種用 51 單片機實現從 1901 年到 2099 年 199 年公歷日到農歷日及星期的轉
換方法并向讀者提供完整的 51 匯編程序
二基本原理
實現公歷與農歷的轉換一般采用查表法按日查表是速度快的方法但 51 單片機尋址能力有限不可能采用
按日查表的方法除按日查外我們可以通過按月查表和按年查表的方法再通過適當的計算來確定公歷日所對應的
農歷日期本文采用的是按年查表法限度地減少表格所占的程序空間
對于農歷月來說大月為 30 天小月為 29 天這是固定不變的這樣我們就可用 1 個 BIT 位來表示大小月信
息農歷一年如有閏月為 13 個月否則是 12 個月所以一年需要用 13 個 BIT 閏月在農歷年中所在的月份并不固定
大部分閏月分布在農歷 2 8 月但也有少量年份在 9 月以后所以要表示閏月的信息至少要 4BIT 在這里我們用 4BIT
的值來表示閏月的月份值為 0 表示本年沒有閏月有了以上信息還不足以判斷公歷日對應的農歷日因為還需要一
個參照日我們選用農歷正月初一所對應的公歷日期作參照日公歷日為 31 日需要 5BIT 來表示而春節所在的
月份不是 1 月就是 2 月用 1BIT 就夠了考慮到表達方便我們用 2BIT 來表示春節月 2BIT 的值直接表示月份這
樣一年的農歷信息只用 3 個字節就全部包括了
計算公歷日對應的農歷日期的方法先計算出公歷日離當年元旦的天數然后查表取得當年的春節日期計算出春
節離元旦的天數二者相減即可算出公歷日離春節的天數以后只要根據大小月和閏月信息減一月天數調整一月農
歷月份即可推算出公歷日所對應的農歷日期如公歷日不到春節日期農歷年要比公歷年小一年農歷大小月取前一
年的信息農歷月從 12 月向前推算
公歷日是非常有規律的所以公歷日所對應的星期天可以通過計算直接得到理論上公元 0 年 1 月 1 日為星期日
只要求得公歷日離公元 0 年 1 月 1 日的日子數除 7 后的余數就是星期天為了簡化計算采用月校正法根據公歷的
年月日可直接計算出星期天其算法是日期年份所過閏年數月校正數之和除 7 的余數就是星期天但如果是在
閏年又不到 3 月份上述之和要減再除 7 其 1 12 月的校正數據為 6 2 2 5 0 3 5 1 4 6 2 4 在
本程序中采用 1 個字節表示年份閏年數也只計算 1900 年以后的閏年數所以實際校正數據也和上述數據不同
- 2 -
三程序流程
由于星期的計算很簡單這里只提供公歷日轉農歷日的程序流程圖
否
是
否
是否
子程序入口
轉換數據到 HEX 格式方便運算
根據公歷年定位數據表的地址
從數據表中取得春節的公歷日期 , 并通過
計算公歷日和春節離元旦的天數算出公歷
日離春節的天數記為 X
農歷年 = 公歷年減
1 數據表地址減
3 定位到前一年
取農歷月信息
農歷月 12 閏月標
志 F0 0 非閏月
X Y
否公歷日在春節以后碼 ?
取農歷月天
數 Y
是
X X Y
F0 1
是
否
是
農歷月減 1
農歷月閏月
F0 取反
是否
農歷日 Y X 1
農歷年 = 公歷年取
農歷月信息
農歷月 1 閏月標
志 F0 0 非閏月
取農歷月
天數 Y
X Y X X Y
農歷月閏月
農歷月加 1
F0 1
F0 取反
是
農歷日 X 1
否
農歷年月日數
據標準化
子程序出口
- 3 -
四程序使用說明
本文提供的子程序在設計時應用了 PCF8563 作時鐘芯片所以其入口格式與 PCF8563 芯片的時鐘信號存儲格式完
全一致年月日均為 BCD 碼其中月的 BIT7 表示世紀為 1 表示 19 世紀為 0 表示 20 世紀采用 PCF8563 時
鐘芯片只要把它的年月日寄存器內容讀出到 time_yeAr time_month 和 time_date 三個單元內即可直接調用本
程序轉換采用其它時鐘芯片調用前要把時鐘格式稍作調整或修改一下程序公歷日轉農歷日程序在 12M 晶振下
執行時間長約 0.48 毫秒實際使用時只需在復位和日期變化時才需要調用一次對于公歷日轉星期天的子程序則
只在設置時鐘時才有用在設置時鐘年月日后調用子程序得到對應的星期天直接寫入時鐘即可
子程序附帶了 1901-2100 年的農歷數據表全部編譯要占 600 字節空間如不需這么多可把不需要的數據刪除然
后修改 stArt_yeAr 值即可 stArt_yeAr 定義了查詢表的起始年份
五子程序清單
start_year EQU 01; 定義查詢表起始年份 ,01--199 表示 1901-2099 年
; 以下三單元為需轉換的公歷日期是子程序的入口數據
time_year DATA 30h
time_month DATA 31h ;BIT7 表示世紀 , 為 1 表示 19 世紀 , 為 0 表示 20 世紀
time_date DATA 32h
; 以下三單元存轉換后農歷日期與入口單元重疊 , 如要保留入口信息 , 請重定義出口地址
CONvert_yeAr DATA 30h
CONvert_mONth DATA 31h ;BIT7 為 1 表示閏月
CONvert_dAte DATA 32h
temp_Byte1 DATA 37h
temp_Byte2 DATA 38h
temp_Byte3 DATA 39h
temp_Byte4 DATA 3Ah
temp_Byte5 DATA 3Bh
time_week DATA 40h ; 星期天出口
; 以下為公歷轉農歷子程序
CONvert: MOV A,time_year ; 將年月日轉化為 HEX 格式
MOV B,#16
DIV AB
MOV CONvert_yeAr,B
MOV B,#10
MUL AB
ADD A,CONvert_yeAr
MOV CONvert_yeAr,A
MOV A,time_month
MOV C,ACC.7
MOV f0,C ;f0 暫存世紀標志 , 僅用于數據表定位
CLR ACC.7
JNB ACC.4,CON_02
- 4 -
CLR ACC.4 ;ACC.4 為 1 表示大于 10 月
ADD A,#10
CON_02: MOV CONvert_mONth,A
MOV A,time_date
MOV B,#16
DIV AB
MOV CONvert_dAte,B
MOV B,#10
MUL AB
ADD A,CONvert_dAte
MOV CONvert_dAte,A
MOV dptr,#mONth_dAtA ; 以下定位本年數據在表格中的位置
MOV A,CONvert_yeAr
JB f0,CON_06 ; 當前為 19 世紀年跳轉
ADD A,#100 ; 從 19 世紀起定義表格起始年 ,20 世紀要加 100 年
CON_06: CLR C
SUBB A,#stArt_yeAr
MOV B,#3 ; 表格每年 3 字節
MUL AB
ADD A,dpl
MOV dpl,A
MOV A,B
ADDC A,dph
MOV dph,A
MOV A,#2
MOVC A,@A+dptr ; 讀本年表格一字節 ( 春節日期 )
CLR ACC.7 ;ACC.7 是閏年第 13 個月大小 , 在此不用
MOV B,#32
DIV AB
MOV temp_Byte1,A ; 春節月份
MOV temp_Byte2,B ; 春節日
; 以下計算當前日期距元旦天數
MOV temp_Byte3,#0 ; 設距元旦天數高位為 0
MOV A,CONvert_mONth
CJNE A,#10,CON_08
CON_08: JC CON_09 ;9 月以前日子數小于 256 天 , 高字節為 0(9 月份過去的整月為 8 個月 )
MOV temp_Byte3,#1
CON_09: MOV A,CONvert_yeAr
ANL A,#03h ;ACC 為除 4 的余數
JNZ CON_10 ; 轉常年處理
; 年除 4 余數為 0 是閏年
MOV A,CONvert_mONth
- 5 -
LCALL get_ruN_dAys_lOw ; 取得閏年過去月的天數的低字節
SJMP CON_12
CON_10: MOV A,CONvert_mONth
LCALL get_dAys_lOw ; 取得常年過去月的天數的低字節
CON_12: MOV B,CONvert_dAte
DEC B ; 因為日期從 1 日起 , 而不是 0 日起
ADD A,B ; 過去的整月天數加當月天數
MOV temp_Byte4,A
JNC CON_14
INC temp_Byte3 ;temp_Byte3,temp_Byte4 分別為公歷年過去的天數的高低字節
; 以下求春節距元旦天數 , 因肯定小于 256 天所以只用一字節表示
CON_14: MOV A,temp_Byte1
LCALL get_dAys_lOw ; 春節不會在 3 月份 , 不用考慮閏年
DEC A ; 因為日期從 1 日起
ADD A,temp_Byte2
MOV temp_Byte5,A ;temp_Byte5, 為春節距元旦天數
MOV A,CONvert_mONth
CJNE A,temp_Byte1,CON_20 ; 轉換月與春節月比較
MOV A,CONvert_dAte
CJNE A,temp_Byte2,CON_20 ; 轉換日與春節日比較
CON_20: JC CON_22
LJMP CON_60 ; 當前日大于等于春節日期 , 公歷年與農歷年同年份
CON_22: MOV A,CONvert_yeAr ; 不到春節 , 農歷年比公歷年低一年
JNZ CON_24
MOV A,#100 ; 年有效數 0-99
CON_24: DEC A
MOV CONvert_yeAr,A
MOV A,dpl
CLR C
SUBB A,#3
MOV dpl,A
JNC CON_26
DEC dph ; 表格指針指向上一年
CON_26: MOV A,temp_Byte5
CLR C
SUBB A,temp_Byte4
MOV temp_Byte3,A ;temp_Byte3 中為當前日離春節的天數
MOV CONvert_mONth,#12 ; 農歷月為 12 月
CLR f0 ;1901-2099 年沒有閏 12 月 , 清閏月標志
CLR A
MOVC A,@A+dptr
- 6 -
ANL A,#0f0h
SWAP A;
MOV temp_Byte4,A ;temp_Byte4 中為閏月
JZ CON_30 ; 沒有閏月轉移
MOV A,#2 ; 有閏月 , 取第 13 個月天數
MOVC A,@A+dptr
MOV C,ACC.7
MOV A,#1
MOVC A,@A+dptr
RLC A ;ACC 中為 6 個月的大小值
SJMP CON_34
CON_30: MOV A,#1
MOVC A,@A+dptr ;ACC 中為 6 個月的大小值
CON_34: MOV temp_Byte5,A
CON_40: MOV A,temp_Byte5
RRC A
MOV temp_Byte5,A
JC CON_42
MOV B,#29 ; 小月 29 天
SJMP CON_44
CON_42: MOV B,#30 ; 大月 30 天
CON_44: MOV A,temp_Byte3
CLR C
SUBB A,B
JZ CON_46 ; 正好夠減 , 就是農歷日 1 日
JNC CON_50
; 不夠減一月天數 , 結束農歷月調整
CPL A ; 求補取值
INC A
CON_46: INC A ; 加 1 即為農歷日
MOV B,#10 ; 轉換并保存農歷日 , 月 , 年
DIV AB
SWAP A
ORL A,B
MOV CONvert_dAte,A
MOV A,CONvert_mONth
MOV B,#10
DIV AB
SWAP A
ORL A,B
MOV C,f0
MOV ACC.7,C
MOV CONvert_mONth,A
- 7 -
MOV A,CONvert_yeAr
MOV B,#10
DIV AB
SWAP A
ORL A,B
MOV CONvert_yeAr,A
RET ; 結束轉換
CON_50: MOV temp_Byte3,A ;temp_Byte3 存減去一月后的天數
JB f0,CON_52 ; 是閏月 , 前推一月 , 月份不減
DEC CONvert_mONth;
CON_52: MOV A,CONvert_mONth
CJNE A,temp_Byte4,CON_54
CPL f0 ; 當前月與閏月相同 , 更改閏月標志
CON_54: SJMP CON_40
CON_60: MOV A,temp_Byte4 ; 春節日小于當前日 , 農歷年同公歷年
CLR C
SUBB A,temp_Byte5
MOV temp_Byte4,A
JNC CON_62
DEC temp_Byte3 ;temp_Byte3 temp_Byte4 中為公歷日離春節的天數
CON_62: MOV CONvert_mONth,#1 ; 農歷月為 1 月
CLR A
MOVC A,@A+dptr
MOV temp_Byte5,A
ANL A,#0f0h
SWAP A;
XCH A,temp_Byte5 ;temp_Byte5 中為閏月 ,ACC 為當年農歷表字節
CLR f0 ; 個月肯定不是閏月
ANL A,#0fh
MOV temp_Byte1,A
MOV A,#1
MOVC A,@A+dptr
MOV temp_Byte2,A
ANL A,#0f0h
ORL A,temp_Byte1
SWAP A
MOV temp_Byte1,A
MOV A,#2
MOVC A,@A+dptr
MOV C,ACC.7
MOV A,temp_Byte2
ANL A,#0fh
- 8 -
SWAP A
MOV ACC.3,C;
MOV temp_Byte2,A ; 以上 temp_Byte1,temp_Byte2 各 BIT 存農歷年大小
CON_70: MOV A,temp_Byte2
RLC A
MOV temp_Byte2,A
MOV A,temp_Byte1
RLC A
MOV temp_Byte1,A
JC CON_72
MOV B,#29 ; 小月 29 天處理
SJMP CON_74
CON_72: MOV B,#30 ; 大月 30 天
CON_74: MOV A,temp_Byte4
CLR C
SUBB A,B
JNC CON_78 ; 低字節夠減跳轉
MOV B,A ; 低字節不夠減 , B 暫存減后結果 ,
MOV A,temp_Byte3
JZ CON_76 ; 高字節為 0, 不夠減
DEC temp_Byte3
MOV temp_Byte4,B
SJMP CON_80
CON_76: MOV A,temp_Byte4 ; 不夠減結束月調整
LJMP CON_46 ; 轉日期加 1 后 , 處理并保存轉換后農歷年月日
CON_78: MOV temp_Byte4,A ;temp_Byte3 temp_Byte4 天數為減去一月后天數
CON_80: MOV A,CONvert_mONth
CJNE A,temp_Byte5,CON_82
CPL f0 ; 當前月與閏月相同 , 更改閏月標志
JNB f0,CON_82 ; 更改標志后是非閏月 , 月份加 1
SJMP CON_70
CON_82: INC CONvert_mONth;
SJMP CON_70
get_dAys_lOw:
MOVC A,@A+PC ; 取得常年過去月的天數的低字節
RET
DB 0,31,59,90,120,151,181,212,243,17,48,78
get_ruN_dAys_lOw:
MOVC A,@A+PC ; 取得閏年過去月的天數的低字節
RET
DB 0,31,60,91,121,152,182,213,244,18,49,79
mONth_dAtA:
; 公歷年對應的農歷數據 , 每年三字節 ,
- 9 -
; 格式字節 BIT7-4 位表示閏月月份 , 值為 0 為無閏月 ,BIT3-0 對應農歷第 1-4 月的大小
; 第二字節 BIT7-0 對應農歷第 5-12 月大小 , 第三字節 BIT7 表示農歷第 13 個月大小
; 月份對應的位為 1 表示本農歷月大 (30 天 ), 為 0 表示小 (29 天 ).
; 第三字節 BIT6-5 表示春節的公歷月份 ,BIT4-0 表示春節的公歷日期
DB 004h,0Aeh,053h; 1901;
DB 00Ah,057h,048h; 1902
DB 055h,026h,0Bdh; 1903
DB 00dh,026h,050h; 1904
DB 00dh,095h,044h; 1905
DB 046h,0AAh,0B9h; 1906
DB 005h,06Ah,04dh; 1907
DB 009h,0Adh,042h; 1908
DB 024h,0Aeh,0B6h; 1909
DB 004h,0Aeh,04Ah; 1910
DB 06Ah,04dh,0Beh; 1911
DB 00Ah,04dh,052h; 1912
DB 00dh,025h,046h; 1913
DB 05dh,052h,0BAh; 1914
DB 00Bh,054h,04eh; 1915
DB 00dh,06Ah,043h; 1916
DB 029h,06dh,037h; 1917
DB 009h,05Bh,04Bh; 1918
DB 074h,09Bh,0C1h; 1919
DB 004h,097h,054h; 1920
DB 00Ah,04Bh,048h; 1921
DB 05Bh,025h,0BCh; 1922
DB 006h,0A5h,050h; 1923
DB 006h,0d4h,045h; 1924
DB 04Ah,0dAh,0B8h; 1925
DB 002h,0B6h,04dh; 1926
DB 009h,057h,042h; 1927
DB 024h,097h,0B7h; 1928
DB 004h,097h,04Ah; 1929
DB 066h,04Bh,03eh; 1930
DB 00dh,04Ah,051h; 1931
DB 00eh,0A5h,046h; 1932
DB 056h,0d4h,0BAh; 1933
DB 005h,0Adh,04eh; 1934
DB 002h,0B6h,044h; 1935
DB 039h,037h,038h; 1936
DB 009h,02eh,04Bh; 1937
DB 07Ch,096h,0Bfh; 1938
DB 00Ch,095h,053h; 1939
DB 00dh,04Ah,048h; 1940
- 10 -
DB 06dh,0A5h,03Bh; 1941
DB 00Bh,055h,04fh; 1942
DB 005h,06Ah,045h; 1943
DB 04Ah,0Adh,0B9h; 1944
DB 002h,05dh,04dh; 1945
DB 009h,02dh,042h; 1946
DB 02Ch,095h,0B6h; 1947
DB 00Ah,095h,04Ah; 1948
DB 07Bh,04Ah,0Bdh; 1949
DB 006h,0CAh,051h; 1950
DB 00Bh,055h,046h; 1951
DB 055h,05Ah,0BBh; 1952
DB 004h,0dAh,04eh; 1953
DB 00Ah,05Bh,043h; 1954
DB 035h,02Bh,0B8h; 1955
DB 005h,02Bh,04Ch; 1956
DB 08Ah,095h,03fh; 1957
DB 00eh,095h,052h; 1958
DB 006h,0AAh,048h; 1959
DB 07Ah,0d5h,03Ch; 1960
DB 00Ah,0B5h,04fh; 1961
DB 004h,0B6h,045h; 1962
DB 04Ah,057h,039h; 1963
DB 00Ah,057h,04dh; 1964
DB 005h,026h,042h; 1965
DB 03eh,093h,035h; 1966
DB 00dh,095h,049h; 1967
DB 075h,0AAh,0Beh; 1968
DB 005h,06Ah,051h; 1969
DB 009h,06dh,046h; 1970
DB 054h,0Aeh,0BBh; 1971
DB 004h,0Adh,04fh; 1972
DB 00Ah,04dh,043h; 1973
DB 04dh,026h,0B7h; 1974
DB 00dh,025h,04Bh; 1975
DB 08dh,052h,0Bfh; 1976
DB 00Bh,054h,052h; 1977
DB 00Bh,06Ah,047h; 1978
DB 069h,06dh,03Ch; 1979
DB 009h,05Bh,050h; 1980
DB 004h,09Bh,045h; 1981
用51單片機實現公歷與農歷星期的轉換
更新時間: 2008-08-04 10:00:22來源: 粵嵌教育瀏覽量:1679