calcular a diferença em meses entre duas datas

128

Em C # /. NET TimeSpantem TotalDays, TotalMinutes, etc, mas eu não posso descobrir uma fórmula para meses totais diferença. Dias variáveis ​​por mês e anos bissextos continuam me excitando. Como posso obter TotalMonths ?

Editar Desculpe por não ser mais claro: eu sei que realmente não consigo entender isso, TimeSpanmas pensei em usar TotalDayse TotalMinutesseria um bom exemplo para expressar o que estava procurando ... exceto que estou tentando obter o total de meses.

Exemplo: 25 de dezembro de 2009 a 6 de outubro de 2009 = 2 meses totais. 6 de outubro a 5 de novembro é igual a 0 meses. Em 6 de novembro, 1 mês. Em 6 de dezembro, 2 meses

Dinah
fonte
2
O que você espera de 25 de dezembro de 2009 a 6 de outubro de 2009?
1013 Jeff Moser
2
Como você define o TimeSpan em meses?
Aliostad 17/03/11
1
@ Aliostad - Sem datas, você pode definir um mês como 30 dias e ser bastante preciso.
ChaosPandion
Foi mesclado com esta pergunta por um mod por algum motivo.
Jamiec
Na verdade, você precisa ler meu post aqui, que responde a essa pergunta e fornece uma solução codificada, stackoverflow.com/questions/1916358/… ignora os trolls (brianary) e presta atenção à minha conversa através de comentários com supercat. Os meses que estamos no início e no final de um período de tempo que estamos chamando de "Meses Órfãos", e a questão se resume a como definir esses meses órfãos em termos de dias - depois que você determinar isso (e como você deseja defini-lo) ), o resto é apenas código (que está incluído). Minha def. é baseada no que eu acho que meus usuários vão esperar
Erx_VB.NExT.Coder

Respostas:

222

Você não poderá obter isso de a TimeSpan, porque um "mês" é uma unidade de medida variável. Você terá que calcular por conta própria e precisará descobrir exatamente como deseja que funcione.

Por exemplo, as datas devem gostar July 5, 2009e August 4, 2009gerar uma diferença de um mês ou zero meses? Se você diz que deve produzir um, então July 31, 2009e quanto August 1, 2009? É que um mês? É simplesmente a diferença dos Monthvalores para as datas ou está mais relacionada a um período de tempo real? A lógica para determinar todas essas regras não é trivial; portanto, você deve determinar por conta própria e implementar o algoritmo apropriado.

Se tudo o que você deseja é simplesmente uma diferença nos meses - desconsiderando completamente os valores da data -, você pode usar o seguinte:

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

Observe que isso retorna uma diferença relativa, o que significa que se rValuefor maior que lValue, então o valor de retorno será negativo. Se você quer uma diferença absoluta, pode usar o seguinte:

