System.DateTime運算小範例(迄今時間計算:年月天)
今天遇到一個以前沒有處理過的需求,就是要計算兩個日期的差距,但必須以過了幾年、幾月、幾天來表示,這事情初步想起來很簡單,用System.TimeSpan就解決了,但仔細一想其實蠻複雜的。
這類型需求的應用,其實並不算罕見,例如我們想要顯示:「你現在已經活了OO年OO月OO天,請把握生命!」、「這篇貼文迄今已經過了OO年OO月OO天」之類的,對於一些強迫症的老闆來說,還真的是會有這樣的要求。例如:
Posted 0 years, 0 months, 0 days, 0 hours, 0 minutes and 0 seconds ago
System.TimeSpan支援方法不足
可惜的是,我們的好朋友System.TimeSpan的Totla系列方法,並沒有提供上面的需求,他只有下列的方法:
- TotalMilliseconds
- TotalSeconds
- TotalMinutes
- TotalHours
- TotalDays
啊,我說那個TotalMonths或是TotalYears呢?!
計算兩個日期相差的年月日時分秒(不考慮閏年與起計月)
在看不到System.TimeSpan提供相關方法後,一度開始想要自幹寫一些判斷閏年天數的方法,但發現不對,我可以使用System.DateTime啊!可以站在巨人的肩膀上何必自己寫演算法,況且既複雜又有可能出錯~以下就是程式碼嘍!
public static void Main()
{ //dArticle為文章張貼日期,可視為dStart之意
System.DateTime dArticle = System.Convert.ToDateTime("2000-02-29");
System.DateTime dNow = System.DateTime.Now;
System.TimeSpan oTS = new System.TimeSpan(dNow.Ticks - dArticle.Ticks);
//減去System.DateTime.MinValue: 1Years 1Months 1Days
System.DateTime oDT = System.DateTime.MinValue.AddTicks(oTS.Ticks).AddYears(-1).AddMonths(-1).AddDays(-1);
Console.WriteLine($"Article Date: {dArticle.ToString("yyyy-MM-dd")} to {dNow.ToString("yyyy-MM-dd")}");
Console.WriteLine($"Posted {oDT.Year} years, {oDT.Month} months, {oDT.Day} days, {oDT.Hour} hours, {oDT.Minute} minutes and {oDT.Second} seconds ago");
}
輸出結果:
Article Date: 2000-02-29 to 2018-10-03
Posted 18 years, 7 months, 5 days, 10 hours, 23 minutes and 30 seconds ago
用Windows小算盤驗證結果相符:
注意
上面這種運算解法,是巧妙的透過TimeSpan的Ticks轉回至DateTime類別,並由西元元年開始計算(System.DateTime.MinValue),這會導致兩個隱性的BUG:
- 如果計算週期小於一年期,則會引爆加減日期的System.ArgumentOutOfRangeException錯誤。
- 重新起計的西元元年未必會對齊當前程式運算dArticle表定的閏年順序與月份順序,因此計算出來的日期也會有數天誤差。
基於上面BUG,如果計算長跨度的人員不太在意這兩三天「認知等級」的差距倒也無妨,畢竟運算規則簡單好理解。但如果是涉及保險理賠或年金、或是精實老闆的喜好之計算,可能得好好的考量修正議題。
計算兩個日期相差的年月日時分秒(閏年版)
其實,保險或年金這種長跨度的計算,法規上都是以每個月30日列計,沒有人在那邊跟你囉嗦閏年的問題。但如果需要考慮閏年的情況,就必須要重新撰寫計算的方法拋棄整體差值的思路,改採自起始日真實起計的思維,由於程式碼開始變長因此將其包裝成方法以利後續引用,但整體的概念仍然是站在巨人的肩膀上操弄DateTime物件:(dArticle置換成dStart較易理解)
public static (int iYears, int iMonths, int iDays, int iHours, int iMinutes, int iSeconds) CalcDateTimeDuration(System.DateTime dStart, System.DateTime dEnd)
{
//日期若錯置就交換(很重要,可以讓後面的修正程序單純化)
if (dEnd.CompareTo(dStart) < 0)
{ (dStart, dEnd) = (dEnd, dStart); }
//調用.NET方法規避掉閏年計算問題
int iYears = dEnd.Year - dStart.Year;
int iMonths = dEnd.Month - dStart.Month;
int iDays = dEnd.Day - dStart.Day;
int iHours = dEnd.Hour - dStart.Hour;
int iMinutes = dEnd.Minute - dStart.Minute;
int iSeconds = dEnd.Second - dStart.Second;
//修正單純運算後衍生的問題
if (iSeconds < 0)
{ iMinutes--; iSeconds += 60; }
if (iMinutes < 0)
{ iHours--; iMinutes += 60; }
if (iHours < 0)
{ iDays--; iHours += 24; }
if (iDays < 0)
{ iMonths--; iDays += System.DateTime.DaysInMonth(dEnd.AddMonths(-1).Year, dEnd.AddMonths(-1).Month); }
if (iMonths < 0)
{ iYears--; iMonths += 12; }
//回傳
return (iYears, iMonths, iDays, iHours, iMinutes, iSeconds);
}
用來計算2000-02-29 21:22:23~2018-10-03 20:21:22時間區間,可以得到下列輸出結果:
Article Date: 2000-02-29 21:22:23 to 2018-10-03 20:21:22
Posted 18 years, 7 months, 3 days, 22 hours, 58 minutes, and 59 seconds ago
相關連結: