calculer la différence en mois entre deux dates


128

En C # /. NET TimeSpana TotalDays, TotalMinutesetc. mais je ne peux pas trouver de formule pour la différence totale des mois. Les jours variables par mois et les années bissextiles ne cessent de me déranger. Comment puis-je obtenir TotalMonths ?

Modifier Désolé de ne pas être plus clair: je sais que je ne peux pas vraiment obtenir cela, TimeSpanmais j'ai pensé TotalDaysque ce TotalMinutesserait un bon exemple pour exprimer ce que je cherchais ... sauf que j'essaie d'obtenir le nombre total de mois.

Exemple: 25 décembre 2009 - 6 octobre 2009 = 2 mois au total. Du 6 octobre au 5 novembre équivaut à 0 mois. Le 6 novembre, 1 mois. Le 6 décembre, 2 mois


2
Qu'attendez-vous du 25 décembre 2009 au 6 octobre 2009?
Jeff Moser

2
Comment définissez-vous TimeSpan en mois?
Aliostad

1
@Aliostad - Sans dates, vous pouvez définir un mois comme 30 jours et être assez précis.
ChaosPandion

Il a été fusionné avec cette question par un mod pour une raison quelconque.
Jamiec

En fait, vous devez lire mon message ici, qui répond à cette question et fournit une solution codée, stackoverflow.com/questions/1916358/… ignorer les trolls (brianary) et faire attention à ma conversation via des commentaires avec supercat. Les mois qui r au début et à la fin d'un laps de temps sont appelés «mois orphelins», et la question se résume à comment définir ces mois orphelins en termes de jours - une fois que vous avez déterminé cela (et comment vous voulez le définir ), le reste n'est que du code (qui est inclus). Ma déf. est basé sur ce à quoi je pense que mes utilisateurs s'attendront
Erx_VB.NExT.Coder

Réponses:


222

Vous ne pourrez pas obtenir cela à partir de a TimeSpan, car un «mois» est une unité de mesure variable. Vous devrez le calculer vous-même et vous devrez déterminer exactement comment vous voulez que cela fonctionne.

Par exemple, les dates devraient-elles aimer July 5, 2009et August 4, 2009donner un mois ou zéro mois de différence? Si vous dites qu'il devrait en donner un, qu'en est-il July 31, 2009et August 1, 2009? Est- ce un mois? S'agit-il simplement de la différence des Monthvaleurs des dates ou est-ce davantage lié à une période réelle? La logique pour déterminer toutes ces règles n'est pas triviale, vous devrez donc déterminer la vôtre et implémenter l'algorithme approprié.

Si tout ce que vous voulez est simplement une différence dans les mois - sans tenir compte des valeurs de date - alors vous pouvez utiliser ceci:

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

Notez que cela renvoie une différence relative, ce qui signifie que si rValueest supérieur à lValue, la valeur de retour sera négative. Si vous voulez une différence absolue, vous pouvez utiliser ceci:

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

@Dinah c'est juste une approximation, si vous voulez connaître le vrai .Mois et .Years - Je viens de publier une réponse pour ce que vous pouvez lire. Bien que, en ce qui concerne les approximations, il s'agit d'une bonne approximation (accessoires d'Adam Robinson), vous devez cependant garder à l'esprit que si vous utilisez l'une de ces approximations, vous mentez simplement involontairement à vos utilisateurs.
Erx_VB.NExT.Coder

@ Erx_VB.NExT.Coder: Merci pour les accessoires, mais bien que votre réponse indique qu'aucune des réponses ne tient compte du fait qu'un mois est une unité de mesure variable, il semble que la plupart le font; ils n'utilisent tout simplement pas votre approximation particulière. Par exemple, la toute première phrase de ma réponse indique que c'est variable. Toute réponse, y compris la vôtre, est une approximation , simplement parce que ce n'est pas une réponse précise. Votre résultat «2 mois» peut signifier différentes choses pour différentes entrées, il s'agit donc d'une approximation.
Adam Robinson

