公歷農(nóng)歷相互轉(zhuǎn)換的算法及其VCL完成
發(fā)表時間:2023-08-08 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]抱雪 你到過我的主頁嗎?在我的主頁上有這樣一個地方: 你注意到了嗎,在顯示時間的地方除了顯示公歷之外,還顯示了農(nóng)歷:農(nóng)歷辛已(蛇)年二月廿三日未時,比一般的網(wǎng)站上只顯示公歷就酷多了(怎么像自...
抱雪
你到過我的主頁嗎?在我的主頁上有這樣一個地方:
你注意到了嗎,在顯示時間的地方除了顯示公歷之外,還顯示了農(nóng)歷:農(nóng)歷辛已(蛇)年二月廿三日未時,比一般的網(wǎng)站上只顯示公歷就酷多了(怎么像自吹自擂?別的網(wǎng)站千萬別去告我違反了廣告法)。這是怎么做的呢?其實很簡單,只要一個小小的PHP或者JavaScript程序就可以了。
你不要著急地問我要PHP或JS的程序,最關(guān)鍵的是要了解算法,如果你明白了轉(zhuǎn)換的道理,就可以達(dá)到圣人所說的:舉一而三反焉,到時不管是用PHP、DELPHI、JS還是JSP、VB,你都可以很快地寫公歷農(nóng)歷相互轉(zhuǎn)換的程序來出來。我記得有高人曾經(jīng)說過,編程語言只是工具,數(shù)據(jù)結(jié)構(gòu)才是最重要的,此言誠不虛也。
閑話少說,下面我就來介紹一下具體的算法。
首先是要保存公農(nóng)歷之間的轉(zhuǎn)換信息:以任意一年作為起點,把從這一年起若干年(若干是多少?就看你的需要了)的農(nóng)歷信息保存起來(在我的VCL中,是以1921年作為起點)。回想一下,我們平常是怎樣來轉(zhuǎn)換公歷農(nóng)歷的呢?是查萬年歷,萬年歷有每一天的公歷農(nóng)歷,直接一查就可以了。那么我們可不可以也這樣做呢?當(dāng)然可以,但是,這樣做就要收錄每一天的信息,工作量就會很大,所以我們要簡化這些信息。怎么簡化呢?要保存一年的信息其實只要兩個信息就可以了:1、農(nóng)歷每個月的大小;2、今年是否有閏月,閏幾月以及閏月的大小。用一個整數(shù)來保存這些信息就足夠了。具體的方法是:用一位來表示一個月的大小,大月記為1,小月記為0,這樣就用掉12位(無閏月)或13位(有閏月),再用高4位來表示閏月的月份,沒有閏月記為0。比如說,2000年的信息數(shù)據(jù)是是0xC96,化成二進(jìn)制就是110010010110B,表示的含義是指1、2、5、8、10、11月大,其余月;2001年的農(nóng)歷信息數(shù)據(jù)是0x41A95,其中4表示今年閏四月,月份大小信息就是0x1A95(因為閏月,所以有13位),具體的就是1、2、4、5、8、10、12月大,其余月份。0x1A95=1101010010101B),要注意在四月的后面那一個0表示的是閏四月小,接著的那個1表示5月大。這樣就可以用一個數(shù)組來保存這些信息。在我的VCL程序中是用ChineseCalendarData[]這個數(shù)組來保存這些信息。
為了方便對算法的理解,首先來看看我的VCL組件hsDivineCalendar的頭文件
//---------------------------------------------------------------------------
#ifndef hsDivineCalendarH
#define hsDivineCalendarH
#define ALLYEARS 100 //定義轉(zhuǎn)換的年數(shù):100年
//---------------------------------------------------------------------------
#include <SysUtils.hpp>
#include <Controls.hpp>
#include <Classes.hpp>
#include <Forms.hpp>
//---------------------------------------------------------------------------
class PACKAGE ThsDivineCalendar : public TComponent
{
private:
int ChineseCalendarData[ALLYEARS]; //農(nóng)歷數(shù)據(jù)
AnsiString str2,num; //要用的字符串
void __fastcall c2e(); //農(nóng)歷到公歷
void __fastcall e2c(); //公歷到農(nóng)歷
TDateTime TheDate; //日期
int FYear,FMonth,FDay,FTime; //公歷年月日時
int FcYear,FcMonth,FcDay,FcTime; //農(nóng)歷年月日時
AnsiString __fastcall GetDateString(); //獲取日期字符串
AnsiString __fastcall GetcDateString(); //獲取農(nóng)歷日期字符串
int __fastcall GetBit(int m,int n); //獲取1bit
void __fastcall GetYMD(); //獲取年月日
void __fastcall SetDate(TDateTime); //用一個TDateTime類型的變量轉(zhuǎn)換
//-----------分別修改公歷的年月日時-------------------------------------
void __fastcall SetYear(int AYear){SetBy(AYear,FMonth,FDay,FTime);};
void __fastcall SetMonth(int AMonth){SetBy(FYear,AMonth,FDay,FTime);};
void __fastcall SetDay(int ADay){SetBy(FYear,FMonth,ADay,FTime);};
void __fastcall SetTime(int ATime){SetBy(FYear,FMonth,FDay,ATime);};
//-----------分別修改農(nóng)歷的年月日時---------------------------------------
void __fastcall SetcYear(int AcYear){SetByc(AcYear,FcMonth,FcDay,FcTime);};
void __fastcall SetcMonth(int AcMonth){SetByc(FcYear,AcMonth,FcDay,FcTime);};
void __fastcall SetcDay(int AcDay){SetByc(FcYear,FcMonth,AcDay,FcTime);};
void __fastcall SetcTime(int AcTime){SetByc(FcYear,FcMonth,FcDay,AcTime);};
TDateTime __fastcall GetLastJie(); //取得上一個節(jié)
TDateTime __fastcall GetNextJie(); //取得下一個節(jié)
TDateTime __fastcall GetLastQi(); //取得上一個中氣
TDateTime __fastcall GetNextQi(); //取得下一個中氣
int __fastcall GetDayOfWeek(); //取得一周的第幾天
AnsiString __fastcall GetWeekString(); //返回星期幾的字符串
protected:
public:
void __fastcall SetBy(int,int,int,int); //用公歷設(shè)置
void __fastcall SetByc(int,int,int,int); //用農(nóng)歷設(shè)置
__fastcall ThsDivineCalendar(TComponent* Owner);
//屬性:年月日時
__property int Year={read=FYear,write=SetYear};
__property int Month={read=FMonth,write=SetMonth};
__property int Day={read=FDay,write=SetDay};
__property int Time={read=FTime,write=SetTime};
//屬性:農(nóng)歷年月日時
__property int cYear={read=FcYear,write=SetcYear};
__property int cMonth={read=FcMonth,write=SetcMonth};
__property int cDay={read=FcDay,write=SetcDay};
__property int cTime={read=FcTime,write=SetcTime};
//公歷農(nóng)歷日期字符串
__property AnsiString DateString={read=GetDateString};
__property AnsiString cDateString={read=GetcDateString};
//其他屬性
__property TDateTime DateTime={read=TheDate,write=SetDate};
__property TDateTime LastJie = { read=GetLastJie };
__property TDateTime NextJie = { read=GetNextJie };
__property TDateTime LastQi = { read=GetLastQi };
__property TDateTime NextQi = { read=GetNextQi };
__property int DayOfWeek = { read=GetDayOfWeek };
__property AnsiString WeekString = { read=GetWeekString };
__published:
};
//---------------------------------------------------------------------------
#endif
下面介紹轉(zhuǎn)換的具體算法。
一、公歷轉(zhuǎn)換成農(nóng)歷
1、計算出所求時間到起始年正月初一的天數(shù)。
2、從起始年份開始,減去每一月的天數(shù),一直到剩余天數(shù)沒有下一個月多為止
此時,ChineseCalendarData[]的下標(biāo)到了多少,就是減去了多少年,用起始年份加上這個下標(biāo)就可以得到農(nóng)歷年份;然后看減去了幾個月,如果本年不閏月或者閏月還在后面,就可以直接得到農(nóng)歷月份,如果在閏月月份數(shù)后面一個月,則這個月就是閏月,如果在閏月的后面,則要減去1才能得到月份數(shù);剩余的天數(shù)就是農(nóng)歷日;農(nóng)歷時用(公歷時+1)/2就可以簡單地得到了。具體的代碼如下:
//---------------------------------------------------------------------------
void __fastcall ThsDivineCalendar::e2c()
{
int total,m,n,k;
bool isEnd=false; //用以判斷是否不夠減了
total=(int)TheDate-7709; //到1921-2-8(正月初一)的天數(shù)
for(m=0;;m++)
{
/*判斷本年是否閏月,用以確定月份信息的起點
有閏月有13位(0~12),無12位(0~11)*/
k=(ChineseCalendarData[m]<0xfff)?11:12; for(n=k;n>=0;n--)
{
//如果不夠減
if(total<=29+GetBit(ChineseCalendarData[m],n))
{
isEnd=true; //設(shè)置標(biāo)志
break; //退出內(nèi)層循環(huán)
}
/*夠減,減去一個月的天數(shù)
先減去29天如果月大,則對應(yīng)的信息位為1,
又減去一天*/
total=total-29-GetBit(ChineseCalendarData[m],n);
}
if(isEnd)break; //如果不夠減,退出外層循環(huán)
}
FcYear=1921 + m; //農(nóng)歷年=起始年份+下標(biāo)
FcMonth=k-n+1; //農(nóng)歷月=本年的月份數(shù)(k+1)減去已經(jīng)減去的月份數(shù)(n)
FcDay=total; //農(nóng)歷日=剩余天數(shù)
unsigned short int t1,t2,t3,t4;
TheDate.DecodeTime(&t1,&t2,&t3,&t4);
FcTime=(t1+1)>>1; //農(nóng)歷時
if(k==12) //如果本年有閏月
{
if(FcMonth==ChineseCalendarData[m]/0x10000+1)//就是閏月
FcMonth=1-FcMonth;
if(FcMonth>ChineseCalendarData[m]/0x10000+1)//閏月后面
FcMonth--;
}
}
//----------------------------------------------------------------------------
二、農(nóng)歷到公歷的轉(zhuǎn)換
這個算法比較簡單,只要計算所求時候到起始年正月初一的總天數(shù)就可以了,要計算總天數(shù),只要統(tǒng)計出本月以前的大月小月書就可以了,然后把這個值賦予TdateTime類型的TheDate就可以用TdateTime的成員函數(shù)DecodeDate得到公歷的年月日了。具體代碼如下:
//----------------------------------------------------------------------------
void __fastcall ThsDivineCalendar::c2e()
{
int i,k,m,p,y[]={0,0};
//y[0]:小月、y[1]:大月
//本年以前的大月小月數(shù)
for(i=0;i<FcYear-1921;i++)
{
k=(ChineseCalendarData[i]<0xfff)?11:12;
for(m=0;m<=k;m++)
y[GetBit(ChineseCalendarData[i],m)]++;
}
//統(tǒng)計本年本月以前的大月小月數(shù)
//本年不是閏年
if(ChineseCalendarData[i]<0xfff)
for(m=13-FcMonth;m<=11;m++)
y[GetBit(ChineseCalendarData[i],m)]++;
else // 是閏年
{
k=ChineseCalendarData[i]/0x10000;
//根據(jù)在閏月前后決定統(tǒng)計的起始位置
p=(FcMonth>k)?13-FcMonth:14-FcMonth;
if(k+FcMonth==0)p=13+FcMonth; //本月就是閏月
for(m=p;m<=12;m++)
y[GetBit(ChineseCalendarData[i],m)]++;
}
//7709就是1920年臘月三十
TheDate=7709+y[0]*29+y[1]*30+FcDay+FcTime*2.0/24;
}
//----------------------------------------------------------------------------
void __fastcall ThsDivineCalendar::GetYMD()
{
unsigned short y,m,d,t;
TheDate.DecodeDate(&y,&m,&d);
FYear=y;
FMonth=m;
FDay=d;
TheDate.DecodeTime(&t,&y,&m,&d);
FTime=t;
}
//----------------------------------------------------------------------------
以上就是公歷農(nóng)歷相互轉(zhuǎn)換的算法和VCL代碼,只要理解了這些算法,你就不難寫出其他的程序,我就寫了JavaScript和PHP的代碼,其實PHP、JS的代碼簡單得多,只需要有公歷到農(nóng)歷的轉(zhuǎn)換就可以了。如果你想要這些代碼和完整的VCL源代碼,你就說嘛,你不說我怎么知道你想要呢?雖然你很有誠意地看著我……,哈哈,我又中《大話西游》的毒了:=)。其實,這些代碼你可以在《電腦愛好者》網(wǎng)站或者我的主頁(http://bcbtop.126.com)的主頁下載。
http://hugsnow.myetang.com/source/hugsnow1.zip