Dimanche, j'étais inactif comme d'habitude, parcourant Reddit. En parcourant le plaisir des chiots et la mauvaise humeur des programmeurs, un article en particulier a attiré mon attention. C'était un
bogue dans calc.exe .
Calcul de plage de dates incorrect dans la calculatrice Windows"Eh bien, cela ressemble à une curieuse erreur, je me demande ce qui pourrait en être la cause", me suis-je dit. Le nombre de semaines, bien sûr, fait ressembler le bug à une sorte de débordement ou d'erreur de plage, vous savez, des raisons typiques. Mais il peut toujours être un peu inversé par un rayon de haute énergie provenant d'un voisin cosmique ami.
Étant intéressé par la raison, j'ai fait ce que vous faites dans de tels cas: je l'ai essayé sur ma machine pour poster "Tout fonctionne pour moi." Et la répétition de la situation depuis le poste «31 juillet - 31 décembre» sur ma machine a donné le résultat correct de «5 mois». Mais après avoir testé un peu, j'ai trouvé que "31 juillet - 30 décembre" provoque en fait une erreur. La valeur incorrecte «5 mois, 613566756 semaines, 3 jours» s'affiche.
Je n'avais pas fini de secouer le programme, puis je me suis souvenu: "Oh, la calculatrice n'est-elle pas une de ces choses pour lesquelles Microsoft a ouvert la source?"
Et vraiment . Cette erreur ne pouvait pas être trop compliquée, alors j'ai pensé que j'essaierais de la trouver. Le téléchargement des sources était assez simple, et l'ajout de la charge de travail UWP requise à Visual Studio s'est également déroulé sans accroc.
La navigation dans les bases de code que vous ne connaissez pas est une chose à laquelle vous vous habituez avec le temps. Surtout lorsque vous souhaitez contribuer à des projets open source où vous trouvez le bug. Cependant, l'ignorance de XAML ou WinRT, bien sûr, ne facilite pas les choses.
J'ai ouvert le fichier de solution et regardé le projet «Calculatrice» à la recherche de n'importe quel fichier qui devrait être lié au bogue. J'ai trouvé
DateCalculator.xaml
, puis il semble convenir au nom
DateDiff_FromDate to DateCalculatorViewModel.cpp
et, enfin,
DateCalculator.cpp
.
Après avoir défini un point d'arrêt et en regardant certaines variables, j'ai vu que la valeur finale de
DateDifference
déjà incorrecte. Autrement dit, il ne s'agissait pas simplement d'une erreur de conversion en chaîne, mais d'une erreur de calcul réel.
Le calcul réel dans un pseudo-code simplifié ressemble à ceci:
DateDifference calculate_difference(start_date, end_date) { uint[] diff_types = [year, month, week, day] uint[] typical_days_in_type = [365, 31, 7, 1] uint[] calculated_difference = [0, 0, 0, 0] date temp_pivot_date date pivot_date = start_date uint days_diff = calculate_days_difference(start_date, end_date) for(type in differenceTypes) { temp_pivot_date = pivot_date uint current_guess = days_diff /typicalDaysInType[type] if(current_guess !=0) pivot_date = advance_date_by(pivot_date, type, current_guess) int diff_remaining bool best_guess_hit = false do{ diff_remaining = calculate_days_difference(pivot_date, end_date) if(diff_remaining < 0) {
Ça a l'air bien. Il n'y a aucun problème de logique. Essentiellement, la fonction effectue les opérations suivantes:
- compte les années complètes à partir de la date de début
- à partir de la date de la dernière année complète compte les mois
- compte les semaines à partir de la date du dernier mois complet
- à partir de la date de la dernière semaine complète compte les jours restants
En fait, le problème est l'hypothèse que le démarrage séquentiel
date = advance_date_by(date, month, somenumber) date = advance_date_by(date, month, 1)
est égal à
date = advance_date_by(date, month, somenumber + 1)
C'est généralement la même chose. Mais la question se pose:
"Si vous frappez le 31e jour du mois, le mois suivant 30 jours, vous ajoutez un mois, alors où irez-vous?"Il semble que pour
Windows.Globalization.Calendar.AddMonths (Int32), la réponse sera "le 30".
Et cela signifie que:
"31 juillet + 4 mois = 30 novembre"
"30 novembre + 1 mois = 30 décembre"
"31 juillet + 5 mois = 31 décembre"
Ainsi, l'opération AddMonths n'est ni
distributive (avec AddMonth-multiplication), ni
commutative , ni
associative . Ce qui devrait en fait être l'opération "d'addition". N'est-ce pas amusant de travailler avec le temps et les calendriers?
Pourquoi dans ce cas, l'erreur de réglage de la plage conduit à un si grand nombre de semaines? Comme vous l'avez peut-être deviné, cela est dû au fait que
days_diff
est un type non signé. Cela transforme -1 jour en une énorme quantité, qui est ensuite transmise à la prochaine itération du cycle avec des semaines. Qui essaie alors de corriger la situation en diminuant
current_guess
mais pas en diminuant la variable non signée.
Eh bien, c'était une façon intéressante de passer le dimanche. J'ai créé une
demande de pull sur Github avec un «correctif» minimal. Je mets la «correction» entre guillemets, car maintenant le calcul ressemble à ceci:

Je pense que techniquement c'est le résultat correct, si l'on suppose que "31 juillet + 4 mois = 30 novembre". Bien que cette option ne soit pas entièrement compatible avec l'intuition humaine concernant la différence de dates. Mais en tout cas, c'est moins mal qu'il ne l'était.