le mien n'est pas une approximation cependant, si aujourd'hui est le 14 mars, alors les deux mois pervers sont calculés sur la base du fait que jan avait 31 jours dedans, et février a 29 jours dedans. maintenant, vous avez raison en ce que ma méthode n'est pas la définition d'un mois "général", et la vôtre l'est! Cependant, le mien ne s'applique que si vous signalez des choses comme "Ce commentaire a été publié il y a x mois et y jours", la partie "AGO" fait la différence, car elle fait référence aux x mois précédents, ces x mois précédents doivent être calculés basé sur le nombre de jours présents au cours de ces x mois! link ....
Erx_VB.NExT.Coder

Cela a-t-il du sens? Donc, si vous faites référence à des mois particuliers et connus, ma méthode est précise à 100% et vous seriez une approximation, cependant, si vous faites référence à un mois en général, votre approximation serait une meilleure idée, et la mienne serait juste une mauvaise idée (elle n'est pas faite pour ça et ça ne servirait à rien de l'utiliser). Voici le lien vers mon article décrivant le problème et apportant une solution: stackoverflow.com/questions/1916358/…
Erx_VB.NExT.Coder

2
Cela semble être la même logique utilisée par la fonction Sql Server DateDiff (mois, ...). Il a également l'avantage d'être extrêmement concis et facile à expliquer et à comprendre. Je l'expliquerais comme suit ... combien de pages du calendrier auriez-vous à tourner pour passer d'une date à l'autre?
JoelFan

51

(Je me rends compte que c'est une vieille question, mais ...)

C'est relativement pénible à faire en .NET pur. Je recommanderais ma propre bibliothèque Noda Time , qui est particulièrement conçue pour des choses comme celle-ci:

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

(Il existe d'autres options, par exemple, si vous ne voulez qu'un nombre de mois, même sur plusieurs années, vous utiliseriez Period period = Period.Between(start, end, PeriodUnits.Months);)


J'ai téléchargé votre bibliothèque et j'ai copié le code que vous avez écrit ci-dessus, mais je reçois une erreur de compilation. L'opérateur d'erreur 1 «-» ne peut pas être appliqué aux opérandes de type «NodaTime.LocalDate» et «NodaTime.LocalDate». Je connais ce post depuis 5 ans, est-ce que quelque chose a changé depuis ce temps, ce qui fait que ce code ne fonctionne pas?
Hakan Fıstık

1
@HakamFostok: Désolé - cela fonctionnera lorsque la version 2.0 sera publiée, mais d'ici là, vous devez utiliser Period.Between. J'ai édité le code pour qu'il fonctionne avec NodaTime 1.3.1.
Jon Skeet

merci beaucoup la bibliothèque NodaTime a fait exactement ce que je voulais faire. Je voulais calculer non seulement les mois entre deux dates mais aussi les jours restants, et c'est exactement ce que NodaTime a fait, merci encore.
Hakan Fıstık

1
@JonSkeet Cette bibliothèque est vraiment de la magie noire. Les dates me mordent tout le temps. Cet extrait de code m'a fait gagner énormément de temps.
onefootswill

28

Peut-être que vous ne voulez pas connaître les fractions mensuelles; Et ce code?


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
Je ne comprends pas le * 100. Devrait-il être * 12?
Volants

9

Vous devrez définir ce que vous entendez par TotalMonths pour commencer.
Une définition simple met un mois à 30,4 jours (365,25 / 12).

Au-delà de cela, toute définition incluant des fractions semble inutile, et la valeur entière plus courante (mois entiers entre les dates) dépend également de règles métier non standard.


9

J'ai écrit une méthode d'extension très simple DateTimeet DateTimeOffsetpour ce faire. Je voulais que cela fonctionne exactement comme une TotalMonthspropriété sur laquelle TimeSpanfonctionnerait: c'est-à-dire retourner le nombre de mois complets entre deux dates, en ignorant les mois partiels. Parce qu'elle est basée sur DateTime.AddMonths()elle respecte différentes longueurs de mois et renvoie ce qu'un humain comprendrait comme une période de mois.

(Malheureusement, vous ne pouvez pas l'implémenter en tant que méthode d'extension sur TimeSpan car cela ne conserve pas la connaissance des dates réelles utilisées, et pendant des mois, elles sont importantes.)

Le code et les tests sont tous deux disponibles sur GitHub . Le code est très simple:

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

Et il réussit tous ces cas de test unitaires:

// 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
Rustique, mais meilleure solution. Copies et collés. Merci
Daniel Dolz

8

Vous devez le résoudre vous-même en dehors des datetimes. La façon dont vous gérez les jours de bout à la fin dépendra de l'utilisation que vous en faites.

Une méthode serait de compter le mois, puis de corriger les jours à la fin. Quelque chose comme:

   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;

Bon code, cependant, 1 bug: à la place: (comme 28 février + 1 mois == 28 mars) :-) // decimal daysInEndMonth = (end - end.AddMonths (1)). Days; Je suggère: decimal daysInEndMonth = DateTime.DaysInMonth (end.Year, end.Month) * -1;
bezieur

