有是「保存」日期的兩種方法:分別保存年份,月份,日期或從「0點」保存總天數(或小時或分鐘或秒或毫秒...選擇此處的度量單位)。 .NET的DateTime
例如使用100納秒作爲Tick
,0001年1月1日作爲「0點」。 Unix使用1970年1月1日以來的秒數。顯然,.NET和Unix的方式在內存中更加緊湊(單個值來保存),如果你想添加/減少數量(簡單地添加/減少它),它非常有用。問題在於將此內部號碼轉換爲年/月/日或將年/月/日轉換爲此號碼會更復雜。
如何年/月/日,以內部號碼可以做一個簡單的例子:
public class MyDate
{
public int TotalDaysFrom00010101 { get; private set; }
private const int DaysIn400YearCycle = 365 * 400 + 97;
private const int DaysIn100YearCycleNotDivisibleBy400 = 365 * 100 + 24;
private const int DaysIn4YearCycle = 365 * 4 + 1;
private static readonly int[] DaysPerMonthNonLeap = new[]
{
31,
31 + 28,
31 + 28 + 31,
31 + 28 + 31 + 30,
31 + 28 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31 // Useless
};
private static readonly int[] DaysPerMonthLeap = new[]
{
31,
31 + 29,
31 + 29 + 31,
31 + 29 + 31 + 30,
31 + 29 + 31 + 30 + 31,
31 + 29 + 31 + 30 + 31 + 30,
31 + 29 + 31 + 30 + 31 + 30 + 31,
31 + 29 + 31 + 30 + 31 + 30 + 31 + 31,
31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31 // Useless
};
public static bool IsLeap(int year)
{
if (year % 400 == 0) return true;
return (year % 4 == 0) && (year % 100 != 0);
}
public void SetDate(int year, int month, int day)
{
TotalDaysFrom00010101 = 0;
{
int year2 = year - 1;
// Full 400 year cycles
TotalDaysFrom00010101 += (year2/400) * DaysIn400YearCycle;
year2 %= 400;
// Remaining 100 year cycles (0...3)
if (year2 >= 100)
{
year2 -= 100;
TotalDaysFrom00010101 += DaysIn100YearCycleNotDivisibleBy400;
if (year2 >= 100)
{
year2 -= 100;
TotalDaysFrom00010101 += DaysIn100YearCycleNotDivisibleBy400;
if (year2 >= 100)
{
year2 -= 100;
TotalDaysFrom00010101 += DaysIn100YearCycleNotDivisibleBy400;
}
}
}
// Full 4 year cycles
TotalDaysFrom00010101 += (year2/4) * DaysIn4YearCycle;
year2 %= 4;
// Remaining non-leap years
TotalDaysFrom00010101 += year2 * 365;
}
// Days from the previous month
if (month > 1)
{
// -2 is because -1 is for the 1 January == 0 index, plus -1
// because we must add only the previous full months here.
// So if the date is 1 March 2016, we must add the days of
// January + February, so month 3 becomes index 1.
TotalDaysFrom00010101 += DaysPerMonthNonLeap[month - 2];
if (month > 2 && IsLeap(year))
{
TotalDaysFrom00010101 += 1;
}
}
// Days (note that the "instant 0" in this class is day 1, so
// we must add day - 1)
TotalDaysFrom00010101 += day - 1;
}
public void GetDate(out int year, out int month, out int day)
{
int days = TotalDaysFrom00010101;
// year
{
year = days/DaysIn400YearCycle * 400;
days %= DaysIn400YearCycle;
if (days >= DaysIn100YearCycleNotDivisibleBy400)
{
year += 100;
days -= DaysIn100YearCycleNotDivisibleBy400;
if (days >= DaysIn100YearCycleNotDivisibleBy400)
{
year += 100;
days -= DaysIn100YearCycleNotDivisibleBy400;
if (days >= DaysIn100YearCycleNotDivisibleBy400)
{
year += 100;
days -= DaysIn100YearCycleNotDivisibleBy400;
}
}
}
year += days/DaysIn4YearCycle * 4;
days %= DaysIn4YearCycle;
// Special case: 31 dec of a leap year
if (days != 1460)
{
year += days/365;
days %= 365;
}
else
{
year += 3;
days = 365;
}
year++;
}
// month
{
bool isLeap = IsLeap(year);
int[] daysPerMonth = isLeap ? DaysPerMonthLeap : DaysPerMonthNonLeap;
for (month = 0; month < daysPerMonth.Length; month++)
{
if (daysPerMonth[month] > days)
{
if (month > 0)
{
days -= daysPerMonth[month - 1];
}
break;
}
}
month++;
}
// day
{
day = days;
day++;
}
}
public void AddDays(int days)
{
TotalDaysFrom00010101 += days;
}
}
這裏的關鍵是,我們知道,有400年「週期」,而每一個這些「週期」有365 * 400 + 97
天。扣除這些「週期」後,有100年的「週期」較短,每一天有365 * 100 + 24
天。然後我們有4年的「週期」,每個週期爲365 * 4 + 3
天,再加上剩下的年份(0-3),每個週期365天。
在爲「所有前幾年」添加了日子後,我們可以添加「前幾個月」的日子。在這裏,我們必須考慮今年是閏年的可能性。
最後我們添加選定日期。
如何編寫GetDate()
留作練習。
現在...如何檢查結果是否正確?我們可以寫一個基於該DateTime
實施一些單元測試...喜歡的東西:
var date = default(DateTime);
var endDate = new DateTime(2017, 1, 1);
while (date < endDate)
{
int year = date.Year;
int month = date.Month;
int day = date.Day;
int totalDays = (int)(date - default(DateTime)).TotalDays;
var md = new MyDate();
md.SetDate(year, month, day);
if (totalDays != md.TotalDaysFrom00010101)
{
Console.WriteLine("{0:d}: {1} vs {2}", date, totalDays, md.TotalDaysFrom00010101);
}
int year2, month2, day2;
md.GetDate(out year2, out month2, out day2);
if (year != year2 || month != month2 || day != day2)
{
Console.WriteLine("{0:d}: {1:D4}-{2:D2}-{3:D2} vs {4:D4}-{5:D2}-{6:D2}", date, year, month, day, year2, month2, day2);
}
date = date.AddDays(1);
}
(我知道這是不是一個單元測試......但展示瞭如何使用DateTime
做一個比較)
請注意,這是一個如何實現它的例子。我不會這樣實施它。我會做一個不變的struct
像DateTime
,用構造函數而不是SetDate()
。但是你的觀點似乎是一個練習,而且我認爲應該採取小步驟:首先正確地構建一些東西,然後使它「正式地正確」。所以第一步是建立一個正確的,然後你可以正確地封裝在一個更正式的數據結構。這個小樣本甚至缺少一些參數驗證:您可以SetDate(-1, 13, 32)
:-)
GetDate()
構建起來相當複雜。比我想象的要多得多。最後,我能夠理解如何編寫它。請注意,最終它與微軟的實施非常相似(請參閱GetDatePart(int part)
。
* GetTotalDays(new [] {days,months,years})*請勿。請勿將三個完全斷開連接的數字它們是三個參數,它們應該有名稱,比如* year *,* month *,* day *,不是'date [2]','date [1]','date [0]' – xanatos
我是計劃爲他們創建枚舉,但我沒有 – KOPEUE
創建一個類或結構來保存數據並進行計算。也許你不能*使用* DateTime,但你可以模仿它做什麼 – Plutonix