public static int MonthDifference(this DateTime lValue, DateTime rValue)
{
    return Math.Abs((lValue.Month - rValue.Month) + 12 * (lValue.Year - rValue.Year));
}
Adam Robinson
fonte
@ Dinah isso é apenas uma aproximação, se você quiser saber o verdadeiro .Mês e .Anos - Acabei de publicar uma resposta para o que você pode ler. Embora, no que diz respeito às aproximações, essa seja uma boa aproximação (adereços para Adam Robinson), no entanto, você deve ter em mente que, se usar alguma dessas aproximações, estará mentindo involuntariamente para os usuários.
Erx_VB.NExT.Coder
@ Erx_VB.NExT.Coder: Obrigado pelos adereços, mas, embora sua resposta afirme que nenhuma das respostas leva em consideração o fato de que um mês é uma unidade de medida variável, parece que a maioria faz; eles simplesmente não usam sua aproximação particular. Caso em questão, a primeira frase da minha resposta indica que é variável. Qualquer resposta, inclusive a sua, é uma aproximação , simplesmente porque não é uma resposta precisa. O resultado de "2 meses" pode significar coisas diferentes para diferentes entradas, portanto é uma aproximação.
Adam Robinson
o meu não é uma aproximação, se hoje é 14 de março, os dois meses anteriores são calculados com base no fato de que jan tinha 31 dias e fevereiro tinha 29 dias. Agora, você está correto, pois meu método não é a definição de um mês "geral", e o seu é! No entanto, o meu só se aplica se você estiver relatando coisas como "Este comentário foi publicado x meses e dias atrás", a parte "AGO" faz a diferença, porque se refere aos x meses anteriores, esses x meses anteriores precisam ser calculados com base em quantos dias estavam presentes nesses x meses! Link ....
Erx_VB.NExT.Coder
Isso faz sentido? portanto, se você estiver se referindo a meses conhecidos e específicos, meu método é 100% preciso e você seria uma aproximação; no entanto, se você estiver se referindo a um mês em geral, sua aproximação seria uma idéia melhor e a minha seria apenas uma má idéia (não foi feita para isso e não faria sentido em usá-la). Aqui está o link para o meu artigo descrevendo o problema e fornecer uma solução: stackoverflow.com/questions/1916358/...
Erx_VB.NExT.Coder
2
Essa parece ser a mesma lógica usada pela função DateDiff do Sql Server (mês, ...). Também tem a vantagem de ser extremamente conciso e fácil de explicar e entender. Eu explicaria da seguinte maneira ... quantas páginas do calendário você precisaria virar para passar de uma data para a outra?
JoelFan
51

(Eu sei que essa é uma pergunta antiga, mas ...)

Isso é relativamente doloroso em .NET puro. Eu recomendaria minha própria biblioteca Noda Time , que é especialmente projetada para coisas como esta:

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

(Existem outras opções, por exemplo, se você quiser apenas uma contagem de meses, mesmo ao longo dos anos, você usaria Period period = Period.Between(start, end, PeriodUnits.Months);)

Jon Skeet
fonte
Fiz o download da sua biblioteca e copiei o código que você escreveu acima, mas estou recebendo um erro de tempo de compilação. Erro 1 Operador '-' não pode ser aplicado a operandos do tipo 'NodaTime.LocalDate' e 'NodaTime.LocalDate'. Conheço este post há 5 anos, alguma coisa mudou a partir desse momento, o que fez o código não funcionar?
Hakan Fıstık
1
@HakamFostok: Desculpe - ele funcionará quando o 2.0 for lançado, mas até então você precisará usá-lo Period.Between. Editou o código para que ele funcione com o NodaTime 1.3.1.
Jon Skeet
muito obrigado a biblioteca NodaTime fez exatamente o que eu quero fazer. Eu queria calcular não apenas os meses entre duas datas, mas também os dias restantes, e é isso que o NodaTime fez exatamente, obrigado novamente.
Hakan Fıstık
1
@ JonSkeet Essa sua biblioteca é verdadeiramente magia negra. Datas me mordem o tempo todo. Esse trecho de código me salvou uma quantidade enorme de tempo.
Onefootswill 18/03/19
28

Talvez você não queira saber sobre frações mensais; E esse código?


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"


Rubens Farias
fonte
1
Eu não entendo o * 100. Deveria ser * 12?
Ruffles
9

Você terá que definir o que você quer dizer com TotalMonths para começar.
Uma definição simples coloca um mês em 30,4 dias (365,25 / 12).

Além disso, qualquer definição incluindo frações parece inútil, e o valor inteiro mais comum (meses inteiros entre datas) também depende de regras comerciais não padrão.

Henk Holterman
fonte
9

Eu escrevi um método de extensão muito simples DateTimee DateTimeOffsetpara fazer isso. Eu queria que ele funcionasse exatamente como uma TotalMonthspropriedade TimeSpanfuncionaria: por exemplo, retorne a contagem de meses completos entre duas datas, ignorando os meses parciais. Por se basear, DateTime.AddMonths()ele respeita diferentes comprimentos de meses e retorna o que um humano entenderia como um período de meses.

