{"thread":{"id":"27145","subject":"[PATCH/RFH] date: avoid \"X years, 12 months\" in relative dates","startedAt":"2011-04-20T05:24:35Z","lastAt":"2011-04-20T11:00:07Z","messageCount":4,"participants":["Jeff King","Michael J Gruber"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"166125","messageId":"20110420052435.GA28597@sigill.intra.peff.net","threadId":"27145","inReplyTo":null,"subject":"[PATCH/RFH] date: avoid \"X years, 12 months\" in relative dates","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-04-20T05:24:35Z","receivedAt":"2011-04-20T05:24:35Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"When relative dates are more than about a year ago, we start\nwriting them as \"Y years, M months\".  At the point where we\ncalculate Y and M, we have the time delta specified as a\nnumber of days. We calculate these integers as:\n\n  Y = days / 365\n  M = (days % 365 + 15) / 30\n\nThis rounds days in the latter half of a month up to the\nnearest month, so that day 16 is \"1 month\" (or day 381 is \"1\nyear, 1 month\").\n\nWe don't round the year at all, though, meaning we can end\nup with \"1 year, 12 months\", which is silly; it should just\nbe \"2 years\".\n\nThe implementation is crude but effective: we check for this\nsilly case with a conditional and tweak the values:\n\n  if (M == 12) {\n\t  Y++;\n\t  M = 0;\n  }\n\nIt's tempting to look for a purely numerical solution, like\njust rounding better. But it is made difficult by the fact\nthat our approximation of a month is \"30 days\", which means\nthat a year does not actually contain 12 of our approximate\nmonths (there is a 5-day discrepancy).\n\nSo you could do:\n\n  Y = (days + 15) / 365\n\nwhich will round up the year if we are in the last 15 days.\nAnd then you would make a matching tweak for months:\n\n  M = (days + 15) % 365 / 30;\n\nso that they both round at exactly the same day.\n\nBut that doesn't quite work. Day 715 will be properly\nrounded to \"2 years, 0 months\", which is what we want. But\ndays 710 through 714 remain at \"1 year, 12 months\", due to\nthe 5-day discrepancy.\n\nAnother attempt would be to write it as the much more\nreadable:\n\n  total_months = (days + 15) / 30;\n  Y = total_months / 12;\n  M = total_months % 12;\n\nBut our 5-day discrepancy hits us again, and this time much\nworse, since the error compounds with each year. On day\n3650, we should be exactly at \"10 years\", but the above puts\nus at \"10 years, 2 months\".\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\nSo I thought this patch would take me about 2 minutes to write, and (as\nyou can probably guess from the length of the commit message), it turned\nout much more complex. I'd be curious to see if anybody has a numerical\nsolution using integers.\n\nWith floating point, it's pretty simple:\n\n  double one_month = 365 / 12;\n  unsigned long total_months = ((double)days + (one_month / 2)) / one_month;\n  unsigned long years = total_months / 12;\n  unsigned long months = total_months % 12;\n\nbut I'm beginning to think it's not possible to make a simple formula\nwith just integers. Which maybe means we should just use floating point\nhere. Or maybe somebody can prove me to be an idiot.\n\n date.c          |    5 +++++\n t/t0006-date.sh |    1 +\n 2 files changed, 6 insertions(+), 0 deletions(-)\n\ndiff --git a/date.c b/date.c\nindex 00f9eb5..3dfade2 100644\n--- a/date.c\n+++ b/date.c\n@@ -132,6 +132,11 @@ const char *show_date_relative(unsigned long time, int tz,\n \t\tunsigned long years = diff / 365;\n \t\tunsigned long months = (diff % 365 + 15) / 30;\n \t\tint n;\n+\n+\t\tif (months == 12) {\n+\t\t\tyears++;\n+\t\t\tmonths = 0;\n+\t\t}\n \t\tn = snprintf(timebuf, timebuf_size, \"%lu year%s\",\n \t\t\t\tyears, (years > 1 ? \"s\" : \"\"));\n \t\tif (months)\ndiff --git a/t/t0006-date.sh b/t/t0006-date.sh\nindex 1d4d0a5..f87abb5 100755\n--- a/t/t0006-date.sh\n+++ b/t/t0006-date.sh\n@@ -25,6 +25,7 @@ check_show 37500000 '1 year, 2 months ago'\n check_show 55188000 '1 year, 9 months ago'\n check_show 630000000 '20 years ago'\n check_show 31449600 '12 months ago'\n+check_show 62985600 '2 years ago'\n \n check_parse() {\n \techo \"$1 -> $2\" >expect\n-- \n1.7.5.rc2.4.gc3ad3\n"},{"id":"166139","messageId":"1d0633318ffc778dfcc1c32ecf80fca0327349d6.1303290693.git.git@drmicha.warpmail.net","threadId":"27145","inReplyTo":"20110420052435.GA28597@sigill.intra.peff.net","subject":"[PATCH] date: avoid \"X years, 12 months\" in relative dates","fromName":"Michael J Gruber","fromEmail":"git@drmicha.warpmail.net","sentAt":"2011-04-20T09:12:11Z","receivedAt":"2011-04-20T09:12:11Z","isPatch":true,"sender":{"key":"git@grubix.eu","avatar":"https://avatars.githubusercontent.com/u/233215?v=4"},"body":"When relative dates are more than about a year ago, we start\nwriting them as \"Y years, M months\".  At the point where we\ncalculate Y and M, we have the time delta specified as a\nnumber of days. We calculate these integers as:\n\n  Y = days / 365\n  M = (days % 365 + 15) / 30\n\nThis rounds days in the latter half of a month up to the\nnearest month, so that day 16 is \"1 month\" (or day 381 is \"1\nyear, 1 month\").\n\nWe don't round the year at all, though, meaning we can end\nup with \"1 year, 12 months\", which is silly; it should just\nbe \"2 years\".\n\nImplement this differently with months of size\n\n  onemonth = 365/12\n\nso that\n\n  totalmonths = (long)( (days + onemonth/2)/onemonth )\n  years = totalmonths / 12\n  months = totalmonths % 12\n\nIn order to do this without floats, we write the first formula as\n\n  totalmonths = (days*12*2 + 365) / (365*2)\n\nTests and inspiration by Jeff King.\n\nHelped-by: Jeff King <peff@peff.net>\nSigned-off-by: Michael J Gruber <git@drmicha.warpmail.net>\n---\n date.c          |    5 +++--\n t/t0006-date.sh |    1 +\n 2 files changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/date.c b/date.c\nindex 00f9eb5..cde239c 100644\n--- a/date.c\n+++ b/date.c\n@@ -129,8 +129,9 @@ const char *show_date_relative(unsigned long time, int tz,\n \t}\n \t/* Give years and months for 5 years or so */\n \tif (diff < 1825) {\n-\t\tunsigned long years = diff / 365;\n-\t\tunsigned long months = (diff % 365 + 15) / 30;\n+\t\tunsigned long totalmonths = (diff*12*2 + 365)/(365*2);\n+\t\tunsigned long years = totalmonths / 12;\n+\t\tunsigned long months = totalmonths % 12;\n \t\tint n;\n \t\tn = snprintf(timebuf, timebuf_size, \"%lu year%s\",\n \t\t\t\tyears, (years > 1 ? \"s\" : \"\"));\ndiff --git a/t/t0006-date.sh b/t/t0006-date.sh\nindex 1d4d0a5..f87abb5 100755\n--- a/t/t0006-date.sh\n+++ b/t/t0006-date.sh\n@@ -25,6 +25,7 @@ check_show 37500000 '1 year, 2 months ago'\n check_show 55188000 '1 year, 9 months ago'\n check_show 630000000 '20 years ago'\n check_show 31449600 '12 months ago'\n+check_show 62985600 '2 years ago'\n \n check_parse() {\n \techo \"$1 -> $2\" >expect\n-- \n1.7.5.rc1.312.g1936c\n"},{"id":"166140","messageId":"20110420101800.GA24035@sigill.intra.peff.net","threadId":"27145","inReplyTo":"1d0633318ffc778dfcc1c32ecf80fca0327349d6.1303290693.git.git@drmicha.warpmail.net","subject":"Re: [PATCH] date: avoid \"X years, 12 months\" in relative dates","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-04-20T10:18:00Z","receivedAt":"2011-04-20T10:18:00Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Apr 20, 2011 at 11:12:11AM +0200, Michael J Gruber wrote:\n\n> Implement this differently with months of size\n> \n>   onemonth = 365/12\n> \n> so that\n> \n>   totalmonths = (long)( (days + onemonth/2)/onemonth )\n>   years = totalmonths / 12\n>   months = totalmonths % 12\n> \n> In order to do this without floats, we write the first formula as\n> \n>   totalmonths = (days*12*2 + 365) / (365*2)\n\nWell now I feel like an idiot. Algebra to the rescue.\n\nThe extra multiplications introduce the possibility of overflow, but\nsince the number of days was arrived at by dividing an unsigned long\nnumber of seconds by 86400, we are guaranteed to have room to multiply\nby 24. :)\n\nSo looks good to me.\n\n-Peff\n"},{"id":"166145","messageId":"4DAEBCB7.80707@drmicha.warpmail.net","threadId":"27145","inReplyTo":"20110420101800.GA24035@sigill.intra.peff.net","subject":"Re: [PATCH] date: avoid \"X years, 12 months\" in relative dates","fromName":"Michael J Gruber","fromEmail":"git@drmicha.warpmail.net","sentAt":"2011-04-20T11:00:07Z","receivedAt":"2011-04-20T11:00:07Z","isPatch":true,"sender":{"key":"git@grubix.eu","avatar":"https://avatars.githubusercontent.com/u/233215?v=4"},"body":"Jeff King venit, vidit, dixit 20.04.2011 12:18:\n> On Wed, Apr 20, 2011 at 11:12:11AM +0200, Michael J Gruber wrote:\n> \n>> Implement this differently with months of size\n>>\n>>   onemonth = 365/12\n>>\n>> so that\n>>\n>>   totalmonths = (long)( (days + onemonth/2)/onemonth )\n>>   years = totalmonths / 12\n>>   months = totalmonths % 12\n>>\n>> In order to do this without floats, we write the first formula as\n>>\n>>   totalmonths = (days*12*2 + 365) / (365*2)\n> \n> Well now I feel like an idiot. Algebra to the rescue.\n\n:)\n\n> The extra multiplications introduce the possibility of overflow, but\n> since the number of days was arrived at by dividing an unsigned long\n> number of seconds by 86400, we are guaranteed to have room to multiply\n> by 24. :)\n\nAlso, this is inside \"if (diff < 1825)\", so no matter where diff comes\nfrom, \"long\" is really long enough.\n\nCheers,\nMichael\n"}]}