NET framework2.0中的农历类(全)

  一、简介
过年是中国 ( 以及日本、韩国等国 ) 人民的第一大节日。你怎么知道哪天过年?查日历或者听别人说?程序员当然有程序员的办法,就是写程序啦。
虽然公历 ( 俗称的 阳历 ”) 已经成了全世界的通用标准,而且也具有多方面的优越性。但在东亚地区,还是离不开 农历”,春节、元宵、端午、中秋、重阳这些节日是农历的,大部份人的老爸老妈的生日也是农历的。
早在 1.0 框架出来的时候,我就认为微软公司不应该 厚彼薄此 ,在 .net 框架中提供了希伯来历等,却没有提供更广泛使用的 农历
而在 .net 2.0 中,微软公司终于做出了这个小小的改进。
.net 2.0 System.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) 获取指定公历日期的农历天数,这个值根据大月或者小月取值是 1 30 或者 1 29, MSDN 上说的 1 31 显然是错的 , 没有哪个农历月份会有 31 天。
int GetSexagenaryYear (DateTime time) 获取指定公历日期的农历年份的干支纪年,从 1 60 ,分别是甲子、乙丑、丙寅、 …. 癸亥 , 比如戊戌变法、辛亥革命就是按这个来命名的。当然算八字也少不了这个。
int GetCelestialStem (int sexagenaryYear) 获取一个天支的天干 , 1 10, 表示甲、乙、丙 …. ,说白了就是对 10 取模。
int GetTerrestrialBranch (int sexagenaryYear) ) 获取一个干支的地支, , 1 12, 表示子、丑、寅、 今年是狗年,那么今年年份的地支就是“戌”。
有了这几个方法,显示某天的农历月份日期、农历节日等都是小菜一碟,算命先生排八字用这几个方法,又快又准确,写出的代码也很短。
 
四、几种东亚农历类的区别
经过我的测试,ChineseLunisolarCalendar, JapaneseLunisolarCalendar, KoreanLunisolaCalendarr, TaiwanLunisolarCalendar这四种日历,无论哪一种,以2006年2月6日为参数,调用它们的GetMonth方法得到的结果都是1,GetDayOfMonth得到的结果都是8。想想也是,我们过的端午节和韩国的不太可能不是一天。
但是调用GetYear方法得到结果就有区别了ChineseLunisolarCalendar和KoreanLunisolarCalendar都返回2006,也就是公历纪年,TaiwanLunisolarCalendar的返回值是95,依然是民国纪年,JapaneseLunisolarCalendar的返回值是18, 平成纪年。
另外的一个区别是这四种日历的MinSupportedDateTime 和MaxSupportedDateTime各不一样,以下是对照表:

日历类MinSupportedDateTimeMaxSupportedDateTime
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  -  1 1 +  TerrestrialBranch.Substring(sexagenaryYear  %  12  -  1 1 );
        
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);
}
 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值