kowala's home

kowala's home
這裡是我的學習筆記,陸續增加中。
http://kowala21.blogspot.com

2012-11-09

用批次檔來刪除檔案目錄

在寫程式時,常常需要反覆的測試,有時候就要常常建檔、建目錄等等,這時候批次檔就很好用了,下面舉一個例子來說明怎樣用批次檔來處理檔案目錄。

一開始的檔案目錄結構
 

接著是產生很多測試目錄及檔案後
 

 我們要刪掉上面紅線框起來的檔案或目錄,但有些目錄不是每次都會產生,所以要先檢查是否存在,若存在再刪。

下面是批次檔的寫法

BACK.bat
 ------------------------------------------------------
@echo off
echo 還原最初狀態... > msg.txt
echo 刪除 V-01 - V-04  >> msg.txt
del V* >> msg.txt
echo 刪除 MM14 TBAT  >> msg.txt
del MM14 TBAT >> msg.txt
echo 刪除 HISTORY >> msg.txt
del HISTORY >> msg.txt

:NNN
if not exist NNN goto MMM
echo 刪除 \NNN\*  >> msg.txt
cd NNN
del *
cd..
rd NNN

:MMM
if not exist M01 goto WWW
echo 刪除 \M01\* - \M04\*  >> msg.txt
cd M01
del *
cd..
rd M01

cd M02
del *
cd..
rd M02

cd M03
del *
cd..
rd M03

cd M04
del *
cd..
rd M04

:WWW
if not exist W01 goto VVV
echo 刪除 \W01\* - \W04\*  >> msg.txt
cd W01
del *
cd..
rd W01

cd W02
del *
cd..
rd W02

cd W03
del *
cd..
rd W03

cd W04
del *
cd..
rd W04

:VVV
if not exist V01 goto quit
echo 刪除 \V01\* - \V04\*  >> msg.txt
cd V01
del *
cd..
rd V01

cd V02
del *
cd..
rd V02

cd V03
del *
cd..
rd V03

cd V04
del *
cd..
rd V04

:quit
echo 還原初始值,copy test\101\* . >> msg.txt
del GO10*
copy test\101\* .

notepad msg.txt
del msg.*


執行結果


 順便看看訊息


清乾淨了,再把初始資料 copy 進來,有批次檔幫忙,工作簡單多了。



2012-10-12

農曆程式概念探討(6)

kowala's home
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)



2012-09-21

釣魚台是中國人的,支持收復釣魚台,趕走日本人。。。

支持收復釣魚台,趕走日本人。。。
歷史教訓猶在眼前,南京大屠殺國仇家恨。
地不分東南西北,人不分男女老少,聯手抗日。
日本必須付出代價!

阿~ 好像太激動了!這樣會被扣上 "忿青" 帽子。。。

"忿青" > "糞青" 一字之隔而已。。。

算了,我就是不喜歡日本!


看不見的戰爭 - 值得一看
http://www.tudou.com/programs/view/07aItsudrAQ/

RedDragonCastle's channel - 釣魚台新聞頻道
釣魚台相關新聞

2012-10-08 美國會報告:美從未承認日本擁有釣魚台主權

這新聞如屬實,或許可以避免一場戰爭。

2012-09-26 谴责日本"购岛"破坏战后秩序 领导人展现钓鱼岛新思路



2012-09-25 保釣挺進釣魚台3浬 台日水柱對峙


马英九:最近发现清朝古籍有记载钓鱼岛情况 (SD)



2012-09-22 台湾拖救船从钓鱼岛返航 说明出海保钓理由
馬英九硬起來。。。



 海峡两岸2012-09-20 日本首相承认错估中方反应



海峡两岸2012-09-20 日媒:日本自卫队战舰向钓鱼岛移动


正点新闻2012-09-20 石原慎太郎再放厥词警告要"砍"中国海监船


美国否认在钓鱼岛问题上为日本"撑腰"


 

鄰國動向

 凤凰早班车 2012-09-26 美澳韩日联合军演风波不断 韩国拒日舰入港

video


军事报道2012-09-20 俄罗斯"高加索 — 2012"战略军演举行



菲律宾对华外交起内讧 "口水仗"打出惊人内幕


印度试射"烈火4"型弹道导弹 

想知道艦艇如何防禦與追蹤--以色列拉斐爾武器發展局開發的艦載紅外搜索與跟踪系統
 RAFAEL's Sea Spotter System


2012-09-20

農曆程式概念探討(5)

kowala's home
http://kowala21.blogspot.com

前一篇 討論了編表規則,本篇就說明如何把日期資料取出來,原作對農曆主演算法是採用查表,計算過程是先設定一基準日做為第一天,然後計算某日距離基準日之累積日數,再對干支定位。這樣農曆基本日期就完成了,再來是潤飾部分,節氣、生肖、節慶、初十廿卅等等,最後定週休顏色等,這個已經是到了 HTML 語法,屬於介面顯示部分了。

先來看看從表格取出資料的函數

1.取出某年的總日數 lYearDays(y)

function lYearDays(y) {
   var i, sum = 348;
   for(i=0x8000; i>0x8; i>>=1)
      sum += (lunarInfo[y-1900] & i)? 1: 0;  
   return(sum+leapDays(y));
}


先把一年日數以小月29日放進去,再比對大月有幾個,加回去,最後是檢查有無潤月,潤大還是閏小,再加進去。

sum = 348 = 12*29


編表規則中,我們知道最右邊四位元是潤月,我們只需要用到 5-16 位元,代表月份,用紅色來表示,
下面的迴圈中,需要說明的是位元移位的概念,我們用二進位來看 i 就很清楚了。

for(i=0x8000; i>0x8; i>>=1)

i = 0x8000 = 1000 0000 0000 0000    i >>= 1 右旋一次
i = 0x4000 = 0100 0000 0000 0000    i >>= 1 右旋一次
i = 0x2000 = 0010 0000 0000 0000    i >>= 1 右旋一次
i = 0x1000 = 0001 0000 0000 0000    i >>= 1 右旋一次
i = 0x0800 = 0000 1000 0000 0000    i >>= 1 右旋一次
i = 0x0400 = 0000 0100 0000 0000    i >>= 1 右旋一次
i = 0x0200 = 0000 0010 0000 0000    i >>= 1 右旋一次
i = 0x0100 = 0000 0001 0000 0000    i >>= 1 右旋一次
i = 0x0080 = 0000 0000 1000 0000    i >>= 1 右旋一次
i = 0x0040 = 0000 0000 0100 0000    i >>= 1 右旋一次
i = 0x0020 = 0000 0000 0010 0000    i >>= 1 右旋一次
i = 0x0010 = 0000 0000 0001 0000    i >>= 1 右旋一次
i = 0x8; 停止

這樣就可以依序取出月份位元,在利用 AND i 把不要的位元消去,AND 運算符號為 &


lunarInfo[y-1900] & i

如果運算後值為1,代表是大月 30 天,就把 sum +1 否則 + 0

以1901年為例 lunarInfo[1] = 0x04ae0,代表月份的位元為