(Infelizmente, você não pode implementá-lo como um método de extensão no TimeSpan, porque isso não retém o conhecimento das datas reais usadas, e por meses elas são importantes.)

O código e os testes estão disponíveis no GitHub . O código é muito simples:

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;
}

E passa em todos esses casos de teste de unidade:

// 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)));
Mark Whitaker
fonte
3
Solução rústica, mas melhor. Cópias e coladas. Obrigado
Daniel Dolz
8

Você precisa resolver isso às vezes. Como você lida com os dias stub no final dependerá do que você deseja usá-lo.

Um método seria contar o mês e depois corrigir os dias no final. Algo como:

   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
fonte
Código legal, no entanto, 1 bug: em vez disso: (como 28 de fevereiro + 1 mês == 28 de março) :-) // decimal daysInEndMonth = (end - end.AddMonths (1)). Days; Sugiro: decimal daysInEndMonth = DateTime.DaysInMonth (end.Year, end.Month) * -1;
31514 bezieur
3

Eu faria assim:

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;
}
Maximilian Mayerl
fonte
4
Isso é certamente um algoritmo, mas poderia ser muito simplificado parareturn (dtOther.Month - dtThis.Month) + 12 * (dtOther.Year - dtThis.Year);
Adam Robinson
1
Dois problemas: você está iniciando em 2 datas, não em um TimeSpan. Segundo, você calcula entre o primeiro de dois meses, que é uma definição muito questionável. Embora possa estar certo às vezes.
Henk Holterman
@ Henk: Sim, claro que nem sempre é certo, é por isso que eu disse que era assim que eu faria, não como alguém deveria fazê-lo. O OP não especificou como o resultado deve ser calculado. @ Adam: Uau, eu pensei muito complicado mais uma vez ... isso acontece com muita frequência para mim. Obrigado pelo comentário, você está obviamente certo, sua versão é muito melhor. Vou usar isso a partir de agora.
Maximilian Mayerl 06/10/09
@ Adam: por que você não envia isso como uma resposta real ?! Este é o mais compacto até agora. Muito liso.
Dinah
@Dinah: Eu não queria assumir que era isso que você realmente queria. Se for, editei minha resposta anterior para incluir essa abordagem.
9309 Adam Robinson
3

Não há muitas respostas claras sobre isso, porque você está sempre assumindo as coisas.

Esta solução calcula entre duas datas nos meses entre a suposição de que você deseja salvar o dia do mês para comparação (o que significa que o dia do mês é considerado no cálculo)

Por exemplo, se você tem uma data de 30 de janeiro de 2012, 29 de fevereiro de 2012 não será um mês, mas 01 de março de 2013 será.

Ele foi testado minuciosamente, provavelmente o limpará mais tarde enquanto o usamos, e leva duas datas em vez de um Timespan, o que provavelmente é melhor. Espero que isso ajude mais alguém.

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
fonte
3

A resposta aceita funciona perfeitamente quando você quer meses inteiros.

Eu precisava de meses parciais. Esta é a solução que encontrei por meses parciais:

    /// <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
    }

Eu também precisava de uma diferença de ano com a mesma necessidade de anos parciais. Aqui está a solução que eu vim com:

    /// <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
fonte
Você teve erro lógica na sua YearDifferencefunção quando lValue.Month < rValue.Month- eu fixo que agora, você pode querer rever ...
Stobor
2

Velha pergunta eu sei, mas pode ajudar alguém. Eu usei a resposta aceita @Adam acima, mas depois verifiquei se a diferença é 1 ou -1 e verifique se é a diferença completa de um mês. Portanto, 21/07/55 e 20/08/55 não seriam um mês inteiro, mas 21/07/55 e 21/07/55 seriam.

/// <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
fonte
2
case IntervalType.Month:
    returnValue = start.AddMonths(-end.Month).Month.ToString();
    break;
