{"thread":{"id":"5402","subject":"Relative timestamps in git log","startedAt":"2006-08-26T22:45:26Z","lastAt":"2006-08-27T01:22:44Z","messageCount":2,"participants":["Linus Torvalds","Junio C Hamano"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"25962","messageId":"Pine.LNX.4.64.0608261537120.11811@g5.osdl.org","threadId":"5402","inReplyTo":null,"subject":"Relative timestamps in git log","fromName":"Linus Torvalds","fromEmail":"torvalds@osdl.org","sentAt":"2006-08-26T22:45:26Z","receivedAt":"2006-08-26T22:45:26Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\nI noticed that I was looking at the kernel gitweb output at some point \nrather than just do \"git log\", simply because I liked seeing the \nsimplified date-format, ie the \"5 days ago\" rather than a full date.\n\nThis adds infrastructure to do that for \"git log\" too. It does NOT add the \nactual flag to enable it, though, so right now this patch is a no-op, but \nit should now be easy to add a command line flag (and possibly a config \nfile option) to just turn on the \"relative\" date format.\n\nThe exact cut-off points when it switches from days to weeks etc are \ntotally arbitrary, but are picked somewhat to avoid the \"1 weeks ago\" \nthing (by making it show \"10 days ago\" rather than \"1 week\", or \"70 \nminutes ago\" rather than \"1 hour ago\").\n\nSigned-off-by: Linus Torvalds <torvalds@osdl.org>\n---\nWith this, and changing the \", 0\" for CMIT_FMT_MEDIUM into a \", 1\", I get \noutput like:\n\n\tcommit a7f051987c5f020e60da1e5d6ddefc3d443d3299\n\tMerge: 030b520... a8e0d16...\n\tAuthor: Junio C Hamano <junkio@cox.net>\n\tDate:   22 hours ago\n\t\n\t    Merge branch 'gl/cleanup'\n\t    \n\t    * gl/cleanup:\n\t      Convert memset(hash,0,20) to hashclr(hash).\n\t      Convert memcpy(a,b,20) to hashcpy(a,b).\n\t\n\tcommit 030b52087f0a4b7b1178d34839868ce438eb2f0e\n\tAuthor: Jakub Narebski <jnareb@gmail.com>\n\tDate:   20 hours ago\n\t\n\t    gitweb: git_annotate didn't expect negative numeric timezone\n\t    \n\t    Signed-off-by: Jakub Narebski <jnareb@gmail.com>\n\t    Signed-off-by: Junio C Hamano <junkio@cox.net>\n\t\n\tcommit b22d449721b22f6ec090f22c418ae6b0a560f78d\n\tAuthor: Eric Wong <normalperson@yhbt.net>\n\tDate:   23 hours ago\n\t\n\t    git-svn: add the 'dcommit' command\n\t    \n\t    This is a high-level wrapper around the 'commit-diff' command\n\t    and used to produce cleaner history against the mirrored repository\n\t    through rebase/reset usage.\n\t    \n\t    It's basically a more polished version of this:\n\t    \n\t    for i in `git rev-list --no-merges remotes/git-svn..HEAD | tac`; do\n\t        git-svn commit-diff $i~1 $i\n\t    done\n\t    git reset --hard remotes/git-svn\n\t    \n\t    Signed-off-by: Eric Wong <normalperson@yhbt.net>\n\t    Signed-off-by: Junio C Hamano <junkio@cox.net>\n\nwhich looks quite readable..\n\ndiff --git a/builtin-cat-file.c b/builtin-cat-file.c\nindex 7a6fa56..6c16bfa 100644\n--- a/builtin-cat-file.c\n+++ b/builtin-cat-file.c\n@@ -54,7 +54,7 @@ static void pprint_tag(const unsigned ch\n \t\t\t\t\twrite_or_die(1, tagger, sp - tagger);\n \t\t\t\t\tdate = strtoul(sp, &ep, 10);\n \t\t\t\t\ttz = strtol(ep, NULL, 10);\n-\t\t\t\t\tsp = show_date(date, tz);\n+\t\t\t\t\tsp = show_date(date, tz, 0);\n \t\t\t\t\twrite_or_die(1, sp, strlen(sp));\n \t\t\t\t\txwrite(1, \"\\n\", 1);\n \t\t\t\t\tbreak;\ndiff --git a/cache.h b/cache.h\nindex 1f212d7..ccb83a1 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -286,7 +286,7 @@ extern void *read_object_with_reference(\n \t\t\t\t\tunsigned long *size,\n \t\t\t\t\tunsigned char *sha1_ret);\n \n-const char *show_date(unsigned long time, int timezone);\n+const char *show_date(unsigned long time, int timezone, int relative);\n const char *show_rfc2822_date(unsigned long time, int timezone);\n int parse_date(const char *date, char *buf, int bufsize);\n void datestamp(char *buf, int bufsize);\ndiff --git a/commit.c b/commit.c\nindex 00bc3de..c3ff9b4 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -507,14 +507,14 @@ static int add_user_info(const char *wha\n \t}\n \tswitch (fmt) {\n \tcase CMIT_FMT_MEDIUM:\n-\t\tret += sprintf(buf + ret, \"Date:   %s\\n\", show_date(time, tz));\n+\t\tret += sprintf(buf + ret, \"Date:   %s\\n\", show_date(time, tz, 0));\n \t\tbreak;\n \tcase CMIT_FMT_EMAIL:\n \t\tret += sprintf(buf + ret, \"Date: %s\\n\",\n \t\t\t       show_rfc2822_date(time, tz));\n \t\tbreak;\n \tcase CMIT_FMT_FULLER:\n-\t\tret += sprintf(buf + ret, \"%sDate: %s\\n\", what, show_date(time, tz));\n+\t\tret += sprintf(buf + ret, \"%sDate: %s\\n\", what, show_date(time, tz, 0));\n \t\tbreak;\n \tdefault:\n \t\t/* notin' */\ndiff --git a/date.c b/date.c\nindex d780846..92e3f6e 100644\n--- a/date.c\n+++ b/date.c\n@@ -37,6 +37,16 @@ static const char *weekday_names[] = {\n \t\"Sundays\", \"Mondays\", \"Tuesdays\", \"Wednesdays\", \"Thursdays\", \"Fridays\", \"Saturdays\"\n };\n \n+static time_t gm_time_t(unsigned long time, int tz)\n+{\n+\tint minutes;\n+\n+\tminutes = tz < 0 ? -tz : tz;\n+\tminutes = (minutes / 100)*60 + (minutes % 100);\n+\tminutes = tz < 0 ? -minutes : minutes;\n+\treturn time + minutes * 60;\n+}\n+\n /*\n  * The \"tz\" thing is passed in as this strange \"decimal parse of tz\"\n  * thing, which means that tz -0100 is passed in as the integer -100,\n@@ -44,21 +54,56 @@ static const char *weekday_names[] = {\n  */\n static struct tm *time_to_tm(unsigned long time, int tz)\n {\n-\ttime_t t;\n-\tint minutes;\n-\n-\tminutes = tz < 0 ? -tz : tz;\n-\tminutes = (minutes / 100)*60 + (minutes % 100);\n-\tminutes = tz < 0 ? -minutes : minutes;\n-\tt = time + minutes * 60;\n+\ttime_t t = gm_time_t(time, tz);\n \treturn gmtime(&t);\n }\n \n-const char *show_date(unsigned long time, int tz)\n+const char *show_date(unsigned long time, int tz, int relative)\n {\n \tstruct tm *tm;\n \tstatic char timebuf[200];\n \n+\tif (relative) {\n+\t\tunsigned long diff;\n+\t\ttime_t t = gm_time_t(time, tz);\n+\t\tstruct timeval now;\n+\t\tgettimeofday(&now, NULL);\n+\t\tif (now.tv_sec < t)\n+\t\t\treturn \"in the future\";\n+\t\tdiff = now.tv_sec - t;\n+\t\tif (diff < 90) {\n+\t\t\tsnprintf(timebuf, sizeof(timebuf), \"%lu seconds ago\", diff);\n+\t\t\treturn timebuf;\n+\t\t}\n+\t\t/* Turn it into minutes */\n+\t\tdiff = (diff + 30) / 60;\n+\t\tif (diff < 90) {\n+\t\t\tsnprintf(timebuf, sizeof(timebuf), \"%lu minutes ago\", diff);\n+\t\t\treturn timebuf;\n+\t\t}\n+\t\t/* Turn it into hours */\n+\t\tdiff = (diff + 30) / 60;\n+\t\tif (diff < 36) {\n+\t\t\tsnprintf(timebuf, sizeof(timebuf), \"%lu hours ago\", diff);\n+\t\t\treturn timebuf;\n+\t\t}\n+\t\t/* Turn it into days */\n+\t\tdiff = (diff + 12) / 24;\n+\t\tif (diff < 14) {\n+\t\t\tsnprintf(timebuf, sizeof(timebuf), \"%lu days ago\", diff);\n+\t\t\treturn timebuf;\n+\t\t}\n+\t\tif (diff < 53) {\n+\t\t\tsnprintf(timebuf, sizeof(timebuf), \"%lu weeks ago\", (diff + 3) / 7);\n+\t\t\treturn timebuf;\n+\t\t}\n+\t\tif (diff < 365) {\n+\t\t\tsnprintf(timebuf, sizeof(timebuf), \"%lu months ago\", (diff + 15) / 30);\n+\t\t\treturn timebuf;\n+\t\t}\n+\t\t/* Else fall back on absolute format.. */\n+\t}\n+\n \ttm = time_to_tm(time, tz);\n \tif (!tm)\n \t\treturn NULL;\n"},{"id":"25966","messageId":"7vodu7yntn.fsf@assigned-by-dhcp.cox.net","threadId":"5402","inReplyTo":"Pine.LNX.4.64.0608261537120.11811@g5.osdl.org","subject":"Re: Relative timestamps in git log","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-08-27T01:22:44Z","receivedAt":"2006-08-27T01:22:44Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Linus Torvalds <torvalds@osdl.org> writes:\n\n> The exact cut-off points when it switches from days to weeks etc are \n> totally arbitrary, but are picked somewhat to avoid the \"1 weeks ago\" \n> thing (by making it show \"10 days ago\" rather than \"1 week\", or \"70 \n> minutes ago\" rather than \"1 hour ago\").\n\nIt might be arbitrary but I agree they make sense.  \n\nThe code was quite confusing in that after you turn it into days\nyou stop converting to larger scale and I was going to nitpick\nthat 365 weeks do not make much sense.  I think you meant to do\na \"Turn it into weeks\" conversion before comparing diff with 53\nand then changed your mind, got confused yourself that diff is\nstill in days and decided to say \"months\" for the past year.\n\nIOW, I think there is a bug around weeks/months code ;-).  On\ntop of your patch how about the attached?\n\nPersonally I have mixed feeling about \"months\" scale.  On one\nhand, I think it could extend to 30 months or so without losing\nreadability when one is interested in \"how long ago did this\nhappen\".  On the other hand, it would take more time for me if I\nsee \"this happened 5 months ago\" than \"this happened in March\nthis year\" to recollect what the context of the particular\nchange was (\"ah I needed that feature when I was preparing the\ncommitter/author graph for OLS paper deadline\").\n\nP.S. welcome back to the list.\n\ndiff --git a/date.c b/date.c\nindex 92e3f6e..914fd82 100644\n--- a/date.c\n+++ b/date.c\n@@ -87,17 +87,19 @@ const char *show_date(unsigned long time\n \t\t\tsnprintf(timebuf, sizeof(timebuf), \"%lu hours ago\", diff);\n \t\t\treturn timebuf;\n \t\t}\n-\t\t/* Turn it into days */\n+\t\t/* Turn it into days; from here on we deal with days */\n \t\tdiff = (diff + 12) / 24;\n \t\tif (diff < 14) {\n \t\t\tsnprintf(timebuf, sizeof(timebuf), \"%lu days ago\", diff);\n \t\t\treturn timebuf;\n \t\t}\n-\t\tif (diff < 53) {\n+\t\t/* Say weeks for the past 6 months or so */\n+\t\tif (diff < 180) {\n \t\t\tsnprintf(timebuf, sizeof(timebuf), \"%lu weeks ago\", (diff + 3) / 7);\n \t\t\treturn timebuf;\n \t\t}\n-\t\tif (diff < 365) {\n+\t\t/* Say months for the past 30 months or so */\n+\t\tif (diff < 912) {\n \t\t\tsnprintf(timebuf, sizeof(timebuf), \"%lu months ago\", (diff + 15) / 30);\n \t\t\treturn timebuf;\n \t\t}\n"}]}