3

Je le ferais comme ceci:

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
C'est certainement un algorithme, mais il pourrait être considérablement simplifié àreturn (dtOther.Month - dtThis.Month) + 12 * (dtOther.Year - dtThis.Year);
Adam Robinson

1
Deux problèmes: vous partez de 2 dates, pas d'un TimeSpan. Deuxièmement, vous calculez entre le 1er des deux mois, c'est une définition très discutable. Bien que cela puisse être juste parfois.
Henk Holterman

@Henk: Oui, bien sûr, ce n'est pas toujours vrai, c'est pourquoi j'ai dit que c'est comme ça que je le ferais, pas comment tout le monde devrait le faire. Le PO n'a pas précisé comment le résultat devrait être calculé. @Adam: Wow, je pensais encore une fois beaucoup trop compliqué ... cela m'arrive trop souvent. Merci pour le commentaire, vous avez évidemment raison, votre version est bien meilleure. Je vais l'utiliser à partir de maintenant.
Maximilian Mayerl

@Adam: pourquoi ne soumettez-vous pas ceci comme une vraie réponse?! C'est le plus compact à ce jour. Très lisse.
Dinah

@Dinah: Je ne voulais pas supposer que c'était ce que vous vouliez réellement. Si c'est le cas, j'ai modifié ma réponse précédente pour inclure cette approche.
Adam Robinson

3

Il n'y a pas beaucoup de réponses claires à ce sujet parce que vous assumez toujours les choses.

Cette solution calcule entre deux dates les mois entre la supposition que vous souhaitez enregistrer le jour du mois à des fins de comparaison, (ce qui signifie que le jour du mois est pris en compte dans le calcul)

Par exemple, si vous avez une date du 30 janvier 2012, le 29 février 2012 ne sera pas un mois mais le 1er mars 2013 le sera.

Il a été testé assez minutieusement, il sera probablement nettoyé plus tard au fur et à mesure que nous l'utilisons, et prend deux dates au lieu d'un Timespan, ce qui est probablement mieux. J'espère que cela aide quelqu'un d'autre.

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
}

3

La réponse acceptée fonctionne parfaitement lorsque vous voulez des mois complets.

J'avais besoin de mois partiels. Voici la solution que j'ai proposée pendant des mois partiels:

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

J'avais aussi besoin d'une différence d'année avec le même besoin d'années partielles. Voici la solution que j'ai trouvée:

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

Vous avez eu un bogue de logique dans votre YearDifferencefonction quand lValue.Month < rValue.Month- j'ai corrigé cela maintenant, vous voudrez peut-être revoir ...
Stobor

2

Ancienne question que je connais, mais pourrait aider quelqu'un. J'ai utilisé la réponse acceptée par @Adam ci-dessus, mais j'ai ensuite vérifié si la différence était de 1 ou -1, puis vérifiez s'il s'agit de la différence d'un mois civil complet. Ainsi, le 21/07/55 et le 20/08/55 ne seraient pas un mois complet, mais le 21/07/55 et le 21/07/55 le seraient.

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

2
case IntervalType.Month:
    returnValue = start.AddMonths(-end.Month).Month.ToString();
    break;
case IntervalType.Year:
    returnValue = (start.Year - end.Year).ToString();
    break;

2
Une description accompagnant le code serait également bénéfique pour les autres lecteurs.
Boeckm

