一简介 过年是中国(以及日本韩国等国)人民的第一大节日你怎么知道哪天过年?查日历或者听别人说?程序员当然有程序员的办法就是写程序啦 虽然公历(俗称的阳历)已经成了全世界的通用标准而且也具有多方面的优越性但在东亚地区还是离不开农历春节元宵端午中秋重阳这些节日是农历的大部份人的老爸老妈的生日也是农历的 早在框架出来的时候我就认为微软公司不应该厚彼薄此框架中提供了希伯来历等却没有提供更广泛使用的农历 而 中微软公司终于做出了这个小小的改进 在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); } |