五、写自已的日期格式化器

昨天看了一篇文章,说目前大家用的“农历”这个术语是文革时期才有的,目的是反封建。这里为了省事,还是继续使用这个术语。而英文名称ChineseLunisolarCalendar太长,我自己的代码中就用ChineseCalendar为相关功能命名,这个名字也还过得去吧。

我原先设想自定义一个类,使得能写出这样的代码:

string s= DateTime.Now.ToString(new MyFormatProvider());

就能得出我想要的农历日期字符串,经过测试却失败了,依据我的分析,微软公司在.net框架中把日期时间型的格式写死了,只能依据相关的地区采用固定的几种显示格式,没法再自行定义。而前文已经说过,而所有的相关格式微软公司都放到一个名为culture.nlp的文件中(这个文件在以前的.net框架是一个独立的文件,在.net 2.0被作为一个资源编译到mscorlib.dll中。) (我的这个不能为DateTime写自已的格式化器的观点没有资料佐证,如有不当之处,请大家指正)

 

虽然不能为DataTime写自定义的格式器,但还有另外一个途径,就是为String类的Format方法写自定义格式化器,我测试了一下,效果还不错,调用方式如下:

string s= String.Format(new ChineseCalendarFormatter(), "{0:D}",DateTime.Now);

可以得到“二〇〇六年正月初九”

 

string s= String.Format(new ChineseCalendarFormatter(), "{0:d}",DateTime.Now);

可以得到“丙戌年正月初九”

 

虽然没有前面所设想的方便,但也还能接受,全部代码帖出如下:

第一个类,主要是封装了农历的一些常用字符和对日历处理的最基本功能

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;
    }
}
这段代码中间处理格式那部份稍做改进,就可以支持更多的日期格式。

有了这两段代码为原型,要实现计算和显示一个日期的农历日期及其它功能,基本上就很容易了。