2012-06-24 3 views
2

Я работаю над полупортами C++ структуры C# DateTime. (Это общественное достояние, если вам нужно check it out.) Мне очень нравится, когда класс вращается вокруг одного частного 64-битного целого. Это делает многие операции супер простыми и сохраняет класс легким. Части, над которыми я борюсь, - это расчеты с тиков на год, месяц или день. В настоящее время, I use loops, чтобы получить правильный ответ: я вычитаю за год количество тиков за раз, чтобы я мог вычесть ценность тисков високосного года в нужное время.Как реализована System.DateTime.Year (и другие ее свойства)?

Хорошей новостью является то, что он получает правильный ответ. Я просто предпочел бы использовать прямой математический подход, если это возможно. Я знаю, что C# не является открытым исходным кодом, но есть ли способ увидеть реализацию DateTime? Если нет, где я могу найти математику для преобразования N дней в год, месяц и день?

Пожалуйста, никаких комментариев о преждевременной оптимизации. Я не нахожусь в крайнем сроке. Я просто хочу сделать это лучше. Это опыт обучения.

ОБНОВЛЕНИЕ. Любому любопытно, мне также удалось увидеть реализацию в Mono. В исходном коде есть простой текст DateTime.cs.

+2

Вы уверены, что хотите это сделать? Даты - яма нескончаемой сдержанности разработчиков, и лучше всего оставить других людей, поэтому легко распределить вину, когда все пойдет не так! – spender

+0

Вот дегустация некоторых ужасных ошибок, которые вы тоже можете поверить. http: //infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-time ... и еще здесь ... http://infiniteundo.com/post/25509354022/more-falsehoods-programmers-believe -бам-время-мудрость – spender

+0

@spender Hahaha действительно. Поверьте мне, у меня есть ограничения. Я не планирую революцию в области API DateTime. Может быть, я просто пристрастился к малым головоломкам. – TheBuzzSaw

ответ

2

Ниже приведены фрагменты из структуры Date, которую я ранее реализовал. Конструктор содержит основную логику преобразования количества дней с 01.01.00 («серийный номер») в год, месяц и день. Он основан на фактическом исходном коде для 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 - мой проект даты и времени для .NET на основе Joda Time. Это, очевидно, должно делать подобные вещи.

Лично я не хотел использовать DateTime в качестве отправной точки для даты и времени API. Он имеет various unfortunate aspects. Конечно, есть намного лучшие API-интерфейсы даты/времени (не только время Noda, конечно).

Смежные вопросы