0x4ae = 0100 1010 1110
月份   = 1234 5678 9abc

若要取出 5 月,則 i = 0x0800 = 0000 1000 0000

    0100 1010 1110
& 0000 1000 0000
=          1

sum +1

最後是檢查該年有無潤月,若有則加潤月天數,使用函式 leapDays(y) 來求算。


2.檢查該年有無潤月

function leapMonth(y) {
   return(lunarInfo[y-1900] & 0xf)
}

這是直接取最右邊4個位元來比對,0表示無潤月,其他數值代表潤幾月。

lunarInfo[y-1900] = 0x04ae0


0x04ae0 = 0000 0100 1010 1110 0000
&       0xf = 0000 0000 0000 0000 1111
=                                                          0

3.傳回潤月天數

function leapDays(y) {
   if(leapMonth(y))  return((lunarInfo[y-1900] & 0x10000)? 30: 29)
   else return(0)
}

假設1  lunarInfo[y-1900] = 0x14b63
有潤月 leapMonth(y) =3

    0x14b63 = 0001 0100 1011 0110 0011
& 0x10000 = 0001 0000 0000 0000 0000
=                          1 結果會傳回 30

假設2  lunarInfo[y-1900] = 0x0b557
有潤月 leapMonth(y) =7

    0x0b557 = 0000 1011 0101 0101 0111
& 0x10000 = 0001 0000 0000 0000 0000
=                          0 結果會傳回 29

條件式 (bool)?true:false;
這等於

if (bool)
  true;
else
  false;

農曆主演算法

上述說明了如何查表取出相應的數值,接著說明如何求出某一日轉農曆日期的物件 Lunar(objDate)。

function Lunar(objDate){...}

objDate 是待查的日期物件,資料型態為 Date() 物件,我們可以賦予它一個值。

例如:2012-10-09

objDate = new Date(2012,9,9); //月份是從 0-11, 日是 1-31

這樣我們就指定了某一天給 objDate 物件了。

而在轉農曆日期的物件 Lunar() 中,使用一些屬性,隨後會透過計算而把值填進去下列屬性。

this.year    //陰曆年(陽曆年)[1]
this.month    //陰曆月(陽曆月)[1]
this.day    //陰曆日(陽曆日)[1]

this.yearCyl    //陰曆年
this.monCyl    //陰曆月
this.dayCyl    //陰曆日

this.isLeap    //潤年

該物件沒有方法,只是負責把指定了某一天轉成相應數值,填入上列屬性中。


轉換過程說明

Lunar() 物件被建立

1. 取基準日 1900-1-31 (此為庚子年農曆正月初一)

2. 將指定日減去基準日再轉豪秒為天數,存放於變數 offset,此為某日距離基準日的日數。

一日 = 24*60*60*1000 = 86400000 ms

offset = (objDate - baseDate) / 86400000;

3. 將此日數 offset 做干支定位

由於基準日是甲辰日,在干支表是排在第 41,所以 offset 必須 +40,這樣就正確定位了。

4. 將月數做干支定位

我們先將 offset 對基準月做一校正,基準月是丁丑月,在干支表是排在第 14,所以先把它 +14 完成初步定位。

干支表

甲子     乙丑     丙寅     丁卯     戊辰     己巳     庚午     辛未     壬申     癸酉
甲戌     乙亥     丙子     丁丑     戊寅     己卯     庚辰     辛巳     壬午     癸未
甲申     乙酉     丙戌     丁亥     戊子     己丑     庚寅     辛卯     壬辰     癸巳
甲午     乙未     丙申     丁酉     戊戌     己亥     庚子     辛丑     壬寅     癸卯
甲辰     乙巳     丙午     丁未     戊申     己酉     庚戌     辛亥     壬子     癸丑
甲寅     乙卯     丙辰     丁巳     戊午     己未     庚申     辛酉     壬戌     癸亥

月數計算比較複雜些,因為陰曆大小月份並不固定,必須查表逐一計算,從基準日到指定日經過了幾個月,再取出對應的干支,潤月則重複該月干支一次。

一開始先由某年總日數開始倒扣 offset,直到 offset 為負值,代表最後一年,不足一年,扣過頭須加回。
接著計算該年某月總日數,開始倒扣 offset,直到 offset 為負值,代表最後一月,不足一月,扣過頭須加回。
在計算某月時,還須考慮潤月,潤月須扣除 offset日數,但不可以累計月數,因為潤月重複該月干支一次。
最後剩下的offset就是日數了,如此完成了陰曆轉換。

這段說明請參考程式碼觀看,便知道我在說甚麼了。

//算出農曆, 傳入日期物件
function Lunar(objDate){  
   var i, leap=0, temp=0;  
   var baseDate = new Date(1900,0,31);//基準日 = 農曆第一天  
   var offset = (objDate - baseDate)/86400000; //一天 = 24*60*60*1000=86400000ms
  
   this.dayCyl = offset + 40;//日干支定位
   this.monCyl = 14;//月干支定位

   //--------------------------------------
   for(i=1900; i<2060 && offset>0; i++) {
      temp = lYearDays(i);//取i年的總天數
      offset -= temp;
      this.monCyl += 12;
   }
   //最後一年
   if(offset<0) {
      offset += temp;
      i--;  //西元 i 年
      this.monCyl -= 12;
   }
   this.year = i
   this.yearCyl = i-1864;//甲子年(公元1864年,1924年,1984年)

   //--------------------------------------
   leap = leapMonth(i);//潤哪個月
   this.isLeap = false;
   for(i=1; i<13 && offset>0; i++) {
      //閏月
      if(leap>0 && i==(leap+1) && this.isLeap==false)
         { --i; this.isLeap = true; temp = leapDays(this.year); }
      else
         { temp = monthDays(this.year, i); }
      //閏月已處裡
      if(this.isLeap==true && i==(leap+1)) this.isLeap = false;

      offset -= temp
      if(this.isLeap == false) this.monCyl++;
   }
   //最後一月
   if(offset==0 && leap>0 && i==leap+1)
      if(this.isLeap)
         { this.isLeap = false; }
      else
         { this.isLeap = true; --i; --this.monCyl;}
   if(offset<0){ offset += temp; --i; --this.monCyl; }
   this.month = i;
   this.day = offset + 1;
}

待續...

[1] .依陳先生指出錯誤改正為農曆年月日

2012-09-09

農曆程式概念探討(4)

kowala's home
http://kowala21.blogspot.com

農曆規則與置閏

開始進入農曆主演算法,原作是採用查表法,對于如何排定表,如何取出對應的日期,後面均會做一說明,但開始前,先對農曆規則做一簡要說明。

一個月,就是月亮繞地球一圈,就是月相由新月(朔)循環到新月(朔)所經過的時間(圖一),平均為 29.530588天(29天12小時44分2.8秒)[1],月球繞地球轉動,又被地球帶著繞日公轉,所以它的軌跡是呈現螺旋前進(圖二),這使得它的計算比地球對日要來得複雜些。

