Подтвердить что ты не робот

Добавление двух объектов DateTime вместе

Есть ли лучший способ добавить один объект DateTime к другому, чем это:

DateTime first = new DateTime(2000, 1, 1);
DateTime second = new DateTime(11, 2, 5, 10, 10, 11);

DateTime result = first.AddYears(second.Year);
DateTime result = first.AddMonths(second.Month);
...

и т.д.

В этом примере я хотел бы получить DateTime(2011, 3, 6, 10, 10, 11)

ИЗМЕНИТЬ

После интенсивного мозгового штурма, похоже, нет другого способа, но чтобы облегчить его, он может быть помещен внутри дополнительного класса и оператора + точно так же, как в ответе JonSkeet

4b9b3361

Ответ 1

Не имеет смысла добавлять два значения DateTime вместе. Если вы хотите представить "11 лет, 2 месяца, 5 дней, 10 часов, 10 минут и 11 секунд", то вы должны это представить. Это не то же самое, что 0011-02-05T10: 10: 11. В частности, вы никогда не сможете добавить "2 месяца и 30 дней", например. Аналогично, вы никогда не сможете добавить только один год, потому что у вас не может быть 0 значений месяца и дня в течение даты.

Теперь нет типа BCL, чтобы представить идею "11 лет [...]", но вы могли бы создать свой собственный достаточно легко. В качестве альтернативы вы можете использовать мой проект Noda Time, который имеет Period именно для этой цели:

var localDateTime = new LocalDate(2000, 1, 10).AtMidnight();
var period = new PeriodBuilder {
    Years = 11, Months = 2, Days = 5,
    Hours = 10, Minutes = 10, Seconds = 11
}.Build();
var result = localDateTime + period;

В отличие от некоторых других приведенных здесь ответов, вы не можете использовать TimeSpan для этой цели. TimeSpan не имеет понятия месяцев и лет, поскольку они различаются по длине, тогда как a TimeSpan представляет собой фиксированное количество тиков. (Если ваша самая большая единица - это дни, то вы можете использовать TimeSpan, но, учитывая ваш пример, я предполагаю, что вам нужны месяцы и годы.)

Если вы не хотите использовать Noda Time, я рекомендую вам подделать класс Period -like. Это достаточно легко сделать - например:

// Untested and quickly hacked up. Lots more API you'd probably
// want, string conversions, properties etc.
public sealed class Period
{
    private readonly int years, months, days, hours, minutes, seconds;

    public Period(int years, int months, int days,
                  int hours, int minutes, int seconds)
    {
        this.years = years;
        this.months = months;
        this.days = days;
        this.hours = hours;
        this.minutes = minutes;
        this.seconds = seconds;
    }

    public static DateTime operator+(DateTime lhs, Period rhs)
    {
        // Note: order of operations is important here.
        // Consider January 1st + (1 month and 30 days)...
        // what do you want the result to be?
        return lhs.AddYears(rhs.years)
                  .AddMonths(rhs.months)
                  .AddDays(rhs.days)
                  .AddHours(rhs.hours)
                  .AddMinutes(rhs.minutes)
                  .AddSeconds(rhs.seconds);
    }
}

Использование:

DateTime first = new DateTime(2000, 1, 1);
Period second = new Period(11, 2, 5, 10, 10, 11);
DateTime result = first + second;

Вам нужно знать, как DateTime.Add будет обрабатывать невозможные ситуации - например, добавление месяца к 31 января даст вам 28 или 29 февраля в зависимости от того, является ли он високосным годом.

Простой подход, который я перечислил здесь, проходя промежуточные значения, имеет свои недостатки, потому что это усечение может произойти дважды (добавление лет, а затем добавление месяцев), когда это не нужно - например, "29 февраля + 1 год + 1 месяц" может быть логически "29 марта", но на самом деле это будет "28 марта", так как усечение до 28 февраля произойдет до того, как будет добавлен месяц.

Попытка разработать "правильный" способ выполнения календарной арифметики жестоко трудна, особенно, поскольку в некоторых случаях люди могут не согласиться с тем, что означает "правильный" ответ, В приведенном выше коде я выбрал простоту и предсказуемость - в зависимости от ваших реальных требований вам может понадобиться нечто более сложное.

Ответ 2

