вычисление разницы в месяцах между двумя датами

128

В C # /. NET TimeSpanимеет TotalDays, TotalMinutesи т.д. , но я не могу понять формулу для разницы всего месяца. Переменные дни в месяц и високосные годы меня сбивают с толку. Как я могу получить TotalMonths ?

Редактировать Извините за непонятность: я знаю, что на самом деле не могу получить это, TimeSpanно я подумал, что использование TotalDaysи TotalMinutesбудет хорошим примером, чтобы выразить то, что я искал ... за исключением того, что я пытаюсь получить общее количество месяцев.

Пример: 25 декабря 2009 г. - 6 октября 2009 г. = 2 TotalMonths. С 6 октября по 5 ноября равно 0 месяцев. 6 ноября, 1 месяц. 6 декабря, 2 месяца

Дина
источник
2
Чего вы ждете от 25 декабря 2009 г. по 6 октября 2009 г.?
Джефф Мозер,
2
Как вы определяете TimeSpan в месяцах?
Aliostad
1
@Aliostad - Без дат вы могли бы определить месяц как 30 дней и быть довольно точным.
ChaosPandion
Он почему-то слился с этим вопросом модом.
Jamiec
На самом деле, вам нужно прочитать мой пост здесь, который отвечает на этот вопрос и предоставляет закодированное решение, stackoverflow.com/questions/1916358/… игнорируйте троллей (brianary) и обратите внимание на мою беседу через комментарии с supercat. Месяцы, которые находятся в начале и в конце промежутка времени, мы называем «Месяцами-сиротами», и вопрос сводится к тому, как определить эти «осиротевшие месяцы» в днях - как только вы определили это (и как вы хотите это определить ), остальное - просто код (который прилагается). Моя деф. основан на том, что, я думаю, ожидают мои пользователи
Erx_VB.NExT.Coder

Ответы:

222

Вы не сможете получить это из a TimeSpan, потому что «месяц» - это переменная единица измерения. Вам придется рассчитать это самостоятельно, и вам нужно будет выяснить, как именно вы хотите, чтобы это работало.

Например, должны ли даты быть такими, как July 5, 2009и August 4, 2009давать разницу в один месяц или ноль месяцев? Если вы говорите, что он должен уступить один, то как насчет July 31, 2009и August 1, 2009? Является ли, что в месяц? Это просто разница Monthзначений дат или это больше связано с фактическим промежутком времени? Логика определения всех этих правил нетривиальна, поэтому вам придется определить свое собственное и реализовать соответствующий алгоритм.

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

public static int MonthDifference(this DateTime lValue, DateTime rValue)
{
    return (lValue.Month - rValue.Month) + 12 * (lValue.Year - rValue.Year);
}

Обратите внимание, что это возвращает относительную разницу, что означает, что если rValueбольше lValue, то возвращаемое значение будет отрицательным. Если вам нужна абсолютная разница, вы можете использовать это:

public static int MonthDifference(this DateTime lValue, DateTime rValue)
{
    return Math.Abs((lValue.Month - rValue.Month) + 12 * (lValue.Year - rValue.Year));
}
Адам Робинсон
источник
@ Дина, это всего лишь приближение, если вы хотите знать истинные .Month и .Years - я только что опубликовал ответ на то, что вы можете прочитать. Хотя, что касается приближений, это хорошее приближение (реквизит Адама Робинсона), однако вы должны иметь в виду, что если вы используете какое-либо из этих приближений, вы просто непреднамеренно лжете своим пользователям.
Erx_VB.NExT.Coder
@ Erx_VB.NExT.Coder: Спасибо за реквизиты, но хотя в вашем ответе говорится, что ни один из ответов не учитывает тот факт, что месяц является переменной единицей измерения, похоже, что большинство из них это делает; они просто не используют ваше конкретное приближение. Показательный пример: самое первое предложение в моем ответе указывает на то, что оно переменное. Любой ответ, включая ваш, является приблизительным просто потому, что это неточный ответ. Ваш результат «2 месяца» может означать разные вещи для разных входных данных, поэтому это приблизительное значение.
Адам Робинсон
у меня не является приблизительным значением, если сегодня 14 марта, то два предыдущих месяца рассчитываются на основе того факта, что в январе было 31 день, а в feb - 29 дней. Теперь вы правы в том, что мой метод не является определением "общего" месяца, а ваш - так! Однако мой вариант применим только в том случае, если вы сообщаете о таких вещах, как «Этот комментарий был опубликован x месяцев и y дней НАЗАД», часть «НАЗАД» имеет значение, потому что это относится к предыдущим x месяцам, эти предыдущие x месяцев должны быть рассчитаны. исходя из того, сколько дней было в этих x месяцах! ссылка ....
Erx_VB.NExT.Coder
Имеет ли это смысл? поэтому, если вы имеете в виду определенные, известные месяцы, то мой метод точен на 100%, и вы будете приближением, однако, если вы имеете в виду месяц в целом, ваше приближение было бы лучшей идеей, и моя была бы просто плохой идеей (она не предназначена для этого и нет смысла ее использовать). Вот ссылка на мою статью с описанием проблемы и предложением решения: stackoverflow.com/questions/1916358/…
Erx_VB.NExT.Coder
2
Похоже, что это та же логика, что используется функцией Sql Server DateDiff (month, ...). Он также имеет то преимущество, что он чрезвычайно краток и прост для объяснения и понимания. Я бы объяснил это следующим образом ... сколько страниц в календаре вам придется перелистывать, чтобы перейти от одной даты к другой?
JoelFan
51