圖一,月相循環


新月(朔)就是月球在地球與太陽一直線上,定義為一個月的開始日。

圖二,月球螺旋前進軌跡模擬計算

十九年置七閏

由月球繞地一周為一個月來推算月球繞日會發現,它的實際月數與陽曆的月數不相符,這必須調整,就好像陽曆 4 年一潤,補一天一樣,但如何去補,這就是十九年置七閏的由來。

月亮繞地球一圈平均為 29.530588 天,一年通常有 12 朔望月 = 354.367056 日,這與回歸年 365.2421990741 日,少了 10.8751430741 日,取其差最少為19年+7閏月。

19 回歸年 365.2421990741*19 = 6939.6017824079 日
十九年置七閏 = (19*12+7)*29.530588 = 6939.68818 日

兩者的天數幾乎相等,因此每隔 19 個回歸年,陽曆與農曆再次同步。


置閏規則

觀察朔日定為每月初一,並應該與節氣配合,合理分配24節氣,這樣才是能夠實用的曆法,一年有24節氣,然而12 朔望月僅僅只有354.367056 日,與回歸年 365.2421990741 日短少,使得24節氣無法平均分配,如何合理分配,何時補足差的日數,這便是置閏規則。

圖三,月相變化圖
 
通常以立春及雨水定在一月裡,跟著以驚蟄及春分定二月,若是閏年就加多一個閏月,但一年 365 天裡只能出現十二個中氣,所以就把沒有中氣的那個月份作為閏月,例如閏二月、閏三月 … 閏十一月,但不會出現閏十二月和閏一月,因為這時地球位於近日點附近,速度較快掃過角度大,故此十二月和一月必有中氣和節氣出現,其他月份較有機會出現無中氣[2]。


編表規則

由於大小月出現並無一定規則,為了準確及簡化計算,我們必須找尋一個標準來制定對照表,這裡以香港天文台公佈的公曆與農曆日期對照表[3]為例來說明編表規則。


由上表可以得知1901年沒有閏月,從1月到12月的天數依次為:
29、30、29、29、30、29、30、29、30、30、30、29。

令大月為1,小月為0,則可以表示為

0100 1010 1110  (不包含閏月)

以16進位表示則為

0x4ae

我們可以用小算盤來轉16進位碼


再把閏月的月份擺在右邊,無閏月設為0

0x4ae0

再以閏大月為1,閏小月為0,擺在左邊

0x04ae0

這樣就完成了編表,以下再舉幾個範例


1982年的數據是:0x0a974
0000 1010 1010 0111 0100
表示1982年的4月為閏月,即有第二個4月,且是閏小月。
從1月到13月的天數依次為:
30、29、30、29、29(閏小)、30、29、30、29、29、30、30、30。

2017年的數據是:0x15176
0001 0101 0001 0111 0110
表示2017年的6月為閏月,即有第二個6月,且是閏大月。
從1月到13月的天數依次為:
29、30、29、30、29、29、30(閏大)、29、30、29、30、30、30。

從1900年到2061年的編表如下

var lunarInfo=new Array(
0x04bd8,0x04ae0,0x0a570,0x054d5,0x0d260,0x0d950,0x16554,0x056a0,0x09ad0,0x055d2,
0x04ae0,0x0a5b6,0x0a4d0,0x0d250,0x1d255,0x0b540,0x0d6a0,0x0ada2,0x095b0,0x14977,
0x04970,0x0a4b0,0x0b4b5,0x06a50,0x06d40,0x1ab54,0x02b60,0x09570,0x052f2,0x04970,
0x06566,0x0d4a0,0x0ea50,0x06e95,0x05ad0,0x02b60,0x186e3,0x092e0,0x1c8d7,0x0c950,
0x0d4a0,0x1d8a6,0x0b550,0x056a0,0x1a5b4,0x025d0,0x092d0,0x0d2b2,0x0a950,0x0b557,
0x06ca0,0x0b550,0x15355,0x04da0,0x0a5d0,0x14573,0x052d0,0x0a9a8,0x0e950,0x06aa0,
0x0aea6,0x0ab50,0x04b60,0x0aae4,0x0a570,0x05260,0x0f263,0x0d950,0x05b57,0x056a0,
0x096d0,0x04dd5,0x04ad0,0x0a4d0,0x0d4d4,0x0d250,0x0d558,0x0b540,0x0b5a0,0x195a6,
0x095b0,0x049b0,0x0a974,0x0a4b0,0x0b27a,0x06a50,0x06d40,0x0af46,0x0ab60,0x09570,
0x04af5,0x04970,0x064b0,0x074a3,0x0ea50,0x06b58,0x055c0,0x0ab60,0x096d5,0x092e0,
0x0c960,0x0d954,0x0d4a0,0x0da50,0x07552,0x056a0,0x0abb7,0x025d0,0x092d0,0x0cab5,
0x0a950,0x0b4a0,0x0baa4,0x0ad50,0x055d9,0x04ba0,0x0a5b0,0x15176,0x052b0,0x0a930,
0x07954,0x06aa0,0x0ad50,0x05b52,0x04b60,0x0a6e6,0x0a4e0,0x0d260,0x0ea65,0x0d530,
0x05aa0,0x076a3,0x096d0,0x04bd7,0x04ad0,0x0a4d0,0x1d0b6,0x0d250,0x0d520,0x0dd45,
0x0b5a0,0x056d0,0x055b2,0x049b0,0x0a577,0x0a4b0,0x0aa50,0x1b255,0x06d20,0x0ada0,
0x14b63);

待續...

參考資料
1.  维基百科干支  http://zh.wikipedia.org/wiki/朔望月
2.  廿四節氣和閏月 http://forum.hkas.org.hk/viewthread.php?tid=4430
3.  公曆與農曆日期對照表  http://www.hko.gov.hk/gts/time/conversionc.htm

圖一,http://www.cwb.gov.tw/V7/knowledge/planning/sun_moon.htm
圖二,http://blog.sciencenet.cn/blog-362400-278987.html
圖三,http://blog.udn.com/chyyang01/5732435

2012-09-02

農曆程式概念探討(3)

kowala's home
http://kowala21.blogspot.com

開始演算法部分,從哪裡開始呢?先從能夠獨立的模塊開始好了。

二十四節氣

原作者基本上是使用查表法,依照陽曆的節氣順序來定義表格,從陽曆1/1開始,首先遇到的節氣不是"立春",而是"小寒",是在1/6日,幾乎每年都固定在這一天,作者分別計算各節氣距離這基準點的分鐘數而製成下表。

var sTermInfo=new Array(
0,21208,42467,63836,85337,107014,128867,150921,173149,195551,218072,240693,263343,285989,308563,331033,353350,375494,397447,419210,440795,462224,483532,504758);

前面的說明可知,地球繞日的軌道是橢圓,所以並非等速前進,可以參考橢圓軌道的計算方法,或是直接查表,這程式開始是西元1900年,我們把它的數據套進Excel中試算,可以得到上表相同的結果,如圖二所示。

