??之前寫了個(gè)小倒計(jì)時(shí)工具砌烁,用到了農(nóng)歷日期的轉(zhuǎn)換筐喳,后來(lái)想想直接封裝個(gè)類得了,以后用起來(lái)也方便函喉,像國(guó)人的生日大多也都是農(nóng)歷來(lái)算的避归。于是研究了下農(nóng)歷的規(guī)則,結(jié)合了網(wǎng)上的一些算法管呵,封裝了下面的類(ChineseDateTime)槐脏。主要用到的是.Net類庫(kù)中的農(nóng)歷類System.Globalization.ChineseLunisolarCalendar(公元紀(jì)年與中國(guó)傳統(tǒng)農(nóng)歷紀(jì)年之間的相互轉(zhuǎn)換),日期范圍在1901-02-19到2101-01-28 23:59:59.999之間撇寞,也夠日常使用了顿天。不多說(shuō)了,直接上代碼:
using System;
using System.Globalization;
using System.Text;
using System.Linq;
using System.Linq.Expressions;
namespace Ich.Timer
{
/// <summary>
/// ChineseDateTime
/// 一日有十二時(shí)辰蔑担,一時(shí)辰有四刻牌废,一刻有三盞茶,一盞茶有兩柱香
/// 一柱香有五分啤握,一分有六彈指鸟缕,一彈指有十剎那,一剎那為一念
/// </summary>
public class ChineseDateTime
{
#region ====== 內(nèi)部常量 ======
private readonly ChineseLunisolarCalendar _chineseDateTime;
private readonly DateTime _dateTime;
private readonly int _serialMonth;
private static readonly string[] _chineseNumber = { "〇", "一", "二", "三", "四", "五", "六", "七", "八", "九" };
private static readonly string[] _chineseMonth =
{
"正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "冬", "臘"
};
private static readonly string[] _chineseDay =
{
"初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十",
"十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十",
"廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十"
};
private static readonly string[] _chineseWeek =
{
"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"
};
private static readonly string[] _celestialStem = { "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸" };
private static readonly string[] _terrestrialBranch = { "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥" };
private static readonly string[] _chineseZodiac = { "鼠", "牛", "虎", "免", "龍", "蛇", "馬", "羊", "猴", "雞", "狗", "豬" };
private static readonly string[] _solarTerm =
{
"小寒", "大寒", "立春", "雨水", "驚蟄", "春分",
"清明", "谷雨", "立夏", "小滿", "芒種", "夏至",
"小暑", "大暑", "立秋", "處暑", "白露", "秋分",
"寒露", "霜降", "立冬", "小雪", "大雪", "冬至"
};
private static readonly int[] _solarTermInfo = {
0, 21208, 42467, 63836, 85337, 107014, 128867, 150921, 173149, 195551, 218072, 240693, 263343, 285989,
308563, 331033, 353350, 375494, 397447, 419210, 440795, 462224, 483532, 504758
};
#endregion
#region ======= 構(gòu)建日期 ======
public ChineseDateTime(DateTime dateTime)
{
_chineseDateTime = new ChineseLunisolarCalendar();
if (dateTime < _chineseDateTime.MinSupportedDateTime || dateTime > _chineseDateTime.MaxSupportedDateTime)
{
throw new ArgumentOutOfRangeException(
$"參數(shù)日期不在有效的范圍內(nèi):只支持{_chineseDateTime.MinSupportedDateTime.ToShortTimeString()}到{_chineseDateTime.MaxSupportedDateTime}");
}
Year = _chineseDateTime.GetYear(dateTime);
Month = _chineseDateTime.GetMonth(dateTime);
Day = _chineseDateTime.GetDayOfMonth(dateTime);
IsLeep = _chineseDateTime.IsLeapMonth(Year, Month);
_dateTime = dateTime;
_serialMonth = Month;
var leepMonth = _chineseDateTime.GetLeapMonth(Year);
if (leepMonth > 0 && leepMonth <= Month) Month--;
}
/// <summary>
/// 參數(shù)為農(nóng)歷的年月日及是否潤(rùn)月
/// </summary>
/// <param name="year"></param>
/// <param name="month"></param>
/// <param name="day"></param>
/// <param name="isLeap"></param>
public ChineseDateTime(int year, int month, int day, bool isLeap = false)
: this(year, month, day, 0, 0, 0, isLeap)
{
}
public ChineseDateTime(int year, int month, int day, int hour, int minute, int second, bool isLeap = false)
: this(year, month, day, hour, minute, second, 0, isLeap)
{
}
public ChineseDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, bool isLeap = false)
{
_chineseDateTime = new ChineseLunisolarCalendar();
if (year < _chineseDateTime.MinSupportedDateTime.Year || year >= _chineseDateTime.MaxSupportedDateTime.Year)
{
throw new ArgumentOutOfRangeException(
$"參數(shù)年份不在有效的范圍內(nèi)排抬,只支持{_chineseDateTime.MinSupportedDateTime.Year}到{_chineseDateTime.MaxSupportedDateTime.Year - 1}");
}
if (month < 1 || month > 12) throw new ArgumentOutOfRangeException($"月份只支持1-12");
IsLeep = isLeap;
var leepMonth = _chineseDateTime.GetLeapMonth(year);
if (leepMonth - 1 != month)
IsLeep = false;
_serialMonth = month;
if (leepMonth > 0 && (month == leepMonth - 1 && isLeap || month > leepMonth - 1))
_serialMonth = month + 1;
if (_chineseDateTime.GetDaysInMonth(year, _serialMonth) < day || day < 1)
throw new ArgumentOutOfRangeException($"指定的月份天數(shù)懂从,不在有效的范圍內(nèi)");
Year = year;
Month = month;
Day = day;
_dateTime = _chineseDateTime.ToDateTime(Year, _serialMonth, Day, hour, minute, second, millisecond);
}
public static ChineseDateTime Now => new ChineseDateTime(DateTime.Now);
#endregion
#region ====== 年月日潤(rùn)屬性 ======
public int Year { get; }
public int Month { get; }
public int Day { get; }
/// <summary>
/// 是否為潤(rùn)月
/// </summary>
public bool IsLeep { get; }
#endregion
#region ====== 輸出常規(guī)日期 ======
/// <summary>
/// 轉(zhuǎn)換為公歷
/// </summary>
/// <returns></returns>
public DateTime ToDateTime()
{
return _chineseDateTime.ToDateTime(Year, _serialMonth, Day, _dateTime.Hour,
_dateTime.Minute,
_dateTime.Second, _dateTime.Millisecond);
}
/// <summary>
/// 短日期(農(nóng)歷)
/// </summary>
/// <returns></returns>
public string ToShortDateString()
{
return $"{Year}-{GetLeap(false)}{Month}-{Day}";
}
/// <summary>
/// 長(zhǎng)日期(農(nóng)歷)
/// </summary>
/// <returns></returns>
public string ToLongDateString()
{
return $"{Year}年{GetLeap()}{Month}月-{Day}日";
}
public new string ToString()
{
return $"{Year}-{GetLeap(false)}{Month}-{Day} {_dateTime.Hour}:{_dateTime.Minute}:{_dateTime.Second}";
}
#endregion
#region ====== 輸出中文日期及星期 ======
public string ToChineseString()
{
return ToChineseString("yMd");
}
public string ToChineseString(string format)
{
var year = GetYear();
var month = GetMonth();
var day = GetDay();
var date = new StringBuilder();
foreach (var item in format.ToCharArray())
{
switch (item)
{
case 'y':
date.Append($"{year}年");
break;
case 'M':
date.Append($"{month}月");
break;
case 'd':
date.Append($"{day}");
break;
default:
date.Append(item);
break;
}
}
var def = $"{year}年{month}月{day}";
var result = date.ToString();
return string.IsNullOrEmpty(result) ? def : result;
}
public string ChineseWeek => _chineseWeek[(int)_dateTime.DayOfWeek];
#endregion
#region ====== 輸出天干地支生肖 ======
public string ToChineseEraString()
{
return ToChineseEraString("yMdHm");
}
public string ToChineseEraString(string format)
{
var year = GetEraYear();
var month = GetEraMonth();
var day = GetEraDay();
var hour = GetEraHour();
var minute = GetEraMinute();
var date = new StringBuilder();
foreach (var item in format.ToCharArray())
{
switch (item)
{
case 'y':
date.Append($"{year}年");
break;
case 'M':
date.Append($"{month}月");
break;
case 'd':
date.Append($"{day}日");
break;
case 'H':
date.Append($"{hour}時(shí)");
break;
case 'm':
date.Append($"{minute}刻");
break;
default:
date.Append(item);
break;
}
}
var def = $"{year}年{month}月{day}日{(diào)hour}時(shí)";
var result = date.ToString();
return string.IsNullOrEmpty(result) ? def : result;
}
public string ChineseZodiac => _chineseZodiac[(Year - 4) % 12];
#endregion
#region ====== 輔助方法(Chinese) ======
private string GetYear()
{
var yearArray = Array.ConvertAll(Year.ToString().ToCharArray(), x => int.Parse(x.ToString()));
var year = new StringBuilder();
foreach (var item in yearArray)
year.Append(_chineseNumber[item]);
return year.ToString();
}
private string GetMonth()
{
return $"{GetLeap()}{_chineseMonth[Month - 1]}";
}
private string GetDay()
{
return _chineseDay[Day - 1];
}
private string GetLeap(bool isChinese = true)
{
return IsLeep ? isChinese ? "潤(rùn)" : "L" : "";
}
#endregion
#region ====== 輸助方法(天干地支)======
//年采用的頭尾法,月采用的是節(jié)令法蹲蒲,主流日歷基本上都這種結(jié)合番甩,如百度的日歷
private string GetEraYear()
{
var sexagenaryYear = _chineseDateTime.GetSexagenaryYear(_dateTime);
var stemIndex = _chineseDateTime.GetCelestialStem(sexagenaryYear) - 1;
var branchIndex = _chineseDateTime.GetTerrestrialBranch(sexagenaryYear) - 1;
return $"{_celestialStem[stemIndex]}{_terrestrialBranch[branchIndex]}";
}
private string GetEraMonth()
{
#region ====== 節(jié)令法 ======
var solarIndex = SolarTermFunc((x, y) => x <= y, out var dt);
solarIndex = solarIndex == -1 ? 23 : solarIndex;
var currentIndex = (int)Math.Floor(solarIndex / (decimal)2);
//天干
var solarMonth = currentIndex == 0 ? 11 : currentIndex - 1; //計(jì)算天干序(月份)
var sexagenaryYear = _chineseDateTime.GetSexagenaryYear(_dateTime);
var stemYear = _chineseDateTime.GetCelestialStem(sexagenaryYear) - 1;
if (solarMonth == 0) //立春時(shí),春節(jié)前后的不同處理
{
var year = _chineseDateTime.GetYear(dt);
var month = _chineseDateTime.GetMonth(dt);
stemYear = year == Year && month != 1 ? stemYear + 1 : stemYear;
}
if (solarMonth == 11) //立春在春節(jié)后届搁,對(duì)前一節(jié)氣春節(jié)前后不同處理
{
var year = _chineseDateTime.GetYear(dt);
stemYear = year != Year ? stemYear - 1 : stemYear;
}
int stemIndex;
switch (stemYear)
{
case 0:
case 5:
stemIndex = 3;
break;
case 1:
case 6:
stemIndex = 5;
break;
case 2:
case 7:
stemIndex = 7;
break;
case 3:
case 8:
stemIndex = 9;
break;
default:
stemIndex = 1;
break;
}
//天干序
stemIndex = (stemIndex - 1 + solarMonth) % 10;
//地支序
var branchIndex = currentIndex >= 11 ? currentIndex - 11 : currentIndex + 1;
return $"{_celestialStem[stemIndex]}{_terrestrialBranch[branchIndex]}";
#endregion
#region ====== 頭尾法 ======
//這里算法要容易些缘薛,原理和節(jié)令法一樣,只需取農(nóng)歷整年整月即可卡睦。未貼上來(lái)
#endregion
}
private string GetEraDay()
{
var ts = _dateTime - new DateTime(1901, 2, 15);
var offset = ts.Days;
var sexagenaryDay = offset % 60;
return $"{_celestialStem[sexagenaryDay % 10]}{_terrestrialBranch[sexagenaryDay % 12]}";
}
private string GetEraHour()
{
var hourIndex = (int)Math.Floor((_dateTime.Hour + 1) / (decimal)2);
hourIndex = hourIndex == 12 ? 0 : hourIndex;
return _terrestrialBranch[hourIndex];
}
private string GetEraMinute()
{
var realMinute = (_dateTime.Hour % 2 == 0 ? 60 : 0) + _dateTime.Minute;
return $"{_chineseNumber[(int)Math.Floor(realMinute / (decimal)30) + 1]}";
}
#endregion
#region ====== 24節(jié)氣 ======
/// <summary>
/// 當(dāng)前節(jié)氣宴胧,沒(méi)有則返回空
/// </summary>
public string SolarTerm
{
get
{
var i = SolarTermFunc((x, y) => x == y, out var dt);
return i == -1 ? "" : _solarTerm[i];
}
}
/// <summary>
/// 上一個(gè)節(jié)氣
/// </summary>
public string SolarTermPrev
{
get
{
var i = SolarTermFunc((x, y) => x < y, out var dt);
return i == -1 ? "" : _solarTerm[i];
}
}
/// <summary>
/// 下一個(gè)節(jié)氣
/// </summary>
public string SolarTermNext
{
get
{
var i = SolarTermFunc((x, y) => x > y, out var dt);
return i == -1 ? "" : $"{_solarTerm[i]}";
}
}
/// <summary>
/// 節(jié)氣計(jì)算(當(dāng)前年),返回指定條件的節(jié)氣序及日期(公歷)
/// </summary>
/// <param name="func"></param>
/// <param name="dateTime"></param>
/// <returns>-1時(shí)即沒(méi)找到</returns>
private int SolarTermFunc(Expression<Func<int, int, bool>> func, out DateTime dateTime)
{
var baseDateAndTime = new DateTime(1900, 1, 6, 2, 5, 0); //#1/6/1900 2:05:00 AM#
var year = _dateTime.Year;
int[] solar = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 };
var expressionType = func.Body.NodeType;
if (expressionType != ExpressionType.LessThan && expressionType != ExpressionType.LessThanOrEqual &&
expressionType != ExpressionType.GreaterThan && expressionType != ExpressionType.GreaterThanOrEqual &&
expressionType != ExpressionType.Equal)
{
throw new NotSupportedException("不受支持的操作符");
}
if (expressionType == ExpressionType.LessThan || expressionType == ExpressionType.LessThanOrEqual)
{
solar = solar.OrderByDescending(x => x).ToArray();
}
foreach (var item in solar)
{
var num = 525948.76 * (year - 1900) + _solarTermInfo[item - 1];
var newDate = baseDateAndTime.AddMinutes(num); //按分鐘計(jì)算
if (func.Compile()(newDate.DayOfYear, _dateTime.DayOfYear))
{
dateTime = newDate;
return item - 1;
}
}
dateTime = _chineseDateTime.MinSupportedDateTime;
return -1;
}
#endregion
}
}
類行數(shù)長(zhǎng)了點(diǎn)哈??表锻。
農(nóng)歷類的基本使用
1恕齐、當(dāng)前日期實(shí)例: var cdt = ChineseDateTime.Now;
2、輸出中文年月日:cdt.ToChineseString();
結(jié)果:二〇一八年六月初九
??支持簡(jiǎn)易參數(shù)"yMd"瞬逊,可分別單獨(dú)傳显歧。
??如:cdt.ToChineseString("yM");
結(jié)果:二〇一八年六月
3补胚、輸出天干地支:cdt.ToChineseEraString();
結(jié)果:戊戌年己未月甲寅日午時(shí)二刻
??支持簡(jiǎn)易參數(shù)"yMdHm",傳法同中文日期
4追迟、取得星期和生肖:
??星期:cdt.ChineseWeek;
結(jié)果:星期六
??生肖:cdt.ChineseZodiac;
結(jié)果:狗
5、24節(jié)氣骚腥,這里主要用于節(jié)令法干支的月份計(jì)算敦间,順便可以輸出一下。
??上一個(gè)節(jié)氣:cdt.SolarTermPrev;
結(jié)果:小暑束铭。(同年沒(méi)有則為空值廓块,非null)
其它一些說(shuō)明
側(cè)重點(diǎn)寫了些基礎(chǔ)使用的值,至于輸出的結(jié)果契沫,可根據(jù)自己的需求带猴,靈活調(diào)整。
主要用到ChineseLunisolarCalendar
類的如下方法:
1懈万、GetYear(DateTime dateTime)
返回指定日期中的農(nóng)歷年份
2拴清、GetMonth(DateTime dateTime)
返回指定日期中的農(nóng)歷月份。(1-13)有潤(rùn)月則順延
3会通、GetDayOfMonth(DateTime dateTime)
返回指定日期中的農(nóng)歷日
4口予、IsLeapMonth(int year,int month)
確定指定年份中的指定的月份是否為潤(rùn)月(農(nóng)歷)
5、GetLeapMonth(int year)
計(jì)算指定年份的閏月涕侈。無(wú)返回0沪停,如潤(rùn)6月,則返回 7
6裳涛、GetSexagenaryYear(DateTime dateTime)
計(jì)算甲子 (60 年) 循環(huán)木张,對(duì)應(yīng)于指定日期的年份
7、GetCelestialStem(int sexagenaryYear)
計(jì)算甲子 (60 年) 循環(huán)中的指定年份天干
8端三、GetTerrestrialBranch(int sexagenaryYear)
計(jì)算甲子 (60 年) 循環(huán)中的指定年份的地支
尾聲
不足之處多多指教 ?--快樂(lè)泥巴