case IntervalType.Year:
    returnValue = (start.Year - end.Year).ToString();
    break;
Everton
fonte
2
Uma descrição para acompanhar o código também seria benéfica para outros leitores.
Boeckm
Sim, por favor, adicione alguns comentários.
Amar
1

O problema com meses é que não é realmente uma medida simples - eles não são de tamanho constante. Você precisaria definir suas regras para o que deseja incluir e trabalhar a partir daí. Por exemplo, de 1 de janeiro a 1 de fevereiro - você pode argumentar que há 2 meses envolvidos ou pode-se dizer que é um mês. Então, que tal "de 1 de janeiro às 20:00" a "1 de fevereiro às 00:00" - não é um mês inteiro. Isso é 0? 1? e o contrário (1 de janeiro de 00:00 a 1 de fevereiro de 20:00) ... 1? 2?

Primeiro defina as regras, então você terá que codificá-lo, receio ...

Marc Gravell
fonte
1

Se você deseja obter um resultado 1entre 28th Febe 1st March:

DateTime date1, date2;
int monthSpan = (date2.Year - date1.Year) * 12 + date2.Month - date1.Month
Urso de neve
fonte
Essa parece ser a mesma lógica usada pela função DateDiff do Sql Server (mês, ...). Também tem a vantagem de ser extremamente conciso e fácil de explicar e entender. Eu explicaria da seguinte maneira ... quantas páginas do calendário você precisaria virar para passar de uma data para a outra?
JoelFan
1

Esta biblioteca calcula a diferença de meses, considerando todas as partes do 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

fonte
1

Abaixo, na verdade, é a maneira mais precisa de fazê-lo, uma vez que a definição de "1 mês" muda dependendo do mês em que é, e nenhuma das outras respostas leva isso em consideração! Se você deseja obter mais informações sobre o problema que não está incorporado na estrutura, pode ler esta postagem: Um objeto de período de tempo real com anos e meses (no entanto, não é necessário ler essa publicação para entender e usar a função abaixo, funciona 100%, sem as imprecisões inerentes à aproximação que outras pessoas adoram usar - e sinta-se à vontade para substituir a função .ReverseIt pela função .Reverse incorporada que você pode ter em sua estrutura (está aqui para ser completo).

Observe que você pode obter qualquer número de precisão de datas / horas, segundos e minutos ou segundos, minutos e dias, em qualquer lugar até anos (que conteria 6 partes / segmentos). Se você especificar os dois primeiros e tiver mais de um ano, ele retornará "1 ano e 3 meses atrás" e não retornará o restante porque você solicitou dois segmentos. se tiver apenas algumas horas, retornará apenas "2 horas e 1 minuto atrás". Obviamente, as mesmas regras se aplicam se você especificar 1, 2, 3, 4, 5 ou 6 segmets (no máximo 6, pois segundos, minutos, horas, dias, meses, anos e anos produzem apenas 6 tipos). Ele também corrige problemas gramaticais como "minutos" vs "minuto", dependendo de se levar 1 minuto ou mais, o mesmo para todos os tipos, e a "sequência" gerada sempre estará gramaticalmente correta.

Aqui estão alguns exemplos para uso: bAllowSegments identifica quantos segmentos serão exibidos ... ou seja: se 3, a string de retorno seria (como exemplo) ... "3 years, 2 months and 13 days"(não incluirá horas, minutos e segundos como os três primeiros) categorias são retornadas); se, no entanto, a data era uma data mais recente, como algo há alguns dias atrás, especificar os mesmos segmentos (3) retornará "4 days, 1 hour and 13 minutes ago", então isso leva tudo em consideração!

se bAllowSegments for 2, ele retornará "3 years and 2 months"e se 6 (valor máximo) retornará "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds", mas lembre-se de que será NEVER RETURNalgo assim "0 years, 0 months, 0 days, 3 hours, 2 minutes and 13 seconds ago", pois entende que não há dados de data nos 3 principais segmentos e os ignora, mesmo se você especificar 6 segmentos , então não se preocupe :). Obviamente, se houver um segmento com 0, isso será levado em consideração ao formar a string e será exibido como "3 days and 4 seconds ago"e ignorando a parte "0 horas"! Aproveite e comente se quiser.

 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

