NET framework2.0中的農曆類(全)

 一、簡介
過年是中國(以及日本、韓國等國)人民的第一大節日。你怎麼知道哪天過年?查日曆或者聽別人說?程序員當然有程序員的辦法,就是寫程序啦。
雖然公曆(俗稱的陽曆”)已經成了全世界的通用標準,而且也具有多方面的優越性。但在東亞地區,還是離不開農曆”,春節、元宵、端午、中秋、重陽這些節日是農曆的,大部份人的老爸老媽的生日也是農曆的。
早在1.0框架出來的時候,我就認爲微軟公司不應該厚彼薄此,在.net框架中提供了希伯來曆等,卻沒有提供更廣泛使用的農曆
而在.net 2.0中,微軟公司終於做出了這個小小的改進。
.net 2.0System.Globalization命名空間中新增加了EastAsianLunisolarCalendar 類及以繼承它的ChineseLunisolarCalendar, JapaneseLunisolarCalendar, KoreanLunisolarCalendar, TaiwanLunisolarCalendar等幾個類。LunisolarCalendar顧名思義應爲陰陽曆,我的理解是因爲我們所用的農曆雖然按照月亮公轉來編月份,但用閏月的方式來調整年份與地球公轉的誤差,嚴格意義上來說是結合了月亮公轉和地球公轉的成份,因此屬於陰陽曆。但我這裏還是按照習慣稱之爲農曆

二、新的農曆類還是沒有公民待遇
爲了測試新的日曆類,我興沖沖地寫了幾句代碼:(省略了調用這個方法的其它代碼)

運行報錯,錯誤信息是:"Not a valid calendar for the given culture "

爲了說明問題,繼續測試

可以正常運行,結果是95年x月x日(民國紀年),註釋掉中間那條語句,結果是2006年x月x日(也就是使用公曆),將中間那條語句修改成:ci.DateTimeFormat.Calendar = new TaiwanLunisolarCalendar(),照樣出錯。查相關資料,原來DateTimeFormat的Calendar屬性只能爲CultureInfo的OptionalCalendars屬性所指定範圍。
於是再寫一段代碼測試OptionalCalendars的內容,對於zh-CN語言,惟一可用於日期格式的calendar是本地化的GregorianCalendar(也就是公曆)。對於zh-TW,可用於日期格式的calendar是美國英語和本地化的GregorianCalendar以及TaiwanCalendar(即公曆的年份減1911),都沒有包括農曆。
也就是說.net2.0雖然提供了農歷類,但對它的支持並不及同樣有閏月的希伯來曆。我查資料的時候找到了博客堂的一篇文章http://blog.joycode.com/percyboy/archive/2004/09/17.aspx ,作者在一年半以前發現了農曆類不支持日期格式化的問題,並認爲這是一個bug。當然還算不上bug,只不過微軟沒有重視而已(責任在微軟嗎?我想應該不是,在商業社會我們有多重視微軟就會有多重視。和以色列比起來,我們對傳統文化的重視程度差得太遠)。
三、農曆類的使用
既然.net框架不支持直接將日期轉換成農曆格式的字符串,那麼要將顯示農曆格式的日期,就只要自已寫代碼了。不過由於已經有了ChineseLunisolarCalendar類實現了公曆轉換爲農曆日期的功能,所以要寫這樣的代碼也比較簡單。需要用到ChineseLunisolarCalendar以下幾個主要方法:
int GetYear (DateTime time) 獲取指定公曆日期的農曆年份,使用的還是公曆紀元。在每年的元旦之後春節之前農曆的紀年會比公曆小1,其它時候等於公曆紀年。雖然農曆使用傳說中的耶穌生日紀元似乎不太妥當,不過我們確實已經幾十年沒有實行一個更好的紀年辦法,也只有將就了。
int GetMonth (DateTime time) 獲取指定公曆日期的農曆月份。這裏要注意了,由於農曆有接近三分之一的年份存在閏月,則在這些年份裏會有十三個,而具體哪一個月是閏月也說不準,這裏不同於希伯來曆。以今年爲例,今年閏七月,則此方法在參數爲閏七月的日期是返回值爲8,參數爲農曆十二月的日期時返回值爲13
bool IsLeapMonth ( int year,   int month) 獲取指定農曆年份和月份是否爲閏月,這個函數和上個函數配合使用就可以算出農曆的月份了。
int GetDayOfMonth (DateTime time) 獲取指定公曆日期的農曆天數,這個值根據大月或者小月取值是130或者129, MSDN上說的131顯然是錯的, 沒有哪個農曆月份會有31天。
int GetSexagenaryYear (DateTime time) 獲取指定公曆日期的農曆年份的干支紀年,從160,分別是甲子、乙丑、丙寅、….癸亥, 比如戊戌變法、辛亥革命就是按這個來命名的。當然算八字也少不了這個。
int GetCelestialStem (int sexagenaryYear) 獲取一個天支的天干, 110, 表示甲、乙、丙….,說白了就是對10取模。
int GetTerrestrialBranch (int sexagenaryYear) ) 獲取一個干支的地支,, 112, 表示子、醜、寅、今年是狗年,那麼今年年份的地支就是“戌”。
有了這幾個方法,顯示某天的農曆月份日期、農曆節日等都是小菜一碟,算命先生排八字用這幾個方法,又快又準確,寫出的代碼也很短。
 
