http://kowala21.blogspot.com
在上一篇,我們討論了如何給定一個日期點而求出對應的陰曆日期,透過物件 Lunar 來做轉換,這篇是準備說明如何使用 Lunar 物件來產生月曆。
首先,我們先準備一個參數物件 calElement 來存放由 Lunar 物件所產生之結果,也可以把它視為格子物件,一個月有31天,一天就用一格代表,接著是做個月曆物件 calendar ,準備好了就可以開始產生月曆了。
產生月曆步驟
先給定 y 年 m 月,取得該月起點,計算該月終點,再使用迴圈逐日產生格子物件陣列 calElement,把 Lunar 物件所產生的陰曆日期填入格子物件 calElement,這樣月曆架構就完成了,最後再把一些修飾資料補齊,來完整它。
格子物件示意圖
程式碼如下:
//月曆物件 y 年 m 月 , m = 0-11
function calendar(y,m) {
var sDObj, lDObj, lY, lM, lD=1, lL, lX=0, tmp1, tmp2;
var lDPOS = new Array(3);
var n = 0;
var firstLM = 0;
sDObj = new Date(y,m,1); //該月起點
this.length = solarDays(y,m); //該月終點
this.firstWeek = sDObj.getDay(); //起點日星期幾
for(var i=0;i<this.length;i++) {
if(lD>lX) {
sDObj = new Date(y,m,i+1); //逐日遞增
lDObj = new Lunar(sDObj); //農曆
lY = lDObj.year; //農曆年
lM = lDObj.month; //農曆月
lD = lDObj.day; //農曆日
lL = lDObj.isLeap; //農曆是否閏月
lX = lL? leapDays(lY): monthDays(lY,lM); //農曆當月最後一天
if(n==0) firstLM = lM;
lDPOS[n++] = i-lD+1;
}
this[i] = new calElement(
y, m+1, i+1, nStr1[(i+this.firstWeek)%7], lY, lM, lD++, lL,
cyclical(lDObj.yearCyl), //年干支
cyclical(lDObj.monCyl), //月干支
cyclical(lDObj.dayCyl++) //日干支
);
...
}
...
}
上面有用到的函式說明
//該月終點 = y 年 m 月的天數
function solarDays(y,m) {
if(m==1) return(((y%4 == 0) && (y%100 != 0) || (y%400 == 0))?29:28); //2月
else return(solarMonth[m]); //其他月
}
//傳入次序,傳回干支,0=甲子
function cyclical(num) {
return(Gan[num%10]+Zhi[num%12])
}
至此,月曆大致完成,接著是補足修飾資料,節氣、節日、顯示顏色等。節日又分為三種,陽曆節日,陰曆節日,週序節日等。
修飾資料
節日以字串方式存放於陣列
//國曆節日 *表示放假日
var sFtv = new Array(
"0101*元旦",
"0214 情人節",
"0308 婦女節",
...}
//農曆節日 *表示放假
var lFtv = new Array(
"0100*除夕",
"0101*春節,彌勒佛聖誕",
"0106 定光佛聖誕",
...}
//週序節日
var wFtv = new Array(
"0520 母親節",
"0730 被奴役國家週");
取出方法
原作是採用正則表示式,也有稱呼正規表示式,原文是 Regular Expression、regex 或regexp,縮寫為 RE[1]。
若以取出國曆節日來說,我們可以在條件式看到正則表示式的寫法
if(sFtv[i].match(/^(\d{2})(\d{2})([\s\*])(.+)$/))
這代表甚麼意思呢,其實是依規則取出所需的字串
sFtv[i].match(...) //指定國曆節日字串,然後比對字串
正則表示式
/^(\d{2})(\d{2})([\s\*])(.+)$/
/ 逸脫符號
^ 字串開始
$ 字串結束
\d 數字
{2} 重複2次
[] 範圍運算子
\s 空白
\. 任意字
\* 任意次
+ 代表 1-任意次
正則表示式說明
/^....................$/ 代表字串開始與結束
在 /^ $/ 之間有4組(...),代表把字串分為四個,放在記憶體中。
/^ (...)(...)(...)(...) $/
第一、二個是2位數
(\d{2})(\d{2})
第三個是"空白"或是"*"
([\s\*])
第四個是任意次字串
(.+)
來看看國曆節日字串
sFtv[0]="0101*元旦"
經此規則 /^(\d{2})(\d{2})([\s\*])(.+)$/ 過濾後可以得到四個字串,放在記憶體中,
以 RegExp.$1, RegExp.$2, RegExp.$3, RegExp.$4 取出變數
RegExp.$1= "01"
RegExp.$2= "01"
RegExp.$3= "*"
RegExp.$4= "元旦"
這樣我們就可以依條件判定,繼續補足修飾資料,節日完整函式如下:
//國曆節日
for(i in sFtv)
if(sFtv[i].match(/^(\d{2})(\d{2})([\s\*])(.+)$/))
if(Number(RegExp.$1)==(m+1)) {
this[Number(RegExp.$2)-1].solarFestival += RegExp.$4 + ' '
if(RegExp.$3=='*') this[Number(RegExp.$2)-1].color = 'red'
}
//週序節日
for(i in wFtv)
if(wFtv[i].match(/^(\d{2})(\d)(\d)([\s\*])(.+)$/))
if(Number(RegExp.$1)==(m+1)) {
tmp1=Number(RegExp.$2)
tmp2=Number(RegExp.$3)
this[((this.firstWeek>tmp2)?7:0) + 7*(tmp1-1) + tmp2 - this.firstWeek].solarFestival += RegExp.$5 + ' '
}
//農曆節日
for(i in lFtv)
if(lFtv[i].match(/^(\d{2})(.{2})([\s\*])(.+)$/)) {
tmp1=Number(RegExp.$1)-firstLM
if(tmp1==-11) tmp1=1
if(tmp1 >=0 && tmp1<n) {
tmp2 = lDPOS[tmp1] + Number(RegExp.$2) -1
if( tmp2 >= 0 && tmp2<this.length) {
this[tmp2].lunarFestival += RegExp.$4 + ' '
if(RegExp.$3=='*') this[tmp2].color = 'red'
}
}
}
寫到這裡,本篇已經是尾聲了,雖然本篇討論對象是用 javascript 寫的,但完全可以用 C++ 去寫一遍,並且修改幅度不大,這樣就有了自己的農曆產生器了。
參考原始碼:
http://dolphin.cc.ncu.edu.tw/lunar.html
http://38time.artjoey.com/calendar_big.htm
http://www.time.ac.cn/nongli.htm
參考資料
1. 正規表示式 http://zh.wikipedia.org/wiki/正規表示式
農曆程式概念探討(1)
http://kowala21.blogspot.tw/2012/08/1.html
農曆程式概念探討(2)
http://kowala21.blogspot.tw/2012/08/2.html
農曆程式概念探討(3)
http://kowala21.blogspot.tw/2012/09/3.html
農曆程式概念探討(4)
http://kowala21.blogspot.tw/2012/09/4.html
農曆程式概念探討(5)
http://kowala21.blogspot.tw/2012/09/5.html
農曆程式概念探討(6)
讚!
回覆刪除