ouais s'il vous plaît ajouter quelques commentaires.
Amar

1

Le problème avec les mois est que ce n'est pas vraiment une mesure simple - ils ne sont pas de taille constante. Vous devrez définir vos règles pour ce que vous voulez inclure et travailler à partir de là. Par exemple, du 1er janvier au 1er février - vous pourriez dire que 2 mois sont impliqués là-bas, ou vous pourriez dire que c'est un mois. Alors que dire du «1er janvier 20h00» au «1er février 00h00» - ce n'est pas tout à fait un mois complet. Est-ce 0? 1? qu'en est-il de l'inverse (du 1er janvier 00h00 au 1er février 20h00) ... 1? 2?

Définissez d'abord les règles, puis vous devrez le coder vous-même, j'en ai bien peur ...


1

Si vous voulez avoir un résultat 1entre 28th Febet 1st March:

DateTime date1, date2;
int monthSpan = (date2.Year - date1.Year) * 12 + date2.Month - date1.Month

Cela semble être la même logique utilisée par la fonction Sql Server DateDiff (mois, ...). Il a également l'avantage d'être extrêmement concis et facile à expliquer et à comprendre. Je l'expliquerais comme suit ... combien de pages du calendrier auriez-vous à tourner pour passer d'une date à l'autre?
JoelFan

1

Cette bibliothèque calcule la différence de mois, en tenant compte de toutes les parties de 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

Voici en fait la façon la plus précise de le faire, car la définition de «1 mois» change en fonction du mois, et aucune des autres réponses n'en tient compte! Si vous voulez plus d'informations sur le problème qui n'est pas intégré au framework, vous pouvez lire cet article: Un objet Real Timespan avec .Years & .Months (cependant, la lecture de cet article n'est pas nécessaire pour comprendre et utiliser la fonction ci-dessous, cela fonctionne à 100%, sans les inexactitudes inhérentes à l'approximation que les autres aiment utiliser - et n'hésitez pas à remplacer la fonction .ReverseIt par la fonction .Reverse intégrée que vous pouvez avoir sur votre framework (c'est juste ici pour l'exhaustivité).

Veuillez noter que vous pouvez obtenir n'importe quel nombre de dates / heures de précision, secondes et minutes, ou secondes, minutes et jours, n'importe où jusqu'à des années (qui contiendraient 6 parties / segments). Si vous spécifiez les deux premiers et qu'il date de plus d'un an, il renverra "Il y a 1 an et 3 mois" et ne renverra pas le reste, car vous avez demandé deux segments. s'il n'a que quelques heures, il ne retournera que "il y a 2 heures et 1 minute". Bien sûr, les mêmes règles s'appliquent si vous spécifiez 1, 2, 3, 4, 5 ou 6 segments (maximum à 6 car les secondes, minutes, heures, jours, mois, années ne font que 6 types). Il corrigera également les problèmes de grammaire tels que "minutes" vs "minute" selon qu'il s'agit d'une minute ou plus, même pour tous les types, et la "chaîne" générée sera toujours grammaticalement correcte.

Voici quelques exemples d'utilisation: bAllowSegments identifie le nombre de segments à afficher ... c'est-à-dire: si 3, alors la chaîne de retour serait (à titre d'exemple) ... "3 years, 2 months and 13 days"(n'inclut pas les heures, les minutes et les secondes comme les 3 premières heures catégories sont renvoyées), si toutefois la date était une date plus récente, comme quelque chose d'il y a quelques jours, la spécification des mêmes segments (3) reviendra à la "4 days, 1 hour and 13 minutes ago"place, donc il prend tout en compte!

si bAllowSegments vaut 2, il retournera "3 years and 2 months"et si 6 (valeur maximale) retournera "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds", mais rappelez-vous que ce sera NEVER RETURNquelque chose comme ça "0 years, 0 months, 0 days, 3 hours, 2 minutes and 13 seconds ago"car il comprend qu'il n'y a pas de données de date dans les 3 premiers segments et les ignore, même si vous spécifiez 6 segments , alors ne vous inquiétez pas :). Bien sûr, s'il y a un segment avec 0 dedans, il en tiendra compte lors de la formation de la chaîne, et s'affichera comme "3 days and 4 seconds ago"et en ignorant la partie "0 heures"! Profitez-en et commentez si vous le souhaitez.

 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