四、幾種東亞農曆類的區別
經過我的測試,ChineseLunisolarCalendar, JapaneseLunisolarCalendar, KoreanLunisolaCalendarr, TaiwanLunisolarCalendar這四種日曆,無論哪一種,以2006年2月6日爲參數,調用它們的GetMonth方法得到的結果都是1,GetDayOfMonth得到的結果都是8。想想也是,我們過的端午節和韓國的不太可能不是一天。
但是調用GetYear方法得到結果就有區別了ChineseLunisolarCalendar和KoreanLunisolarCalendar都返回2006,也就是公曆紀年,TaiwanLunisolarCalendar的返回值是95,依然是民國紀年,JapaneseLunisolarCalendar的返回值是18, 平成紀年。
另外的一個區別是這四種日曆的MinSupportedDateTime和MaxSupportedDateTime各不一樣,以下是對照表:

日曆類 MinSupportedDateTime MaxSupportedDateTime
ChineseLunisolarCalendar 公元19011月初1 公元21001229
TaiwanLunisolarCalendar 民國11月初1 民國1391229
JapaneseLunisolarCalendar 昭和351月初1 平成611229
KoreanLunisolarCalendar 公元9181月初1 公元20501229
韓國農曆類支持的最小日期爲918年(也即高麗王朝建立的年份),以此而論,中國農曆類支持的最小日期不說從商周算起,從漢唐算總該沒問題吧?微軟公司啊,又在“厚彼薄此”,唉。
其次,日本還以天皇紀年,如果哪天xxxx, 豈不是使用JapaneseLunisolarCalendar寫出的程序都有問題啦?
五、寫自已的日期格式化器
昨天看了一篇文章,說目前大家用的“農曆”這個術語是文革時期纔有的,目的是反封建。這裏爲了省事,還是繼續使用這個術語。而英文名稱ChineseLunisolarCalendar太長,我自己的代碼中就用ChineseCalendar爲相關功能命名,這個名字也還過得去吧。
我原先設想自定義一個類,使得能寫出這樣的代碼:
string s= DateTime.Now.ToString(new MyFormatProvider());
 
雖然不能爲DataTime寫自定義的格式器,但還有另外一個途徑,就是爲String類的Format方法寫自定義格式化器,我測試了一下,效果還不錯,調用方式如下:
string s= String.Format(new ChineseCalendarFormatter(), "{0:D}",DateTime.Now);
可以得到“二〇〇六年正月初九”
string s= String.Format(new ChineseCalendarFormatter(), "{0:d}",DateTime.Now);
可以得到“丙戌年正月初九”
雖然沒有前面所設想的方便,但也還能接受,全部代碼帖出如下:

第一個類,主要是封裝了農曆的一些常用字符和對日曆處理的最基本功能
就能得出我想要的農曆日期字符串,經過測試卻失敗了,依據我的分析,微軟公司在.net框架中把日期時間型的格式寫死了,只能依據相關的地區採用固定的幾種顯示格式,沒法再自行定義。而前文已經說過,而所有的相關格式微軟公司都放到一個名爲culture.nlp的文件中(這個文件在以前的.net框架是一個獨立的文件,在.net 2.0被作爲一個資源編譯到mscorlib.dll中。) (我的這個不能爲DateTime寫自已的格式化器的觀點沒有資料佐證,如有不當之處,請大家指正)
using System;
using System.Collections.Generic;
using System.Text;

using System.Globalization;