(Я понимаю, что это старый вопрос, но ...)

Это относительно болезненно делать в чистом .NET. Я бы порекомендовал свою собственную библиотеку Noda Time , специально разработанную для таких вещей:

LocalDate start = new LocalDate(2009, 10, 6);
LocalDate end = new LocalDate(2009, 12, 25);
Period period = Period.Between(start, end);
int months = period.Months;

(Есть и другие варианты, например, если вам нужен только счет месяцев даже по годам, вы должны использовать Period period = Period.Between(start, end, PeriodUnits.Months);)

Джон Скит
источник
Я загрузил вашу библиотеку и скопировал код, который вы написали выше, но получаю ошибку времени компиляции. Ошибка 1 Оператор «-» не может применяться к операндам типа «NodaTime.LocalDate» и «NodaTime.LocalDate». Я знаю этот пост за 5 лет, изменилось ли что-нибудь с того времени, из-за чего этот код не работает?
Hakan Fıstık
1
@HakamFostok: Извините - он будет работать, когда выйдет 2.0, но до тех пор вам нужно использовать Period.Between. Отредактировали код, чтобы он работал с NodaTime 1.3.1.
Джон Скит,
Большое спасибо, библиотека NodaTime сделала именно то, что я хочу делать. Я хотел рассчитать не только месяцы между двумя датами, но и оставшиеся дни, и это именно то, что NodaTime сделал в точности, еще раз спасибо.
Hakan Fıstık
1
@JonSkeet Эта твоя библиотека - настоящая черная магия. Финики меня все время кусают. Этот фрагмент кода сэкономил мне массу времени.
onefootswill
27

Может быть, вы не хотите знать о долях месяца; А что насчет этого кода?


public static class DateTimeExtensions
{
    public static int TotalMonths(this DateTime start, DateTime end)
    {
        return (start.Year * 12 + start.Month) - (end.Year * 12 + end.Month);
    }
}

//  Console.WriteLine(
//     DateTime.Now.TotalMonths(
//         DateTime.Now.AddMonths(-1))); // prints "1"


Рубенс Фариас
источник
1
Я не понимаю * 100. Должно быть * 12?
Ruffles
9

Для начала вам нужно будет определить, что вы имеете в виду под TotalMonths.
Простое определение помещает месяц в 30,4 дня (365,25 / 12).

Кроме того, любое определение, включая дроби, кажется бесполезным, а более распространенное целочисленное значение (целые месяцы между датами) также зависит от нестандартных бизнес-правил.

Хенк Холтерман
источник
9

Я написал очень простой метод расширения DateTimeи DateTimeOffsetдля этого. Я хотел, чтобы он работал точно так же, как работает TotalMonthsсвойство TimeSpan: то есть возвращал количество полных месяцев между двумя датами, игнорируя любые неполные месяцы. Потому что он основан на том, DateTime.AddMonths()что учитывает разную продолжительность месяца и возвращает то, что человек понимает как период месяцев.

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

Код и тесты доступны на GitHub . Код очень простой:

public static int GetTotalMonthsFrom(this DateTime dt1, DateTime dt2)
{
    DateTime earlyDate = (dt1 > dt2) ? dt2.Date : dt1.Date;
    DateTime lateDate = (dt1 > dt2) ? dt1.Date : dt2.Date;

    // Start with 1 month's difference and keep incrementing
    // until we overshoot the late date
    int monthsDiff = 1;
    while (earlyDate.AddMonths(monthsDiff) <= lateDate)
    {
        monthsDiff++;
    }

    return monthsDiff - 1;
}

И он проходит все эти модульные тесты:

