一簡介
過年是中國(以及日本韓國等國)人民的第一大節日你怎麼知道哪天過年?查日歷或者聽別人說?程序員當然有程序員的辦法就是寫程序啦
雖然公歷(俗稱的陽歷)已經成了全世界的通用標准而且也具有多方面的優越性但在東亞地區還是離不開農歷春節元宵端午中秋重陽這些節日是農歷的大部份人的老爸老媽的生日也是農歷的
早在框架出來的時候我就認為微軟公司不應該厚彼薄此框架中提供了希伯來歷等卻沒有提供更廣泛使用的農歷
而 中微軟公司終於做出了這個小小的改進
在SystemGlobalization命名空間中新增加了EastAsianLunisolarCalendar 類及以繼承它的ChineseLunisolarCalendar JapaneseLunisolarCalendar KoreanLunisolarCalendar TaiwanLunisolarCalendar等幾個類LunisolarCalendar顧名思義應為陰陽歷我的理解是因為我們所用的農歷雖然按照月亮公轉來編月份但用閏月的方式來調整年份與地球公轉的誤差嚴格意義上來說是結合了月亮公轉和地球公轉的成份因此屬於陰陽歷但我這裡還是按照習慣稱之為農歷
二新的農歷類還是沒有公民待遇
為了測試新的日歷類我興沖沖地寫了幾句代碼(省略了調用這個方法的其它代碼)
運行報錯錯誤信息是Not a valid calendar for the given culture
為了說明問題繼續測試
可以正常運行結果是年x月x日(民國紀年)注釋掉中間那條語句結果是年x月x日(也就是使用公歷)將中間那條語句修改成ciDateTimeFormatCalendar = new TaiwanLunisolarCalendar()照樣出錯查相關資料原來DateTimeFormat的Calendar屬性只能為CultureInfo的OptionalCalendars屬性所指定范圍
於是再寫一段代碼測試OptionalCalendars的內容對於zhCN語言惟一可用於日期格式的calendar是本地化的GregorianCalendar(也就是公歷)對於zhTW可用於日期格式的calendar是美國英語和本地化的GregorianCalendar以及TaiwanCalendar(即公歷的年份減)都沒有包括農歷
也就是雖然提供了農歷類但對它的支持並不及同樣有閏月的希伯來歷我查資料的時候找到了博客堂的一篇文章<; 作者在一年半以前發現了農歷類不支持日期格式化的問題並認為這是一個bug當然還算不上bug只不過微軟沒有重視而已(責任在微軟嗎?我想應該不是在商業社會我們有多重視微軟就會有多重視和以色列比起來我們對傳統文化的重視程度差得太遠)
三農歷類的使用
既框架不支持直接將日期轉換成農歷格式的字符串那麼要將顯示農歷格式的日期就只要自已寫代碼了不過由於已經有了ChineseLunisolarCalendar類實現了公歷轉換為農歷日期的功能所以要寫這樣的代碼也比較簡單需要用到ChineseLunisolarCalendar以下幾個主要方法
int GetYear (DateTime time) 獲取指定公歷日期的農歷年份使用的還是公歷紀元在每年的元旦之後春節之前農歷的紀年會比公歷小其它時候等於公歷紀年雖然農歷使用傳說中的耶稣生日紀元似乎不太妥當不過我們確實已經幾十年沒有實行一個更好的紀年辦法也只有將就了
int GetMonth (DateTime time) 獲取指定公歷日期的農歷月份這裡要注意了由於農歷有接近三分之一的年份存在閏月則在這些年份裡會有十三個而具體哪一個月是閏月也說不准這裡不同於希伯來歷以今年為例今年閏七月則此方法在參數為閏七月的日期是返回值為參數為農歷十二月的日期時返回值為
bool IsLeapMonth ( int year int month) 獲取指定農歷年份和月份是否為閏月這個函數和上個函數配合使用就可以算出農歷的月份了
int GetDayOfMonth (DateTime time) 獲取指定公歷日期的農歷天數這個值根據大月或者小月取值是到或者到 MSDN上說的到顯然是錯的 沒有哪個農歷月份會有天
int GetSexagenaryYear (DateTime time) 獲取指定公歷日期的農歷年份的干支紀年從到分別是甲子乙丑丙寅…癸亥 比如戊戌變法辛亥革命就是按這個來命名的當然算八字也少不了這個
int GetCelestialStem (int sexagenaryYear) 獲取一個天支的天干 從到 表示甲乙丙…說白了就是對取模
int GetTerrestrialBranch (int sexagenaryYear) ) 獲取一個干支的地支 從到 表示子丑寅…今年是狗年那麼今年年份的地支就是戌
有了這幾個方法顯示某天的農歷月份日期農歷節日等都是小菜一碟算命先生排八字用這幾個方法又快又准確寫出的代碼也很短
四幾種東亞農歷類的區別
經過我的測試ChineseLunisolarCalendar JapaneseLunisolarCalendar KoreanLunisolaCalendarr TaiwanLunisolarCalendar這四種日歷無論哪一種以年月日為參數調用它們的GetMonth方法得到的結果都是GetDayOfMonth得到的結果都是想想也是我們過的端午節和韓國的不太可能不是一天
但是調用GetYear方法得到結果就有區別了ChineseLunisolarCalendar和KoreanLunisolarCalendar都返回也就是公歷紀年TaiwanLunisolarCalendar的返回值是依然是民國紀年JapaneseLunisolarCalendar的返回值是 平成紀年
另外的一個區別是這四種日歷的MinSupportedDateTime和MaxSupportedDateTime各不一樣以下是對照表
日歷類MinSupportedDateTimeMaxSupportedDateTime
ChineseLunisolarCalendar公元年月初公元年月
TaiwanLunisolarCalendar民國年月初民國年月
JapaneseLunisolarCalendar昭和年月初平成年月
KoreanLunisolarCalendar公元年月初公元年月
韓國農歷類支持的最小日期為年(也即高麗王朝建立的年份)以此而論中國農歷類支持的最小日期不說從商周算起從漢唐算總該沒問題吧?微軟公司啊又在厚彼薄此唉
其次日本還以天皇紀年如果哪天xxxx 豈不是使用JapaneseLunisolarCalendar寫出的程序都有問題啦?
五寫自已的日期格式化器
昨天看了一篇文章說目前大家用的農歷這個術語是文革時期才有的目的是反封建這裡為了省事還是繼續使用這個術語而英文名稱ChineseLunisolarCalendar太長我自己的代碼中就用ChineseCalendar為相關功能命名這個名字也還過得去吧
我原先設想自定義一個類使得能寫出這樣的代碼
string s= DateTimeNowToString(new MyFormatProvider());
雖然不能為DataTime寫自定義的格式器但還有另外一個途徑就是為String類的Format方法寫自定義格式化器我測試了一下效果還不錯調用方式如下
string s= StringFormat(new ChineseCalendarFormatter() {:D}DateTimeNow);
可以得到二〇〇六年正月初九
string s= StringFormat(new ChineseCalendarFormatter() {:d}DateTimeNow);
可以得到丙戌年正月初九
雖然沒有前面所設想的方便但也還能接受全部代碼帖出如下
第一個類主要是封裝了農歷的一些常用字符和對日歷處理的最基本功能
就能得出我想要的農歷日期字符串經過測試卻失敗了依據我的分析微軟公司框架中把日期時間型的格式寫死了只能依據相關的地區采用固定的幾種顯示格式沒法再自行定義而前文已經說過而所有的相關格式微軟公司都放到一個名為culturenlp的文件中(這個文件在以前框架是一個獨立的文件 被作為一個資源編譯到mscorlibdll中) (我的這個不能為DateTime寫自已的格式化器的觀點沒有資料佐證如有不當之處請大家指正)
ChineseCalendarHelpercs
using System;
using SystemCollectionsGeneric;
using SystemText;
using SystemGlobalization;
public static class ChineseCalendarHelper
{
public static string GetYear(DateTime time)
{
StringBuilder sb = new StringBuilder();
int year = calendarGetYear(time);
int d;
do
{
d = year % ;
sbInsert( ChineseNumber[d]);
year = year / ;
} while (year > );
return sbToString();
}
public static string GetMonth(DateTime time)
{
int month = calendarGetMonth(time);
int year = calendarGetYear(time);
int leap = ;
//正月不可能閏月
for (int i = ; i <= month; i++)
{
if (calendarIsLeapMonth(year i))
{
leap = i;
break; //一年中最多有一個閏月
}
}
if (leap > ) month;
return (leap == month + ? 閏 : ) + ChineseMonthName[month ];
}
public static string GetDay(DateTime time)
{
return ChineseDayName[calendarGetDayOfMonth(time) ];
}
public static string GetStemBranch(DateTime time)
{
int sexagenaryYear = calendarGetSexagenaryYear(time);
string stemBranch = CelestialStemSubstring(sexagenaryYear % ) + TerrestrialBranchSubstring(sexagenaryYear % );
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[] { 正 二 三 四 五 六 七 八 九 十 十一 十二 };
}
第二個類為自定義格式化器
ChineseCalendarFormattercs
using System;
using SystemCollectionsGeneric;
using SystemText;
using SystemGlobalization;
using SystemThreading;
public class ChineseCalendarFormatter : IFormatProvider ICustomFormatter
{
//實現IFormatProvider
public object GetFormat(Type formatType)
{
if (formatType == typeof(ICustomFormatter))
return this;
else
return ThreadCurrentThreadCurrentCultureGetFormat(formatType);
}
//實現ICustomFormatter
public string Format(string format object arg IFormatProvider formatProvider)
{
string s;
IFormattable formattable = arg as IFormattable;
if (formattable == null)
s = argToString();
else
s = formattableToString(format formatProvider);
if (argGetType() == typeof(DateTime))
{
DateTime time = (DateTime)arg;
switch (format)
{
case D: //長日期格式
s = StringFormat({}年{}月{}
ChineseCalendarHelperGetYear(time)
ChineseCalendarHelperGetMonth(time)
ChineseCalendarHelperGetDay(time));
break;
case d: //短日期格式
s = StringFormat({}年{}月{} ChineseCalendarHelperGetStemBranch(time)
ChineseCalendarHelperGetMonth(time)
ChineseCalendarHelperGetDay(time));
break;
case M: //月日格式
s = StringFormat({}月{} ChineseCalendarHelperGetMonth(time)
ChineseCalendarHelperGetDay(time));
break;
case Y: //年月格式
s = StringFormat({}年{}月 ChineseCalendarHelperGetYear(time)
ChineseCalendarHelperGetMonth(time));
break;
default:
s = StringFormat({}年{}月{} ChineseCalendarHelperGetYear(time)
ChineseCalendarHelperGetMonth(time)
ChineseCalendarHelperGetDay(time));
break;
}
}
return s;
}
}
這段代碼中間處理格式那部份稍做改進就可以支持更多的日期格式
有了這兩段代碼為原型要實現計算和顯示一個日期的農歷日期及其它功能基本上就很容易了
實現
private string getDateString(DateTime dt)
{
CultureInfo ci = new CultureInfo(zhTW);
ciDateTimeFormatCalendar = new TaiwanCalendar();
return dtToString(Dci);
}
private string getDateString(DateTime dt)
{
CultureInfo ci = new CultureInfo(zhCN);
ciDateTimeFormatCalendar = new ChineseLunisolarCalendar();
return dtToString(Dci);
}
From:http://tw.wingwit.com/Article/program/ASP/201311/21710.html