public static class ChineseCalendarHelper
{
    
public static string GetYear(DateTime time)
    {
        StringBuilder sb 
= new StringBuilder();
        
int year = calendar.GetYear(time);
        
int d;
        
do
        {
            d 
= year % 10;
            sb.Insert(
0, ChineseNumber[d]);
            year 
= year / 10;
        } 
while (year > 0);
        
return sb.ToString();
    }

    
public static string GetMonth(DateTime time)
    {
        
int month = calendar.GetMonth(time);
        
int year = calendar.GetYear(time);
        
int leap = 0;

        
//正月不可能閏月
        for (int i = 3; i <= month; i++)
        {
            
if (calendar.IsLeapMonth(year, i))
            {
                leap 
= i;
                
break;  //一年中最多有一個閏月
            }

        }
        
if (leap > 0) month--;
        
return (leap == month + 1 ? "" : ""+ ChineseMonthName[month - 1];
    }

    
public static string GetDay(DateTime time)
    {
        
return ChineseDayName[calendar.GetDayOfMonth(time) - 1];
    }

    
public static string GetStemBranch(DateTime time)
    {
        
int sexagenaryYear = calendar.GetSexagenaryYear(time);
        
string stemBranch = CelestialStem.Substring(sexagenaryYear % 10 - 11+ TerrestrialBranch.Substring(sexagenaryYear % 12 - 11);
        
return stemBranch;
    }

    
private static ChineseLunisolarCalendar calendar = new ChineseLunisolarCalendar();
    
private static string ChineseNumber = "〇一二三四五六七八九";
    
public const string CelestialStem = "甲乙丙丁戊己庚辛壬癸";
    
public const string TerrestrialBranch = "子醜寅卯辰巳午未申酉戌亥";
    
public static readonly string[] ChineseDayName = new string[] {
            "初一","初二","初三","初四","初五","初六","初七","初八","初九","初十",
            
"十一","十二","十三","十四","十五","十六","十七","十八","十九","二十",
            
"廿一","廿二","廿三","廿四","廿五","廿六","廿七","廿八","廿九","三十"};
    
public static readonly string[] ChineseMonthName = new string[] { """""""""""""""""""""十一""十二" };
}

第二個類爲自定義格式化器:
using System;
using System.Collections.Generic;
using System.Text;

using System.Globalization;
using System.Threading;

public class ChineseCalendarFormatter : IFormatProvider, ICustomFormatter
{
    
//實現IFormatProvider
    public object GetFormat(Type formatType)
    {
        
if (formatType == typeof(ICustomFormatter))
            
return this;
        
else
            
return Thread.CurrentThread.CurrentCulture.GetFormat(formatType);
    }

    
//實現ICustomFormatter
    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        
string s;
        IFormattable formattable 
= arg as IFormattable;
        
if (formattable == null)
            s 
= arg.ToString();
        
else
            s 
= formattable.ToString(format, formatProvider);
        
if (arg.GetType() == typeof(DateTime))
        {
            DateTime time 
= (DateTime)arg;
            
switch (format)
            {
                
case "D"//長日期格式
                    s = String.Format("{0}年{1}月{2}",
                        ChineseCalendarHelper.GetYear(time),
                        ChineseCalendarHelper.GetMonth(time),
                        ChineseCalendarHelper.GetDay(time));
                    
break;
                
case "d"//短日期格式
                    s = String.Format("{0}年{1}月{2}", ChineseCalendarHelper.GetStemBranch(time),
                        ChineseCalendarHelper.GetMonth(time),
                        ChineseCalendarHelper.GetDay(time));
                    
break;
                
case "M"//月日格式
                    s = String.Format("{0}月{1}", ChineseCalendarHelper.GetMonth(time),
                        ChineseCalendarHelper.GetDay(time));
                    
break;
                
case "Y"//年月格式
                    s = String.Format("{0}年{1}月", ChineseCalendarHelper.GetYear(time),
                        ChineseCalendarHelper.GetMonth(time));
                    
break;
                
default:
                    s 
= String.Format("{0}年{1}月{2}", ChineseCalendarHelper.GetYear(time),
                        ChineseCalendarHelper.GetMonth(time),
                        ChineseCalendarHelper.GetDay(time));
                    
break;
            }
        }
        
return s;
    }
}
這段代碼中間處理格式那部份稍做改進,就可以支持更多的日期格式。

有了這兩段代碼爲原型,要實現計算和顯示一個日期的農曆日期及其它功能,基本上就很容易了。
 
private string getDateString(DateTime dt)
{
    CultureInfo ci 
= new CultureInfo("zh-TW");
    ci.DateTimeFormat.Calendar 
= new TaiwanCalendar();
    
return dt.ToString("D",ci);
}
private string getDateString(DateTime dt)
{
    CultureInfo ci 
= new CultureInfo("zh-CN");
    ci.DateTimeFormat.Calendar 
= new ChineseLunisolarCalendar();
    
return dt.ToString("D",ci);
}
 
發佈了14 篇原創文章 · 獲贊 1 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章