圖一、 二十四節氣時刻


圖二、 二十四節氣時刻試算[3]

先計算出從1/1到1/6日"小寒"經過的秒數525837,令它為基準 0,爾後各節氣累計秒數皆須減525837,再來把它轉成分鐘,就完成了上表了。

使用上則是計算某日到陽曆1/6日"小寒"所經過的分鐘數,然後查表就可以判定哪個節氣,或是某個節氣落在哪一日了。

原程式可以求得 y 年的第 n 個節氣的日期

function sTerm(y,n) {
   var offDate = new Date(
       ( 31556925974*(y-1900)*1000 + sTermInfo[n]*60*1000  ) + Date.UTC(1900,0,6,2,5)
   );
   return(offDate.getUTCDate());
}

說明
31556925974 = 365.2421990741*24*60*60 = 一個回歸年秒數
31556925974*(y-1900)*1000 = y 年距離基準年(1900)的毫秒數
sTermInfo[n]*60*1000 = 第 n 個節氣距離"小寒"的毫秒數
Date.UTC(1900,0,6,2,5)  = 基準年
所以
offDate = (y年毫秒數+第n個節氣毫秒數+ 基準年小寒毫秒數)
轉回日期格式
offDate.getUTCDate()

生肖定位

原作者是取陽曆年來對應生肖,其實應該要取陰曆年才對,不過沒關係,我們就假裝不知道,繼續討論下去。
十二生肖就是12年一循環,如下陣列 Animals[12],所以只要除以12取餘數就能定位了,但是 1900 年是鼠年,應該對應餘數 0,但 1900 不是剛好整除 12,所以要調整為整除,就是1900-4=1896,這樣就恰好整除,對應"鼠"年了。

西元 1900-1-31 光緒26 年 - 農曆歲次-庚子年元月初一【鼠】

先宣告一個十二生肖陣列

var Animals=new Array("鼠","牛","虎","兔","龍","蛇","馬","羊","猴","雞","狗","豬");



var sy=1900;
Animals[(sy-4)%12];

這樣就會取到"鼠"了。


干支定位

中國早在商朝時代,就已經開始使用干支記日了[1],那什麼是干支的規則呢,干支就是天干地支,天干有十地支十二,分別列出如下。

天干 = 甲,乙,丙,丁,戊,己,庚,辛,壬,癸
地支 = 子,丑,寅,卯,辰,巳,午,未,申,酉,戌,亥

配對規則就是天干配地支,依序循環。

第一個天干配第一個地支,就是甲子。
第二個天干配第二個地支,就是乙丑。
第三個天干配第三個地支,就是丙寅。
...
到了第十個就是
第十個天干配第十個地支,就是癸酉。

第十一個呢,就變成天干又重頭開始了
第十一個天干配第十一個地支,就是甲戌。
第十二個天干配第十二個地支,就是乙亥。

所以第十三個換地支重頭開始。
第十三個天干配第十三個地支,就是丙子。
如此循環不已,到了下次甲子,剛好60年一循環。

干支60年各年份名稱
甲子     乙丑     丙寅     丁卯     戊辰     己巳     庚午     辛未     壬申     癸酉
甲戌     乙亥     丙子     丁丑     戊寅     己卯     庚辰     辛巳     壬午     癸未
甲申     乙酉     丙戌     丁亥     戊子     己丑     庚寅     辛卯     壬辰     癸巳
甲午     乙未     丙申     丁酉     戊戌     己亥     庚子     辛丑     壬寅     癸卯
甲辰     乙巳     丙午     丁未     戊申     己酉     庚戌     辛亥     壬子     癸丑
甲寅     乙卯     丙辰     丁巳     戊午     己未     庚申     辛酉     壬戌     癸亥

知道60年一甲子後,該是把今年定位到干支表上,概念是這樣的,必須先找個基準點,然後求出距離幾年,再查表取出干支。
以甲子年為基準年來算,我們很容易可以查到  甲子年有 公元1864年,1924年,1984年。
 
方法1.取1864年為基準,那今年2012年,距離就是

 2012-1864=148年 +1(種樹問題要加1,別跟我說沒種過樹 = =)
 149=60+60+29 => 第 29 個序列  => 壬辰

方法2.干支個別取法,因為陣列索引由 0 起算,所以不用+1
           
天干取 148 % 10 =  8 => 壬
地支取 148 % 12 =  4 => 辰

原作程式寫法就是這樣子(程式寫的真好 ^^)

var Gan=new Array("甲","乙","丙","丁","戊","己","庚","辛","壬","癸");
var Zhi=new Array("子","丑","寅","卯","辰","巳","午","未","申","酉","戍","亥");

function cyclical(num) {
   return(Gan[num%10]+Zhi[num%12]);
}


var year=2012;
cyclical(year-1864);

方法3.或是取公倍數定位法
已知 1924 年為甲子年,該年餘數必須調整為 0,
即 (1924-x)%60 = 0,可得 x 最小為 4

var year=2012;
cyclical(year-4);

這樣也可以取得正確的干支。
上述三種方法都可以正確定位,任選一種使用即可。


民國定位

今年西元 2012 等於民國 101年,兩者差為 2012-101=1911年,所以互換的常數就是 1911。

西元 - 1911 = 民國
民國 + 1911= 西元

我們可以寫個轉換式如下

function getMG(year) {
   return('民國 '+(year-1911)+' 年');
}

getMG(2012);  //取得民國年


共和國定位

共和國是大陸地區使用的記年,全名是中華人民共和國,這裡不上歷史課,簡單說明就好,要深入了解的,自己去查,民國全名是中華民國的簡稱,是國父孫中山先生所創立,後經過了國共之戰,民國38年後,分裂為兩個地區,大陸地區由毛澤東領導,以共和國紀年,台灣地區由蔣中正領導沿用中華民國記年。

民國38年大陸地區為共和國元年

民國38年 + 1911 = 1949 = 共和國元年

所以

西元 - 1949 = 共和國
共和國 + 1949= 西元

//西元 to 共和國
function getGB(year) {
   return('共和國 '+(year-1949)+' 年');
}

若要互換

民國 - 38 = 共和國
共和國 + 38 = 民國

//民國 to 共和國
function getB2G(year) {
   return('共和國 '+(year-38)+' 年');
}

//共和國 to 民國
function getG2B(year) {
   return('民國 '+(year+38)+' 年');
}


佛曆年定位

西元1950年,首屆「世界佛教徒友誼會」在錫蘭首都可倫坡舉辦。會中議決:佛陀誕生於西元前623年,成道於西元前588年,去世於西元前543年。1954年,該年會於緬甸仰光舉行,會中再決議佛教國家以「佛曆」紀元,並以釋迦牟尼涅槃年推算,西元1954年為佛曆2497年。[2]

所以佛曆與西元互換的常數就是 2497-1954 = 543

西元 + 543 = 佛曆
佛曆  - 543 = 西元

轉換式如下

function getBD(year) {
   return('佛曆 '+(year+543)+' 年'); 
}

getBD(2012); //取得佛曆年


