{"thread":{"id":"13","subject":"Date handling.","startedAt":"2005-04-14T08:16:32Z","lastAt":"2005-04-25T01:32:01Z","messageCount":13,"participants":["David Woodhouse","Linus Torvalds","tony.luck@intel.com","Jan Harkes","James Purser","Russ Allbery"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"52","messageId":"1113466592.12012.192.camel@baythorne.infradead.org","threadId":"13","inReplyTo":null,"subject":"Date handling.","fromName":"David Woodhouse","fromEmail":"dwmw2@infradead.org","sentAt":"2005-04-14T08:16:32Z","receivedAt":"2005-04-14T08:16:32Z","isPatch":false,"sender":{"key":"dwmw2@infradead.org","avatar":"https://gravatar.com/avatar/7afd4f07e0cf7d7e046ae2d23678296b37777c96488e6f3451e78a5514154ebd?d=mp&s=160"},"body":"The date handling is somewhat unreliable. We render dates into textual\nrepresentation using the committer's locale (day names, etc), then later\nattempt to interpret that in some other locale. And we were just using\nlocaltime without even specifying the timezone so the timestamp was\nfairly randomised anyway. In fact, an $AUTHOR_DATE environment variable\nwas making its way into the database entirely unchecked. \n\nI see two possible solutions:\n\t1. Just store seconds-since-GMT-epoch and if we really want, the\n\t   timezone as auxiliary information.\n\t2. Store dates in RFC2822 form.\n\nUnless someone convincingly expresses a preference before I get to work\nand start playing with it, I'll implement the latter.\n\n-- \ndwmw2\n\n\n"},{"id":"57","messageId":"Pine.LNX.4.58.0504140153230.7211@ppc970.osdl.org","threadId":"13","inReplyTo":"1113466592.12012.192.camel@baythorne.infradead.org","subject":"Re: Date handling.","fromName":"Linus Torvalds","fromEmail":"torvalds@osdl.org","sentAt":"2005-04-14T09:00:05Z","receivedAt":"2005-04-14T09:00:05Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Thu, 14 Apr 2005, David Woodhouse wrote:\n> \n> I see two possible solutions:\n> \t1. Just store seconds-since-GMT-epoch and if we really want, the\n> \t   timezone as auxiliary information.\n\nYeah, I think this is the right thing to do. I can change \"commit\" to do \nit.\n\nI used to think that the date was purely a \"enforced comment\" (like the\ncommitter info is, as far as git is concerned), which is why I used the \nsimple textual representation. But yes, when I wrote that \"rev-tree\" thing \nI did curse that and consider just changing it.\n\nIt's still just technically a \"hint\", since time isn't synchronized in any \nway (and in a distributed system, time _cannot_ be synchronized). But \nit's a useful hint, so ..\n\n> \t2. Store dates in RFC2822 form.\n> \n> Unless someone convincingly expresses a preference before I get to work\n> and start playing with it, I'll implement the latter.\n\nI do like text output, but if it is painful, the \"unix seconds\" format is \ncertainly a hell of a lot simpler. And quite frankly, if we change it, we \nmight as well just change it all the way. So I'd almost prefer (1).\n\nBut \"He who does the work gets to choose the implementation\". And I do\nagree that this is a bad format decision, and that we should change it. It \nshouldn't even be that painful. Only \"rev-tree\" cares, and even rev-tree \ndoesn't care _that_ deeply.\n\n\t\tLinus\n"},{"id":"59","messageId":"Pine.LNX.4.58.0504140212100.7211@ppc970.osdl.org","threadId":"13","inReplyTo":"Pine.LNX.4.58.0504140153230.7211@ppc970.osdl.org","subject":"Re: Date handling.","fromName":"Linus Torvalds","fromEmail":"torvalds@osdl.org","sentAt":"2005-04-14T09:12:33Z","receivedAt":"2005-04-14T09:12:33Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Thu, 14 Apr 2005, Linus Torvalds wrote:\n> \n> Yeah, I think this is the right thing to do. I can change \"commit\" to do \n> it.\n\nI take that back. I'd be much happier with you doing and testing it, \nbecause now I'm crashing.\n\n\t\tLinus\n"},{"id":"61","messageId":"1113471061.20848.178.camel@hades.cambridge.redhat.com","threadId":"13","inReplyTo":"Pine.LNX.4.58.0504140153230.7211@ppc970.osdl.org","subject":"Re: Date handling.","fromName":"David Woodhouse","fromEmail":"dwmw2@infradead.org","sentAt":"2005-04-14T09:31:00Z","receivedAt":"2005-04-14T09:31:00Z","isPatch":false,"sender":{"key":"dwmw2@infradead.org","avatar":"https://gravatar.com/avatar/7afd4f07e0cf7d7e046ae2d23678296b37777c96488e6f3451e78a5514154ebd?d=mp&s=160"},"body":"On Thu, 2005-04-14 at 02:00 -0700, Linus Torvalds wrote:\n> I do like text output, but if it is painful, the \"unix seconds\" format is \n> certainly a hell of a lot simpler. And quite frankly, if we change it, we \n> might as well just change it all the way. So I'd almost prefer (1).\n\nText _output_ is easy to generate; we don't need to store text in the\ndatabase for that. So I've changed my mind -- I prefer (1) too.\n\n-- \ndwmw2\n\n"},{"id":"95","messageId":"1113500316.27227.8.camel@hades.cambridge.redhat.com","threadId":"13","inReplyTo":"Pine.LNX.4.58.0504140212100.7211@ppc970.osdl.org","subject":"Re: Date handling.","fromName":"David Woodhouse","fromEmail":"dwmw2@infradead.org","sentAt":"2005-04-14T17:38:36Z","receivedAt":"2005-04-14T17:38:36Z","isPatch":false,"sender":{"key":"dwmw2@infradead.org","avatar":"https://gravatar.com/avatar/7afd4f07e0cf7d7e046ae2d23678296b37777c96488e6f3451e78a5514154ebd?d=mp&s=160"},"body":"On Thu, 2005-04-14 at 02:12 -0700, Linus Torvalds wrote:\n> I take that back. I'd be much happier with you doing and testing it, \n> because now I'm crashing.\n\nOK. commit-tree now eats RFC2822 dates as AUTHOR_DATE because that's\nwhat you're going to want to feed it. We store seconds since UTC epoch,\nwe add the author's or committer's timezone as auxiliary data so that\ndates can be pretty-printed in the original timezone later if anyone\ncares. I left the date parsing in rev-tree.c for backward compatibility\nbut it can be dropped when we change to base64 :)\n\nYes, glibc sucks and strptime is a pile of crap. We have to parse it\nourselves.\n\nIndex: commit-tree.c\n--- 1756b578489f93999ded68ae347bef7d6063101c/commit-tree.c  (mode:100664 sha1:12196c79f31d004dff0df1f50dda67d8204f5568)\n+++ 82ba574c85e9a2e4652419c88244e9dd1bfa8baa/commit-tree.c  (mode:100644 sha1:35cb09402c9868499bcaf6de42afbad9fdfebe05)\n@@ -7,6 +7,9 @@\n \n #include <pwd.h>\n #include <time.h>\n+#include <string.h>\n+#include <ctype.h>\n+#include <time.h>\n \n #define BLOCKING (1ul << 14)\n #define ORIG_OFFSET (40)\n@@ -95,6 +98,148 @@\n \t}\n }\n \n+static const char *month_names[] = {\n+        \"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\",\n+        \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"\n+};\n+\n+static const char *weekday_names[] = {\n+        \"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"\n+};\n+\n+\n+static char *skipfws(char *str)\n+{\n+\twhile (isspace(*str))\n+\t\tstr++;\n+\treturn str;\n+}\n+\n+\t\n+/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822\n+   (i.e. English) day/month names, and it doesn't work correctly with %z. */\n+static void parse_rfc2822_date(char *date, char *result, int maxlen)\n+{\n+\tstruct tm tm;\n+\tchar *p;\n+\tint i, offset;\n+\ttime_t then;\n+\n+\tmemset(&tm, 0, sizeof(tm));\n+\n+\t/* Skip day-name */\n+\tp = skipfws(date);\n+\tif (!isdigit(*p)) {\n+\t\tfor (i=0; i<7; i++) {\n+\t\t\tif (!strncmp(p,weekday_names[i],3) && p[3] == ',') {\n+\t\t\t\tp = skipfws(p+4);\n+\t\t\t\tgoto day;\n+\t\t\t}\n+\t\t}\n+\t\treturn;\n+\t}\t\t\t\t\t\n+\n+\t/* day */\n+ day:\n+\ttm.tm_mday = strtoul(p, &p, 10);\n+\n+\tif (tm.tm_mday < 1 || tm.tm_mday > 31)\n+\t\treturn;\n+\n+\tif (!isspace(*p))\n+\t\treturn;\n+\n+\tp = skipfws(p);\n+\n+\t/* month */\n+\n+\tfor (i=0; i<12; i++) {\n+\t\tif (!strncmp(p, month_names[i], 3) && isspace(p[3])) {\n+\t\t\ttm.tm_mon = i;\n+\t\t\tp = skipfws(p+strlen(month_names[i]));\n+\t\t\tgoto year;\n+\t\t}\n+\t}\n+\treturn; /* Error -- bad month */\n+\n+\t/* year */\n+ year:\t\n+\ttm.tm_year = strtoul(p, &p, 10);\n+\n+\tif (!tm.tm_year && !isspace(*p))\n+\t\treturn;\n+\n+\tif (tm.tm_year > 1900)\n+\t\ttm.tm_year -= 1900;\n+\t\t\n+\tp=skipfws(p);\n+\n+\t/* hour */\n+\tif (!isdigit(*p))\n+\t\treturn;\n+\ttm.tm_hour = strtoul(p, &p, 10);\n+\t\n+\tif (!tm.tm_hour > 23)\n+\t\treturn;\n+\n+\tif (*p != ':')\n+\t\treturn; /* Error -- bad time */\n+\tp++;\n+\n+\t/* minute */\n+\tif (!isdigit(*p))\n+\t\treturn;\n+\ttm.tm_min = strtoul(p, &p, 10);\n+\t\n+\tif (!tm.tm_min > 59)\n+\t\treturn;\n+\n+\tif (isspace(*p))\n+\t\tgoto zone;\n+\n+\tif (*p != ':')\n+\t\treturn; /* Error -- bad time */\n+\tp++;\n+\n+\t/* second */\n+\tif (!isdigit(*p))\n+\t\treturn;\n+\ttm.tm_sec = strtoul(p, &p, 10);\n+\t\n+\tif (!tm.tm_sec > 59)\n+\t\treturn;\n+\n+\tif (!isspace(*p))\n+\t\treturn;\n+\n+ zone:\n+\tp = skipfws(p);\n+\n+\tif (*p == '-')\n+\t\toffset = -60;\n+\telse if (*p == '+')\n+\t\toffset = 60;\n+\telse\n+\t       return;\n+\n+\tif (!isdigit(p[1]) || !isdigit(p[2]) || !isdigit(p[3]) || !isdigit(p[4]))\n+\t\treturn;\n+\n+\ti = strtoul(p+1, NULL, 10);\n+\toffset *= ((i % 100) + ((i / 100) * 60));\n+\n+\tif (*(skipfws(p + 5)))\n+\t\treturn;\n+\n+\tthen = mktime(&tm); /* mktime appears to ignore the GMT offset, stupidly */\n+\tif (then == -1)\n+\t\treturn;\n+\n+\tthen -= offset;\n+\n+\tsnprintf(result, maxlen, \"%lu %5.5s\", then, p);\n+}\n+\n /*\n  * Having more than two parents may be strange, but hey, there's\n  * no conceptual reason why the file format couldn't accept multi-way\n@@ -114,10 +259,12 @@\n \tunsigned char commit_sha1[20];\n \tchar *gecos, *realgecos;\n \tchar *email, realemail[1000];\n-\tchar *date, *realdate;\n+\tchar date[20], realdate[20];\n+\tchar *audate;\n \tchar comment[1000];\n \tstruct passwd *pw;\n \ttime_t now;\n+\tstruct tm *tm;\n \tchar *buffer;\n \tunsigned int size;\n \n@@ -142,15 +289,19 @@\n \trealemail[len] = '@';\n \tgethostname(realemail+len+1, sizeof(realemail)-len-1);\n \ttime(&now);\n-\trealdate = ctime(&now);\n+\ttm = localtime(&now);\n+\n+\tstrftime(realdate, sizeof(realdate), \"%s %z\", tm);\n+\tstrcpy(date, realdate);\n \n \tgecos = getenv(\"AUTHOR_NAME\") ? : realgecos;\n \temail = getenv(\"AUTHOR_EMAIL\") ? : realemail;\n-\tdate = getenv(\"AUTHOR_DATE\") ? : realdate;\n+\taudate = getenv(\"AUTHOR_DATE\");\n+\tif (audate)\n+\t\tparse_rfc2822_date(audate, date, sizeof(date));\n \n \tremove_special(gecos); remove_special(realgecos);\n \tremove_special(email); remove_special(realemail);\n-\tremove_special(date); remove_special(realdate);\n \n \tinit_buffer(&buffer, &size);\n \tadd_buffer(&buffer, &size, \"tree %s\\n\", sha1_to_hex(tree_sha1));\nIndex: rev-tree.c\n--- 1756b578489f93999ded68ae347bef7d6063101c/rev-tree.c  (mode:100664 sha1:7bf9e9a92f528485360f374239809714ce7a19f5)\n+++ 82ba574c85e9a2e4652419c88244e9dd1bfa8baa/rev-tree.c  (mode:100644 sha1:9443f4560beedcf81f2f5e7e0664d169cb5c8527)\n@@ -1,4 +1,5 @@\n #define _XOPEN_SOURCE /* glibc2 needs this */\n+#define _BSD_SOURCE /* for tm.tm_gmtoff */\n #include <time.h>\n #include <ctype.h>\n \n@@ -21,6 +22,7 @@\n \tchar buffer[100];\n \tstruct tm tm;\n \tconst char *formats[] = {\n+\t\t\"%s\",\n \t\t\"%c\",\n \t\t\"%a %b %d %T %y\",\n \t\tNULL\n@@ -30,7 +32,7 @@\n \tp = buffer;\n \twhile (isspace(c = *buf))\n \t\tbuf++;\n-\twhile ((c = *buf++) != '\\n')\n+\twhile ((c = *buf++) != '\\n' && c)\n \t\t*p++ = c;\n \t*p++ = 0;\n \tbuf = buffer;\n@@ -50,6 +52,8 @@\n \n static unsigned long parse_commit_date(const char *buf)\n {\n+\tunsigned long time;\n+\n \tif (memcmp(buf, \"author\", 6))\n \t\treturn 0;\n \twhile (*buf++ != '\\n')\n@@ -58,7 +62,11 @@\n \t\treturn 0;\n \twhile (*buf++ != '>')\n \t\t/* nada */;\n-\treturn parse_time(buf);\n+\n+\ttime = strtoul(buf, NULL, 10);\n+\tif (!time)\n+\t\ttime = parse_time(buf);\n+\treturn time;\n }\n \n static int parse_commit(unsigned char *sha1)\n\n\n-- \ndwmw2\n\n"},{"id":"110","messageId":"200504141919.j3EJJfG04166@unix-os.sc.intel.com","threadId":"13","inReplyTo":"1113500316.27227.8.camel@hades.cambridge.redhat.com","subject":"Re: Date handling.","fromName":"","fromEmail":"tony.luck@intel.com","sentAt":"2005-04-14T19:19:41Z","receivedAt":"2005-04-14T19:19:41Z","isPatch":false,"sender":{"key":"tony.luck@intel.com","avatar":"https://avatars.githubusercontent.com/u/5446021?v=4"},"body":"> OK. commit-tree now eats RFC2822 dates as AUTHOR_DATE because that's\n> what you're going to want to feed it. We store seconds since UTC epoch,\n> we add the author's or committer's timezone as auxiliary data so that\n> dates can be pretty-printed in the original timezone later if anyone\n> cares.\n\nWith a UTC date, why would anyone care in which timezone the commit was\nmade?  Any pretty printing would most likely be prettiest if it is done\nrelative to the timezone of the person looking at the commit record, not\nthe person who created the record.\n\nIf we do need the timezone, then I think we also need the latitude of the\ncommitter too, so that we know whether to interpret \"July\" as summer or\nwinter :-)\n\n-Tony\n"},{"id":"114","messageId":"1113506597.12012.223.camel@baythorne.infradead.org","threadId":"13","inReplyTo":"200504141919.j3EJJfG04166@unix-os.sc.intel.com","subject":"Re: Date handling.","fromName":"David Woodhouse","fromEmail":"dwmw2@infradead.org","sentAt":"2005-04-14T19:23:17Z","receivedAt":"2005-04-14T19:23:17Z","isPatch":false,"sender":{"key":"dwmw2@infradead.org","avatar":"https://gravatar.com/avatar/7afd4f07e0cf7d7e046ae2d23678296b37777c96488e6f3451e78a5514154ebd?d=mp&s=160"},"body":"On Thu, 2005-04-14 at 12:19 -0700, tony.luck@intel.com wrote:\n> With a UTC date, why would anyone care in which timezone the commit was\n> made?  Any pretty printing would most likely be prettiest if it is done\n> relative to the timezone of the person looking at the commit record, not\n> the person who created the record.\n\nI'd prefer not to lose the information. If someone has committed a\nchange at 2am, I like to know that it was 2am for _them_. It helps me\ndecide where to look first for the cause of problems. :)\n\nIt also helps disambiguate certain comments, especially those involving\nwords or phrases such as \"yesterday\" or \"this afternoon\".\n\n-- \ndwmw2\n\n\n"},{"id":"1469","messageId":"20050424030416.GE16751@delft.aura.cs.cmu.edu","threadId":"13","inReplyTo":"1113500316.27227.8.camel@hades.cambridge.redhat.com","subject":"Re: Date handling.","fromName":"Jan Harkes","fromEmail":"jaharkes@cs.cmu.edu","sentAt":"2005-04-24T03:04:16Z","receivedAt":"2005-04-24T03:04:16Z","isPatch":false,"sender":{"key":"jaharkes@cs.cmu.edu","avatar":"https://gravatar.com/avatar/cf95aecd150ca8ef33d6edc337ac4bb9e13aa4246fc3679257d578c7fddc1633?d=mp&s=160"},"body":"On Thu, Apr 14, 2005 at 06:38:36PM +0100, David Woodhouse wrote:\n> +/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822\n> +   (i.e. English) day/month names, and it doesn't work correctly with %z. */\n> +static void parse_rfc2822_date(char *date, char *result, int maxlen)\n> +{\n...\n> +\tthen = mktime(&tm); /* mktime appears to ignore the GMT offset, stupidly */\n\nI noticed that some commit timestamps seemed to be off, looking into it\na bit more it seems like mktime is influenced by the setting of the\nlocal TZ environment. However in parse_rfc2822_date we are trying to\ninterpret a time in the timezone of the original author not in the\ntimezone of the committer.\n\nHere is a short test program that I believe shows the problem.\n\nThe question is, do we want to just calculate the time_t offset\nourselves without using mktime, or force the TZ environment to UTC.\n\nJan\n\n\n/* cc -o mktime mktime.c ; ./mktime\n *\n * I get the following output,\n *   current 18000\n *   TZ=EST 18000\n *   TZ=UTC 0\n *   TZ=CET -3600\n */\n\n#include <time.h>\n#include <stdlib.h>\n#include <stdio.h>\n\nint main(int argc, char **argv)\n{\n    struct tm tm = { 0, };\n    time_t zero;\n\n    /* 1970-01-01 00:00:00 UTC, should map to 'time_t 0' */\n    tm.tm_mday = 1;\n    tm.tm_year = 70;\n\t\t\t    zero = mktime(&tm); printf(\"current %d\\n\", zero);\n    setenv(\"TZ\", \"EST\", 1); zero = mktime(&tm); printf(\"TZ=EST %d\\n\", zero);\n    setenv(\"TZ\", \"UTC\", 1); zero = mktime(&tm); printf(\"TZ=UTC %d\\n\", zero);\n    setenv(\"TZ\", \"CET\", 1); zero = mktime(&tm); printf(\"TZ=CET %d\\n\", zero);\n}\n"},{"id":"1471","messageId":"1114313615.25535.1.camel@kryten","threadId":"13","inReplyTo":"20050424030416.GE16751@delft.aura.cs.cmu.edu","subject":"Re: Date handling.","fromName":"James Purser","fromEmail":"purserj@ksit.dynalias.com","sentAt":"2005-04-24T03:33:35Z","receivedAt":"2005-04-24T03:33:35Z","isPatch":false,"sender":{"key":"purserj@ksit.dynalias.com","avatar":null},"body":"Wouldn't it be easier to force GMT or UTC as the base timezone for the\napplication. This would remove any confusion between different\ntimezones.\n-- \nJames Purser\nhttp://ksit.dynalias.com\n\n"},{"id":"1501","messageId":"1114324729.3419.78.camel@localhost.localdomain","threadId":"13","inReplyTo":"20050424030416.GE16751@delft.aura.cs.cmu.edu","subject":"Re: Date handling.","fromName":"David Woodhouse","fromEmail":"dwmw2@infradead.org","sentAt":"2005-04-24T06:38:49Z","receivedAt":"2005-04-24T06:38:49Z","isPatch":false,"sender":{"key":"dwmw2@infradead.org","avatar":"https://gravatar.com/avatar/7afd4f07e0cf7d7e046ae2d23678296b37777c96488e6f3451e78a5514154ebd?d=mp&s=160"},"body":"On Sat, 2005-04-23 at 23:04 -0400, Jan Harkes wrote:\n> I noticed that some commit timestamps seemed to be off, looking into it\n> a bit more it seems like mktime is influenced by the setting of the\n> local TZ environment.\n\nEwww. I missed that in the documentation. I suppose I should have worked\nit out having empirically determined that it ignores the tm_gmtoff\nfield.\n\n> The question is, do we want to just calculate the time_t offset\n> ourselves without using mktime, or force the TZ environment to UTC.\n\nI don't think we want to be in the business of counting leap seconds; we\nneed to let the system do it. I don't much like setting TZ to UTC though\n-- how about we use your test case to find the offset and subtract that?\n\nDoes this work?\n\nIndex: commit-tree.c\n===================================================================\n--- 31e9af73983d640090508b06784ef7db4816c957/commit-tree.c  (mode:100644 sha1:c0b07f89286c3f6cceae8122b4c3142c8efaf8e1)\n+++ uncommitted/commit-tree.c  (mode:100664)\n@@ -138,10 +138,14 @@\n \tstruct tm tm;\n \tchar *p;\n \tint i, offset;\n-\ttime_t then;\n+\ttime_t then, localofs;\n \n \tmemset(&tm, 0, sizeof(tm));\n \n+\ttm.tm_mday = 1;\n+\ttm.tm_year = 70;\n+\tlocalofs = mktime(&tm);\n+\n \t/* Skip day-name */\n \tp = skipfws(date);\n \tif (!isdigit(*p)) {\n@@ -246,7 +250,9 @@\n \tif (*(skipfws(p + 5)))\n \t\treturn;\n \n-\tthen = mktime(&tm); /* mktime appears to ignore the GMT offset, stupidly */\n+\t/* No way to convert to a time_t and honour tm_gmtoff; we have to\n+\t   do the evil trick by subtracting the local offset */\n+\tthen = mktime(&tm) - localofs;\n \tif (then == -1)\n \t\treturn;\n \n\n\n-- \ndwmw2\n\n"},{"id":"1502","messageId":"87r7h0u12e.fsf@windlord.stanford.edu","threadId":"13","inReplyTo":"1114324729.3419.78.camel@localhost.localdomain","subject":"Re: Date handling.","fromName":"Russ Allbery","fromEmail":"rra@stanford.edu","sentAt":"2005-04-24T06:43:37Z","receivedAt":"2005-04-24T06:43:37Z","isPatch":false,"sender":{"key":"rra@stanford.edu","avatar":null},"body":"David Woodhouse <dwmw2@infradead.org> writes:\n\n> I don't think we want to be in the business of counting leap seconds; we\n> need to let the system do it. I don't much like setting TZ to UTC though\n> -- how about we use your test case to find the offset and subtract that?\n\n> Does this work?\n\nNope, daylight savings time breaks this, since you may or may not be in\nthe same time zone on January 1st as you are at the current time.\n\nHowever, you don't need to count leap seconds when you implement your own\nmktime, since mktime doesn't have to take leap seconds into account.  Unix\ntimestamps, unless you're using TAI, don't include leap seconds.\n\n-- \nRuss Allbery (rra@stanford.edu)             <http://www.eyrie.org/~eagle/>\n"},{"id":"1571","messageId":"20050425012216.GH29939@delft.aura.cs.cmu.edu","threadId":"13","inReplyTo":"1114324729.3419.78.camel@localhost.localdomain","subject":"Re: Date handling.","fromName":"Jan Harkes","fromEmail":"jaharkes@cs.cmu.edu","sentAt":"2005-04-25T01:22:23Z","receivedAt":"2005-04-25T01:22:23Z","isPatch":false,"sender":{"key":"jaharkes@cs.cmu.edu","avatar":"https://gravatar.com/avatar/cf95aecd150ca8ef33d6edc337ac4bb9e13aa4246fc3679257d578c7fddc1633?d=mp&s=160"},"body":"On Sun, Apr 24, 2005 at 04:38:49PM +1000, David Woodhouse wrote:\n> On Sat, 2005-04-23 at 23:04 -0400, Jan Harkes wrote:\n> > I noticed that some commit timestamps seemed to be off, looking into it\n> > a bit more it seems like mktime is influenced by the setting of the\n> > local TZ environment.\n> \n> Ewww. I missed that in the documentation. I suppose I should have worked\n> it out having empirically determined that it ignores the tm_gmtoff\n> field.\n> \n> > The question is, do we want to just calculate the time_t offset\n> > ourselves without using mktime, or force the TZ environment to UTC.\n> \n> I don't think we want to be in the business of counting leap seconds; we\n> need to let the system do it. I don't much like setting TZ to UTC though\n> -- how about we use your test case to find the offset and subtract that?\n> \n> Does this work?\n\nAs Russ mentioned, that probably doesn't work with daylight savings\ntime. However I did some testing and it looks like the following lines\naround mktime make it work as we would expect.\n\n    tm.tm_isdst = -1;\n    then = mktime(&tm);\n    then += tm.tm_gmtoff;\n\nAttached is the program I used to test it, it seems pretty much unfazed\nby changes to the TZ environment variable. Although I tested around a\ndaylight savings time switch, I'm still not 100% sure if it doesn't mess\nup in some corner case.\n\nJan\n\n\n#include <time.h>\n#include <stdlib.h>\n#include <stdio.h>\n\ntime_t mkutctime(struct tm *tm, int offset)\n{\n    time_t time;\n\n    /* we don't know whether our timezone happens to be dst or not, let libc\n     * figure that one out. */\n    tm->tm_isdst = -1;\n\n    /* interpret struct tm in the local timezone */\n    time = mktime(tm);\n    if (time == -1) return -1;\n\n    /* libc lets us know how many seconds our local time differs from UTC\n     * this is a non-standard BSD extension, which is probably not as\n     * portable, but it seems to work. */\n    time += tm->tm_gmtoff;\n\n    /* However as the passed in struct tm was not UTC but in some other\n     * timezone, we still have subtract the offset that came with the\n     * RFC2822 date */\n    time -= offset;\n\n    return time;\n}\n\nint main(int argc, char **argv)\n{\n    struct tm tm = { 0, };\n    time_t time;\n\n    tm.tm_year = 70;\n    tm.tm_mday = 1;\n    time = mkutctime(&tm, 0);\n    printf(\"1970-01-01 00:00:00 UTC = 0 (%d)\\n\", time);\n\n    tm.tm_year = 105;\n    tm.tm_mon = 2;\n    tm.tm_mday = 17;\n    tm.tm_hour = 20;\n    tm.tm_min = 58;\n    tm.tm_sec = 31;\n    time = mkutctime(&tm, -5 * 3600);\n    printf(\"2005-03-17 20:58:31 EST = 1111111111 (%d)\\n\", time);\n\n    tm.tm_mon = 3;\n    tm.tm_mday = 3;\n    tm.tm_hour = 1;\n    tm.tm_min = 59;\n    tm.tm_sec = 59;\n    time = mkutctime(&tm, -5 * 3600);\n    printf(\"2005-04-03 01:59:59 EST = 1112511599 (%d)\\n\", time);\n\n    tm.tm_hour = 3;\n    tm.tm_min = 0;\n    tm.tm_sec = 0;\n    time = mkutctime(&tm, -4 * 3600);\n    printf(\"2005-04-03 03:00:00 EDT = 1112511600 (%d)\\n\", time);\n}\n\n"},{"id":"1573","messageId":"87br83wsj2.fsf@windlord.stanford.edu","threadId":"13","inReplyTo":"20050425012216.GH29939@delft.aura.cs.cmu.edu","subject":"Re: Date handling.","fromName":"Russ Allbery","fromEmail":"rra@stanford.edu","sentAt":"2005-04-25T01:32:01Z","receivedAt":"2005-04-25T01:32:01Z","isPatch":false,"sender":{"key":"rra@stanford.edu","avatar":null},"body":"Jan Harkes <jaharkes@cs.cmu.edu> writes:\n\n> As Russ mentioned, that probably doesn't work with daylight savings\n> time. However I did some testing and it looks like the following lines\n> around mktime make it work as we would expect.\n\n>     tm.tm_isdst = -1;\n>     then = mktime(&tm);\n>     then += tm.tm_gmtoff;\n\n> Attached is the program I used to test it, it seems pretty much unfazed\n> by changes to the TZ environment variable. Although I tested around a\n> daylight savings time switch, I'm still not 100% sure if it doesn't mess\n> up in some corner case.\n\nI don't know what sort of portability you're striving for, but many\nplatforms don't have tm.tm_gmtoff.  But reimplementing mktime from scratch\nisn't particularly hard so long as you don't need some of the \"extra\"\nfeatures of mktime (canonicalizing a struct tm or accepting out of range\nvalues and doing the \"right thing\").\n\nI came in a little late to this discussion, but I gather that the overall\ngoal here is parsing RFC 2822 dates.  You're all certainly welcome to take\nthe code that I wrote for INN to do this if you wish, although it parses\nthe full RFC 2822 syntax and therefore may accept things you consider\ninsane (comments, newlines, etc.)  Or you're welcome to cherry-pick bits\nand pieces out of it (like mktime_utc).  This code has a fairly extensive\ntest suite and has also been tested against the old INN parsedate function\non ~2M Usenet articles.\n\nAll of this code is my own work, and as far as I'm concerned it's in the\npublic domain or as close of an approximation that one can get to that in\nyour local legal environment.\n\nThe code is largish and needs some Autoconf support, so I won't just send\nit to the list unless someone wants it, but let me know if you do.  You\ncan also get it by downloading INN from:\n\n    <ftp://ftp.isc.org/isc/inn/snapshots/>\n\n(getting the latest CURRENT snapshot) and looking in lib/date.c.  You\ndon't need the parsedate_nntp stuff, and you probably don't care about\nparsedate_rfc2822_lax, which accepts common violations of RFC 2822 syntax\nfound in Usenet messages.  The test suite is in tests/lib/date-t.c.\n\n-- \nRuss Allbery (rra@stanford.edu)             <http://www.eyrie.org/~eagle/>\n"}]}