У вас есть DateTime, который представляет собой момент времени. И вы хотите добавить к нему несколько лет/месяцев/дней/часов/минут/секунд.

Изменение в DataTime не является точкой, это вектор (разность между точками). Это очень легко ошибочно принять одно для другого, поскольку они часто имеют схожую структуру. Однако такая ошибка типа приводит к болью.

Избегание этого не устраняет вашу боль, но делает его управляемым.

Добавление двух DateTime вместе добавляет две точки вместе. Это похоже на добавление местоположения Лос-Анджелеса в Нью-Йорк.

Теперь добавление "вектора" Лос-Анджелеса в Нью-Йорк к Лондону имеет смысл, потому что вектор путешествия является вектором, а не точкой. А point + vector - это просто точка.

Итак, это означает, что вам нужно создать тип вектора времени. Простой промежуток времени - это вариант, но, вероятно, не подходит: потому что вам нужны месяцы, годы и дни, а не наносекунды или абсолютные длительности времени.

Я напишу имя вектора a CalendarVector, поскольку оно представляет движение в календаре, а не во времени.

Простым первым проходом является создание кортежа каждого подтипа времени - лет, месяцев, дней и т.д. - затем добавьте их в произвольном порядке к вашему оригинальному DateTime с перегруженным operator+.

Вы должны поддерживать:

DateTime = DateTime + CalendarVector
CalendarVector = CalendarVector + CalendarVector
CalendarVector = CalendarVector - CalendarVector
CalendarVector = int * CalendarVector
CalendarVector = - CalendarVector
DateTime = DateTime - CalendarVector
CalendarVector = DateTime - DateTime

в идеале. Перегрузка CalendarVector + DateTime является необязательной, но, вероятно, не нужна.

Однако, это только доставит вам половину пути.

Большая оставшаяся проблема заключается в том, что дополнение CalendarVector не коммутирует. Добавление 1 месяца в DateTime, а затем добавление 1 дня, отличается от добавления 1 дня, а затем добавления 1 месяца.

И это фундаментально.

Возникает проблема "что значит быть через месяц после 31 января", на что можно ответить, но любой разумный ответ на этот вопрос не решает проблему коммутации.

Ваш планируемый конструктор, где вы его кормите, количество лет, месяцев, дней, часов, минут секунд, таким образом, неоднозначно в том, что это значит.

Таким образом, надежное решение не должно иметь этого конструктора.

Решение состоит в создании типов Years, Months, Days, Hours, Minutes и Seconds, которые вы явно добавляете вместе. Порядок, в котором они добавляются вместе, - это порядок, в котором они применяются к DateTime, к которому вы добавляете. Запрещается коммутация и "упрощение", пока окончательное приложение на DateTime - поэтому +1 year, +2 days, -1 month, -1 year, -2 days, +1 month не является нулевым преобразованием.

Существует проблема с DateTime-DateTime - она ​​должна возвращать CalendarVector v такой, что lhs = rhs + v, но есть несколько таких векторов. Та же проблема может возникнуть со сферическими координатами - вы имеете в виду короткий путь вокруг Земли или долгий путь? Это не имеет значения в некоторых контекстах, но затем вы уменьшите вдвое результат, чтобы найти среднюю точку. Кроме того, вы получаете разрывы, когда приближаетесь к "дальней стороне света".

Поэтому мой совет состоял бы в том, чтобы сохранить список преобразований в объекте DateTime. 1 year - это преобразование, состоящее из добавления поля 1 в год и последующего восстановления других полей, чтобы они были согласованы. Эти преобразования поддерживают отрицание. Дополнение применяет их по одному, слева направо. Отрицание также может отменить порядок применения, а смежные преобразования "одного и того же типа" могут объединяться (так что +1 месяц -1 месяц становится преобразованием идентичности вместо операции зажима, основанной на следующем месяце в конце месяца), или нет (так что x = x+1 month, то x = x-1 month на следующей строке совпадает с x = x + 1 month - 1 month).

Еще один подход заключается в том, чтобы настаивать на том, чтобы пользователь предоставлял политику для того, что делать в этих исключительных обстоятельствах (которые происходят... все время), потому что эта проблема достаточно тернична, что библиотека, которая "решает" проблему, может в лучшем случае выявить проблемы и заставить программиста-клиента думать о них и принимать решения.