Bien sûr, vous aurez besoin d'une fonction "ReplaceLast", qui prend une chaîne source, et un argument spécifiant ce qui doit être remplacé, et un autre argument spécifiant par quoi vous voulez le remplacer, et il ne remplace que la dernière occurrence de cette chaîne ... j'ai inclus mon un si vous n'en avez pas ou ne voulez pas l'implémenter, alors le voici, il fonctionnera "tel quel" sans aucune modification nécessaire. Je sais que la fonction reverseit n'est plus nécessaire (existe dans .net) mais les fonctions ReplaceLast et ReverseIt sont reportées des jours pre-.net, alors veuillez excuser à quel point elle peut sembler datée (fonctionne toujours à 100%, utilise em depuis plus de dix ans, peut garantir qu'ils sont sans bogue) ... :). à votre santé.

<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 

0

Si vous voulez le nombre exact, vous ne pouvez pas seulement le Timespan, car vous devez savoir à quels mois vous avez affaire et si vous avez affaire à une année bissextile, comme vous l'avez dit.

Soit optez pour un nombre approximatif, soit faites un peu de travail avec les DateTimes d'origine



0

Il n'y a pas de moyen intégré de faire cela avec précision dans idiomatic-c #. Il existe cependant des solutions de contournement, telles que cet exemple CodeProject que les gens ont codé.


0

Si vous avez affaire à des mois et des années, vous avez besoin de quelque chose qui sait combien de jours chaque mois compte et quelles années sont des années bissextiles.

Entrez le calendrier grégorien (et tout autre calendrier spécifique à la culture implémentations de ).

Bien que Calendar ne fournisse pas de méthodes pour calculer directement la différence entre deux points dans le temps, il dispose de méthodes telles que

DateTime AddWeeks(DateTime time, int weeks)
DateTime AddMonths(DateTime time, int months)
DateTime AddYears(DateTime time, int years)

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;

0

La méthode renvoie une liste qui contient 3 éléments: le premier est l'année, le second le mois et l'élément de fin le jour:

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

Voici ma contribution pour obtenir la différence en mois que j'ai trouvée exacte:

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

Usage:

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

Vous pouvez créer une autre méthode appelée DiffYears et appliquer exactement la même logique que ci-dessus et AddYears au lieu de AddMonths dans la boucle while.


0

Bien tard dans le match, mais j'imagine que cela peut être utile à quelqu'un. La majorité des gens ont tendance à mesurer mois par mois par date, à l'exclusion du fait que les mois se présentent sous différentes variantes. En utilisant ce cadre de pensée, j'ai créé une ligne unique qui compare les dates pour nous. En utilisant le processus suivant.

  1. Tout nombre d'années sur 1 lors de la comparaison de l'année sera multiplié par 12, il n'y a aucun cas où cela peut être égal à moins d'une année complète.
  2. Si l'année de fin est supérieure, nous devons évaluer si le jour en cours est supérieur ou égal au jour précédent 2A. Si le jour de fin est supérieur ou égal, nous prenons le mois en cours, puis ajoutons 12 mois soustrayons le mois du mois de début 2B. Si le jour de fin est inférieur au jour de début, nous effectuons la même chose que ci-dessus sauf que nous ajoutons 1 au mois de début avant de soustraire
  3. Si l'année de fin n'est pas plus grande, nous effectuons la même chose que 2A / 2B, mais sans ajouter les 12 mois car nous n'avons pas besoin d'évaluer autour de l'année.

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

Mort par ternaire?
SpaceBison

0

Mon point de vue sur cette réponse utilise également une méthode d'extension , mais elle peut renvoyer un résultat positif ou négatif.

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

Quelques tests:

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

0

En combinant deux des réponses ci-dessus, une autre méthode d'extension est:

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

Merci à @AdamRobinson et @MarkWhittaker


-1

Calculez le nombre de mois entre 2 dates:

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

1
Ceci est PHP, pas C #.
AFract
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.