// Simple comparison
Assert.AreEqual(1, new DateTime(2014, 1, 1).GetTotalMonthsFrom(new DateTime(2014, 2, 1)));
// Just under 1 month's diff
Assert.AreEqual(0, new DateTime(2014, 1, 1).GetTotalMonthsFrom(new DateTime(2014, 1, 31)));
// Just over 1 month's diff
Assert.AreEqual(1, new DateTime(2014, 1, 1).GetTotalMonthsFrom(new DateTime(2014, 2, 2)));
// 31 Jan to 28 Feb
Assert.AreEqual(1, new DateTime(2014, 1, 31).GetTotalMonthsFrom(new DateTime(2014, 2, 28)));
// Leap year 29 Feb to 29 Mar
Assert.AreEqual(1, new DateTime(2012, 2, 29).GetTotalMonthsFrom(new DateTime(2012, 3, 29)));
// Whole year minus a day
Assert.AreEqual(11, new DateTime(2012, 1, 1).GetTotalMonthsFrom(new DateTime(2012, 12, 31)));
// Whole year
Assert.AreEqual(12, new DateTime(2012, 1, 1).GetTotalMonthsFrom(new DateTime(2013, 1, 1)));
// 29 Feb (leap) to 28 Feb (non-leap)
Assert.AreEqual(12, new DateTime(2012, 2, 29).GetTotalMonthsFrom(new DateTime(2013, 2, 28)));
// 100 years
Assert.AreEqual(1200, new DateTime(2000, 1, 1).GetTotalMonthsFrom(new DateTime(2100, 1, 1)));
// Same date
Assert.AreEqual(0, new DateTime(2014, 8, 5).GetTotalMonthsFrom(new DateTime(2014, 8, 5)));
// Past date
Assert.AreEqual(6, new DateTime(2012, 1, 1).GetTotalMonthsFrom(new DateTime(2011, 6, 10)));
Марк Уитакер
источник
3
Деревенское, но лучшее решение. Копии и наклеены. Спасибо
Даниэль Дольз
8

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

Один из способов - подсчитать месяц, а затем исправить дни в конце. Что-то вроде:

   DateTime start = new DateTime(2003, 12, 25);
   DateTime end = new DateTime(2009, 10, 6);
   int compMonth = (end.Month + end.Year * 12) - (start.Month + start.Year * 12);
   double daysInEndMonth = (end - end.AddMonths(1)).Days;
   double months = compMonth + (start.Day - end.Day) / daysInEndMonth;
JDunkerley
источник
Хороший код, хотя, 1 ошибка: вместо: (28 февраля + 1 месяц == 28 марта) :-) // десятичные дниInEndMonth = (end - end.AddMonths (1)). Days; Я предлагаю: decimal daysInEndMonth = DateTime.DaysInMonth (end.Year, end.Month) * -1;
bezieur
3

Я бы сделал так:

static int TotelMonthDifference(this DateTime dtThis, DateTime dtOther)
{
    int intReturn = 0;

    dtThis = dtThis.Date.AddDays(-(dtThis.Day-1));
    dtOther = dtOther.Date.AddDays(-(dtOther.Day-1));

    while (dtOther.Date > dtThis.Date)
    {
        intReturn++;     
        dtThis = dtThis.AddMonths(1);
    }

    return intReturn;
}
Максимилиан Майерл
источник
4
Это, безусловно, один алгоритм, но его можно значительно упростить доreturn (dtOther.Month - dtThis.Month) + 12 * (dtOther.Year - dtThis.Year);
Адам Робинсон
1
Две проблемы: вы начинаете с двух дат, а не с временного интервала. Во-вторых, вы рассчитываете между первым числом обоих месяцев, это очень сомнительное определение. Хотя иногда это могло быть и правильно.
Хенк Холтерман,
@Henk: Да, конечно, это не всегда правильно, поэтому я сказал, что я буду это делать так, а не как кто-то должен это делать. В OP не указано, как рассчитывать результат. @ Адам: Вау, я снова подумал, что это слишком сложно ... это слишком часто случается со мной. Спасибо за комментарий, вы явно правы, ваша версия намного лучше. Я буду использовать это с этого момента.
Максимилиан Майерл
@ Адам: почему бы тебе не представить это как настоящий ответ ?! На данный момент это самый компактный. Очень красиво.
Дина,
@ Дина: Я не хотела думать, что ты действительно этого хотела. Если это так, я отредактировал свой предыдущий ответ, чтобы включить этот подход.
Адам Робинсон,
3

На этот счет не так много четких ответов, потому что вы всегда предполагаете что-то.

Это решение рассчитывает между двумя датами месяцы между предполагаемым, что вы хотите сохранить день месяца для сравнения (это означает, что день месяца учитывается в вычислении)

Например, если у вас есть дата 30 января 2012 года, 29 февраля 2012 года будет не месяцем, а 01 марта 2013 года.

Он был протестирован довольно тщательно, вероятно, он будет очищен позже, когда мы его будем использовать, и принимает две даты вместо Timespan, что, вероятно, лучше. Надеюсь, это поможет кому-нибудь еще.

