Comment obtenir la différence entre deux dates d'année/mois/semaine/jour de manière efficace?
par exemple. La différence entre deux dates est de 1 an, 2 mois, 3 semaines, 4 jours.
La différence représente le nombre d’années, de mois, de semaines et de jours entre deux dates.
C'est en fait assez délicat. Un nombre total de jours différent peut donner le même résultat. Par exemple:
19 juin 2008 au 19 juin 2010 = 2 ans, mais aussi 365 * 2 jours
19 juin 2006 au 19 juin 2008 = 2 ans, mais aussi 365 + 366 jours en raison d'années bissextiles
Vous voudrez peut-être soustraire des années jusqu’à ce que vous ayez deux dates séparées de moins d’un an. Soustrayez ensuite les mois jusqu'à ce que vous obteniez deux dates distantes de moins d'un mois.
Confusion supplémentaire: la soustraction (ou l’addition) de mois est délicate lorsque vous pouvez commencer par une date du 30 mars - qu’est-ce qu’un mois plus tôt?
Encore plus de confusion (peut ne pas être pertinent): même un jour n'est pas toujours 24 heures. Heure avancée à quelqu'un?
Encore plus de confusion (presque certainement pas pertinent): même une minute ne dure pas toujours 60 secondes. Les secondes sont une source de grande confusion ...
Je n'ai pas le temps de trouver la bonne façon de procéder pour le moment. Cette réponse vise principalement à souligner le fait que ce n'est pas aussi simple que cela puisse paraître.
EDIT: Malheureusement, je ne vais pas avoir assez de temps pour répondre pleinement à cette question. Je vous suggérerais de commencer par définir une structure représentant une Period
:
public struct Period
{
private readonly int days;
public int Days { get { return days; } }
private readonly int months;
public int Months { get { return months; } }
private readonly int years;
public int Years { get { return years; } }
public Period(int years, int months, int days)
{
this.years = years;
this.months = months;
this.days = days;
}
public Period WithDays(int newDays)
{
return new Period(years, months, newDays);
}
public Period WithMonths(int newMonths)
{
return new Period(years, newMonths, days);
}
public Period WithYears(int newYears)
{
return new Period(newYears, months, days);
}
public static DateTime operator +(DateTime date, Period period)
{
// TODO: Implement this!
}
public static Period Difference(DateTime first, DateTime second)
{
// TODO: Implement this!
}
}
Je vous suggère tout d'abord d'implémenter l'opérateur +, ce qui devrait informer la méthode Difference
- vous devez vous assurer que first + (Period.Difference(first, second)) == second
pour toutes les valeurs first
/second
.
Commencez par écrire toute une série de tests unitaires - au début, des cas "faciles", puis passez aux plus difficiles qui impliquent des années bissextiles. Je sais que l’approche normale consiste à écrire un test à la fois, mais j’en avais personnellement un certain nombre avant de commencer tout travail de mise en œuvre.
Accordez-vous une journée pour mettre cela en œuvre correctement. C'est un truc délicat.
Notez que j'ai omis des semaines ici - cette valeur au moins est facile, car c'est toujours 7 jours. Donc, étant donné une période (positive), vous auriez:
int years = period.Years;
int months = period.Months;
int weeks = period.Days / 7;
int daysWithinWeek = period.Days % 7;
(Je vous suggère d'éviter même de penser aux périodes négatives - assurez-vous que tout est positif, tout le temps.)
En partie comme préparation pour essayer de répondre correctement à cette question (et peut-être même définitivement ...), en partie pour examiner à quel point on peut faire confiance au code qui est collé sur SO, et en partie pour tenter de trouver des bugs, j’ai créé tests unitaires pour cette question et les a appliqués à de nombreuses solutions proposées à partir de cette page et à quelques doublons.
Les résultats sont concluants: pas une seule des contributions de code ne répond exactement à la question. Mise à jour: J'ai maintenant quatre solutions correctes à cette question, y compris la mienne, voir mises à jour ci-dessous.
De cette question, j'ai testé le code par les utilisateurs suivants: Mohammed Ijas Nasirudeen, Ruffin, Malu, MN, Dave, pk., Jani, lc.
C'étaient toutes les réponses qui fournissaient les trois années, mois et jours dans leur code. Notez que deux d'entre eux, Dave et Jani, ont donné le nombre total de jours et de mois, plutôt que le nombre total de mois restant après le comptage des années et le nombre total de jours restant après le comptage des mois. Je pense que les réponses sont fausses quant à ce que le PO semblait vouloir, mais les tests unitaires ne vous en disent évidemment pas grand-chose. (Notez que dans le cas de Jani, c'était mon erreur et son code était en fait correct - voir Mise à jour 4 ci-dessous)
Les réponses de Jon Skeet, Aghasoleimani, Mukesh Kumar, Richard, Colin, Sheir, que je viens de voir, Chalkey et Andy, étaient incomplètes. Cela ne veut pas dire que les réponses ne sont pas bonnes, en fait, plusieurs d'entre elles sont des contributions utiles à une solution. Cela signifie simplement qu'il n'y avait pas de code prenant deux DateTime
s et renvoyant 3 int
s que je pouvais tester correctement. Cependant, quatre d’entre eux parlent d’utiliser TimeSpan
. Comme beaucoup de personnes l'ont mentionné, TimeSpan
ne renvoie pas le nombre d'éléments supérieurs à jours.
Les autres réponses que j'ai testées venaient de
cette réponse de_____ curieux_geek est un code sur une page à laquelle il a accédé, ce que je ne pense pas qu'il a écrit. La réponse de Jani est la seule à utiliser une bibliothèque externe, Time Period Library for .Net.
Toutes les autres réponses à toutes ces questions semblaient être incomplètes. La question 9 concerne l'âge en années et les trois réponses sont celles qui dépassent les années, mois et jours brefs et calculés. Si quelqu'un trouve d'autres doublons de cette question, veuillez me le faire savoir.
Tout simplement: j'ai fait une interface
public interface IDateDifference
{
void SetDates(DateTime start, DateTime end);
int GetYears();
int GetMonths();
int GetDays();
}
Pour chaque réponse, j'ai écrit une classe implémentant cette interface, en utilisant le code copié et collé comme base. Bien sûr, je devais adapter des fonctions avec différentes signatures, etc., mais j’essayais de faire les modifications minimales nécessaires, tout en préservant tout le code logique.
J'ai écrit un tas de tests NUnit dans une classe générique abstraite
[TestFixture]
public abstract class DateDifferenceTests<DDC> where DDC : IDateDifference, new()
et ajouté une classe dérivée vide
public class Rajeshwaran_S_P_Test : DateDifferenceTests<Rajeshwaran_S_P>
{
}
dans le fichier source pour chaque classe IDateDifference
.
NUnit est assez intelligent pour faire le reste.
Quelques-unes d'entre elles ont été écrites à l'avance et les autres ont été écrites pour tenter de casser des implémentations apparemment efficaces.
[TestFixture]
public abstract class DateDifferenceTests<DDC> where DDC : IDateDifference, new()
{
protected IDateDifference ddClass;
[SetUp]
public void Init()
{
ddClass = new DDC();
}
[Test]
public void BasicTest()
{
ddClass.SetDates(new DateTime(2012, 12, 1), new DateTime(2012, 12, 25));
CheckResults(0, 0, 24);
}
[Test]
public void AlmostTwoYearsTest()
{
ddClass.SetDates(new DateTime(2010, 8, 29), new DateTime(2012, 8, 14));
CheckResults(1, 11, 16);
}
[Test]
public void AlmostThreeYearsTest()
{
ddClass.SetDates(new DateTime(2009, 7, 29), new DateTime(2012, 7, 14));
CheckResults(2, 11, 15);
}
[Test]
public void BornOnALeapYearTest()
{
ddClass.SetDates(new DateTime(2008, 2, 29), new DateTime(2009, 2, 28));
CheckControversialResults(0, 11, 30, 1, 0, 0);
}
[Test]
public void BornOnALeapYearTest2()
{
ddClass.SetDates(new DateTime(2008, 2, 29), new DateTime(2009, 3, 1));
CheckControversialResults(1, 0, 0, 1, 0, 1);
}
[Test]
public void LongMonthToLongMonth()
{
ddClass.SetDates(new DateTime(2010, 1, 31), new DateTime(2010, 3, 31));
CheckResults(0, 2, 0);
}
[Test]
public void LongMonthToLongMonthPenultimateDay()
{
ddClass.SetDates(new DateTime(2009, 1, 31), new DateTime(2009, 3, 30));
CheckResults(0, 1, 30);
}
[Test]
public void LongMonthToShortMonth()
{
ddClass.SetDates(new DateTime(2009, 8, 31), new DateTime(2009, 9, 30));
CheckControversialResults(0, 1, 0, 0, 0, 30);
}
[Test]
public void LongMonthToPartWayThruShortMonth()
{
ddClass.SetDates(new DateTime(2009, 8, 31), new DateTime(2009, 9, 10));
CheckResults(0, 0, 10);
}
private void CheckResults(int years, int months, int days)
{
Assert.AreEqual(years, ddClass.GetYears());
Assert.AreEqual(months, ddClass.GetMonths());
Assert.AreEqual(days, ddClass.GetDays());
}
private void CheckControversialResults(int years, int months, int days,
int yearsAlt, int monthsAlt, int daysAlt)
{
// gives the right output but unhelpful messages
bool success = ((ddClass.GetYears() == years
&& ddClass.GetMonths() == months
&& ddClass.GetDays() == days)
||
(ddClass.GetYears() == yearsAlt
&& ddClass.GetMonths() == monthsAlt
&& ddClass.GetDays() == daysAlt));
Assert.IsTrue(success);
}
}
La plupart des noms sont légèrement ridicules et n'expliquent pas vraiment pourquoi le code peut échouer au test. Toutefois, l'examen des deux dates et la réponse devraient suffire à comprendre le test.
Il y a deux fonctions qui font toutes les Assert
s, CheckResults()
et CheckControversialResults()
. Celles-ci fonctionnent bien pour sauvegarder la frappe et donner les bons résultats, mais malheureusement, il est plus difficile de voir exactement ce qui ne va pas (parce que Assert
dans CheckControversialResults()
échouera avec "Expected true", plutôt que de vous dire quelle valeur était incorrecte Si quelqu'un a un meilleur moyen de le faire (évitez d'écrire les mêmes chèques à chaque fois, mais ayez des messages d'erreur plus utiles), veuillez me le faire savoir.
CheckControversialResults()
est utilisé dans quelques cas où il semble y avoir deux opinions différentes sur ce qui est juste. J'ai une opinion personnelle, mais je pensais que je devrais être libéral dans ce que j'ai accepté ici. L’essentiel est de décider si un an après le 29 février est le 28 février ou le 1 er mars.
Ces tests constituent le noeud du problème et il pourrait très bien y avoir des erreurs. Merci de faire un commentaire, le cas échéant. Il serait également bon d’entendre des suggestions pour d’autres tests afin de vérifier les futures itérations de réponses.
Aucun test ne concerne l’heure de la journée - tous les DateTime
s sont à minuit. Y compris les heures, tant qu’il est clair que l’arrondi en jours (je pense que c’est le cas), pourrait révéler encore plus de défauts.
Le tableau de bord complet des résultats est le suivant:
ChuckRostance_Test 3 failures S S S F S S F S F
Dave_Test 6 failures F F S F F F F S S
Dylan_Hayes_Test 9 failures F F F F F F F F F
ho1_Test 3 failures F F S S S S F S S
Jani_Test 6 failures F F S F F F F S S
Jon_Test 1 failure S S S S S S F S S
lc_Test 2 failures S S S S S F F S S
LukeH_Test 1 failure S S S S S S F S S
Malu_MN_Test 1 failure S S S S S S S F S
Mohammed_Ijas_Nasirudeen_Test 2 failures F S S F S S S S S
pk_Test 6 failures F F F S S F F F S
Rajeshwaran_S_P_Test 7 failures F F S F F S F F F
ruffin_Test 3 failures F S S F S S F S S
this_curious_geek_Test 2 failures F S S F S S S S S
Mais notez que la solution de Jani était en fait correcte et qu'elle a réussi tous les tests - voir la mise à jour 4 ci-dessous.
Les colonnes sont dans l'ordre alphabétique du nom du test:
Trois réponses n'ont échoué que pour un seul test, celui de Jon, celui de LukeH et celui de Manu MN. Gardez à l'esprit que ces tests ont probablement été écrits spécifiquement pour corriger les failles de ces réponses.
Chaque test a été passé avec au moins un code, ce qui rassure un peu qu'aucun de ces tests ne soit erroné.
Certaines réponses ont échoué à de nombreux tests. J'espère que personne ne sent qu'il s'agit d'une condamnation des efforts de cette affiche. Premièrement, le nombre de succès est assez arbitraire, car les tests ne couvrent pas de manière uniforme les problèmes de l’espace de la question. Deuxièmement, il ne s'agit pas d'un code de production: les réponses sont affichées afin que les utilisateurs puissent en tirer des enseignements et non les copier exactement dans leurs programmes. Un code qui échoue à de nombreux tests peut toujours contenir de bonnes idées. Au moins une pièce qui a échoué à de nombreux tests comportait un petit bug que je n’ai pas corrigé. Je suis reconnaissant à tous ceux qui ont pris le temps de partager leur travail avec tous les autres pour avoir rendu ce projet si intéressant.
Il ya trois:
Les calendriers sont difficiles. J'ai écrit neuf tests, dont trois où deux réponses sont possibles. Certains des tests pour lesquels je n'avais qu'une réponse pourraient ne pas être unanimement acceptés. Réfléchir à ce que nous entendons exactement lorsque nous disons "1 mois plus tard" ou "2 ans plus tôt" est délicat dans de nombreuses situations. Et aucun de ce code n’a eu à traiter de toutes les complexités de choses telles que l’établissement lorsque les années bissextiles sont terminées. Tout cela utilise le code de la bibliothèque pour gérer les dates. Si vous imaginez la spécification pour indiquer l'heure en jours, semaines, mois et années, il y a toutes sortes de choses cruelles. Parce que nous le savons assez bien depuis l’école primaire et que nous l’utilisons tous les jours, nous sommes aveugles à beaucoup de ces idiosyncracies. La question n’est pas une question académique - divers types de décomposition de périodes en années, trimestres et mois sont essentiels dans les logiciels de comptabilité pour obligations et autres produits financiers.
Écrire du code correct est difficile. Il y avait beaucoup de bugs. Dans des sujets un peu plus obscurs ou dans des questions moins populaires que les chances qu'un bogue existe sans avoir été signalé par un intervenant sont beaucoup, beaucoup plus grandes que pour cette question. Vous ne devriez vraiment jamais, jamais copier le code de SO dans votre programme sans comprendre exactement ce qu'il fait. Inversement, vous ne devriez probablement pas écrire dans votre réponse du code prêt à être copié et collé, mais un pseudo-code plutôt intelligent et expressif qui permet à une personne de comprendre la solution et de mettre en œuvre sa propre version (avec ses propres bogues). !)
Les tests unitaires sont utiles. Je compte toujours publier ma propre solution à ce problème lorsque j'y reviendrai (pour que quelqu'un d'autre trouve les suppositions cachées et incorrectes!). Cela était un excellent exemple de "sauvegarde des bugs" en les transformant en tests unitaires. corrigez la prochaine version du code avec.
L'ensemble du projet est maintenant à https://github.com/jwg4/date-difference Cela inclut ma propre tentative jwg.cs
, qui passe tous les tests que j'ai actuellement, y compris quelques nouveaux ceux qui vérifient le bon moment de la journée. N'hésitez pas à ajouter plus de tests pour casser cela et d'autres implémentations, ou un meilleur code pour répondre à la question.
@MattJohnson a ajouté une implémentation qui utilise NodaTime de Jon Skeet. Il passe tous les tests en cours.
La réponse de @ KirkWoll à Différence en mois entre deux dates a été ajoutée au projet sur github. Il passe tous les tests en cours.
@Jani a souligné dans un commentaire que j'avais mal utilisé son code. Il a suggéré des méthodes qui comptaient correctement les années, les mois et les jours (à côté de celles qui comptaient le nombre total de jours et de mois, pas les autres), mais j’ai utilisé par erreur les mauvaises méthodes dans mon code de test. J'ai corrigé mon emballage autour de son code et il passe maintenant tous les tests. Il y a maintenant quatre solutions correctes, dont celle de Jani. Deux bibliothèques d'utilisation (Intenso.TimePeriod et NodaTime) et deux sont écrites à partir de zéro.
Pour le calcul correct de la différence Années/Mois/Semaines, le Calendrier du CultureInfo doit être pris en compte:
La classe DateDiff de la bibliothèque de périodes Time pour .NET respecte tous ces facteurs:
// ----------------------------------------------------------------------
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
// description
Console.WriteLine( "DateDiff.GetDescription(1): {0}", dateDiff.GetDescription( 1 ) );
// > DateDiff.GetDescription(1): 1 Year
Console.WriteLine( "DateDiff.GetDescription(2): {0}", dateDiff.GetDescription( 2 ) );
// > DateDiff.GetDescription(2): 1 Year 4 Months
Console.WriteLine( "DateDiff.GetDescription(3): {0}", dateDiff.GetDescription( 3 ) );
// > DateDiff.GetDescription(3): 1 Year 4 Months 12 Days
Console.WriteLine( "DateDiff.GetDescription(4): {0}", dateDiff.GetDescription( 4 ) );
// > DateDiff.GetDescription(4): 1 Year 4 Months 12 Days 12 Hours
Console.WriteLine( "DateDiff.GetDescription(5): {0}", dateDiff.GetDescription( 5 ) );
// > DateDiff.GetDescription(5): 1 Year 4 Months 12 Days 12 Hours 41 Mins
Console.WriteLine( "DateDiff.GetDescription(6): {0}", dateDiff.GetDescription( 6 ) );
// > DateDiff.GetDescription(6): 1 Year 4 Months 12 Days 12 Hours 41 Mins 29 Secs
} // DateDiffSample
DateDiff calcule également la différence des trimestres.
Les années bissextiles et les mois inégaux en font un problème non trivial. Je suis sûr que quelqu'un peut trouver un moyen plus efficace, mais voici une option - approcher d'abord le plus petit et ajuster (non testé):
public static void GetDifference(DateTime date1, DateTime date2, out int Years,
out int Months, out int Weeks, out int Days)
{
//assumes date2 is the bigger date for simplicity
//years
TimeSpan diff = date2 - date1;
Years = diff.Days / 366;
DateTime workingDate = date1.AddYears(Years);
while(workingDate.AddYears(1) <= date2)
{
workingDate = workingDate.AddYears(1);
Years++;
}
//months
diff = date2 - workingDate;
Months = diff.Days / 31;
workingDate = workingDate.AddMonths(Months);
while(workingDate.AddMonths(1) <= date2)
{
workingDate = workingDate.AddMonths(1);
Months++;
}
//weeks and days
diff = date2 - workingDate;
Weeks = diff.Days / 7; //weeks always have 7 days
Days = diff.Days % 7;
}
Qu'en est-il de l'utilisation de l'espace de noms System.Data.Linq
et de sa méthode SqlMethods.DateDiffMonth
?
Par exemple, dites:
DateTime starDT = {01-Jul-2009 12:00:00 AM}
DateTime endDT = {01-Nov-2009 12:00:00 AM}
Ensuite:
int monthDiff = System.Data.Linq.SqlClient.SqlMethods.DateDiffMonth(startDT, endDT);
==> 4
Il existe d'autres méthodes DateDiff
statiques dans la classe SqlMethods
.
Soustrayez deux instances DateTime
pour vous donner une TimeSpan
qui possède une propriété Days
. (Par exemple, dans PowerShell):
PS> ([datetime] :: today - [datetime] "2009-04-07") Jours: 89 Heures: 0 Minutes: 0 Secondes: 0 Millisecondes: 0 Ticks: 76896000000000 TotalDays: 89 Total heures: 2136 TotalMinutes: 128160 TotalSecondes: 7689600
La conversion de jours en années ou en semaines est relativement facile (les jours dans une année peuvent être 365, 365,25, ... selon le contexte). Les mois sont beaucoup plus difficiles, car sans date de base, vous ne savez pas quelles durées de mois s'appliquent.
En supposant que vous commenciez avec votre date de base, vous pouvez soustraire progressivement en comptant les premières années (vérification des années bissextiles), puis les longueurs des mois (indexation à partir de startDate.Month), puis les semaines (jours restants divisés par 7), puis les jours ).
Il y a beaucoup de cas Edge à considérer, par exemple: 2005-03-01 est un an du 2004-03-01, et du 2004-02-29 selon ce que vous entendez par "Année".
Eh bien, @ Jon Skeet, si nous ne sommes pas inquiets de perdre plus de temps que de jours (et de rouler encore de jours en unités plus grandes plutôt que de compter le nombre total de jours), selon le PO, ce n’est vraiment pas si difficile en C #. Ce qui rend les calculs de date si difficiles, c'est que le nombre d'unités dans chaque unité composite change souvent. Imaginez si chaque 3 gallons d'essence ne contenait que 3 litres, mais que chaque 12 était de 7 litres, sauf le vendredi, lorsque ...
Heureusement, les dates ne sont qu'un long trajet à travers la plus grande fonction entière . Ces exceptions folles sont exaspérantes, à moins que vous n'ayez parcouru complètement l'unité insensée, quand ce n'est plus un problème. Si vous êtes né le 25/12/1900, vous êtes toujours EXACTEMENT 100 le 25/12/2000, quelles que soient les années bissextiles, les secondes ou l’heure d’été. Dès que vous avez parcouru les pourcentages qui composent la dernière unité composite, vous revenez à l'unité. Vous en avez ajouté un et vous devez recommencer.
Ce qui revient à dire que si vous faites des années, des mois, des jours, la seule unité étrangement comprise est le mois (en jours). Si vous devez emprunter à partir de la valeur du mois pour gérer un endroit où vous soustrayez plus de jours que vous n'en avez, vous devez simplement connaître le nombre de jours du mois précédent. Aucun autre cas particulier ne compte.
Et C # vous le donne dans System.DateTime.DaysInMonth (intYear, intMonth).
(Si votre mois Maintenant est plus petit que votre mois Puis, il n'y a pas de problème. Chaque année a 12 mois.)
Et le même accord si nous allons plus granulaire ... il vous suffit de savoir combien (petites unités) sont dans la dernière (unité composite). Une fois que vous êtes passé, vous obtenez une autre valeur entière plus de (unité composite). Puis soustrayez le nombre de petites unités que vous avez manquées en commençant là où vous avez fait Puis, et rajoutez le nombre de celles que vous avez dépassées après la rupture de l’unité composite avec votre valeur Now.
Alors voici ce que j'ai de ma première coupe à la soustraction de deux dates. Cela peut fonctionner. J'espère utile.
(EDIT: Changé NewMonth> OldMonth chèque à NewMonth> = OldMonth, car nous n'avons pas besoin d'emprunter un si les mois sont les mêmes (idem pour les jours). C'est-à-dire, 11 novembre 2011 moins 9 novembre 2010 donnait -1 année , 12 mois, 2 jours (c’est-à-dire 2 jours, mais le royal que nous avons emprunté alors que la royauté n’en avait pas besoin.)
(EDIT: il fallait vérifier le mois = le mois où nous devions emprunter des jours pour soustraire un jour de congé. De dteNow.Day & dteNow.Day <dteThen.Day, car nous devions soustraire un an pour obtenir 11 mois et les jours supplémentaires OK, donc il y a quelques points aberrants; ^ Je pense que je suis proche maintenant.)
private void Form1_Load(object sender, EventArgs e) {
DateTime dteThen = DateTime.Parse("3/31/2010");
DateTime dteNow = DateTime.Now;
int intDiffInYears = 0;
int intDiffInMonths = 0;
int intDiffInDays = 0;
if (dteNow.Month >= dteThen.Month)
{
if (dteNow.Day >= dteThen.Day)
{ // this is a best case, easy subtraction situation
intDiffInYears = dteNow.Year - dteThen.Year;
intDiffInMonths = dteNow.Month - dteThen.Month;
intDiffInDays = dteNow.Day - dteThen.Day;
}
else
{ // else we need to substract one from the month diff (borrow the one)
// and days get wacky.
// Watch for the outlier of Month = Month with DayNow < DayThen, as then we've
// got to subtract one from the year diff to borrow a month and have enough
// days to subtract Then from Now.
if (dteNow.Month == dteThen.Month)
{
intDiffInYears = dteNow.Year - dteThen.Year - 1;
intDiffInMonths = 11; // we borrowed a year and broke ONLY
// the LAST month into subtractable days
// Stay with me -- because we borrowed days from the year, not the month,
// this is much different than what appears to be a similar calculation below.
// We know we're a full intDiffInYears years apart PLUS eleven months.
// Now we need to know how many days occurred before dteThen was done with
// dteThen.Month. Then we add the number of days we've "earned" in the current
// month.
//
// So 12/25/2009 to 12/1/2011 gives us
// 11-9 = 2 years, minus one to borrow days = 1 year difference.
// 1 year 11 months - 12 months = 11 months difference
// (days from 12/25 to the End Of Month) + (Begin of Month to 12/1) =
// (31-25) + (0+1) =
// 6 + 1 =
// 7 days diff
//
// 12/25/2009 to 12/1/2011 is 1 year, 11 months, 7 days apart. QED.
int intDaysInSharedMonth = System.DateTime.DaysInMonth(dteThen.Year, dteThen.Month);
intDiffInDays = intDaysInSharedMonth - dteThen.Day + dteNow.Day;
}
else
{
intDiffInYears = dteNow.Year - dteThen.Year;
intDiffInMonths = dteNow.Month - dteThen.Month - 1;
// So now figure out how many more days we'd need to get from dteThen's
// intDiffInMonth-th month to get to the current month/day in dteNow.
// That is, if we're comparing 2/8/2011 to 11/7/2011, we've got (10/8-2/8) = 8
// full months between the two dates. But then we've got to go from 10/8 to
// 11/07. So that's the previous month's (October) number of days (31) minus
// the number of days into the month dteThen went (8), giving the number of days
// needed to get us to the end of the month previous to dteNow (23). Now we
// add back the number of days that we've gone into dteNow's current month (7)
// to get the total number of days we've gone since we ran the greatest integer
// function on the month difference (23 to the end of the month + 7 into the
// next month == 30 total days. You gotta make it through October before you
// get another month, G, and it's got 31 days).
int intDaysInPrevMonth = System.DateTime.DaysInMonth(dteNow.Year, (dteNow.Month - 1));
intDiffInDays = intDaysInPrevMonth - dteThen.Day + dteNow.Day;
}
}
}
else
{
// else dteThen.Month > dteNow.Month, and we've got to amend our year subtraction
// because we haven't earned our entire year yet, and don't want an obo error.
intDiffInYears = dteNow.Year - dteThen.Year - 1;
// So if the dates were THEN: 6/15/1999 and NOW: 2/20/2010...
// Diff in years is 2010-1999 = 11, but since we're not to 6/15 yet, it's only 10.
// Diff in months is (Months in year == 12) - (Months lost between 1/1/1999 and 6/15/1999
// when dteThen's clock wasn't yet rolling == 6) = 6 months, then you add the months we
// have made it into this year already. The clock's been rolling through 2/20, so two months.
// Note that if the 20 in 2/20 hadn't been bigger than the 15 in 6/15, we're back to the
// intDaysInPrevMonth trick from earlier. We'll do that below, too.
intDiffInMonths = 12 - dteThen.Month + dteNow.Month;
if (dteNow.Day >= dteThen.Day)
{
intDiffInDays = dteNow.Day - dteThen.Day;
}
else
{
intDiffInMonths--; // subtract the month from which we're borrowing days.
// Maybe we shoulda factored this out previous to the if (dteNow.Month > dteThen.Month)
// call, but I think this is more readable code.
int intDaysInPrevMonth = System.DateTime.DaysInMonth(dteNow.Year, (dteNow.Month - 1));
intDiffInDays = intDaysInPrevMonth - dteThen.Day + dteNow.Day;
}
}
this.addToBox("Years: " + intDiffInYears + " Months: " + intDiffInMonths + " Days: " + intDiffInDays); // adds results to a rich text box.
}
Si vous soustrayez deux instances de DateTime,
qui renverront une instance de TimeSpan , qui représentera la différence entre les deux dates.
TimeSpan period = endDate.AddDays(1) - startDate;
DateTime date = new DateTime(period.Ticks);
int totalYears = date.Year - 1;
int totalMonths = ((date.Year - 1) * 12) + date.Month - 1;
int totalWeeks = (int)period.TotalDays / 7;
date.année - 1 car l'année 0 n'existe pas . date.mois - 1, le mois 0 n'existe pas
DateTime dt1 = new DateTime(2009, 3, 14);
DateTime dt2 = new DateTime(2008, 3, 15);
int diffMonth = Math.Abs((dt2.Year - dt1.Year)*12 + dt1.Month - dt2.Month)
Je suis tombé sur ce post en cherchant à résoudre un problème similaire. J'essayais de trouver l'âge d'un animal en unités d'années, de mois, de semaines et de jours. Ces valeurs sont ensuite affichées dans SpinEdits où l'utilisateur peut modifier manuellement les valeurs pour rechercher/estimer une date de naissance. Lorsque ma forme a été passée une date de naissance d'un mois avec moins de 31 jours, la valeur calculée était 1 jour de congé. J'ai basé ma solution sur la réponse d'Ic ci-dessus.
Méthode de calcul principale appelée après le chargement de mon formulaire.
birthDateDisplay.Text = birthDate.ToString("MM/dd/yyyy");
DateTime currentDate = DateTime.Now;
Int32 numOfDays = 0;
Int32 numOfWeeks = 0;
Int32 numOfMonths = 0;
Int32 numOfYears = 0;
// changed code to follow this model http://stackoverflow.com/posts/1083990/revisions
//years
TimeSpan diff = currentDate - birthDate;
numOfYears = diff.Days / 366;
DateTime workingDate = birthDate.AddYears(numOfYears);
while (workingDate.AddYears(1) <= currentDate)
{
workingDate = workingDate.AddYears(1);
numOfYears++;
}
//months
diff = currentDate - workingDate;
numOfMonths = diff.Days / 31;
workingDate = workingDate.AddMonths(numOfMonths);
while (workingDate.AddMonths(1) <= currentDate)
{
workingDate = workingDate.AddMonths(1);
numOfMonths++;
}
//weeks and days
diff = currentDate - workingDate;
numOfWeeks = diff.Days / 7; //weeks always have 7 days
// if bday month is same as current month and bday day is after current day, the date is off by 1 day
if(DateTime.Now.Month == birthDate.Month && DateTime.Now.Day < birthDate.Day)
numOfDays = diff.Days % 7 + 1;
else
numOfDays = diff.Days % 7;
// If the there are fewer than 31 days in the birth month, the date calculated is 1 off
// Dont need to add a day for the first day of the month
int daysInMonth = 0;
if ((daysInMonth = DateTime.DaysInMonth(birthDate.Year, birthDate.Month)) != 31 && birthDate.Day != 1)
{
startDateforCalc = DateTime.Now.Date.AddDays(31 - daysInMonth);
// Need to add 1 more day if it is a leap year and Feb 29th is the date
if (DateTime.IsLeapYear(birthDate.Year) && birthDate.Day == 29)
startDateforCalc = startDateforCalc.AddDays(1);
}
yearsSpinEdit.Value = numOfYears;
monthsSpinEdit.Value = numOfMonths;
weeksSpinEdit.Value = numOfWeeks;
daysSpinEdit.Value = numOfDays;
Et puis, dans mon gestionnaire d'événements spinEdit_EditValueChanged, je calcule la nouvelle date de naissance à partir de mon startDateforCalc en fonction des valeurs des modifications apportées. (SpinEdits sont obligés de n'autoriser que> = 0)
birthDate = startDateforCalc.Date.AddYears(-((Int32)yearsSpinEdit.Value)).AddMonths(-((Int32)monthsSpinEdit.Value)).AddDays(-(7 * ((Int32)weeksSpinEdit.Value) + ((Int32)daysSpinEdit.Value)));
birthDateDisplay.Text = birthDate.ToString("MM/dd/yyyy");
Je sais que ce n’est pas la solution la plus jolie, mais cela semble fonctionner pour moi pour tous les mois et toutes les années.
Jours: (endDate - startDate) .Days
Semaines: (endDate - startDate) .Days/7
Années: Mois/12
Mois: un TimeSpan ne fournissant que des jours, utilisez donc le code suivant pour obtenir le nombre de mois entiers compris entre une date de début et une date de fin spécifiées. Par exemple, le nombre de mois entiers entre 01/10/2000 et 02/10/2000 est 1. Le nombre de mois entiers entre 01/10/2000 et 02/09/2000 est 0.
public int getMonths(DateTime startDate, DateTime endDate)
{
int months = 0;
if (endDate.Month <= startDate.Month)
{
if (endDate.Day < startDate.Day)
{
months = (12 * (endDate.Year - startDate.Year - 1))
+ (12 - startDate.Month + endDate.Month - 1);
}
else if (endDate.Month < startDate.Month)
{
months = (12 * (endDate.Year - startDate.Year - 1))
+ (12 - startDate.Month + endDate.Month);
}
else // (endDate.Month == startDate.Month) && (endDate.Day >= startDate.Day)
{
months = (12 * (endDate.Year - startDate.Year));
}
}
else if (endDate.Day < startDate.Day)
{
months = (12 * (endDate.Year - startDate.Year))
+ (endDate.Month - startDate.Month) - 1;
}
else // (endDate.Month > startDate.Month) && (endDate.Day >= startDate.Day)
{
months = (12 * (endDate.Year - startDate.Year))
+ (endDate.Month - startDate.Month);
}
return months;
}
private void dateTimePicker1_ValueChanged(object sender, EventArgs e)
{
int gyear = dateTimePicker1.Value.Year;
int gmonth = dateTimePicker1.Value.Month;
int gday = dateTimePicker1.Value.Day;
int syear = DateTime.Now.Year;
int smonth = DateTime.Now.Month;
int sday = DateTime.Now.Day;
int difday = DateTime.DaysInMonth(syear, gmonth);
agedisplay = (syear - gyear);
lmonth = (smonth - gmonth);
lday = (sday - gday);
if (smonth < gmonth)
{
agedisplay = agedisplay - 1;
}
if (smonth == gmonth)
{
if (sday < (gday))
{
agedisplay = agedisplay - 1;
}
}
if (smonth < gmonth)
{
lmonth = (-(-smonth)+(-gmonth)+12);
}
if (lday < 0)
{
lday = difday - (-lday);
lmonth = lmonth - 1;
}
if (smonth == gmonth && sday < gday&&gyear!=syear)
{
lmonth = 11;
}
ageDisplay.Text = Convert.ToString(agedisplay) + " Years, " + lmonth + " Months, " + lday + " Days.";
}
Si vous devez trouver la différence entre originalDate et la date du jour, voici un algorithme fiable sans autant de vérifications de condition.
J'ai utilisé les fonctions System.Data.Linq pour trouver les différences avec l'année, le mois et le jour. Veuillez trouver le code c # ci-dessous
DateTime todaysDate = DateTime.Now;
DateTime interimDate = originalDate;
///Find Year diff
int yearDiff = System.Data.Linq.SqlClient.SqlMethods.DateDiffYear(interimDate, todaysDate);
interimDate = interimDate.AddYears(yearDiff);
if (interimDate > todaysDate)
{
yearDiff -= 1;
interimDate = interimDate.AddYears(-1);
}
///Find Month diff
int monthDiff = System.Data.Linq.SqlClient.SqlMethods.DateDiffMonth(interimDate, todaysDate);
interimDate = interimDate.AddMonths(monthDiff);
if (interimDate > todaysDate)
{
monthDiff -= 1;
interimDate = interimDate.AddMonths(-1);
}
///Find Day diff
int daysDiff = System.Data.Linq.SqlClient.SqlMethods.DateDiffDay(interimDate, todaysDate);
Utilisez Noda Time :
var ld1 = new LocalDate(2012, 1, 1);
var ld2 = new LocalDate(2013, 12, 25);
var period = Period.Between(ld1, ld2);
Debug.WriteLine(period); // "P1Y11M24D" (ISO8601 format)
Debug.WriteLine(period.Years); // 1
Debug.WriteLine(period.Months); // 11
Debug.WriteLine(period.Days); // 24
Utilisez la méthode Subtract
de l'objet DateTime
qui renvoie TimeSpan
...
DateTime dt1 = new DateTime(2009, 3, 14);
DateTime dt2 = new DateTime(2008, 3, 15);
TimeSpan ts = dt1.Subtract(dt2);
Double days = ts.TotalDays;
Double hours = ts.TotalHours;
Double years = ts.TotalDays / 365;
Sur la base de la réponse de Joaquim, mais en fixant le calcul lorsque le mois de la date de fin est inférieur au mois de la date de début et en ajoutant du code pour gérer la date de fin avant la date de début:
public static class GeneralHelper
{
public static int GetYears(DateTime startDate, DateTime endDate)
{
if (endDate < startDate)
return -GetYears(endDate, startDate);
int years = (endDate.Year - startDate.Year);
if (endDate.Year == startDate.Year)
return years;
if (endDate.Month < startDate.Month)
return years - 1;
if (endDate.Month == startDate.Month && endDate.Day < startDate.Day)
return years - 1;
return years;
}
public static int GetMonths(DateTime startDate, DateTime endDate)
{
if (startDate > endDate)
return -GetMonths(endDate, startDate);
int months = 12 * GetYears(startDate, endDate);
if (endDate.Month > startDate.Month)
months = months + endDate.Month - startDate.Month;
else
months = 12 - startDate.Month + endDate.Month;
if (endDate.Day < startDate.Day)
months = months - 1;
return months;
}
}
[TestClass()]
public class GeneralHelperTest
{
[TestMethod]
public void GetYearsTest()
{
Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2000, 12, 31)));
Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2001, 4, 4)));
Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2001, 5, 4)));
Assert.AreEqual(1, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2001, 5, 5)));
Assert.AreEqual(1, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2001, 12, 31)));
Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2000, 12, 31), new DateTime(2000, 5, 5)));
Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2001, 4, 4), new DateTime(2000, 5, 5)));
Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2001, 5, 4), new DateTime(2000, 5, 5)));
Assert.AreEqual(-1, GeneralHelper.GetYears(new DateTime(2001, 5, 5), new DateTime(2000, 5, 5)));
Assert.AreEqual(-1, GeneralHelper.GetYears(new DateTime(2001, 12, 31), new DateTime(2000, 5, 5)));
}
[TestMethod]
public void GetMonthsTest()
{
Assert.AreEqual(0, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2000, 6, 4)));
Assert.AreEqual(1, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2000, 6, 5)));
Assert.AreEqual(1, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2000, 6, 6)));
Assert.AreEqual(11, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2001, 5, 4)));
Assert.AreEqual(12, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2001, 5, 5)));
Assert.AreEqual(13, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2001, 6, 6)));
Assert.AreEqual(0, GeneralHelper.GetMonths(new DateTime(2000, 6, 4), new DateTime(2000, 5, 5)));
Assert.AreEqual(-1, GeneralHelper.GetMonths(new DateTime(2000, 6, 5), new DateTime(2000, 5, 5)));
Assert.AreEqual(-1, GeneralHelper.GetMonths(new DateTime(2000, 6, 6), new DateTime(2000, 5, 5)));
Assert.AreEqual(-11, GeneralHelper.GetMonths(new DateTime(2001, 5, 4), new DateTime(2000, 5, 5)));
Assert.AreEqual(-12, GeneralHelper.GetMonths(new DateTime(2001, 5, 5), new DateTime(2000, 5, 5)));
Assert.AreEqual(-13, GeneralHelper.GetMonths(new DateTime(2001, 6, 6), new DateTime(2000, 5, 5)));
}
}
EDITNon, cela ne fonctionne toujours pas. Il échoue ce test:
Assert.AreEqual(24, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2003, 5, 5)));
Attendu: <24>. Réel: <12>
J'essayais de trouver une réponse claire pour les années, les mois et les jours, et je n'ai rien trouvé de clair. Si vous cherchez toujours, vérifiez cette méthode:
public static string GetDifference(DateTime d1, DateTime d2)
{
int[] monthDay = new int[12] { 31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
DateTime fromDate;
DateTime toDate;
int year;
int month;
int day;
int increment = 0;
if (d1 > d2)
{
fromDate = d2;
toDate = d1;
}
else
{
fromDate = d1;
toDate = d2;
}
// Calculating Days
if (fromDate.Day > toDate.Day)
{
increment = monthDay[fromDate.Month - 1];
}
if (increment == -1)
{
if (DateTime.IsLeapYear(fromDate.Year))
{
increment = 29;
}
else
{
increment = 28;
}
}
if (increment != 0)
{
day = (toDate.Day + increment) - fromDate.Day;
increment = 1;
}
else
{
day = toDate.Day - fromDate.Day;
}
// Month Calculation
if ((fromDate.Month + increment) > toDate.Month)
{
month = (toDate.Month + 12) - (fromDate.Month + increment);
increment = 1;
}
else
{
month = (toDate.Month) - (fromDate.Month + increment);
increment = 0;
}
// Year Calculation
year = toDate.Year - (fromDate.Year + increment);
return year + " years " + month + " months " + day + " days";
}
J'ai ci-dessous la solution qui fonctionne correctement pour moi (après avoir fait quelques cas de test) . Les cas de test supplémentaires sont acceptables.
public class DateDiffernce
{
private int m_nDays = -1;
private int m_nWeek;
private int m_nMonth = -1;
private int m_nYear;
public int Days
{
get
{
return m_nDays;
}
}
public int Weeks
{
get
{
return m_nWeek;
}
}
public int Months
{
get
{
return m_nMonth;
}
}
public int Years
{
get
{
return m_nYear;
}
}
public void GetDifferenceBetwwenTwoDate(DateTime objDateTimeFromDate, DateTime objDateTimeToDate)
{
if (objDateTimeFromDate.Date > objDateTimeToDate.Date)
{
DateTime objDateTimeTemp = objDateTimeFromDate;
objDateTimeFromDate = objDateTimeToDate;
objDateTimeToDate = objDateTimeTemp;
}
if (objDateTimeFromDate == objDateTimeToDate)
{
//textBoxDifferenceDays.Text = " Same dates";
//textBoxAllDifference.Text = " Same dates";
return;
}
// If From Date's Day is bigger than borrow days from previous month
// & then subtract.
if (objDateTimeFromDate.Day > objDateTimeToDate.Day)
{
objDateTimeToDate = objDateTimeToDate.AddMonths(-1);
int nMonthDays = DateTime.DaysInMonth(objDateTimeToDate.Year, objDateTimeToDate.Month);
m_nDays = objDateTimeToDate.Day + nMonthDays - objDateTimeFromDate.Day;
}
// If From Date's Month is bigger than borrow 12 Month
// & then subtract.
if (objDateTimeFromDate.Month > objDateTimeToDate.Month)
{
objDateTimeToDate = objDateTimeToDate.AddYears(-1);
m_nMonth = objDateTimeToDate.Month + 12 - objDateTimeFromDate.Month;
}
//Below are best cases - simple subtraction
if (m_nDays == -1)
{
m_nDays = objDateTimeToDate.Day - objDateTimeFromDate.Day;
}
if (m_nMonth == -1)
{
m_nMonth = objDateTimeToDate.Month - objDateTimeFromDate.Month;
}
m_nYear = objDateTimeToDate.Year - objDateTimeFromDate.Year;
m_nWeek = m_nDays / 7;
m_nDays = m_nDays % 7;
}
}
int day=0,month=0,year=0;
DateTime smallDate = Convert.ToDateTime(string.Format("{0}", "01.01.1900"));
DateTime bigDate = Convert.ToDateTime(string.Format("{0}", "05.06.2019"));
TimeSpan timeSpan = new TimeSpan();
//timeSpan is diff between bigDate and smallDate as days
timeSpan = bigDate - smallDate;
//year is totalDays / 365 as int
year = timeSpan.Days / 365;
//smallDate.AddYears(year) is closing the difference for year because we found the year variable
smallDate = smallDate.AddYears(year);
//again subtraction because we don't need the year now
timeSpan = bigDate - smallDate;
//month is totalDays / 30 as int
month = timeSpan.Days / 30;
//smallDate.AddMonths(month) is closing the difference for month because we found the month variable
smallDate = smallDate.AddMonths(month);
if (bigDate > smallDate)
{
timeSpan = bigDate - smallDate;
day = timeSpan.Days;
}
//else it is mean already day is 0