待續...

參考資料
1.  维基百科干支  http://zh.wikipedia.org/wiki/干支
2.  维基百科佛曆年 http://zh.wikipedia.org/zh-tw/佛曆
3.  1900年二十四節氣表.xls https://docs.google.com/open?id=0B9_qAWWvUIuUSFh1V3lqcnhLdVU

2012-08-28

農曆程式概念探討(2)

kowala's home
http://kowala21.blogspot.com

前一篇簡單介紹了農曆的一些名詞,本篇先把框架清出來。為了方便查看原始碼,我們必須先把它作一些簡化,只留下必須的框架就好,並且把Javascript 分離出來,用一個檔案儲存它。


 HTML原始碼 (calendar_test.htm)
<HTML>
<HEAD>
<TITLE>Test</TITLE>
<META http-equiv=Content-Type content="text/html; charset=UTF-8">
<script language="JavaScript" src="calendar_test.js"></script>
</HEAD>

<BODY onload=initial() >
<HR align=center width=600 color=black>
<CENTER><FORM name=CLD >
<TABLE><TBODY>
<TR><BR>
  <TD align=middle>   

    <FONT  color=#660080 size=2>時鐘: </FONT>
    <FONT id=Clock color=#660080 size=2 align="center"></FONT>

    <DIV style="Z-INDEX: -1; POSITION: absolute; TOP: 55px">
      <FONT id = YMBG style="FONT-SIZE: 100pt; COLOR: #f0f0f0;
       FONT-FAMILY: 'Arial Black'">&nbsp;2012<BR>&nbsp;SEP</FONT>
    </DIV>

    <TABLE border=0><TBODY>     
     <TR>
        <TD bgColor=#3065bc colSpan=7>

          <FONT style="FONT-SIZE: 9pt" color=#ffffff size=2>&nbsp;西曆&nbsp;
          <SELECT style="FONT-SIZE: 9pt" onchange = changeCld() name= SY >
          <SCRIPT language=JavaScript><!--
            for(i=1900;i<2061;i++) document.write('<option>'+i) ;
          //--></SCRIPT>
          </SELECT>&nbsp;年&nbsp;
          <SELECT style="FONT-SIZE: 9pt" onchange = changeCld() name= SM >
          <SCRIPT language=JavaScript><!--
            for(i=1;i<13;i++) document.write('<option>'+i)
          //--></SCRIPT>
          </SELECT>&nbsp;月&nbsp;
          </FONT>

          <FONT id=GZ face=新細明體 color=#ffffff size=2></FONT><BR>
        </TD>
      </TR>
 
      <TR align=middle bgColor=#e8e8e8>
        <TD width=60>日</TD>
        <TD width=60>一</TD>
        <TD width=60>二</TD>
        <TD width=60>三</TD>
        <TD width=60>四</TD>
        <TD width=60>五</TD>
        <TD width=60>六</TD>
      </TR>

      <SCRIPT language=JavaScript><!--
        showCal();
      //--></SCRIPT>
    </TBODY></TABLE>
  </TD>
  <TD vAlign=top align=middle width=60><BR><BR><BR><BR><BR><BR>
<BUTTON style="FONT-SIZE: 9pt" onClick="pushBtm('YU')" type="button">年↑</BUTTON><BR>
<BUTTON style="FONT-SIZE: 9pt" onClick="pushBtm('YD')" type="button">年↓</BUTTON><BR><BR>
<BUTTON style="FONT-SIZE: 9pt" onClick="pushBtm('MU')" type="button">月↑</BUTTON><BR>
<BUTTON style="FONT-SIZE: 9pt" onClick="pushBtm('MD')" type="button">月↓</BUTTON><BR><BR>
<BUTTON style="FONT-SIZE: 9pt" onClick="pushBtm('XD')" type="button">當月</BUTTON><BR>
  </TD>
</TR></TBODY></TABLE>
</FORM></CENTER>
<HR align=center width=600 color=black>
</BODY></HTML>
 Javascript 原始碼 (calendar_test.js)

//---------------------------------------------------
//訊息盒 Debug 用
function MsgBox(str){alert(str);}
//---------------------------------------------------
var today;
//初始化
function initial(){
   myClock();//啟動時鐘 
}

//時鐘,每秒跳一次
function myClock(){
   today = new Date();
   Clock.innerHTML = today.toLocaleString().replace(/(年|月)/g, "-").replace(/日/, "");
   window.setTimeout("myClock()", 1000);
}

//combobox onchange事件
function changeCld(){
   var y,m,g;
   y=CLD.SY.selectedIndex+1900;
   m=CLD.SM.selectedIndex+1;
   MsgBox(y+' 年 '+m+' 月'); 
   if(y>1874 && y<1909){g=y-1874;GZ.innerHTML = '  光緒 ' + g +' 年 '+m+' 月';}
   if(y>1908 && y<1912){g=y-1908;GZ.innerHTML = '  宣統 ' + g +' 年 '+m+' 月';}
   if(y>1911){g=y-1911;GZ.innerHTML = '  民國 ' + g +' 年 '+m+' 月';}
}


//開始秀陽曆日期+顏色
function showCal(){
  //MsgBox('開始秀陽曆日期');  //Debug 用
  var gNum;
  for(i=0;i<6;i++) { //垂直 有6行
    document.write('<tr align=center>');
    for(j=0;j<7;j++) { //水平
      gNum = i*7+j; //數字定位
      //秀陽曆數字
      document.write('<td id="GD'+gNum+'" onMouseOver="mOvr('+gNum+')" onMouseOut="mOut()"><font id="SD'+
      gNum+'" size=5 face="Arial Black" color=green >'+gNum+'</font>');

      //秀陰曆數字+顏色+節慶      document.write('<br><font id="LD'+ gNum +'"size=2 style="font-size:9pt">'+ gNum +'</font></td>');
    }
    document.write('</tr>');
  }
}

//table onMouseOver事件
function mOvr(){    MsgBox('mOvr()');}

//table onMouseOut事件
function mOut(){    MsgBox('mOut()');}

//右邊按鈕群
function pushBtm(v){
   switch(v){
      case 'YU' :
         MsgBox('pushBtm = YU');
         break;
      case 'YD' :
         MsgBox('pushBtm = YD');
         break;
      case 'MU' :
         MsgBox('pushBtm = MU');
         break;
      case 'MD' :
         MsgBox('pushBtm = MD');
         break;
      default :
         MsgBox('pushBtm = '+v);
         break;
   }
}
 使用到的事件列表
 <BODY onload=initial() >
 <SELECT style="FONT-SIZE: 9pt" onchange = changeCld() name= SY >
 <td id="GD0" onmouseover="mOvr(0)" onmouseout="mOut()">
 <BUTTON style="FONT-SIZE: 9pt" onClick="pushBtm('YU')">年↑</BUTTON>
最後是 javascript 事件
 window.setTimeout("myClock()", 1000);

好了,框架大概就這麼多了,用到的這些都是 HTML 及 javascript 語法。

待續...

2012-08-23