Obviamente, você precisará de uma função "ReplaceLast", que aceita uma string de origem e um argumento que especifique o que precisa ser substituído, e outro argumento que especifique com o que você deseja substituí-lo, e substitui apenas a última ocorrência dessa string ... incluí o meu, se você não tiver um ou não quiser implementá-lo, então aqui está, ele funcionará "como está" sem nenhuma modificação necessária. Eu sei que a função reverseit não é mais necessária (existe em .net), mas a função ReplaceLast e ReverseIt são transferidas dos dias anteriores à net, portanto, desculpe o quão datada ela pode parecer (ainda funciona 100%, usando por mais de dez anos, pode garantir que eles estão livres de erros) ... :). Felicidades.

<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
fonte
0

Se você deseja o número exato, não pode, apenas no Timespan, já que precisa saber em quais meses está negociando e se está lidando com um ano bissexto, como você disse.

Escolha um número aproximado ou remexa-se com o DateTimes original

Rik
fonte
0

Não existe uma maneira integrada de fazer isso com precisão no idiomático-c #. Existem algumas soluções alternativas, como este exemplo do CodeProject que as pessoas codificaram.

Matt
fonte
0

Se você está lidando com meses e anos, precisa de algo que saiba quantos dias cada mês tem e quais anos são anos bissextos.

Digite o calendário gregoriano (e outras implementações de calendário específicas da cultura ).

Embora o Calendário não forneça métodos para calcular diretamente a diferença entre dois pontos no tempo, ele possui métodos como

DateTime AddWeeks(DateTime time, int weeks)
DateTime AddMonths(DateTime time, int months)
DateTime AddYears(DateTime time, int years)
Mattk
fonte
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
fonte
0

O método retorna uma lista que contém 3 elementos: primeiro é o ano, o segundo é o mês e o elemento final é o dia:

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;
        }
    }
Alireza
fonte
0

Aqui está minha contribuição para obter a diferença nos meses que eu achei precisos:

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;
        }
    }
}

Uso:

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

Você pode criar outro método chamado DiffYears e aplicar exatamente a mesma lógica acima e AddYears em vez de AddMonths no loop while.

Morgs
fonte
0

Muito tarde para o jogo, mas imagino que isso possa ser útil para alguém. A maioria das pessoas tende a medir mês a mês por data, excluindo o fato de que os meses ocorrem em diferentes variações. Usando esse quadro de pensamento, criei uma linha que compara as datas para nós. Usando o seguinte processo.

  1. Qualquer número de anos acima de 1 ao comparar o ano será multiplicado por 12, não há caso em que isso possa ser igual a menos de 1 ano inteiro.
  2. Se o ano final for maior, precisamos avaliar se o dia atual é maior ou igual ao dia anterior 2A. Se o dia final for maior ou igual, pegamos o mês atual e adicionamos 12 meses subtraímos o mês do mês inicial 2B. Se o dia final for menor que o dia inicial, executaremos o mesmo que acima, exceto que adicionamos 1 ao mês inicial antes de subtrair
  3. Se o ano final não for maior, executamos o mesmo que 2A / 2B, mas sem adicionar os 12 meses, porque não precisamos avaliar o ano todo.

        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
fonte
Morte por ternário?
SpaceBison
0

Minha opinião sobre esta resposta também usa um método de extensão , mas pode retornar um resultado positivo ou negativo.

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);
}

Alguns testes:

// 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
fonte
0

Combinando duas das respostas acima, outro método de extensão é:

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;
}

Obrigado a @AdamRobinson e @MarkWhittaker

Peter Smith
fonte
-1

Calcular o número de meses entre 2 datas:

$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
fonte
1
Isso é PHP, não C #.
AFract