private static int TotalMonthDifference(DateTime dtThis, DateTime dtOther)
{
    int intReturn = 0;
    bool sameMonth = false;

    if (dtOther.Date < dtThis.Date) //used for an error catch in program, returns -1
        intReturn--;

    int dayOfMonth = dtThis.Day; //captures the month of day for when it adds a month and doesn't have that many days
    int daysinMonth = 0; //used to caputre how many days are in the month

    while (dtOther.Date > dtThis.Date) //while Other date is still under the other
    {
        dtThis = dtThis.AddMonths(1); //as we loop, we just keep adding a month for testing
        daysinMonth = DateTime.DaysInMonth(dtThis.Year, dtThis.Month); //grabs the days in the current tested month

        if (dtThis.Day != dayOfMonth) //Example 30 Jan 2013 will go to 28 Feb when a month is added, so when it goes to march it will be 28th and not 30th
        {
            if (daysinMonth < dayOfMonth) // uses day in month max if can't set back to day of month
                dtThis.AddDays(daysinMonth - dtThis.Day);
            else
                dtThis.AddDays(dayOfMonth - dtThis.Day);
        }
        if (((dtOther.Year == dtThis.Year) && (dtOther.Month == dtThis.Month))) //If the loop puts it in the same month and year
        {
            if (dtOther.Day >= dayOfMonth) //check to see if it is the same day or later to add one to month
                intReturn++;
            sameMonth = true; //sets this to cancel out of the normal counting of month
        }
        if ((!sameMonth)&&(dtOther.Date > dtThis.Date))//so as long as it didn't reach the same month (or if i started in the same month, one month ahead, add a month)
            intReturn++;
    }
    return intReturn; //return month
}
GreatNate
источник
3

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

Мне понадобились неполные месяцы. Вот решение, которое я придумал несколько месяцев:

    /// <summary>
    /// Calculate the difference in months.
    /// This will round up to count partial months.
    /// </summary>
    /// <param name="lValue"></param>
    /// <param name="rValue"></param>
    /// <returns></returns>
    public static int MonthDifference(DateTime lValue, DateTime rValue)
    {
        var yearDifferenceInMonths = (lValue.Year - rValue.Year) * 12;
        var monthDifference = lValue.Month - rValue.Month;

        return yearDifferenceInMonths + monthDifference + 
            (lValue.Day > rValue.Day
                ? 1 : 0); // If end day is greater than start day, add 1 to round up the partial month
    }

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

    /// <summary>
    /// Calculate the differences in years.
    /// This will round up to catch partial months.
    /// </summary>
    /// <param name="lValue"></param>
    /// <param name="rValue"></param>
    /// <returns></returns>
    public static int YearDifference(DateTime lValue, DateTime rValue)
    {
        return lValue.Year - rValue.Year +
               (lValue.Month > rValue.Month // Partial month, same year
                   ? 1
                   : ((lValue.Month = rValue.Month) 
                     && (lValue.Day > rValue.Day)) // Partial month, same year and month
                   ? 1 : 0);
    }
endyourif
источник
У вас была логическая ошибка в вашей YearDifferenceфункции, когда lValue.Month < rValue.Month- я исправил это сейчас, вы можете проверить ...
Stobor
2

Старый вопрос я знаю, но может кому-то помочь. Я использовал принятый ответ @Adam выше, но затем проверил, составляет ли разница 1 или -1, а затем проверьте, есть ли разница в полном календарном месяце. Таким образом, 21.07.55 и 20.08.55 не будут полным месяцем, а 21.07.55 и 21.07.55 будут.

/// <summary>
/// Amended date of birth cannot be greater than or equal to one month either side of original date of birth.
/// </summary>
/// <param name="dateOfBirth">Date of birth user could have amended.</param>
/// <param name="originalDateOfBirth">Original date of birth to compare against.</param>
/// <returns></returns>
public JsonResult ValidateDateOfBirth(string dateOfBirth, string originalDateOfBirth)
{
    DateTime dob, originalDob;
    bool isValid = false;

    if (DateTime.TryParse(dateOfBirth, out dob) && DateTime.TryParse(originalDateOfBirth, out originalDob))
    {
        int diff = ((dob.Month - originalDob.Month) + 12 * (dob.Year - originalDob.Year));

        switch (diff)
        {
            case 0:
                // We're on the same month, so ok.
                isValid = true;
                break;
            case -1:
                // The month is the previous month, so check if the date makes it a calendar month out.
                isValid = (dob.Day > originalDob.Day);
                break;
            case 1:
                // The month is the next month, so check if the date makes it a calendar month out.
                isValid = (dob.Day < originalDob.Day);
                break;
            default:
                // Either zero or greater than 1 month difference, so not ok.
                isValid = false;
                break;
        }
        if (!isValid)
            return Json("Date of Birth cannot be greater than one month either side of the date we hold.", JsonRequestBehavior.AllowGet);
    }
    else
    {
        return Json("Date of Birth is invalid.", JsonRequestBehavior.AllowGet);
    }
    return Json(true, JsonRequestBehavior.AllowGet);
}
NRG
источник
2
case IntervalType.Month:
    returnValue = start.AddMonths(-end.Month).Month.ToString();
    break;
