{"id":35903,"date":"2005-04-14T09:02:39","date_gmt":"2005-04-14T09:02:39","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2005\/04\/14\/computing-the-interval-between-two-moments-in-time\/"},"modified":"2005-04-14T09:02:39","modified_gmt":"2005-04-14T09:02:39","slug":"computing-the-interval-between-two-moments-in-time","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20050414-39\/?p=35903","title":{"rendered":"Computing the interval between two moments in time"},"content":{"rendered":"<p>\nComputing the interval between two moments in time is easy:\nIt&#8217;s just subtraction, but subtraction may not be what you want.\n<\/p>\n<p>\nIf you are displaying time units on the order of months and\nyears, then you run into the problem that a month is of variable\nlength.\n<a HREF=\"http:\/\/groups.google.com\/groups?selm=aNidndhYae_Ukz_cRVn-2g@rogers.com\">\nsome people just take the value relative to a base date of January 1\nand extract the year and month counts<\/a>.\n<\/p>\n<p>\nUnfortunately, this results in somewhat non-intuitive results.\nLet&#8217;s illustrate with some examples.  I&#8217;m going to write this in C#\nbecause it lets me focus on the algorithm instead of getting distracted\nby &#8220;oh dear how do I convert between SYSTEMTIME and FILETIME?&#8221; issues,\nand because it hightlights some new issues.\n<\/p>\n<pre>\n\/\/ Remember, code in italics is wrong\nusing System;\nusing SC = System.Console;\nclass Program {\n static void PrintAge(DateTime bday, DateTime asof)\n {\n  <i>TimeSpan span = asof - bday;\n  SC.WriteLine(span);<\/i>\n }\n public static void Main(string[] args) {\n  DateTime bday = DateTime.Parse(args[0]);\n  DateTime asof = DateTime.Parse(args[1]);\n  if (bday &gt; asof) { SC.WriteLine(\"not born yet\"); return; }\n  PrintAge(bday, asof);\n }\n}\n<\/pre>\n<p>\nThe two parameters to the program are the victim&#8217;s birthday\nand the date as of which you want to compute the victim&#8217;s age.\n<\/p>\n<p>\nHere&#8217;s a sample run:\n<\/p>\n<pre>\n&gt; howold 1\/1\/2001 1\/1\/2002\n365.00:00:00\n<\/pre>\n<p>\nObserve that the <code>TimeSpan<\/code> structure does not attempt\nto produce results in any unit larger than a day, since the authors\nof <code>TimeSpan<\/code> realized that months and years are variable-length.\n<\/p>\n<p>\nA naive implementation might go like this:\n<\/p>\n<pre>\nstatic void PrintAge(DateTime bday, DateTime asof)\n{\n <\/i>TimeSpan span = asof - bday;\n DateTime dt = (new DateTime(1900, 1, 1)).Add(span);\n SC.WriteLine(\"{0} years, {1} months, {2} days\",\n              dt.Year - 1900, dt.Month - 1, dt.Day - 1);<\/i>\n}\n<\/pre>\n<p>\nTry it with some command lines and see what happens:\n<\/p>\n<pre>\n&gt; howold 1\/1\/2001 1\/1\/2002\n1 years, 0 months, 0 days \/\/ good\n&gt; howold 1\/1\/2001 3\/1\/2001\n0 years, 2 months, 0 days \/\/ good\n&gt; howold 1\/1\/2000 1\/1\/2001\n1 years, 0 months, 1 days \/\/ wrong\n&gt; howold 9\/1\/2000 11\/1\/2000\n0 years, 2 months, 2 days \/\/ wrong\n<\/pre>\n<p>\nWhy  does it say that a person born on January 1, 2000 is\none year and one day old on January 1, 2001?\nThe person is clearly exactly one year old on that day.\nSimilarly, it thinks that November first is two months and two days\nafter September first, when it is clearly two months exactly.\n<\/p>\n<p>\nThe reason is that months and years are variable-length, but our\nalgorithm assumes that they are constant. Specifically, months and\nyears are context-sensitive but the algorithm assumes that they are\ntranslation-invariant.\nThe lengths of months and years depend which month and year\nyou&#8217;re talking about.\nLeap years are longer than non-leap years.\nMonths have all different lengths.\n<\/p>\n<p>\nHow do you fix this?\nWell, first you have to figure out how human beings compute the\ndifference between dates when variable-length units are involved.\nThe most common algorithm is to declare that\none year has elapsed when the same month and day have arrived in the year\nfollowing the starting point.\nSimilarly, a month has elapsed when the same numerical date has arrived\nin the month following the starting point.\n<\/p>\n<p>\nMentally, you add years until you can&#8217;t add years any more without\novershooting.  Then you add as many months as fit, and then finish\noff with days.  (Some people subtract, but the result is the same.)\n<\/p>\n<p>\nNow you get to mimic this algorithm in code.\n<\/p>\n<pre>\nstatic void PrintAge(DateTime bday, DateTime asof)\n{\n \/\/ Accumulate years without going over.\n int years = asof.Year - bday.Year;\n DateTime t = bday.AddYears(years);\n if (t &gt; asof) { years--; t = bday.AddYears(years); }\n \/\/ Accumulate months without going over.\n int months = asof.Month - bday.Month; \/\/ fixed 10pm\n if (asof.Day &lt; bday.Day) months--;\n months = (months + 12) % 12;\n t = t.AddMonths(months);\n \/\/ Days are constant-length, woo-hoo!\n int days = (asof - t).Days;\n SC.WriteLine(\"{0} years, {1} months, {2} days\",\n              years, months, days);\n}\n<\/pre>\n<p>\nNotice that this algorithm agrees with the common belief that\npeople born on February 29th have birthdays only once every four years.\n<\/p>\n<p>\n<strong>Exercise<\/strong>:\nExplain what goes wrong if you change the line\n<\/p>\n<pre>\n if (t &gt; asof) { years--; t = bday.AddYears(years); }\n<\/pre>\n<p>\nto\n<\/p>\n<pre>\n if (t &gt; asof) { years--; t = t.AddYears(-1); }\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Computing the interval between two moments in time is easy: It&#8217;s just subtraction, but subtraction may not be what you want. If you are displaying time units on the order of months and years, then you run into the problem that a month is of variable length. some people just take the value relative to [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-35903","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Computing the interval between two moments in time is easy: It&#8217;s just subtraction, but subtraction may not be what you want. If you are displaying time units on the order of months and years, then you run into the problem that a month is of variable length. some people just take the value relative to [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/35903","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=35903"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/35903\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=35903"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=35903"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=35903"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}