農曆程式概念探討(1)


kowala's home
http://kowala21.blogspot.com

El Universo Hoy - Luna llena. México. 10 de noviembre de 2011. [13]

今天來探討的主題是農曆,這也是常用的功能,但似乎是不那麼好懂,還好,網路上可以找到一支近乎"公版"的農曆程式,也不知原作者姓氏,兩岸三地都有人採用,我們就以它為對象來討論,24節氣精確度計算到分鐘(這隻程式原本的計算方式),沒打算要更精確,基準年依照原程式為1900年小寒為起點。

西元1900年1月  6日 → 清德宗光緒25年(歲次己亥)12月6日 小寒 黃經 285 度[1]。
西元1900年1月31日 → 清德宗光緒26年(歲次庚子) 1月1日【鼠】


http://dolphin.cc.ncu.edu.tw/lunar.html
http://38time.artjoey.com/calendar_big.htm
http://www.time.ac.cn/nongli.htm
...

首先,須先有個曆法的概念,曆法的目的都是用來計算時間,基礎都是建立在觀察,若以觀察對象來分,一般可分為陽曆與陰曆,分別是觀察太陽與月亮。

先說太陽,地球自轉一周為一日,繞日一周為一年(回歸年) = 365.2421990741日 = 365天5小時48分46秒[2],約為 365 又 1/4 天。一般取整數 365 算一年,但少了 1/4 天怎麼辦?那就補回來呀,既是 1/4 天,那就 4 年補一天,調整回來,這就是潤年,多一天。(陽曆)

再來就是陰曆,因為用太陽來排日期,一年轉一周,比較粗糙,改成月亮繞地球一周為一個朔望月[3],每月一循環,這就是陰曆了。當然,這規則遠比陽曆來的複雜許多,而且是建立在觀測上,所以都要以天文台發佈的資料為準,並且各地區(緯度)會有不同的觀測結果。

中國是以農立國,為了在農業應用(主要目的),所以要含入四季的變化,古人把一年分割為24個節氣[4],這是以繞太陽公轉(陽曆)的角度為基礎,再把它加入陰曆之中,稱之為農曆,所以會說農曆是陰陽曆。

二十四節氣
節氣以太陽視黃經為準,從春分起算為黃經0度,視黃經每增15度得一氣,依漢代以來通行之名稱及次序,每年得24氣,有中氣及節氣之分,俗名統稱節氣。

春季
    立春 太陽位於黃經315度,2月3-5日交節
    雨水 太陽位於黃經330度,2月18-20日交節
    驚蟄 太陽位於黃經345度,3月5-7日交節
    春分 太陽位於黃經0度,3月20-22日交節
    清明 太陽位於黃經15度,4月4-6日交節
    穀雨 太陽位於黃經30度,4月19-21日交節
夏季
    立夏 太陽位於黃經45度,5月5-7日交節
    小滿 太陽位於黃經60度,5月20-22日交節
    芒種 太陽位於黃經75度,6月5-7日交節
    夏至 太陽位於黃經90度,6月20-22日交節
    小暑 太陽位於黃經105度,7月6-8日交節
    大暑 太陽位於黃經120度,7月22-24日交節
秋季
    立秋 太陽位於黃經135度,8月7-9日交節
    處暑 太陽位於黃經150度,8月22-24日交節
    白露 太陽位於黃經165度,9月7-9日交節
    秋分 太陽位於黃經180度,9月22-24日交節
    寒露 太陽位於黃經195度,10月7-9日交節
    霜降 太陽位於黃經210度,10月23-24日交節
冬季
    立冬 太陽位於黃經225度,11月7-8日交節
    小雪 太陽位於黃經240度,11月21-23日交節
    大雪 太陽位於黃經255度,12月6-8日交節
    冬至 太陽位於黃經270度,12月21-23日交節
    小寒 太陽位於黃經285度,1月5-7日交節
    大寒 太陽位於黃經300度,1月19-21日交節

二十四節氣(圖一)

二十四節氣(圖二)

二十四節氣(圖三)


二十四節氣運行時間
一般若使用平均時間來計算各節氣到達時間,則會產生相當大的誤差,這是因為地球運行軌道是橢圓形,運行的速率並不是等速,所以運行到各節氣(角度)的時間並不相等,計算方法可以參考"用橢圓軌道公式求出24節氣"[5]及"用天文方法計算二十四節氣"[6]等文章,也可以到這裡[7]查詢某年的節氣時間。

 二十四節氣軌道(圖四)

2012年二十四節氣時間


二十四節氣的意義[8]
以下的解釋是適用於中原地區,為古代農務的依據。

立春:春季開始,立是開始的意思,春是蠢動,表示萬物開始有生氣。
雨水:春到人間,降雨開始增多,春雨綿綿。
驚蟄:蟲類冬眠或隱藏起來,伏著不動,叫做蟄。春雷響起,驚醒蟄伏地下冬眠的蟲類,將開始出土活動。
春分:春季過了一半,此時陽光直射赤道上,這一天太陽從正東方昇起,落於正西方,地球上南北半球受光相等,晝夜長短相等,古代曾稱春分與秋分為晝夜分。
清明:天氣逐漸和暖,春暖花開,草木開始萌發茂盛,大地一片氣清景明的現象。
穀雨:雨生百穀的意思,此時農夫剛完成春耕,田裡的秧苗正需大量的雨水滋潤,適時且足夠的雨水才能使穀物成長茁壯。但此時的氣候,卻時晴時雨,時冷時熱,最讓人不易捉摸。
立夏:夏季開始,此時已出現溫暖的氣候,萬物迅速生長。
小滿:滿指穀物籽粒飽滿,稻穀和麥類等夏熟農作物行將結實,等待成熟,但尚未達到飽滿的程度。
芒種:有芒作物開始成熟,結實成穗,此時也是秋季作物播種的適當時節。
夏至:炎熱的夏天真正到來,此時陽光直射北回歸線上,北半球受光最多,是白天最長黑夜最短的一天,中午時太陽的仰角是一年裡最高的,因此日影是一年中最短的,過了夏至日,白天漸漸變短,夜晚慢慢加長。
小暑:暑是炎熱之意,此時天氣開始逐漸炎熱,但是還沒有熱到極點,雖然夏至時北半球受陽光照射時間最長,由於太陽射來的熱力必須先對地面和大氣加溫,才能把熱儲存於大氣中,所以天氣從夏至開始慢慢加熱,經過小暑後,熱度才會逐漸昇高到極點。
大暑:氣候酷熱到達高峰。
立秋:秋季開始,氣溫將由熱轉涼,涼爽舒適的秋天就要來臨。
處暑:處是止的意思,表示夏天的暑氣到此終止,但有時晴天的下午,炎熱不亞於暑夏,可視為夏的迴光返照。
白露:天氣已經轉涼,夜晚時空氣中所含的水汽,接觸到地面上因輻射而迅速冷卻的物體,於是部份凝結為水滴而附於地面的花草樹葉上,這些透明晶瑩的水珠,我們就稱它為白露。
秋分:秋季過了一半,同春分一樣,此時陽光直射赤道上,地球上南北半球受光相等,晝夜長短相等。
寒露:此時已屆深秋,天氣轉冷,早晚所接觸到的霧氣和露水,感覺寒意沁心,而草木行將枯萎。
霜降:天氣漸寒,當地面的物體溫度降至攝氏零度或以下,接觸的水汽直接結霜附於其上。
立冬:冬季開始,冬是終了,作物已收割貯藏,農事完成。
小雪:氣候寒冷,此時節空氣中的水汽在溫度冷至攝氏零度以下時,會凝成結晶狀的固體由空中降下,稱為降雪,不過降雪量不多且不大。
大雪:天氣更寒冷,大雪紛飛,地面積雪。
冬至:嚴冬來臨,此時陽光直射南回歸線上,北半球受光最少,是白天最短黑夜最長的一天,中午時太陽的仰角是一年裡最低的,日影是一年中最長的。
小寒:天氣相當寒冷,雖進入嚴冬但尚未到達最冷的時候。
大寒:天氣酷寒,是一年中最冷的日子。