case IntervalType.Year:
    returnValue = (start.Year - end.Year).ToString();
    break;
Эвертон
источник
2
Описание к коду будет полезно и другим читателям.
Boeckm
да, пожалуйста, добавьте комментарий.
Amar
1

Проблема с месяцами в том, что это непростая мера - они не постоянный размер. Вам нужно будет определить свои правила для того, что вы хотите включить, и работать с ними. Например, с 1 января по 1 февраля - вы можете утверждать, что это 2 месяца, или вы можете сказать, что это один месяц. А как насчет «1 января 20:00» до «1 февраля 00:00» - это не совсем полный месяц. Это 0? 1? а как насчет наоборот (с 00:00 1 января до 20:00 1 февраля) ... 1? 2?

Сначала определите правила, а потом, боюсь, вам придется самому это кодировать ...

Марк Гравелл
источник
1

Если вы хотите получить результат 1между 28th Febи 1st March:

DateTime date1, date2;
int monthSpan = (date2.Year - date1.Year) * 12 + date2.Month - date1.Month
Snowbear
источник
Похоже, что это та же логика, что используется функцией Sql Server DateDiff (month, ...). Он также имеет то преимущество, что он чрезвычайно краток и прост для объяснения и понимания. Я бы объяснил это следующим образом ... сколько страниц в календаре вам придется перелистывать, чтобы перейти от одной даты к другой?
JoelFan
1

Эта библиотека вычисляет разницу месяцев с учетом всех частей DateTime:

// ----------------------------------------------------------------------
public void DateDiffSample()
{
  DateTime date1 = new DateTime( 2009, 11, 8, 7, 13, 59 );
  Console.WriteLine( "Date1: {0}", date1 );
  // > Date1: 08.11.2009 07:13:59
  DateTime date2 = new DateTime( 2011, 3, 20, 19, 55, 28 );
  Console.WriteLine( "Date2: {0}", date2 );
  // > Date2: 20.03.2011 19:55:28

  DateDiff dateDiff = new DateDiff( date1, date2 );

  // differences
  Console.WriteLine( "DateDiff.Years: {0}", dateDiff.Years );
  // > DateDiff.Years: 1
  Console.WriteLine( "DateDiff.Quarters: {0}", dateDiff.Quarters );
  // > DateDiff.Quarters: 5
  Console.WriteLine( "DateDiff.Months: {0}", dateDiff.Months );
  // > DateDiff.Months: 16
  Console.WriteLine( "DateDiff.Weeks: {0}", dateDiff.Weeks );
  // > DateDiff.Weeks: 70
  Console.WriteLine( "DateDiff.Days: {0}", dateDiff.Days );
  // > DateDiff.Days: 497
  Console.WriteLine( "DateDiff.Weekdays: {0}", dateDiff.Weekdays );
  // > DateDiff.Weekdays: 71
  Console.WriteLine( "DateDiff.Hours: {0}", dateDiff.Hours );
  // > DateDiff.Hours: 11940
  Console.WriteLine( "DateDiff.Minutes: {0}", dateDiff.Minutes );
  // > DateDiff.Minutes: 716441
  Console.WriteLine( "DateDiff.Seconds: {0}", dateDiff.Seconds );
  // > DateDiff.Seconds: 42986489

  // elapsed
  Console.WriteLine( "DateDiff.ElapsedYears: {0}", dateDiff.ElapsedYears );
  // > DateDiff.ElapsedYears: 1
  Console.WriteLine( "DateDiff.ElapsedMonths: {0}", dateDiff.ElapsedMonths );
  // > DateDiff.ElapsedMonths: 4
  Console.WriteLine( "DateDiff.ElapsedDays: {0}", dateDiff.ElapsedDays );
  // > DateDiff.ElapsedDays: 12
  Console.WriteLine( "DateDiff.ElapsedHours: {0}", dateDiff.ElapsedHours );
  // > DateDiff.ElapsedHours: 12
  Console.WriteLine( "DateDiff.ElapsedMinutes: {0}", dateDiff.ElapsedMinutes );
  // > DateDiff.ElapsedMinutes: 41
  Console.WriteLine( "DateDiff.ElapsedSeconds: {0}", dateDiff.ElapsedSeconds );
  // > DateDiff.ElapsedSeconds: 29
} // DateDiffSample

