2012-06-24 79 views
2

我正在研究C#DateTime結構的C++半端口。 (如果你關注check it out,這是公有領域。)我真的很喜歡讓這個類圍繞一個私有的64位整數。它使得許多操作變得非常簡單並且保持了類的輕量級。我正在努力的部分是從蜱到年,月或日的計算。目前,I use loops可以得到正確的答案:我每次減去一年的蜱數,這樣我就可以在正確的時間減去閏年的蜱數。System.DateTime.Year(及其他屬性)如何實現?

好消息是它得到正確的答案。如果可能的話,我寧願使用直接的數學方法。我知道C#不是開源的,但有沒有辦法看到DateTime的實現?如果不是,我可以在哪裏找到將N天轉換爲年,月和日的數學?

請不要評論過早優化。我沒有截止日期。我只是想讓這個更好。這是一種學習體驗。

更新 - 對任何人都好奇,我也設法看到單聲道的實施。源代碼中有一個純文本DateTime.cs。

+2

您確定要這樣嗎?日期是一個無休止的開發者悲傷的坑,最好留給其他人,所以當一切都出錯時,很容易分攤責任! – spender

+0

這裏有一些你可能錯誤地相信的糟糕的錯誤。 http://infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-time ...還有更多這裏... http://infiniteundo.com/post/25509354022/more-falsehoods-programmers-believe -about-time-wisdom – spender

+0

@spender哈哈哈確實。相信我,我確實有限制。我不打算革新DateTime API領域。也許我只是沉迷於小數學難題。 – TheBuzzSaw

回答

2

以下是我之前實施的Date結構的摘錄。構造函數包含將自1/1/0001(「序列號」)以來的天數轉換爲年,月和日的主要邏輯。它基於System.DateTime的實際源代碼,但我使用了更多描述性變量名稱並添加了註釋。

DateTime的刻度數轉換時,您首先需要除以TimeSpan.TicksPerDay以獲取序列號。

/// <summary> 
/// Represents a date between January 1, 0001 CE, and December 31, 9999 CE, in the proleptic Gregorian calendar. 
/// </summary> 
public struct Date 
{ 
    public const Int32 DaysPerYear = 365; 
    public const Int32 MonthsPerYear = 12; 

    private const UInt32 MaxSerialNumber = 3652058; 
    private const UInt32 December = 11; // 0-based 
    private const UInt32 DaysInDecember = 31; 

    private const UInt32 LeapYearInterval1 = 4; 
    private const UInt32 LeapYearInterval2 = 100; 
    private const UInt32 LeapYearInterval3 = 400; 

    private const UInt32 DaysPerLeapYearInterval1 = 
     DaysPerYear * LeapYearInterval1 + 1; // +1 leap day every 4 years 
    private const UInt32 DaysPerLeapYearInterval2 = 
     DaysPerLeapYearInterval1 * (LeapYearInterval2/LeapYearInterval1) - 1; // -1 leap day every 100 years 
    private const UInt32 DaysPerLeapYearInterval3 = 
     DaysPerLeapYearInterval2 * (LeapYearInterval3/LeapYearInterval2) + 1; // +1 leap day every 400 years 

    private static readonly UInt32[] DaysOfYear = 
     new UInt32[(MonthsPerYear + 1) * 2] 
     { 
      0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365, 
      0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366, 
     }; 

    private readonly UInt16 _zeroBasedYear; // 0 to 9998 
    private readonly Byte _zeroBasedMonth; // 0 to 11 
    private readonly Byte _zeroBasedDay; // 0 to 30 

    /// <summary> 
    /// Initializes a new instance of the <see cref="Date" /> structure to a specified serial number. 
    /// </summary> 
    /// <param name="serialNumber"> 
    /// The <see cref="SerialNumber" /> of the new <see cref="Date" />. 
    /// </param> 
    /// <exception cref="ArgumentOutOfRangeException"> 
    /// <paramref name="serialNumber" /> is less than 0 or greater than 3652058. 
    /// </exception> 
    public Date(Int32 serialNumber) 
    { 
     Require.IsBetween("serialNumber", serialNumber, 0, (Int32)MaxSerialNumber); 
     UInt32 days = (UInt32)serialNumber; 

     // Find the first year of the 400-year period that contains the date: 
     UInt32 zeroBasedYear = days/DaysPerLeapYearInterval3 * LeapYearInterval3; 
     days %= DaysPerLeapYearInterval3; 

     // Within the 400-year period, advance to the first year of the century that contains the date: 
     UInt32 centuries = days/DaysPerLeapYearInterval2; 
     zeroBasedYear += centuries * LeapYearInterval2; 

     // Special case: If the date is the last day (December 31) of the 400-year period, 
     // then "centuries" will be out of range because the fourth century has one more day than the others: 
     if (centuries == LeapYearInterval3/LeapYearInterval2) 
      goto December31; 

     days %= DaysPerLeapYearInterval2; 

     // Within the century, advance to the first year of the 4-year period that contains the date: 
     zeroBasedYear += days/DaysPerLeapYearInterval1 * LeapYearInterval1; 
     days %= DaysPerLeapYearInterval1; 

     // Within the 4-year period, advance to the year that contains the date: 
     UInt32 years = days/DaysPerYear; 
     zeroBasedYear += years; 

     // Special case: If the date is the last day (December 31) of the 4-year period, 
     // then "years" will be out of range because the fourth year has one more day than the others: 
     if (years == LeapYearInterval1) 
      goto December31; 

     days %= DaysPerYear; 

     // Estimate the month using an efficient divisor: 
     Int32 index = GetDaysOfYearIndex(zeroBasedYear); 
     UInt32 zeroBasedMonth = days/32; 

     // If the estimate was too low, adjust it: 
     if (days >= DaysOfYear[index + (Int32)zeroBasedMonth + 1]) 
      ++zeroBasedMonth; 

     _zeroBasedYear = (UInt16)zeroBasedYear; 
     _zeroBasedMonth = (Byte)zeroBasedMonth; 
     _zeroBasedDay = (Byte)(days - DaysOfYear[index + (Int32)zeroBasedMonth]); 
     return; 

    December31: 
     _zeroBasedYear = (UInt16)(zeroBasedYear - 1); 
     _zeroBasedMonth = (Byte)December; 
     _zeroBasedDay = (Byte)(DaysInDecember - 1); 
    } 

    private static Int32 GetDaysOfYearIndex(UInt32 zeroBasedYear) 
    { 
     return !InternalIsLeapYear(zeroBasedYear) ? 0 : MonthsPerYear + 1; 
    } 

    private static Boolean InternalIsLeapYear(UInt32 zeroBasedYear) 
    { 
     UInt32 year = zeroBasedYear + 1; 
     return 
      (year % LeapYearInterval1 == 0) && 
      (year % LeapYearInterval2 != 0 || year % LeapYearInterval3 == 0); 
    } 
} 
+0

現在我們在說話'。而且,是的,在開始計算之前,我確實計算出將我的刻度轉換爲幾天。這就像我在恐懼之後蜷縮在我的圈子裏一樣。 – TheBuzzSaw

0

嘗試使用反射器來查看實現?儘管它最終可能會調用某些非託管代碼。或者,看一下Mono,看看他們是否有實施。

0

你不能輕易得到.NET的實現(無需反編譯),但歡迎來看Noda Time的源碼 - 我的基於Joda Time的.NET日期和時間項目。這顯然必須做類似的事情。

個人我不會使用DateTime作爲日期和時間API的起點。它有various unfortunate aspects它。有更好的日期/時間API來看待(當然不只是Noda時間)。

相關問題