時刻
我國是以中央氣象局所在地之時刻為準,欲求其它各地之日中天時刻,應加各地經度差之修正。本地標準時欲改為某地之本地平時者,應視本地經度在時區標準經線之東或西之度數,每度加減4分,東加西減[9]。
地球自轉一周為一日,凌晨12:00(子時)為一日之始。


名詞解釋:
這可以跳過,也可以詳查,並不會影響程式寫作。
朔望月[3] = 指月球連續兩次合朔的時間間隔。因為攝動的關係,朔望月的長度大約在29.27至29.83天之間變動著,長期的平均長度是29.530588天(29天12小時44分2.8秒),或大約是29.5天。新月出現那天為初一。
中氣[10],中氣為「二十四節氣」中,每月的第二個節氣。
包括雨水、春分、穀雨、小滿、夏至、大暑、處暑、秋分、霜降、小雪、冬至和大寒。
黃經[11](太陽經度或天球經度)是在黃道座標系統中用來確定天體在天球上位置的一個座標值(另一個值是黃緯),在這個系統中,天球被黃道平面分割為南北兩個半球。
黃道面[12],地球繞日公轉的軌道平面。


二十四節氣(圖一) http://www.xuanyi.com/zhuanjiaku/qizhengyang/2011/0216/4173.html
二十四節氣(圖二) http://www.worldce.com.cn/wenhua_xiang.asp?id=891
二十四節氣(圖三) http://www.hko.gov.hk/gts/time/24solartermsc.htm
二十四節氣軌道(圖四) http://bieyu.com/astp-doc/a07.htm

待續...

參考資料
1. 兩千年中西曆轉換(西元元年2月11日至2100年2月9日) http://sinocal.sinica.edu.tw
2. 维基百科回歸年  http://zh.wikipedia.org/wiki/回歸年
3. 维基百科朔望月 http://zh.wikipedia.org/zh-tw/朔望月
4. 维基百科農曆 http://zh.wikipedia.org/zh-tw/節氣
5. 用橢圓軌道公式求出24節氣 http://bieyu.com/astp-doc/a07.htm
6. 用天文方法計算二十四節氣 http://blog.csdn.net/orbit/article/details/7910220
7. 查詢二十四節氣表 jieqi.911cha.com
8. 24節氣概述 http://www.youngsun.org.tw:8080/calendar/terms.asp
9. 內政部 國民曆說明 http://www.moi.gov.tw/dca/custom100/home.asp
10. 中氣 http://zh.wikipedia.org/zh-tw/中氣
11. 黃經 http://zh.wikipedia.org/wiki/黃經
12. 黃道面 http://zh.wikipedia.org/wiki/黃道
13. El Universo Hoy  https://www.facebook.com/photo.php?fbid=299524833410887&set=a.292863737410330.86885.211717025525002&type=1&theater#

2012-07-27

wxDev C++ 使用 winFrame 視窗+測試鍵盤 wxKeyEvent 之2

轉載請註明出處,謝謝。

kowala's home
http://kowala21.blogspot.com

前一篇 wxDev C++ 使用 winFrame 視窗+測試鍵盤 wxKeyEvent 之1

我們必須先取出按鍵值,然後解析它。這裡的目標是要把全部的按鍵都抓到,先區分按鍵為可列印按鍵(printable character)或是擴展按鍵(special key),可以這樣來區分[1]。

int code=event.GetModifiers();//組合鍵狀態
int keyCode = event.GetKeyCode();//ascii 鍵值
if ( wuChar != WXK_NONE && code == 0){ //一般鍵
    wxLogMessage("printable key '%c'", event.GetUnicodeKey());
}else{//組合鍵
    wxLogMessage("special key '%c'", event.GetUnicodeKey());
}

在一般鍵裡頭,包含有命令鍵,如 Enter,TAB,BACK等等,它並不可視,所以要加一個 switch () 來把它們分離出來,我使用一個字串( TCHAR str[] )來幫忙顯示結果,在wxDev C++之中,似乎是使用 unicode,這意味著,使用中文會有亂碼問題[2]。