источник
1

Ниже приведен наиболее точный способ сделать это, поскольку определение «1 месяц» меняется в зависимости от того, какой сейчас месяц, и ни один из других ответов не учитывает это! Если вам нужна дополнительная информация о проблеме, которая не встроена в структуру, вы можете прочитать этот пост: Объект Real Timespan с .Years & .Months (однако чтение этого поста не обязательно для понимания и использования функции ниже, он работает на 100%, без присущих им неточностей приближения, которые любят использовать другие, - и не стесняйтесь заменять функцию .ReverseIt на встроенную функцию .Reverse, которая может быть у вас в вашем фреймворке (она здесь для полноты).

Обратите внимание, что вы можете получить любое количество точных дат / времени, секунд и минут или секунд, минут и дней в любом месте до лет (которые будут содержать 6 частей / сегментов). Если вы укажете два первых сегмента и ему больше года, он вернет «1 год и 3 месяца назад», а остальные не вернет, потому что вы запросили два сегмента. если ему всего несколько часов, он вернет только «2 часа и 1 минуту назад». Конечно, те же правила применяются, если вы указываете 1, 2, 3, 4, 5 или 6 сегментов (максимум 6, потому что секунды, минуты, часы, дни, месяцы, годы составляют только 6 типов). Это также исправит проблемы грамматики, такие как «минуты» против «минут», в зависимости от того, 1 минута или больше, одинаково для всех типов, а сгенерированная «строка» всегда будет грамматически правильной.

Вот несколько примеров для использования: bAllowSegments определяет, сколько сегментов показывать ... то есть: если 3, то возвращаемая строка будет (в качестве примера) ... "3 years, 2 months and 13 days"(не будет включать часы, минуты и секунды в качестве первых 3 значений времени категории), но если дата была более новой датой, например, несколько дней назад, "4 days, 1 hour and 13 minutes ago"вместо этого вернется указание тех же сегментов (3) , поэтому все будет учтено!

если bAllowSegments - 2, он вернется, "3 years and 2 months"а если 6 (максимальное значение) вернется "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds", но напомню, что он будет NEVER RETURNпримерно так, "0 years, 0 months, 0 days, 3 hours, 2 minutes and 13 seconds ago"поскольку он понимает, что в трех верхних сегментах нет данных о дате, и игнорирует их, даже если вы укажете 6 сегментов , так что не волнуйтесь :). Конечно, если есть сегмент с 0 в нем, он учтет это при формировании строки и будет отображаться как "3 days and 4 seconds ago"и игнорируя часть «0 часов»! Наслаждайтесь и прокомментируйте, если хотите.

 Public Function RealTimeUntilNow(ByVal dt As DateTime, Optional ByVal bAllowSegments As Byte = 2) As String
  ' bAllowSegments identifies how many segments to show... ie: if 3, then return string would be (as an example)...
  ' "3 years, 2 months and 13 days" the top 3 time categories are returned, if bAllowSegments is 2 it would return
  ' "3 years and 2 months" and if 6 (maximum value) would return "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds"
  Dim rYears, rMonths, rDays, rHours, rMinutes, rSeconds As Int16
  Dim dtNow = DateTime.Now
  Dim daysInBaseMonth = Date.DaysInMonth(dt.Year, dt.Month)

  rYears = dtNow.Year - dt.Year
  rMonths = dtNow.Month - dt.Month
  If rMonths < 0 Then rMonths += 12 : rYears -= 1 ' add 1 year to months, and remove 1 year from years.
  rDays = dtNow.Day - dt.Day
  If rDays < 0 Then rDays += daysInBaseMonth : rMonths -= 1
  rHours = dtNow.Hour - dt.Hour
  If rHours < 0 Then rHours += 24 : rDays -= 1
  rMinutes = dtNow.Minute - dt.Minute
  If rMinutes < 0 Then rMinutes += 60 : rHours -= 1
  rSeconds = dtNow.Second - dt.Second
  If rSeconds < 0 Then rSeconds += 60 : rMinutes -= 1

  ' this is the display functionality
  Dim sb As StringBuilder = New StringBuilder()
  Dim iSegmentsAdded As Int16 = 0

  If rYears > 0 Then sb.Append(rYears) : sb.Append(" year" & If(rYears <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rMonths > 0 Then sb.AppendFormat(rMonths) : sb.Append(" month" & If(rMonths <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rDays > 0 Then sb.Append(rDays) : sb.Append(" day" & If(rDays <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rHours > 0 Then sb.Append(rHours) : sb.Append(" hour" & If(rHours <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rMinutes > 0 Then sb.Append(rMinutes) : sb.Append(" minute" & If(rMinutes <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rSeconds > 0 Then sb.Append(rSeconds) : sb.Append(" second" & If(rSeconds <> 1, "s", "") & "") : iSegmentsAdded += 1

parseAndReturn:

  ' if the string is entirely empty, that means it was just posted so its less than a second ago, and an empty string getting passed will cause an error
  ' so we construct our own meaningful string which will still fit into the "Posted * ago " syntax...

  If sb.ToString = "" Then sb.Append("less than 1 second")

  Return ReplaceLast(sb.ToString.TrimEnd(" ", ",").ToString, ",", " and")

 End Function

Конечно, вам понадобится функция ReplaceLast, которая принимает исходную строку и аргумент, указывающий, что нужно заменить, и другой аргумент, указывающий, чем вы хотите его заменить, и он заменяет только последнее появление этой строки. ... я включил свой, если у вас его нет или вы не хотите его внедрять, так что вот он, он будет работать «как есть» без каких-либо изменений. Я знаю, что функция reverseit больше не нужна (существует в .net), но функции ReplaceLast и ReverseIt перенесены из дней pre-.net, поэтому, пожалуйста, извините, как устаревшая она может выглядеть (все еще работает на 100%, хотя использовала em более десяти лет, могу гарантировать, что они не содержат ошибок) ... :). веселит.

<Extension()> _ 
Public Function ReplaceLast(ByVal sReplacable As String, ByVal sReplaceWhat As String, ByVal sReplaceWith As String) As String 
    ' let empty string arguments run, incase we dont know if we are sending and empty string or not. 
    sReplacable = sReplacable.ReverseIt 
    sReplacable = Replace(sReplacable, sReplaceWhat.ReverseIt, sReplaceWith.ReverseIt, , 1) ' only does first item on reversed version! 
    Return sReplacable.ReverseIt.ToString 
End Function 

<Extension()> _ 
Public Function ReverseIt(ByVal strS As String, Optional ByVal n As Integer = -1) As String 
    Dim strTempX As String = "", intI As Integer 

    If n > strS.Length Or n = -1 Then n = strS.Length 

    For intI = n To 1 Step -1 
        strTempX = strTempX + Mid(strS, intI, 1) 
    Next intI 

    ReverseIt = strTempX + Right(strS, Len(strS) - n) 

End Function 
Erx_VB.NExT.Coder
источник
0

Если вам нужно точное число, вы не можете использовать только временной интервал, так как вам нужно знать, в какие месяцы вы имеете дело, и имеете ли вы дело с високосным годом, как вы сказали.

Либо выберите приблизительное число, либо немного поиграйте с исходными DateTimes

Rik
источник
0

http://www.astro.uu.nl/~strous/AA/en/reken/juliaansedag.html

Если вы можете преобразовать время из григорианской даты в число дней по юлианскому календарю, вы можете просто создать оператор для сравнения числа зулианских дней, который может иметь тип double, чтобы получить месяцы, дни, секунды и т. Д. Ознакомьтесь с приведенным выше ссылка на алгоритм преобразования григорианского на юлианский.

Джесси О'Брайен
источник
0

В idiomatic-C # нет встроенного способа сделать это точно. Есть некоторые обходные пути, такие как этот пример CodeProject, который люди уже кодировали.

Matt
источник
0

Если вы имеете дело с месяцами и годами, вам нужно что-то, что знает, сколько дней в каждом месяце и какие годы являются високосными.

Войдите в григорианский календарь (и другие реализации Календаря, зависящие от культуры ).

Хотя Календарь не предоставляет методов для прямого вычисления разницы между двумя моментами времени, у него есть такие методы, как

DateTime AddWeeks(DateTime time, int weeks)
DateTime AddMonths(DateTime time, int months)
DateTime AddYears(DateTime time, int years)
mattk
источник
0
DateTime start = new DateTime(2003, 12, 25);
DateTime end = new DateTime(2009, 10, 6);
int compMonth = (end.Month + end.Year * 12) - (start.Month + start.Year * 12);
double daysInEndMonth = (end - end.AddMonths(1)).Days;
double months = compMonth + (start.Day - end.Day) / daysInEndMonth;
SUMIT
источник
0

Метод возвращает список, содержащий 3 элемента, первый - год, второй - месяц, а конечный элемент - день:

public static List<int> GetDurationInEnglish(DateTime from, DateTime to)
    {
        try
        {
            if (from > to)
                return null;

            var fY = from.Year;
            var fM = from.Month;
            var fD = DateTime.DaysInMonth(fY, fM);

            var tY = to.Year;
            var tM = to.Month;
            var tD = DateTime.DaysInMonth(tY, tM);

            int dY = 0;
            int dM = 0;
            int dD = 0;

            if (fD > tD)
            {
                tM--;

                if (tM <= 0)
                {
                    tY--;
                    tM = 12;
                    tD += DateTime.DaysInMonth(tY, tM);
                }
                else
                {
                    tD += DateTime.DaysInMonth(tY, tM);
                }
            }
            dD = tD - fD;

            if (fM > tM)
            {
                tY--;

                tM += 12;
            }
            dM = tM - fM;

            dY = tY - fY;

            return new List<int>() { dY, dM, dD };
        }
        catch (Exception exception)
        {
            //todo: log exception with parameters in db

            return null;
        }
    }
Алиреза
источник
0

Вот мой вклад в улучшение разницы в месяцах, который я считаю точным:

namespace System
{
     public static class DateTimeExtensions
     {
         public static Int32 DiffMonths( this DateTime start, DateTime end )
         {
             Int32 months = 0;
             DateTime tmp = start;

             while ( tmp < end )
             {
                 months++;
                 tmp = tmp.AddMonths( 1 );
             }

             return months;
        }
    }
}

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

Int32 months = DateTime.Now.DiffMonths( DateTime.Now.AddYears( 5 ) );

Вы можете создать другой метод под названием DiffYears и применить ту же логику, что и выше, и AddYears вместо AddMonths в цикле while.

Morgs
источник
0

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

  1. Любое количество лет, превышающее 1 при сравнении года, будет умножено на 12, ни в одном случае это не может быть меньше 1 полного года.
  2. Если год на конец больше, нам нужно оценить, больше ли текущий день предыдущему дню 2A или равен ему. Если конечный день больше или равен, мы берем текущий месяц, а затем добавляем 12 месяцев, вычитаем месяц из начального месяца 2B. Если конечный день меньше, чем начальный день, мы выполняем то же самое, что и выше, за исключением того, что мы добавляем 1 к начальному месяцу перед вычитанием
  3. Если год на конец не больше, мы выполняем то же, что и 2A / 2B, но без добавления 12 месяцев, потому что нам не нужно оценивать год.

        DateTime date = new DateTime(2003, 11, 25);
        DateTime today = new DateTime(2004, 12, 26);
        var time = (today.Year - date.Year > 1 ? (today.Year - date.Year - 1) * 12 : 0) +  (today.Year > date.Year ? (today.Day >= date.Day ? today.Month + 12 - date.Month : today.Month + 12 - (date.Month + 1)) : (today.Day >= date.Day ? today.Month - date.Month : today.Month - (date.Month + 1)));
TheHamstring
источник
Смерть по троице?
SpaceBison
0

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

public static int MonthsBefore(this DateTime dt1, DateTime dt2)
{
    (DateTime early, DateTime late, bool dt2After) = dt2 > dt1 ? (dt1,dt2,true) : (dt2,dt1,false);
    DateTime tmp; // Save the result so we don't repeat work
    int months = 1;
    while ((tmp = early.AddMonths(1)) <= late)
    {
        early = tmp;
        months++;
    }
    return (months-1)*(dt2After ? 1 : -1);
}

Пара тестов:

// Just under 1 month's diff
Assert.AreEqual(0, new DateTime(2014, 1, 1).MonthsBefore(new DateTime(2014, 1, 31)));
// Just over 1 month's diff
Assert.AreEqual(1, new DateTime(2014, 1, 1).MonthsBefore(new DateTime(2014, 2, 2)));    
// Past date returns NEGATIVE
Assert.AreEqual(-6, new DateTime(2012, 1, 1).MonthsBefore(new DateTime(2011, 6, 10)));
ZX9
источник
0

Комбинируя два из приведенных выше ответов, можно получить еще один метод расширения:

public static int ElapsedMonths(this DateTime date1, DateTime date2)
{
    DateTime earlierDate = (date1 > date2) ? date2 : date1;
    DateTime laterDate = (date1 > date2) ? date1 : date2;
    var eMonths = (laterDate.Month - earlierDate.Month) + 12 * (laterDate.Year - earlierDate.Year) - 
                                            ((earlierDate.Day > laterDate.Day) ? 1 : 0);
    return eMonths;
}

Спасибо @AdamRobinson и @MarkWhittaker

Питер Смит
источник
-1

Рассчитайте количество месяцев между двумя датами:

$date1 = '2017-01-20';
$date2 = '2019-01-20';

$ts1 = strtotime($date1);
$ts2 = strtotime($date2);

$year1 = date('Y', $ts1);
$year2 = date('Y', $ts2);

$month1 = date('m', $ts1);
$month2 = date('m', $ts2);

echo $joining_months = (($year2 - $year1) * 12) + ($month2 - $month1);
Kamlesh
источник
1
Это PHP, а не C #.
AFract