50 votes

Comment faire la différence entre deux dates en Année / Mois / Semaine / Jour?

Comment faire 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.

34voto

Jon Skeet Points 692016

C'est en fait assez difficile. Un autre nombre total de jours peuvent aboutir au même résultat. Par exemple:

  • 19 juin 2008 au 19 juin 2010 = 2 ans, mais également 365 * 2 jours

  • 19 juin 2006 à 19 juin 2008 = 2 ans, mais également 365 + 366 jours à cause des années bissextiles

Vous pouvez bien voulez soustraire années jusqu'à ce que vous atteignez le point où vous avez deux dates sont à moins d'un an d'intervalle. Puis soustraire mois jusqu'à ce que vous atteignez le point où vous avez deux dates sont à moins d'un mois d'intervalle.

Plus de confusion: soustraction (ou l'ajout) mois est difficile quand vous pouvez commencer avec une date de "30 Mars" - qu'est ce qu'un mois plus tôt?

Encore plus de confusion (peut ne pas être pertinente): même un jour n'est pas toujours de 24 heures. L'heure d'été à n'importe qui?

Encore plus de confusion (presque certainement pas pertinentes): encore une minute n'est pas toujours de 60 secondes. Les secondes intercalaires sont très déroutant...

Je n'ai pas le temps de travailler exactement la bonne façon de le faire maintenant - cette réponse est la plupart du temps de soulever le fait qu'il n'est pas aussi simple que cela puisse paraître.

EDIT: Malheureusement, je ne vais pas avoir assez de temps pour répondre à cette entièrement. Je vous suggère de commencer par la définition d'une structure représentant un 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 de mettre en œuvre l'opérateur + premier, qui doit en informer l' Difference méthode, vous devriez assurez-vous que first + (Period.Difference(first, second)) == second tous first/second valeurs.

Démarrer avec l'écriture de toute une série de tests unitaires - d'abord "facile" en cas, de passer ensuite à la délicate impliquant la années bissextiles. Je sais que l'approche normale est d'écrire un test à un moment, mais je serais personnellement brainstorm un tas d'entre eux avant de commencer tout travail de mise en œuvre.

Permettez-vous une journée pour mettre en œuvre correctement. Il est difficile de trucs.

Notez que j'ai omis de semaines ici - que la valeur d'au moins c'est facile, parce que c'est toujours de 7 jours. Donc, étant donné un (positive) de la période, vous devez:

int years = period.Years;
int months = period.Months;
int weeks = period.Days / 7;
int daysWithinWeek = period.Days % 7;

(Je vous suggère d'éviter d'y penser négatif périodes - assurez-vous que tout est positif, tout le temps.)

18voto

jwg Points 53

En partie comme une préparation pour essayer de répondre à cette question correctement (et peut-être même définitivement...), en partie à examiner la façon dont beaucoup qui on peut avoir confiance code qui est collé sur de la SORTE, et en partie comme un exercice de trouver des bugs, j'ai créé un tas de tests unitaires pour cette question, et de les appliquer à de nombreuses solutions proposées à partir de cette page et un couple de doublons.

Les résultats sont concluants: pas un seul des contributions de code précision des réponses à la question. Mise à jour: j'ai maintenant quatre bonnes solutions à cette question, y compris mon propre, voir les mises à jour ci-dessous.

Code testé

À partir de cette question, j'ai testé le code par les utilisateurs suivants: Mohammed Ijas Nasirudeen, ruffin, Malu MN, Dave, pk., Jani, lc.

Ce sont toutes les réponses qui a fourni tous les trois des années, des mois et des jours dans leur code. Noter que deux de ces, Dave et Jani, a donné, le nombre total des jours et des mois, plutôt que le nombre total de mois après le décompte des années, et le nombre total de jours après le décompte des mois. Je pense que les réponses sont mauvaises en termes de ce que l'OP a semblé vouloir, mais les tests unitaires ne pas vous en dire beaucoup dans ces cas. (Notez que dans Jani de cas, cela a été mon erreur et de son code a été fait correct - voir mise à Jour 4 ci-dessous)

Les réponses par Jon Skeet, Aghasoleimani, Mukesh Kumar, Richard, Colin, sheir, juste que j'ai vu, Chalkey et Andy, étaient incomplètes. Cela ne veut pas dire que les réponses n'étaient pas tout bon, en fait, plusieurs d'entre eux sont utiles contributions en faveur d'une solution. Cela signifie simplement qu'il n'y avait pas de code à la prise de deux DateTimes et le retour à 3 ints que j'ai pu tester correctement. Quatre de ces cependant, parler à l'aide de TimeSpan. Comme de nombreuses personnes l'ont mentionné, TimeSpan ne rend pas compte de quoi que ce soit plus que les jours.

Les autres réponses que j'ai testé étaient de

  • question 3054715 - LukeH, ho1 et ce. ___curieux_geek
  • question 6260372 - Chuck Rostance et Jani (même réponse que cette question)
  • question 9 (!) - Dylan Hayes, Jon et Rajeshwaran S P

c'.___curieux_geek réponse est code sur une page qu'il a lié pour qui je ne pense pas qu'il a écrit. Jani réponse est le seul qui utilise une bibliothèque externe, la Période de la Bibliothèque pour le .Net.

Toutes les autres réponses à toutes ces questions semble être incomplète. Question 9 sur l'âge en années, et les trois réponses sont celles qui ont dépassé la brève et calculé en années, mois et jours. Si quelqu'un trouve encore des doublons de cette question, s'il vous plaît laissez-moi savoir.

Comment je l'ai testé

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 copier-coller code. Bien sûr, j'ai dû adapter les fonctions avec des signatures différentes, etc, mais j'ai essayé de faire le minimum de modifications à faire, en conservant toute la logique du code.

J'ai écrit un tas de tests NUnit dans un résumé à la classe générique

[TestFixture]
public abstract class DateDifferenceTests<DDC> where DDC : IDateDifference, new()

et ajout d'un vide de la classe dérivée

public class Rajeshwaran_S_P_Test : DateDifferenceTests<Rajeshwaran_S_P>
{
}

pour le fichier source pour chaque IDateDifference classe.

NUnit est assez intelligent pour faire le reste.

Les tests

Un couple de ces derniers ont été écrits à l'avance et le reste a été écrit pour essayer de briser apparemment implémentations de travail.

[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 stupide et n'a pas vraiment expliquer pourquoi le code peut échouer à l'épreuve, mais en regardant les deux dates et la réponse(s) doit être suffisant pour comprendre le test.

Il y a deux fonctions qui font tout l' Asserts, CheckResults() et CheckControversialResults(). Ceux-ci fonctionnent bien pour vous épargner de la saisie et de donner les résultats de la droite, mais malheureusement, il est plus difficile de voir exactement ce qui n'allait pas (parce que l' Assert en CheckControversialResults() échouera avec l'Attendue "vrai", plutôt que de vous dire dont la valeur est incorrecte. Si quelqu'un a une meilleure façon de le faire (éviter d'écrire les mêmes contrôles à chaque fois, mais ont plus de messages d'erreur utiles) s'il vous plaît laissez-moi savoir.

CheckControversialResults() est utilisé pour les quelques cas où il semble y avoir deux opinions différentes sur ce qui est juste. J'ai une opinion de moi-même, mais j'ai pensé que je devrais être libéral dans ce que j'ai accepté ici. L'essentiel, c'est de décider d'un an après le 29 Février est du 28 Février ou mars 1.

Ces tests sont le nœud de la question, et il pourrait très bien être des erreurs, donc s'il vous plaît commentaire si vous en trouvez un qui est mauvais. Il serait également bon d'entendre quelques suggestions pour d'autres tests afin de vérifier les futures itérations de réponses.

Aucun test n'implique du temps de la journée - tous DateTimes sont à minuit. Y compris les temps, aussi longtemps que il sait comment arrondi vers le haut et vers le bas pour jours fonctionne (je pense que c'est), peut se montrer encore plus de défauts.

Les résultats

Le tableau de bord complet de résultats se présente comme suit:

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 Jani solution a été vraiment bon et a réussi tous les tests - voir mise à jour 4 ci-dessous.

Les colonnes sont dans l'ordre alphabétique du nom du test:

  • AlmostThreeYearsTest
  • AlmostTwoYearsTest
  • BasicTest
  • BornOnALeapYearTest
  • BornOnALeapYearTest2
  • LongMonthToLongMonth
  • LongMonthToLongMonthPenultimateDay
  • LongMonthToPartWayThruShortMonth
  • LongMonthToShortMonth

Trois réponses ont échoué à seulement 1 essai chaque, Jon, LukeH et Manu MN. Gardez à l'esprit ces tests ont probablement été écrites spécifiquement pour résoudre les défauts dans ces réponses.

Chaque test a été adoptée par au moins un morceau de code, qui est un peu rassurant de voir que aucun de ces tests sont erronées.

Quelques réponses a échoué à de nombreux tests. J'espère que personne ne se sent c'est une condamnation de la qui affiche les efforts. Tout d'abord le nombre de succès est assez arbitraire, car les tests ne sont pas de recouvrir uniformément la problématique de la question de l'espace. Deuxièmement ce n'est pas le code de production - les réponses sont affichées afin que les gens peuvent apprendre d'eux, de ne pas les copier exactement dans leurs programmes. Code qui échoue à un grand nombre de tests peut encore avoir de grandes idées. Au moins une pièce qui n'a pas beaucoup de tests ont eu un petit bug que je n'ai pas corrigé. Je suis reconnaissant à toute personne qui a pris le temps de partager leur travail avec tout le monde, pour faire de ce projet très intéressant.

Mes conclusions

Il en existe trois:

  1. Les calendriers sont durs. J'ai écrit neuf essais, dont trois où deux réponses sont possibles. Certains des tests où je n'ai eu qu'une réponse pourrait pas faire l'unanimité avec. Il suffit de penser au sujet de exactement ce que nous voulons dire quand nous disons '1 mois plus tard" ou "2 ans plus tôt" est difficile dans beaucoup de situations. Et rien de ce code eu à traiter avec toutes les complexités des choses comme le travail quand les années bissextiles sont. Tous il utilise la bibliothèque de code pour gérer les dates. Si vous imaginez le "spec" pour dire le temps en jours, semaines, mois et années écrit, il y a toutes sortes de trucs. Parce que nous savons assez bien depuis l'école primaire, et utilisez tous les jours, nous sommes aveugles à de nombreuses particularités. La question n'est pas académique - les différents types de décomposition des périodes de temps en années, les quarts et les mois sont essentiels dans le logiciel de comptabilité pour les obligations et autres produits financiers.

  2. L'écriture de code correct est difficile. Il y avait beaucoup de bugs. Dans un peu plus obscur des sujets ou moins populaires de questions que les chances d'un bug existant sans avoir été signalé par un intervenant sont beaucoup, beaucoup plus élevé que pour cette question. Vous devriez vraiment jamais, jamais copier le code de SI dans votre programme, sans comprendre exactement ce qu'il fait. Le revers de la médaille, c'est que vous ne devriez probablement pas écrire de code dans votre réponse, qui est prêt à être copié et collé, mais plutôt intelligente et expressive pseudo-code qui permet à quelqu'un de comprendre la solution et de mettre en œuvre leur propre version (avec leurs propres bugs!)

  3. Les tests unitaires sont utiles. Je suis encore un sens à poster mon propre solution à cela quand je suis toute à elle (pour quelqu'un d'autre pour trouver ce qui est caché, sur des hypothèses erronées dans le!) Faire cela a été un bel exemple de " sauver les insectes en les transformant en des tests unitaires pour fixer la prochaine version du code.

Mise à jour

L'ensemble du projet est maintenant à https://github.com/jwg4/date-difference Cela comprend ma propre tentative jwg.cs, qui passe tous les tests que j'ai actuellement, y compris quelques-uns qui en vérifier le bon moment de la journée de la manipulation. N'hésitez pas à ajouter plus de tests pour briser ce et d'autres implémentations ou un code de meilleure qualité pour répondre à la question.

Mise à jour 2

@MattJohnson a ajouté une mise en œuvre qui utilise Jon Skeet est NodaTime. Elle passe tous les tests actuels.

Mise à jour 3

@KirkWoll la réponse de Différence en nombre de mois entre deux dates a été ajouté au projet sur github. Elle passe tous les tests actuels.

Mise à jour 4

@Jani a souligné dans un commentaire que j'avais utilisé son code à tort. Il n'suggérer des méthodes qui a compté les années, les mois et les jours correctement, (à côté de certains qui permettent de compter le nombre total de jours et de mois, pas les restes) cependant j'ai utilisé à tort la faux dans mon code de test. J'ai corrigé mon wrapper autour de son code et il passe tous les tests. Il y a maintenant quatre bonnes solutions, dont Jani était la première. Deux bibliothèques (Intenso.Laps de temps et NodaTime) et deux sont écrit à partir de zéro.

17voto

Jani Points 1221

Pour le bon calcul de la différence des Années/Mois/Semaines, le Calendrier de la CultureInfo doivent être considérés:

  • bond vs les années non bissextiles
  • mois avec différents nombre de jours
  • ans avec différents nombre de semaines (variable selon le premier jour de la semaine et de la semaine du calendrier de la règle)

Le DateDiff classe de la Période de la Bibliothèque pour le .NET respecte l'ensemble de 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 également calcule la différence de Trimestres.

16voto

lc. Points 50297

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 petit côté 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;
}
 

10voto

sheir Points 242

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 DateDiff des méthodes statiques dans le SqlMethods classe.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X