int code=event.GetModifiers();//組合鍵狀態
int keyCode = event.GetKeyCode();//ascii 鍵值
TCHAR str[]={0};
if ( wuChar != WXK_NONE && code == 0){ //一般鍵
        switch (keyCode){
            case WXK_BACK://8
                wsprintf(str,L"Key : %s",L"BACK");
                MessageBox(0,str,L"Normal Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_TAB://9
                wsprintf(str,L"Key : %s",L"TAB");
                MessageBox(0,str,L"Normal Key",MB_ICONINFORMATION|MB_OK);
                break;
            ......
            default:
                wsprintf(str,L"Key : %c",keyCode);
                MessageBox(0,str,L"Normal Key",MB_OK);
        }
}else{//組合鍵
    wxLogMessage("special key '%c'", event.GetUnicodeKey());
}

執行結果



到這裡,我們已經處理了一般按鍵,接著要來處理組合鍵及特別鍵,像是 Shift+key,Ctrl+Key,PageUP,F1,F2...
組合鍵說明(code 的組合)
Alt=1
Ctrl=2
Shift=4
所以
Ctrl+Alt = 2+1 = 3
Shift+Ctrl = 4+2 = 6
Shift+Ctrl+Alt = 4+2+1 = 7
...
組合鍵要判斷兩次,要判斷 code 的組合,再取 keyCode 鍵值。

wxDevC++已經定義了一些虛擬鍵值,請參考 Type KeyEvent [3] .
為了保持簡潔,把它獨立出來,另外寫個 chkExtKey() 如下。

void chkExtKey(int keyCode){//擴展鍵+組合鍵
    TCHAR str[100]={0};
    switch (keyCode){
            case 324://NumLock 0 = 324
                _stprintf(str,L"code = %d, key = %c",keyCode,'0');
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_NUMPAD1://NumLock 1 = 325
                _stprintf(str,L"code = %d, key = %c",keyCode,'1');
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
                break;
            ..............
            default:               
                _stprintf(str,L"(key,code)=(%c,%d)",keyCode,keyCode);
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
    }
}

執行結果




使用方法

int code=event.GetModifiers();//組合鍵狀態
int keyCode = event.GetKeyCode();//ascii 鍵值
TCHAR str[]={0};
if ( wuChar != WXK_NONE && code == 0){ //一般鍵
        switch (keyCode){...}

}else{//組合鍵
    switch (code){ //code 的組合
            //(Shift,Ctrl,Alt)=(4,2,1)
            case 1:
                MessageBox(0,L"Alt",L"Func Key",MB_ICONINFORMATION|MB_OK);
                break;
            case 2:
                MessageBox(0,L"Ctrl",L"Func Key",MB_ICONINFORMATION|MB_OK);
                break;
            case 3:
                MessageBox(0,L"Ctrl+Alt",L"Func Key",MB_ICONINFORMATION|MB_OK);
                break;
            case 4:
                MessageBox(0,L"Shift",L"Func Key",MB_ICONINFORMATION|MB_OK);
                break;
            case 5:
                MessageBox(0,L"Shift+Alt",L"Func Key",MB_ICONINFORMATION|MB_OK);
                break;
            case 6:
                MessageBox(0,L"Shift+Ctrl",L"Func Key",MB_ICONINFORMATION|MB_OK);
                break;
            case 7:
                MessageBox(0,L"Shift+Ctrl+Alt",L"Func Key",MB_ICONINFORMATION|MB_OK);
                break;          
        }
    chkExtKey(keyCode);//擴展鍵
}

完整代碼

/*
 * kbTestFrmKeyDown
 */
void chkExtKey(int keyCode){//擴展鍵
    TCHAR str[100]={0};
    switch (keyCode){
            case 324://NumLock 0 = 324
                _stprintf(str,L"code = %d, key = %c",keyCode,'0');
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_NUMPAD1://NumLock 1 = 325
                _stprintf(str,L"code = %d, key = %c",keyCode,'1');
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_NUMPAD2://NumLock 2 = 326
                _stprintf(str,L"code = %d, key = %c",keyCode,'2');
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_NUMPAD3://NumLock 3 = 327
                _stprintf(str,L"code = %d, key = %c",keyCode,'3');
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_NUMPAD4://NumLock 4 = 328
                _stprintf(str,L"code = %d, key = %c",keyCode,'4');
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_NUMPAD5://NumLock 5 = 329
                _stprintf(str,L"code = %d, key = %c",keyCode,'5');
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_NUMPAD6://NumLock 6 = 330
                _stprintf(str,L"code = %d, key = %c",keyCode,'6');
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_NUMPAD7://NumLock 7 = 331
                _stprintf(str,L"code = %d, key = %c",keyCode,'7');
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_NUMPAD8://NumLock 8 = 332
                _stprintf(str,L"code = %d, key = %c",keyCode,'8');
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_NUMPAD9://NumLock 9 = 333
                _stprintf(str,L"code = %d, key = %c",keyCode,'9');
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_F1://F1
                _stprintf(str,L"code = %d, key = %s",keyCode,L"F1");
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_F2://F2
                _stprintf(str,L"code = %d, key = %s",keyCode,L"F2");
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_NUMLOCK://NUMLOCK
                _stprintf(str,L"code = %d, key = %s",keyCode,L"NUMLOCK");
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_PAGEUP://PAGEUP
                _stprintf(str,L"code = %d, key = %s",keyCode,L"PAGEUP");
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_SPECIAL1://SPECIAL1
                _stprintf(str,L"code = %d, key = %s",keyCode,L"SPECIAL1");
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
                break;
            default:               
                _stprintf(str,L"(key,code)=(%c,%d)",keyCode,keyCode);
                MessageBox(0,str,L"Extend Key",MB_ICONINFORMATION|MB_OK);
    }
}
void kbTestFrm::kbTestFrmKeyDown(wxKeyEvent& event){
    int code=event.GetModifiers();//組合鍵狀態,(Shift,Ctrl,Alt)=(4,2,1)
    int keyCode = event.GetKeyCode();//ascii 鍵值
    wxUint32 wuChar=event.GetUnicodeKey();//unicode鍵值
    TCHAR str[]={0};
    if ( wuChar != WXK_NONE && code == 0){//一般鍵
        switch (keyCode){
            case WXK_BACK://8
                wsprintf(str,L"Key : %s",L"BACK");
                MessageBox(0,str,L"Normal Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_TAB://9
                wsprintf(str,L"Key : %s",L"TAB");
                MessageBox(0,str,L"Normal Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_RETURN://13
                wsprintf(str,L"Key : %s",L"RETURN");
                MessageBox(0,str,L"Normal Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_ESCAPE://27
                wsprintf(str,L"Key : %s",L"ESCAPE");
                MessageBox(0,str,L"Normal Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_SPACE://32
                wsprintf(str,L"Key : %s",L"SPACE");
                MessageBox(0,str,L"Normal Key",MB_ICONINFORMATION|MB_OK);
                break;
            case WXK_DELETE://127
                wsprintf(str,L"Key : %s",L"DELETE");
                MessageBox(0,str,L"Normal Key",MB_ICONINFORMATION|MB_OK);
                break;
            default:
                wsprintf(str,L"Key : %c",keyCode);
                MessageBox(0,str,L"Normal Key",MB_OK);
        }
    }else{//組合鍵 (Shift,Ctrl,Alt)=(4,2,1)
        switch (code){//code 的組合            //(Shift,Ctrl,Alt)=(4,2,1)
            case 1:
                MessageBox(0,L"Alt",L"Func Key",MB_ICONINFORMATION|MB_OK);
                break;
            case 2:
                MessageBox(0,L"Ctrl",L"Func Key",MB_ICONINFORMATION|MB_OK);
                break;
            case 3:
                MessageBox(0,L"Ctrl+Alt",L"Func Key",MB_ICONINFORMATION|MB_OK);
                break;
            case 4:
                MessageBox(0,L"Shift",L"Func Key",MB_ICONINFORMATION|MB_OK);
                break;
            case 5:
                MessageBox(0,L"Shift+Alt",L"Func Key",MB_ICONINFORMATION|MB_OK);
                break;
            case 6:
                MessageBox(0,L"Shift+Ctrl",L"Func Key",MB_ICONINFORMATION|MB_OK);
                break;
            case 7:
                MessageBox(0,L"Shift+Ctrl+Alt",L"Func Key",MB_ICONINFORMATION|MB_OK);
                break;           
        }
        chkExtKey(keyCode);//擴展鍵
    }
}
參考
[1].wxKeyEvent Class Reference
http://docs.wxwidgets.org/trunk/classwx_key_event.html
[2].wxDev C++ 開始使用它來建立視窗(2)
http://kowala21.blogspot.tw/2012/05/wxdev-c-2.html
[3].Type KeyEvent
http://wxpython.org/docs/api/wx.KeyEvent-class.html